Skip to content

Commit 1765eee

Browse files
mgornymemfrob
authored andcommitted
[lldb] [server] Support for multiprocess extension
Add a minimal support for the multiprocess extension in lldb-server. The server indicates support for it via qSupported, and accepts thread-ids containing a PID. However, it still does not support debugging more than one inferior, so any other PID value results in an error. Differential Revision: https://reviews.llvm.org/D98482
1 parent cc0b7e9 commit 1765eee

File tree

11 files changed

+431
-10
lines changed

11 files changed

+431
-10
lines changed

lldb/include/lldb/Utility/StringExtractorGDBRemote.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,15 @@ class StringExtractorGDBRemote : public StringExtractor {
193193

194194
size_t GetEscapedBinaryData(std::string &str);
195195

196+
static constexpr lldb::pid_t AllProcesses = UINT64_MAX;
197+
static constexpr lldb::tid_t AllThreads = UINT64_MAX;
198+
199+
// Read thread-id from the packet. If the packet is valid, returns
200+
// the pair (PID, TID), otherwise returns llvm::None. If the packet
201+
// does not list a PID, default_pid is used.
202+
llvm::Optional<std::pair<lldb::pid_t, lldb::tid_t>>
203+
GetPidTid(lldb::pid_t default_pid);
204+
196205
protected:
197206
ResponseValidatorCallback m_validator;
198207
void *m_validator_baton;

lldb/packages/Python/lldbsuite/test/tools/lldb-server/gdbremote_testcase.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,8 @@ def add_qSupported_packets(self):
902902
"qXfer:libraries-svr4:read",
903903
"qXfer:features:read",
904904
"qEcho",
905-
"QPassSignals"
905+
"QPassSignals",
906+
"multiprocess",
906907
]
907908

