augusto2112 updated this revision to Diff 314641.
augusto2112 added a comment.
Herald added a subscriber: dang.
Adds support to 'vAttachOrWait', as well as the 'waitfor-interval' and
'waitfor-duration' flags.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D93895/new/
https://reviews.llvm.org/D93895
Files:
lldb/include/lldb/Target/Process.h
lldb/include/lldb/Utility/StringExtractor.h
lldb/include/lldb/lldb-enumerations.h
lldb/source/Commands/CommandObjectProcess.cpp
lldb/source/Commands/Options.td
lldb/source/Interpreter/CommandObject.cpp
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
lldb/source/Utility/StringExtractor.cpp
lldb/test/API/tools/lldb-server/TestGdbRemoteAttachWait.py
Index: lldb/test/API/tools/lldb-server/TestGdbRemoteAttachWait.py
===================================================================
--- /dev/null
+++ lldb/test/API/tools/lldb-server/TestGdbRemoteAttachWait.py
@@ -0,0 +1,69 @@
+
+import os
+from time import sleep
+
+import gdbremote_testcase
+import lldbgdbserverutils
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestGdbRemoteAttachWait(gdbremote_testcase.GdbRemoteTestCaseBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ def test_attach_with_vAttachWait(self):
+ exe = '%s_%d' % (self.testMethodName, os.getpid())
+ self.build(dictionary={'EXE': exe})
+ self.set_inferior_startup_attach_manually()
+
+ server = self.connect_to_debug_monitor()
+ self.assertIsNotNone(server)
+
+
+ self.add_no_ack_remote_stream()
+ self.test_sequence.add_log_lines([
+ # Do the attach.
+ "read packet: $vAttachWait;{}#00".format(lldbgdbserverutils.gdbremote_hex_encode_string(exe)),
+ ], True)
+ # Run the stream until attachWait.
+ context = self.expect_gdbremote_sequence()
+ self.assertIsNotNone(context)
+
+ # Sleep so we're sure that the inferior is launched after we ask for the attach.
+ sleep(1)
+
+ # Launch the inferior.
+ inferior = self.launch_process_for_attach(
+ inferior_args=["sleep:60"],
+ exe_path=self.getBuildArtifact(exe))
+ self.assertIsNotNone(inferior)
+ self.assertTrue(inferior.pid > 0)
+ self.assertTrue(
+ lldbgdbserverutils.process_is_running(
+ inferior.pid, True))
+
+ # Make sure the attach succeeded.
+ self.test_sequence.add_log_lines([
+ {"direction": "send",
+ "regex": r"^\$T([0-9a-fA-F]{2})[^#]*#[0-9a-fA-F]{2}$",
+ "capture": {1: "stop_signal_hex"}},
+ ], True)
+ self.add_process_info_collection_packets()
+
+
+ # Run the stream sending the response..
+ context = self.expect_gdbremote_sequence()
+ self.assertIsNotNone(context)
+
+ # Gather process info response.
+ process_info = self.parse_process_info_response(context)
+ self.assertIsNotNone(process_info)
+
+ # Ensure the process id matches what we expected.
+ pid_text = process_info.get('pid', None)
+ self.assertIsNotNone(pid_text)
+ reported_pid = int(pid_text, base=16)
+ self.assertEqual(reported_pid, inferior.pid)
+
Index: lldb/source/Utility/StringExtractor.cpp
===================================================================
--- lldb/source/Utility/StringExtractor.cpp
+++ lldb/source/Utility/StringExtractor.cpp
@@ -297,11 +297,11 @@
return bytes_extracted;
}
-size_t StringExtractor::GetHexByteString(std::string &str) {
+size_t StringExtractor::GetHexByteString(std::string &str, bool set_eof_on_fail) {
str.clear();
str.reserve(GetBytesLeft() / 2);
char ch;
- while ((ch = GetHexU8()) != '\0')
+ while ((ch = GetHexU8(0, set_eof_on_fail)) != '\0')
str.append(1, ch);
return str.size();
}
Index: lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -1184,11 +1184,24 @@
}
} else
packet.PutCString("vAttachName");
+
packet.PutChar(';');
packet.PutBytesAsRawHex8(process_name, strlen(process_name),
endian::InlHostByteOrder(),
endian::InlHostByteOrder());
+ if (attach_info.HasWaitForLaunchInterval()) {
+ packet.PutChar(';');
+ packet.PutChar('I');
+ packet.PutHex32(attach_info.GetWaitForLaunchInterval());
+ }
+
+ if (attach_info.HasWaitForLaunchDuration()) {
+ packet.PutChar(';');
+ packet.PutChar('d');
+ packet.PutHex32(attach_info.GetWaitForLaunchDuration());
+ }
+
m_async_broadcaster.BroadcastEvent(
eBroadcastBitAsyncContinue,
new EventDataBytes(packet.GetString().data(), packet.GetSize()));
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
+++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h
@@ -59,6 +59,20 @@
/// attach operation.
Status AttachToProcess(lldb::pid_t pid);
+ /// Wait to attach to a process with a given name.
+ ///
+ /// This method supports waiting for the next instance of a process
+ /// with a given name and attaching llgs to that via the configured
+ /// Platform.
+ ///
+ /// \return
+ /// An Status object indicating the success or failure of the
+ /// attach operation.
+ Status AttachWaitProcess(llvm::StringRef process_name,
+ llvm::Optional<uint32_t> user_polling_interval,
+ llvm::Optional<uint32_t> timeout,
+ bool include_existing);
+
// NativeProcessProtocol::NativeDelegate overrides
void InitializeDelegate(NativeProcessProtocol *process) override;
@@ -170,6 +184,12 @@
PacketResult Handle_vAttach(StringExtractorGDBRemote &packet);
+ PacketResult Handle_qVAttachOrWaitSupported(StringExtractorGDBRemote &packet);
+
+ PacketResult Handle_vAttachWait(StringExtractorGDBRemote &packet);
+
+ PacketResult Handle_vAttachOrWait(StringExtractorGDBRemote &packet);
+
PacketResult Handle_D(StringExtractorGDBRemote &packet);
PacketResult Handle_qThreadStopInfo(StringExtractorGDBRemote &packet);
@@ -227,6 +247,12 @@
void StopSTDIOForwarding();
+ PacketResult
+ Handle_vAttachWait_or_vAttachOrWait(StringExtractorGDBRemote &packet,
+ bool include_existing,
+ const char *identifier);
+
+
// For GDBRemoteCommunicationServerLLGS only
GDBRemoteCommunicationServerLLGS(const GDBRemoteCommunicationServerLLGS &) =
delete;
Index: lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
===================================================================
--- lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
+++ lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp
@@ -159,6 +159,15 @@
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_vAttach,
&GDBRemoteCommunicationServerLLGS::Handle_vAttach);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::eServerPacketType_vAttachWait,
+ &GDBRemoteCommunicationServerLLGS::Handle_vAttachWait);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::eServerPacketType_qVAttachOrWaitSupported,
+ &GDBRemoteCommunicationServerLLGS::Handle_qVAttachOrWaitSupported);
+ RegisterMemberFunctionHandler(
+ StringExtractorGDBRemote::eServerPacketType_vAttachOrWait,
+ &GDBRemoteCommunicationServerLLGS::Handle_vAttachOrWait);
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_vCont,
&GDBRemoteCommunicationServerLLGS::Handle_vCont);
@@ -334,6 +343,100 @@
return Status();
}
+Status GDBRemoteCommunicationServerLLGS::AttachWaitProcess(
+ llvm::StringRef process_name,
+ llvm::Optional<uint32_t> user_polling_interval,
+ llvm::Optional<uint32_t> timeout, bool include_existing) {
+ Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+ std::chrono::milliseconds polling_interval;
+ if (user_polling_interval.hasValue()) {
+ polling_interval =
+ std::chrono::milliseconds(user_polling_interval.getValue());
+ LLDB_LOG(log, "using user-defined polling interval '{0}'",
+ polling_interval);
+ } else {
+ polling_interval = std::chrono::milliseconds(1);
+ }
+
+ // Create the matcher used to search the process list.
+ ProcessInstanceInfoList exclusion_list;
+ ProcessInstanceInfoMatch match_info;
+ match_info.GetProcessInfo().GetExecutableFile().SetFile(
+ process_name, llvm::sys::path::Style::posix);
+ match_info.SetNameMatchType(NameMatch::EndsWith);
+
+ if (!include_existing) {
+ // Create the excluded process list before polling begins.
+ Host::FindProcesses(match_info, exclusion_list);
+ LLDB_LOG(log, "placed '{0}' processes in the exclusion list.",
+ exclusion_list.size());
+ } else {
+ LLDB_LOG(log, "including existing processes in search", process_name);
+ }
+
+ LLDB_LOG(log, "waiting for '{0}' to appear", process_name);
+
+ auto is_in_exclusion_list = [&exclusion_list](ProcessInstanceInfo &info) {
+ for (auto &excluded : exclusion_list) {
+ if (excluded.GetProcessID() == info.GetProcessID()) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ auto now = std::chrono::system_clock::now();
+ auto target = now;
+ if (timeout.hasValue())
+ target += std::chrono::seconds(timeout.getValue());
+
+ ProcessInstanceInfoList loop_process_list;
+ while (!timeout.hasValue() || now < target) {
+ loop_process_list.clear();
+ if (Host::FindProcesses(match_info, loop_process_list)) {
+ // Remove all the elements that are in the exclusion list.
+ loop_process_list.erase(std::remove_if(loop_process_list.begin(),
+ loop_process_list.end(),
+ is_in_exclusion_list),
+ loop_process_list.end());
+
+ // One match! We found the desired process.
+ if (loop_process_list.size() == 1) {
+ auto matching_process_pid = loop_process_list[0].GetProcessID();
+ LLDB_LOG(log, "found pid {0}", matching_process_pid);
+ return AttachToProcess(matching_process_pid);
+ }
+
+ // Multiple matches! Return an error reporting the PIDs we found.
+ if (loop_process_list.size() > 1) {
+ StreamString error_stream;
+ error_stream.Printf(
+ "Multiple executables with name: '%s' found. Pids: ",
+ process_name.str().c_str());
+ for (size_t i = 0; i < loop_process_list.size() - 1; ++i) {
+ error_stream.Printf("%lu, ", loop_process_list[i].GetProcessID());
+ }
+ error_stream.Printf("%lu.", loop_process_list.back().GetProcessID());
+
+ Status error;
+ error.SetErrorString(error_stream.GetString());
+ return error;
+ }
+ }
+ // No matches, we have not found the process. Sleep until next poll.
+ LLDB_LOG(log, "sleep {0} seconds", polling_interval);
+ std::this_thread::sleep_for(polling_interval);
+ if (timeout.hasValue())
+ now = std::chrono::system_clock::now();
+ }
+
+ // Timed out
+ Status error;
+ error.SetErrorString("waitfor timed out.");
+ return error;
+}
+
void GDBRemoteCommunicationServerLLGS::InitializeDelegate(
NativeProcessProtocol *process) {
assert(process && "process cannot be NULL");
@@ -3188,6 +3291,94 @@
return SendStopReasonForState(m_debugged_process_up->GetState());
}
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_vAttachWait(
+ StringExtractorGDBRemote &packet) {
+ return Handle_vAttachWait_or_vAttachOrWait(packet, false, "vAttachWait");
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_vAttachOrWait(
+ StringExtractorGDBRemote &packet) {
+ return Handle_vAttachWait_or_vAttachOrWait(packet, true, "vAttachOrWait");
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_qVAttachOrWaitSupported(
+ StringExtractorGDBRemote &packet) {
+ return SendOKResponse();
+}
+
+GDBRemoteCommunication::PacketResult
+GDBRemoteCommunicationServerLLGS::Handle_vAttachWait_or_vAttachOrWait(
+ StringExtractorGDBRemote &packet, bool include_existing,
+ const char *identifier) {
+ Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
+
+ // Consume the ';' after the identifier.
+ packet.SetFilePos(strlen(identifier));
+
+ if (!packet.GetBytesLeft() || packet.GetChar() != ';')
+ return SendIllFormedResponse(
+ packet, "vAttachWait/vAttachOrWait missing expected ';'");
+
+ // Allocate the buffer for the process name from vAttachWait.
+ std::string process_name;
+ if (!packet.GetHexByteString(process_name, false))
+ return SendIllFormedResponse(
+ packet, "vAttachWait/vAttachOrWait failed to parse process name");
+
+ llvm::Optional<uint32_t> polling_interval;
+ llvm::Optional<uint32_t> waitfor_duration;
+
+ while (packet.GetBytesLeft() && *packet.Peek() == ';') {
+ // Skip the semi-colon.
+ packet.GetChar();
+
+ const char action = packet.GetChar();
+ switch (action) {
+ case 'I': {
+ uint32_t interval = packet.GetU32(UINT32_MAX, 16);
+ if (interval != UINT32_MAX) {
+ polling_interval = interval;
+ } else {
+ return SendIllFormedResponse(packet,
+ "vAttachWait/vAttachOrWait failed to "
+ "parse waitfor polling interval");
+ }
+ break;
+ }
+ case 'd': {
+ uint32_t duration = packet.GetU32(UINT32_MAX, 16);
+ if (duration != UINT32_MAX) {
+ waitfor_duration = duration;
+ } else {
+ return SendIllFormedResponse(
+ packet,
+ "vAttachWait/vAttachOrWait failed to parse waitfor duration");
+ }
+ break;
+ }
+ default:
+ return SendIllFormedResponse(packet, "Unsupported vAttachWait action");
+ break;
+ }
+ }
+
+ LLDB_LOG(log, "attempting to attach to process named '{0}'", process_name);
+
+ Status error = AttachWaitProcess(process_name, polling_interval,
+ waitfor_duration, include_existing);
+ if (error.Fail()) {
+ LLDB_LOG(log, "failed to attach to process named '{0}': {1}", process_name,
+ error);
+ return SendErrorResponse(error);
+ }
+
+ // Notify we attached by sending a stop packet.
+ return SendStopReasonForState(m_debugged_process_up->GetState());
+}
+
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerLLGS::Handle_D(StringExtractorGDBRemote &packet) {
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
Index: lldb/source/Interpreter/CommandObject.cpp
===================================================================
--- lldb/source/Interpreter/CommandObject.cpp
+++ lldb/source/Interpreter/CommandObject.cpp
@@ -1088,6 +1088,8 @@
{ eArgTypePermissionsNumber, "perms-numeric", CommandCompletions::eNoCompletion, { nullptr, false }, "Permissions given as an octal number (e.g. 755)." },
{ eArgTypePermissionsString, "perms=string", CommandCompletions::eNoCompletion, { nullptr, false }, "Permissions given as a string value (e.g. rw-r-xr--)." },
{ eArgTypePid, "pid", CommandCompletions::eProcessIDCompletion, { nullptr, false }, "The process ID number." },
+ { eArgTypeWaitforInterval, "waitfor-interval", CommandCompletions::eProcessIDCompletion, { nullptr, false }, "The interval in milliseconds that waitfor uses to poll the list of processes. Defaults to 1." },
+ { eArgTypeWaitforDuration, "waitfor-duration", CommandCompletions::eProcessIDCompletion, { nullptr, false }, "The timout that in seconds when waiting for a process with waitfor. Defaults to infinite." },
{ eArgTypePlugin, "plugin", CommandCompletions::eProcessPluginCompletion, { nullptr, false }, "Help text goes here." },
{ eArgTypeProcessName, "process-name", CommandCompletions::eProcessNameCompletion, { nullptr, false }, "The name of the process." },
{ eArgTypePythonClass, "python-class", CommandCompletions::eNoCompletion, { nullptr, false }, "The name of a Python class." },
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -657,6 +657,12 @@
Group<2>, Desc<"Include existing processes when doing attach -w.">;
def process_attach_waitfor : Option<"waitfor", "w">, Group<2>,
Desc<"Wait for the process with <process-name> to launch.">;
+ def process_attach_waitfor_interval : Option<"waitfor-interval", "I">,
+ Group<2>, Arg<"WaitforInterval">,
+ Desc<"The interval that waitfor uses to poll the list of processes.">;
+ def process_attach_waitfor_duration : Option<"waitfor-duration", "d">,
+ Group<2>, Arg<"WaitforDuration">,
+ Desc<"The interval that waitfor uses to poll the list of processes.">;
}
let Command = "process continue" in {
Index: lldb/source/Commands/CommandObjectProcess.cpp
===================================================================
--- lldb/source/Commands/CommandObjectProcess.cpp
+++ lldb/source/Commands/CommandObjectProcess.cpp
@@ -306,6 +306,26 @@
attach_info.SetIgnoreExisting(false);
break;
+ case 'I':
+ uint32_t interval;
+ if (option_arg.getAsInteger(0, interval)) {
+ error.SetErrorStringWithFormat("invalid polling interval '%s'",
+ option_arg.str().c_str());
+ } else {
+ attach_info.SetWaitForLaunchInterval(interval);
+ }
+ break;
+
+ case 'd':
+ uint32_t duration;
+ if (option_arg.getAsInteger(0, duration)) {
+ error.SetErrorStringWithFormat("invalid duration '%s'",
+ option_arg.str().c_str());
+ } else {
+ attach_info.SetWaitForLaunchDuration(duration);
+ }
+ break;
+
default:
llvm_unreachable("Unimplemented option");
}
Index: lldb/include/lldb/lldb-enumerations.h
===================================================================
--- lldb/include/lldb/lldb-enumerations.h
+++ lldb/include/lldb/lldb-enumerations.h
@@ -555,6 +555,8 @@
eArgTypePermissionsNumber,
eArgTypePermissionsString,
eArgTypePid,
+ eArgTypeWaitforInterval,
+ eArgTypeWaitforDuration,
eArgTypePlugin,
eArgTypeProcessName,
eArgTypePythonClass,
Index: lldb/include/lldb/Utility/StringExtractor.h
===================================================================
--- lldb/include/lldb/Utility/StringExtractor.h
+++ lldb/include/lldb/Utility/StringExtractor.h
@@ -89,7 +89,7 @@
size_t GetHexBytesAvail(llvm::MutableArrayRef<uint8_t> dest);
- size_t GetHexByteString(std::string &str);
+ size_t GetHexByteString(std::string &str, bool set_eof_on_fail = true);
size_t GetHexByteStringFixedLength(std::string &str, uint32_t nibble_length);
Index: lldb/include/lldb/Target/Process.h
===================================================================
--- lldb/include/lldb/Target/Process.h
+++ lldb/include/lldb/Target/Process.h
@@ -111,14 +111,18 @@
ProcessAttachInfo()
: ProcessInstanceInfo(), m_listener_sp(), m_hijack_listener_sp(),
m_plugin_name(), m_resume_count(0), m_wait_for_launch(false),
- m_ignore_existing(true), m_continue_once_attached(false),
- m_detach_on_error(true), m_async(false) {}
+ m_wait_for_launch_interval(UINT32_MAX),
+ m_wait_for_launch_duration(UINT32_MAX), m_ignore_existing(true),
+ m_continue_once_attached(false), m_detach_on_error(true),
+ m_async(false) {}
ProcessAttachInfo(const ProcessLaunchInfo &launch_info)
: ProcessInstanceInfo(), m_listener_sp(), m_hijack_listener_sp(),
m_plugin_name(), m_resume_count(0), m_wait_for_launch(false),
- m_ignore_existing(true), m_continue_once_attached(false),
- m_detach_on_error(true), m_async(false) {
+ m_wait_for_launch_interval(UINT32_MAX),
+ m_wait_for_launch_duration(UINT32_MAX), m_ignore_existing(true),
+ m_continue_once_attached(false), m_detach_on_error(true),
+ m_async(false) {
ProcessInfo::operator=(launch_info);
SetProcessPluginName(launch_info.GetProcessPluginName());
SetResumeCount(launch_info.GetResumeCount());
@@ -131,6 +135,30 @@
void SetWaitForLaunch(bool b) { m_wait_for_launch = b; }
+ bool HasWaitForLaunchInterval() const {
+ return m_wait_for_launch_interval != UINT32_MAX;
+ }
+
+ uint32_t GetWaitForLaunchInterval() const {
+ return m_wait_for_launch_interval;
+ }
+
+ void SetWaitForLaunchInterval(uint32_t new_interval) {
+ m_wait_for_launch_interval = new_interval;
+ }
+
+ bool HasWaitForLaunchDuration() const {
+ return m_wait_for_launch_duration != UINT32_MAX;
+ }
+
+ uint32_t GetWaitForLaunchDuration() const {
+ return m_wait_for_launch_duration;
+ }
+
+ void SetWaitForLaunchDuration(uint32_t new_duration) {
+ m_wait_for_launch_duration = new_duration;
+ }
+
bool GetAsync() const { return m_async; }
void SetAsync(bool b) { m_async = b; }
@@ -160,6 +188,8 @@
m_plugin_name.clear();
m_resume_count = 0;
m_wait_for_launch = false;
+ m_wait_for_launch_interval = UINT32_MAX;
+ m_wait_for_launch_duration = UINT32_MAX;
m_ignore_existing = true;
m_continue_once_attached = false;
}
@@ -199,6 +229,8 @@
std::string m_plugin_name;
uint32_t m_resume_count; // How many times do we resume after launching
bool m_wait_for_launch;
+ uint32_t m_wait_for_launch_interval;
+ uint32_t m_wait_for_launch_duration;
bool m_ignore_existing;
bool m_continue_once_attached; // Supports the use-case scenario of
// immediately continuing the process once
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits