xref: /llvm-project/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py (revision 59a3f65f5ed6b25b584d504fe4cf8473d4029ff3)
1import lldb
2import binascii
3import os.path
4from lldbsuite.test.lldbtest import *
5from lldbsuite.test.decorators import *
6from lldbsuite.test.gdbclientutils import *
7from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase
8
9
10class TestGDBRemoteClient(GDBRemoteTestBase):
11
12    mydir = TestBase.compute_mydir(__file__)
13
14    class gPacketResponder(MockGDBServerResponder):
15        registers = [
16            "name:rax;bitsize:64;offset:0;encoding:uint;format:hex;set:General Purpose Registers;ehframe:0;dwarf:0;",
17            "name:rbx;bitsize:64;offset:8;encoding:uint;format:hex;set:General Purpose Registers;ehframe:3;dwarf:3;",
18            "name:rcx;bitsize:64;offset:16;encoding:uint;format:hex;set:General Purpose Registers;ehframe:2;dwarf:2;generic:arg4;",
19            "name:rdx;bitsize:64;offset:24;encoding:uint;format:hex;set:General Purpose Registers;ehframe:1;dwarf:1;generic:arg3;",
20            "name:rdi;bitsize:64;offset:32;encoding:uint;format:hex;set:General Purpose Registers;ehframe:5;dwarf:5;generic:arg1;",
21            "name:rsi;bitsize:64;offset:40;encoding:uint;format:hex;set:General Purpose Registers;ehframe:4;dwarf:4;generic:arg2;",
22            "name:rbp;bitsize:64;offset:48;encoding:uint;format:hex;set:General Purpose Registers;ehframe:6;dwarf:6;generic:fp;",
23            "name:rsp;bitsize:64;offset:56;encoding:uint;format:hex;set:General Purpose Registers;ehframe:7;dwarf:7;generic:sp;",
24        ]
25
26        def qRegisterInfo(self, num):
27            try:
28                return self.registers[num]
29            except IndexError:
30                return "E45"
31
32        def readRegisters(self):
33            return len(self.registers) * 16 * '0'
34
35        def readRegister(self, register):
36            return "0000000000000000"
37
38    def test_connect(self):
39        """Test connecting to a remote gdb server"""
40        target = self.createTarget("a.yaml")
41        process = self.connect(target)
42        self.assertPacketLogContains(["qProcessInfo", "qfThreadInfo"])
43
44    def test_attach_fail(self):
45        error_msg = "mock-error-msg"
46
47        class MyResponder(MockGDBServerResponder):
48            # Pretend we don't have any process during the initial queries.
49            def qC(self):
50                return "E42"
51
52            def qfThreadInfo(self):
53                return "OK" # No threads.
54
55            # Then, when we are asked to attach, error out.
56            def vAttach(self, pid):
57                return "E42;" + binascii.hexlify(error_msg.encode()).decode()
58
59        self.server.responder = MyResponder()
60
61        target = self.dbg.CreateTarget("")
62        process = self.connect(target)
63        lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, [lldb.eStateConnected])
64
65        error = lldb.SBError()
66        target.AttachToProcessWithID(lldb.SBListener(), 47, error)
67        self.assertEquals(error_msg, error.GetCString())
68
69    def test_launch_fail(self):
70        class MyResponder(MockGDBServerResponder):
71            # Pretend we don't have any process during the initial queries.
72            def qC(self):
73                return "E42"
74
75            def qfThreadInfo(self):
76                return "OK" # No threads.
77
78            # Then, when we are asked to attach, error out.
79            def A(self, packet):
80                return "E47"
81
82        self.server.responder = MyResponder()
83
84        target = self.createTarget("a.yaml")
85        process = self.connect(target)
86        lldbutil.expect_state_changes(self, self.dbg.GetListener(), process, [lldb.eStateConnected])
87
88        error = lldb.SBError()
89        target.Launch(lldb.SBListener(), None, None, None, None, None,
90                None, 0, True, error)
91        self.assertEquals("'A' packet returned an error: 71", error.GetCString())
92
93    def test_read_registers_using_g_packets(self):
94        """Test reading registers using 'g' packets (default behavior)"""
95        self.dbg.HandleCommand(
96                "settings set plugin.process.gdb-remote.use-g-packet-for-reading true")
97        self.addTearDownHook(lambda:
98                self.runCmd("settings set plugin.process.gdb-remote.use-g-packet-for-reading false"))
99        self.server.responder = self.gPacketResponder()
100        target = self.createTarget("a.yaml")
101        process = self.connect(target)
102
103        self.assertEquals(1, self.server.responder.packetLog.count("g"))
104        self.server.responder.packetLog = []
105        self.read_registers(process)
106        # Reading registers should not cause any 'p' packets to be exchanged.
107        self.assertEquals(
108                0, len([p for p in self.server.responder.packetLog if p.startswith("p")]))
109
110    def test_read_registers_using_p_packets(self):
111        """Test reading registers using 'p' packets"""
112        self.dbg.HandleCommand(
113                "settings set plugin.process.gdb-remote.use-g-packet-for-reading false")
114        self.server.responder = self.gPacketResponder()
115        target = self.createTarget("a.yaml")
116        process = self.connect(target)
117
118        self.read_registers(process)
119        self.assertNotIn("g", self.server.responder.packetLog)
120        self.assertGreater(
121                len([p for p in self.server.responder.packetLog if p.startswith("p")]), 0)
122
123    def test_write_registers_using_P_packets(self):
124        """Test writing registers using 'P' packets (default behavior)"""
125        self.server.responder = self.gPacketResponder()
126        target = self.createTarget("a.yaml")
127        process = self.connect(target)
128
129        self.write_registers(process)
130        self.assertEquals(0, len(
131                [p for p in self.server.responder.packetLog if p.startswith("G")]))
132        self.assertGreater(
133                len([p for p in self.server.responder.packetLog if p.startswith("P")]), 0)
134
135    def test_write_registers_using_G_packets(self):
136        """Test writing registers using 'G' packets"""
137
138        class MyResponder(self.gPacketResponder):
139            def readRegister(self, register):
140                # empty string means unsupported
141                return ""
142
143        self.server.responder = MyResponder()
144        target = self.createTarget("a.yaml")
145        process = self.connect(target)
146
147        self.write_registers(process)
148        self.assertEquals(0, len(
149                [p for p in self.server.responder.packetLog if p.startswith("P")]))
150        self.assertGreater(len(
151                [p for p in self.server.responder.packetLog if p.startswith("G")]), 0)
152
153    def read_registers(self, process):
154        self.for_each_gpr(
155                process, lambda r: self.assertEquals("0x0000000000000000", r.GetValue()))
156
157    def write_registers(self, process):
158        self.for_each_gpr(
159                process, lambda r: r.SetValueFromCString("0x0000000000000000"))
160
161    def for_each_gpr(self, process, operation):
162        registers = process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetRegisters()
163        self.assertGreater(registers.GetSize(), 0)
164        regSet = registers[0]
165        numChildren = regSet.GetNumChildren()
166        self.assertGreater(numChildren, 0)
167        for i in range(numChildren):
168            operation(regSet.GetChildAtIndex(i))
169
170    def test_launch_A(self):
171        class MyResponder(MockGDBServerResponder):
172            def __init__(self, *args, **kwargs):
173                self.started = False
174                return super().__init__(*args, **kwargs)
175
176            def qC(self):
177                if self.started:
178                    return "QCp10.10"
179                else:
180                    return "E42"
181
182            def qfThreadInfo(self):
183                if self.started:
184                    return "mp10.10"
185                else:
186                   return "E42"
187
188            def qsThreadInfo(self):
189                return "l"
190
191            def A(self, packet):
192                self.started = True
193                return "OK"
194
195            def qLaunchSuccess(self):
196                if self.started:
197                    return "OK"
198                return "E42"
199
200        self.server.responder = MyResponder()
201
202        target = self.createTarget("a.yaml")
203        # NB: apparently GDB packets are using "/" on Windows too
204        exe_path = self.getBuildArtifact("a").replace(os.path.sep, '/')
205        exe_hex = binascii.b2a_hex(exe_path.encode()).decode()
206        process = self.connect(target)
207        lldbutil.expect_state_changes(self, self.dbg.GetListener(), process,
208                                      [lldb.eStateConnected])
209
210        target.Launch(lldb.SBListener(),
211                      ["arg1", "arg2", "arg3"],  # argv
212                      [],  # envp
213                      None,  # stdin_path
214                      None,  # stdout_path
215                      None,  # stderr_path
216                      None,  # working_directory
217                      0,  # launch_flags
218                      True,  # stop_at_entry
219                      lldb.SBError())  # error
220        self.assertTrue(process, PROCESS_IS_VALID)
221        self.assertEqual(process.GetProcessID(), 16)
222
223        self.assertPacketLogContains([
224          "A%d,0,%s,8,1,61726731,8,2,61726732,8,3,61726733" % (
225              len(exe_hex), exe_hex),
226        ])
227
228    def test_launch_vRun(self):
229        class MyResponder(MockGDBServerResponder):
230            def __init__(self, *args, **kwargs):
231                self.started = False
232                return super().__init__(*args, **kwargs)
233
234            def qC(self):
235                if self.started:
236                    return "QCp10.10"
237                else:
238                    return "E42"
239
240            def qfThreadInfo(self):
241                if self.started:
242                    return "mp10.10"
243                else:
244                   return "E42"
245
246            def qsThreadInfo(self):
247                return "l"
248
249            def vRun(self, packet):
250                self.started = True
251                return "T13"
252
253            def A(self, packet):
254                return "E28"
255
256        self.server.responder = MyResponder()
257
258        target = self.createTarget("a.yaml")
259        # NB: apparently GDB packets are using "/" on Windows too
260        exe_path = self.getBuildArtifact("a").replace(os.path.sep, '/')
261        exe_hex = binascii.b2a_hex(exe_path.encode()).decode()
262        process = self.connect(target)
263        lldbutil.expect_state_changes(self, self.dbg.GetListener(), process,
264                                      [lldb.eStateConnected])
265
266        process = target.Launch(lldb.SBListener(),
267                                ["arg1", "arg2", "arg3"],  # argv
268                                [],  # envp
269                                None,  # stdin_path
270                                None,  # stdout_path
271                                None,  # stderr_path
272                                None,  # working_directory
273                                0,  # launch_flags
274                                True,  # stop_at_entry
275                                lldb.SBError())  # error
276        self.assertTrue(process, PROCESS_IS_VALID)
277        self.assertEqual(process.GetProcessID(), 16)
278
279        self.assertPacketLogContains([
280          "vRun;%s;61726731;61726732;61726733" % (exe_hex,)
281        ])
282
283    def test_launch_QEnvironment(self):
284        class MyResponder(MockGDBServerResponder):
285            def qC(self):
286                return "E42"
287
288            def qfThreadInfo(self):
289               return "E42"
290
291            def vRun(self, packet):
292                self.started = True
293                return "E28"
294
295        self.server.responder = MyResponder()
296
297        target = self.createTarget("a.yaml")
298        process = self.connect(target)
299        lldbutil.expect_state_changes(self, self.dbg.GetListener(), process,
300                                      [lldb.eStateConnected])
301
302        target.Launch(lldb.SBListener(),
303                      [],  # argv
304                      ["PLAIN=foo",
305                       "NEEDSENC=frob$",
306                       "NEEDSENC2=fr*ob",
307                       "NEEDSENC3=fro}b",
308                       "NEEDSENC4=f#rob",
309                       "EQUALS=foo=bar",
310                       ],  # envp
311                      None,  # stdin_path
312                      None,  # stdout_path
313                      None,  # stderr_path
314                      None,  # working_directory
315                      0,  # launch_flags
316                      True,  # stop_at_entry
317                      lldb.SBError())  # error
318
319        self.assertPacketLogContains([
320          "QEnvironment:PLAIN=foo",
321          "QEnvironmentHexEncoded:4e45454453454e433d66726f6224",
322          "QEnvironmentHexEncoded:4e45454453454e43323d66722a6f62",
323          "QEnvironmentHexEncoded:4e45454453454e43333d66726f7d62",
324          "QEnvironmentHexEncoded:4e45454453454e43343d6623726f62",
325          "QEnvironment:EQUALS=foo=bar",
326        ])
327
328    def test_launch_QEnvironmentHexEncoded_only(self):
329        class MyResponder(MockGDBServerResponder):
330            def qC(self):
331                return "E42"
332
333            def qfThreadInfo(self):
334               return "E42"
335
336            def vRun(self, packet):
337                self.started = True
338                return "E28"
339
340            def QEnvironment(self, packet):
341                return ""
342
343        self.server.responder = MyResponder()
344
345        target = self.createTarget("a.yaml")
346        process = self.connect(target)
347        lldbutil.expect_state_changes(self, self.dbg.GetListener(), process,
348                                      [lldb.eStateConnected])
349
350        target.Launch(lldb.SBListener(),
351                      [],  # argv
352                      ["PLAIN=foo",
353                       "NEEDSENC=frob$",
354                       "NEEDSENC2=fr*ob",
355                       "NEEDSENC3=fro}b",
356                       "NEEDSENC4=f#rob",
357                       "EQUALS=foo=bar",
358                       ],  # envp
359                      None,  # stdin_path
360                      None,  # stdout_path
361                      None,  # stderr_path
362                      None,  # working_directory
363                      0,  # launch_flags
364                      True,  # stop_at_entry
365                      lldb.SBError())  # error
366
367        self.assertPacketLogContains([
368          "QEnvironmentHexEncoded:504c41494e3d666f6f",
369          "QEnvironmentHexEncoded:4e45454453454e433d66726f6224",
370          "QEnvironmentHexEncoded:4e45454453454e43323d66722a6f62",
371          "QEnvironmentHexEncoded:4e45454453454e43333d66726f7d62",
372          "QEnvironmentHexEncoded:4e45454453454e43343d6623726f62",
373          "QEnvironmentHexEncoded:455155414c533d666f6f3d626172",
374        ])
375
376    def test_detach_no_multiprocess(self):
377        class MyResponder(MockGDBServerResponder):
378            def __init__(self):
379                super().__init__()
380                self.detached = None
381
382            def qfThreadInfo(self):
383                return "10200"
384
385            def D(self, packet):
386                self.detached = packet
387                return "OK"
388
389        self.server.responder = MyResponder()
390        target = self.dbg.CreateTarget('')
391        process = self.connect(target)
392        process.Detach()
393        self.assertEqual(self.server.responder.detached, "D")
394
395    def test_detach_pid(self):
396        class MyResponder(MockGDBServerResponder):
397            def __init__(self, test_case):
398                super().__init__()
399                self.test_case = test_case
400                self.detached = None
401
402            def qSupported(self, client_supported):
403                self.test_case.assertIn("multiprocess+", client_supported)
404                return "multiprocess+;" + super().qSupported(client_supported)
405
406            def qfThreadInfo(self):
407                return "mp400.10200"
408
409            def D(self, packet):
410                self.detached = packet
411                return "OK"
412
413        self.server.responder = MyResponder(self)
414        target = self.dbg.CreateTarget('')
415        process = self.connect(target)
416        process.Detach()
417        self.assertRegex(self.server.responder.detached, r"D;0*400")
418
419    def test_signal_gdb(self):
420        class MyResponder(MockGDBServerResponder):
421            def qSupported(self, client_supported):
422                return "PacketSize=3fff;QStartNoAckMode+"
423
424            def haltReason(self):
425                return "S0a"
426
427            def cont(self):
428                return self.haltReason()
429
430        self.server.responder = MyResponder()
431
432        self.runCmd("platform select remote-linux")
433        target = self.createTarget("a.yaml")
434        process = self.connect(target)
435
436        self.assertEqual(process.threads[0].GetStopReason(),
437                         lldb.eStopReasonSignal)
438        self.assertEqual(process.threads[0].GetStopDescription(100),
439                         'signal SIGBUS')
440
441    def test_signal_lldb_old(self):
442        class MyResponder(MockGDBServerResponder):
443            def qSupported(self, client_supported):
444                return "PacketSize=3fff;QStartNoAckMode+"
445
446            def qHostInfo(self):
447                return "triple:61726d76372d756e6b6e6f776e2d6c696e75782d676e75;"
448
449            def QThreadSuffixSupported(self):
450                return "OK"
451
452            def haltReason(self):
453                return "S0a"
454
455            def cont(self):
456                return self.haltReason()
457
458        self.server.responder = MyResponder()
459
460        self.runCmd("platform select remote-linux")
461        target = self.createTarget("a.yaml")
462        process = self.connect(target)
463
464        self.assertEqual(process.threads[0].GetStopReason(),
465                         lldb.eStopReasonSignal)
466        self.assertEqual(process.threads[0].GetStopDescription(100),
467                         'signal SIGUSR1')
468
469    def test_signal_lldb(self):
470        class MyResponder(MockGDBServerResponder):
471            def qSupported(self, client_supported):
472                return "PacketSize=3fff;QStartNoAckMode+;native-signals+"
473
474            def qHostInfo(self):
475                return "triple:61726d76372d756e6b6e6f776e2d6c696e75782d676e75;"
476
477            def haltReason(self):
478                return "S0a"
479
480            def cont(self):
481                return self.haltReason()
482
483        self.server.responder = MyResponder()
484
485        self.runCmd("platform select remote-linux")
486        target = self.createTarget("a.yaml")
487        process = self.connect(target)
488
489        self.assertEqual(process.threads[0].GetStopReason(),
490                         lldb.eStopReasonSignal)
491        self.assertEqual(process.threads[0].GetStopDescription(100),
492                         'signal SIGUSR1')
493