908909
def parse_qSupported_response(self, context):

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ class GDBRemoteCommunicationClient : public GDBRemoteClientBase {
558558
LazyBool m_supports_jGetSharedCacheInfo;
559559
LazyBool m_supports_QPassSignals;
560560
LazyBool m_supports_error_string_reply;
561+
LazyBool m_supports_multiprocess;
561562

562563
bool m_supports_qProcessInfoPID : 1, m_supports_qfProcessInfo : 1,
563564
m_supports_qUserName : 1, m_supports_qGroupName : 1,

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ GDBRemoteCommunicationServerCommon::Handle_qSupported(
848848
response.PutCString(";qXfer:auxv:read+");
849849
response.PutCString(";qXfer:libraries-svr4:read+");
850850
#endif
851+
response.PutCString(";multiprocess+");
851852

852853
return SendPacketNoLock(response.GetString());
853854
}

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1732,10 +1732,13 @@ GDBRemoteCommunicationServerLLGS::Handle_vCont(
17321732
// Consume the separator.
17331733
packet.GetChar();
17341734

1735-
thread_action.tid = packet.GetHexMaxU64(false, LLDB_INVALID_THREAD_ID);
1736-
if (thread_action.tid == LLDB_INVALID_THREAD_ID)
1737-
return SendIllFormedResponse(
1738-
packet, "Could not parse thread number in vCont packet");
1735+
llvm::Expected<lldb::tid_t> tid_ret = ReadTid(packet, /*allow_all=*/true);
1736+
if (!tid_ret)
1737+
return SendErrorResponse(tid_ret.takeError());
1738+
1739+
thread_action.tid = tid_ret.get();
1740+
if (thread_action.tid == StringExtractorGDBRemote::AllThreads)
1741+
thread_action.tid = LLDB_INVALID_THREAD_ID;
17391742
}
17401743

17411744
thread_actions.Append(thread_action);
@@ -2220,10 +2223,11 @@ GDBRemoteCommunicationServerLLGS::Handle_H(StringExtractorGDBRemote &packet) {
22202223
}
22212224

22222225
// Parse out the thread number.
2223-
// FIXME return a parse success/fail value. All values are valid here.
2224-
const lldb::tid_t tid =
2225-
packet.GetHexMaxU64(false, std::numeric_limits<lldb::tid_t>::max());
2226+
llvm::Expected<lldb::tid_t> tid_ret = ReadTid(packet, /*allow_all=*/true);
2227+
if (!tid_ret)
2228+
return SendErrorResponse(tid_ret.takeError());
22262229

2230+
lldb::tid_t tid = tid_ret.get();
22272231
// Ensure we have the given thread when not specifying -1 (all threads) or 0
22282232
// (any thread).
22292233
if (tid != LLDB_INVALID_THREAD_ID && tid != 0) {
@@ -3653,3 +3657,36 @@ std::string GDBRemoteCommunicationServerLLGS::XMLEncodeAttributeValue(
36533657
}
36543658
return result;
36553659
}
3660+
3661+
llvm::Expected<lldb::tid_t>
3662+
GDBRemoteCommunicationServerLLGS::ReadTid(StringExtractorGDBRemote &packet,
3663+
bool allow_all) {
3664+
assert(m_debugged_process_up);
3665+
assert(m_debugged_process_up->GetID() != LLDB_INVALID_PROCESS_ID);
3666+
3667+
auto pid_tid = packet.GetPidTid(m_debugged_process_up->GetID());
3668+
if (!pid_tid)
3669+
return llvm::make_error<StringError>(inconvertibleErrorCode(),
3670+
"Malformed thread-id");
3671+
3672+
lldb::pid_t pid = pid_tid->first;
3673+
lldb::tid_t tid = pid_tid->second;
3674+
3675+
if (!allow_all && pid == StringExtractorGDBRemote::AllProcesses)
3676+
return llvm::make_error<StringError>(
3677+
inconvertibleErrorCode(),
3678+
llvm::formatv("PID value {0} not allowed", pid == 0 ? 0 : -1));
3679+
3680+
if (!allow_all && tid == StringExtractorGDBRemote::AllThreads)
3681+
return llvm::make_error<StringError>(
3682+
inconvertibleErrorCode(),
3683+
llvm::formatv("TID value {0} not allowed", tid == 0 ? 0 : -1));
3684+
3685+
if (pid != StringExtractorGDBRemote::AllProcesses) {
3686+
if (pid != m_debugged_process_up->GetID())
3687+
return llvm::make_error<StringError>(
3688+
inconvertibleErrorCode(), llvm::formatv("PID {0} not debugged", pid));
3689+
}
3690+
3691+
return tid;
3692+
}

lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,15 @@ class GDBRemoteCommunicationServerLLGS
244244

245245
void StopSTDIOForwarding();
246246

247+
// Read thread-id from packet. If the thread-id is correct, returns it.
248+
// Otherwise, returns the error.
249+
//
250+
// If allow_all is true, then the pid/tid value of -1 ('all') will be allowed.
251+
// In any case, the function assumes that exactly one inferior is being
252+
// debugged and rejects pid values that do no match that inferior.
253+
llvm::Expected<lldb::tid_t> ReadTid(StringExtractorGDBRemote &packet,
254+
bool allow_all = false);
255+
247256
// For GDBRemoteCommunicationServerLLGS only
248257
GDBRemoteCommunicationServerLLGS(const GDBRemoteCommunicationServerLLGS &) =
249258
delete;

lldb/source/Utility/StringExtractorGDBRemote.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#include <ctype.h>
1212
#include <string.h>
1313

14+
constexpr lldb::pid_t StringExtractorGDBRemote::AllProcesses;
15+
constexpr lldb::tid_t StringExtractorGDBRemote::AllThreads;
16+
1417
StringExtractorGDBRemote::ResponseType
1518
StringExtractorGDBRemote::GetResponseType() const {
1619
if (m_packet.empty())
@@ -606,3 +609,46 @@ bool StringExtractorGDBRemote::ValidateResponse() const {
606609
else
607610
return true; // No validator, so response is valid
608611
}
612+
613+
llvm::Optional<std::pair<lldb::pid_t, lldb::tid_t>>
614+
StringExtractorGDBRemote::GetPidTid(lldb::pid_t default_pid) {
615+
llvm::StringRef view = llvm::StringRef(m_packet).substr(m_index);
616+
size_t initial_length = view.size();
617+
lldb::pid_t pid = default_pid;
618+
lldb::tid_t tid;
619+
620+
if (view.consume_front("p")) {
621+
// process identifier
622+
if (view.consume_front("-1")) {
623+
// -1 is a special case
624+
pid = AllProcesses;
625+
} else if (view.consumeInteger(16, pid) || pid == 0) {
626+
// not a valid hex integer OR unsupported pid 0
627+
m_index = UINT64_MAX;
628+
return llvm::None;
629+
}
630+
631+
// "." must follow if we expect TID too; otherwise, we assume -1
632+
if (!view.consume_front(".")) {
633+
// update m_index
634+
m_index += initial_length - view.size();
635+
636+
return {{pid, AllThreads}};
637+
}
638+
}
639+
640+
// thread identifier
641+
if (view.consume_front("-1")) {
642+
// -1 is a special case
643+
tid = AllThreads;
644+
} else if (view.consumeInteger(16, tid) || tid == 0 || pid == AllProcesses) {
645+
// not a valid hex integer OR tid 0 OR pid -1 + a specific tid
646+
m_index = UINT64_MAX;
647+
return llvm::None;
648+
}
649+
650+
// update m_index
651+
m_index += initial_length - view.size();
652+
653+
return {{pid, tid}};
654+
}

lldb/test/API/tools/lldb-server/TestGdbRemote_vContThreads.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ def send_and_check_signal(self, vCont_data, threads):
4949
for x in range(1, len(threads)+1))
5050
self.assertEqual(tids, sorted(threads))
5151

52+
def get_pid(self):
53+
self.add_process_info_collection_packets()
54+
context = self.expect_gdbremote_sequence()
55+
self.assertIsNotNone(context)
56+
procinfo = self.parse_process_info_response(context)
57+
return int(procinfo['pid'], 16)
58+
5259
@skipIfWindows
5360
@expectedFailureNetBSD
5461
@expectedFailureDarwin # No signals delivered
@@ -88,6 +95,82 @@ def test_signal_all_threads(self):
8895
*threads),
8996
threads)
9097

