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