98+
@skipIfWindows
99+
@expectedFailureNetBSD
100+
def test_signal_process_by_pid(self):
101+
self.build()
102+
self.set_inferior_startup_launch()
103+
104+
threads = self.start_threads(1)
105+
self.send_and_check_signal(
106+
"C{0:x}:p{1:x}".format(
107+
lldbutil.get_signal_number('SIGUSR1'),
108+
self.get_pid()),
109+
threads)
110+
111+
@skipIfWindows
112+
@expectedFailureNetBSD
113+
def test_signal_process_minus_one(self):
114+
self.build()
115+
self.set_inferior_startup_launch()
116+
117+
threads = self.start_threads(1)
118+
self.send_and_check_signal(
119+
"C{0:x}:p-1".format(
120+
lldbutil.get_signal_number('SIGUSR1')),
121+
threads)
122+
123+
@skipIfWindows
124+
@expectedFailureNetBSD
125+
def test_signal_minus_one(self):
126+
self.build()
127+
self.set_inferior_startup_launch()
128+
129+
threads = self.start_threads(1)
130+
self.send_and_check_signal(
131+
"C{0:x}:-1".format(lldbutil.get_signal_number('SIGUSR1')),
132+
threads)
133+
134+
@skipIfWindows
135+
@expectedFailureNetBSD
136+
def test_signal_all_threads_by_pid(self):
137+
self.build()
138+
self.set_inferior_startup_launch()
139+
140+
threads = self.start_threads(1)
141+
# try sending a signal to two threads (= the process)
142+
self.send_and_check_signal(
143+
"C{0:x}:p{1:x}.{2:x};C{0:x}:p{1:x}.{3:x}".format(
144+
lldbutil.get_signal_number('SIGUSR1'),
145+
self.get_pid(),
146+
*threads),
147+
threads)
148+
149+
@skipIfWindows
150+
@expectedFailureNetBSD
151+
def test_signal_minus_one_by_pid(self):
152+
self.build()
153+
self.set_inferior_startup_launch()
154+
155+
threads = self.start_threads(1)
156+
self.send_and_check_signal(
157+
"C{0:x}:p{1:x}.-1".format(
158+
lldbutil.get_signal_number('SIGUSR1'),
159+
self.get_pid()),
160+
threads)
161+
162+
@skipIfWindows
163+
@expectedFailureNetBSD
164+
def test_signal_minus_one_by_minus_one(self):
165+
self.build()
166+
self.set_inferior_startup_launch()
167+
168+
threads = self.start_threads(1)
169+
self.send_and_check_signal(
170+
"C{0:x}:p-1.-1".format(
171+
lldbutil.get_signal_number('SIGUSR1')),
172+
threads)
173+
91174
@skipUnlessPlatform(["netbsd"])
92175
def test_signal_two_of_three_threads(self):
93176
self.build()

lldb/test/API/tools/lldb-server/TestLldbGdbServer.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def test_p_returns_correct_data_size_for_each_qRegisterInfo_launch(self):
349349
# Increment loop
350350
reg_index += 1
351351

352-
def Hg_switches_to_3_threads(self):
352+
def Hg_switches_to_3_threads(self, pass_pid=False):
353353
# Startup the inferior with three threads (main + 2 new ones).
354354
procs = self.prep_debug_monitor_and_inferior(
355355
inferior_args=["thread:new", "thread:new"])
@@ -363,12 +363,16 @@ def Hg_switches_to_3_threads(self):
363363
threads = self.wait_for_thread_count(3)
364364
self.assertEqual(len(threads), 3)
365365

366+
pid_str = ""
367+
if pass_pid:
368+
pid_str = "p{0:x}.".format(procs["inferior"].pid)
369+
366370
# verify we can $H to each thead, and $qC matches the thread we set.
367371
for thread in threads:
368372
# Change to each thread, verify current thread id.
369373
self.reset_test_sequence()
370374
self.test_sequence.add_log_lines(
371-
["read packet: $Hg{0:x}#00".format(thread), # Set current thread.
375+
["read packet: $Hg{0}{1:x}#00".format(pid_str, thread), # Set current thread.
372376
"send packet: $OK#00",
373377
"read packet: $qC#00",
374378
{"direction": "send", "regex": r"^\$QC([0-9a-fA-F]+)#", "capture": {1: "thread_id"}}],
@@ -393,6 +397,50 @@ def test_Hg_switches_to_3_threads_attach(self):
393397
self.set_inferior_startup_attach()
394398
self.Hg_switches_to_3_threads()
395399

400+
@expectedFailureAll(oslist=["windows"]) # expect 4 threads
401+
def test_Hg_switches_to_3_threads_attach_pass_correct_pid(self):
402+
self.build()
403+
self.set_inferior_startup_attach()
404+
self.Hg_switches_to_3_threads(pass_pid=True)
405+
406+
def Hg_fails_on_pid(self, pass_pid):
407+
# Start the inferior.
408+
procs = self.prep_debug_monitor_and_inferior(
409+
inferior_args=["thread:new"])
410+
411+
self.run_process_then_stop(run_seconds=1)
412+
413+
threads = self.wait_for_thread_count(2)
414+
self.assertEqual(len(threads), 2)
415+
416+
if pass_pid == -1:
417+
pid_str = "p-1."
418+
else:
419+
pid_str = "p{0:x}.".format(pass_pid)
420+
thread = threads[1]
421+
422+
self.test_sequence.add_log_lines(
423+
["read packet: $Hg{0}{1:x}#00".format(pid_str, thread), # Set current thread.
424+
"send packet: $Eff#00"],
425+
True)
426+
427+
self.expect_gdbremote_sequence()
428+
429+
def test_Hg_fails_on_another_pid(self):
430+
self.build()
431+
self.set_inferior_startup_launch()
432+
self.Hg_fails_on_pid(1)
433+
434+
def test_Hg_fails_on_zero_pid(self):
435+
self.build()
436+
self.set_inferior_startup_launch()
437+
self.Hg_fails_on_pid(0)
438+
439+
def test_Hg_fails_on_minus_one_pid(self):
440+
self.build()
441+
self.set_inferior_startup_launch()
442+
self.Hg_fails_on_pid(-1)
443+
396444
def Hc_then_Csignal_signals_correct_thread(self, segfault_signo):
397445
# NOTE only run this one in inferior-launched mode: we can't grab inferior stdout when running attached,
398446
# and the test requires getting stdout from the exe.

lldb/unittests/Utility/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ add_lldb_unittest(UtilityTests
2929
StatusTest.cpp
3030
StreamTeeTest.cpp
3131
StreamTest.cpp
32+
StringExtractorGDBRemoteTest.cpp
3233
StringExtractorTest.cpp
3334
StringLexerTest.cpp
3435
StringListTest.cpp

0 commit comments

Comments
 (0)