xref: /llvm-project/lldb/test/API/functionalities/gdb_remote_client/TestGDBRemoteClient.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
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    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(
61            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
62        )
63
64        error = lldb.SBError()
65        target.AttachToProcessWithID(lldb.SBListener(), 47, error)
66        self.assertEquals(error_msg, error.GetCString())
67
68    def test_launch_fail(self):
69        class MyResponder(MockGDBServerResponder):
70            # Pretend we don't have any process during the initial queries.
71            def qC(self):
72                return "E42"
73
74            def qfThreadInfo(self):
75                return "OK"  # No threads.
76
77            # Then, when we are asked to attach, error out.
78            def A(self, packet):
79                return "E47"
80
81        self.server.responder = MyResponder()
82
83        target = self.createTarget("a.yaml")
84        process = self.connect(target)
85        lldbutil.expect_state_changes(
86            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
87        )
88
89        error = lldb.SBError()
90        target.Launch(
91            lldb.SBListener(), None, None, None, None, None, None, 0, True, error
92        )
93        self.assertRegex(error.GetCString(), "Cannot launch '.*a': Error 71")
94
95    def test_launch_rich_error(self):
96        class MyResponder(MockGDBServerResponder):
97            def qC(self):
98                return "E42"
99
100            def qfThreadInfo(self):
101                return "OK"  # No threads.
102
103            # Then, when we are asked to attach, error out.
104            def vRun(self, packet):
105                return "Eff;" + seven.hexlify("I'm a teapot")
106
107        self.server.responder = MyResponder()
108
109        target = self.createTarget("a.yaml")
110        process = self.connect(target)
111        lldbutil.expect_state_changes(
112            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
113        )
114
115        error = lldb.SBError()
116        target.Launch(
117            lldb.SBListener(), None, None, None, None, None, None, 0, True, error
118        )
119        self.assertRegex(error.GetCString(), "Cannot launch '.*a': I'm a teapot")
120
121    def test_read_registers_using_g_packets(self):
122        """Test reading registers using 'g' packets (default behavior)"""
123        self.dbg.HandleCommand(
124            "settings set plugin.process.gdb-remote.use-g-packet-for-reading true"
125        )
126        self.addTearDownHook(
127            lambda: self.runCmd(
128                "settings set plugin.process.gdb-remote.use-g-packet-for-reading false"
129            )
130        )
131        self.server.responder = self.gPacketResponder()
132        target = self.createTarget("a.yaml")
133        process = self.connect(target)
134
135        self.assertEquals(1, self.server.responder.packetLog.count("g"))
136        self.server.responder.packetLog = []
137        self.read_registers(process)
138        # Reading registers should not cause any 'p' packets to be exchanged.
139        self.assertEquals(
140            0, len([p for p in self.server.responder.packetLog if p.startswith("p")])
141        )
142
143    def test_read_registers_using_p_packets(self):
144        """Test reading registers using 'p' packets"""
145        self.dbg.HandleCommand(
146            "settings set plugin.process.gdb-remote.use-g-packet-for-reading false"
147        )
148        self.server.responder = self.gPacketResponder()
149        target = self.createTarget("a.yaml")
150        process = self.connect(target)
151
152        self.read_registers(process)
153        self.assertNotIn("g", self.server.responder.packetLog)
154        self.assertGreater(
155            len([p for p in self.server.responder.packetLog if p.startswith("p")]), 0
156        )
157
158    def test_write_registers_using_P_packets(self):
159        """Test writing registers using 'P' packets (default behavior)"""
160        self.server.responder = self.gPacketResponder()
161        target = self.createTarget("a.yaml")
162        process = self.connect(target)
163
164        self.write_registers(process)
165        self.assertEquals(
166            0, len([p for p in self.server.responder.packetLog if p.startswith("G")])
167        )
168        self.assertGreater(
169            len([p for p in self.server.responder.packetLog if p.startswith("P")]), 0
170        )
171
172    def test_write_registers_using_G_packets(self):
173        """Test writing registers using 'G' packets"""
174
175        class MyResponder(self.gPacketResponder):
176            def readRegister(self, register):
177                # empty string means unsupported
178                return ""
179
180        self.server.responder = MyResponder()
181        target = self.createTarget("a.yaml")
182        process = self.connect(target)
183
184        self.write_registers(process)
185        self.assertEquals(
186            0, len([p for p in self.server.responder.packetLog if p.startswith("P")])
187        )
188        self.assertGreater(
189            len([p for p in self.server.responder.packetLog if p.startswith("G")]), 0
190        )
191
192    def read_registers(self, process):
193        self.for_each_gpr(
194            process, lambda r: self.assertEquals("0x0000000000000000", r.GetValue())
195        )
196
197    def write_registers(self, process):
198        self.for_each_gpr(
199            process, lambda r: r.SetValueFromCString("0x0000000000000000")
200        )
201
202    def for_each_gpr(self, process, operation):
203        registers = process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetRegisters()
204        self.assertGreater(registers.GetSize(), 0)
205        regSet = registers[0]
206        numChildren = regSet.GetNumChildren()
207        self.assertGreater(numChildren, 0)
208        for i in range(numChildren):
209            operation(regSet.GetChildAtIndex(i))
210
211    def test_launch_A(self):
212        class MyResponder(MockGDBServerResponder):
213            def __init__(self, *args, **kwargs):
214                self.started = False
215                return super().__init__(*args, **kwargs)
216
217            def qC(self):
218                if self.started:
219                    return "QCp10.10"
220                else:
221                    return "E42"
222
223            def qfThreadInfo(self):
224                if self.started:
225                    return "mp10.10"
226                else:
227                    return "E42"
228
229            def qsThreadInfo(self):
230                return "l"
231
232            def A(self, packet):
233                self.started = True
234                return "OK"
235
236            def qLaunchSuccess(self):
237                if self.started:
238                    return "OK"
239                return "E42"
240
241        self.server.responder = MyResponder()
242
243        target = self.createTarget("a.yaml")
244        # NB: apparently GDB packets are using "/" on Windows too
245        exe_path = self.getBuildArtifact("a").replace(os.path.sep, "/")
246        exe_hex = binascii.b2a_hex(exe_path.encode()).decode()
247        process = self.connect(target)
248        lldbutil.expect_state_changes(
249            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
250        )
251
252        target.Launch(
253            lldb.SBListener(),
254            ["arg1", "arg2", "arg3"],  # argv
255            [],  # envp
256            None,  # stdin_path
257            None,  # stdout_path
258            None,  # stderr_path
259            None,  # working_directory
260            0,  # launch_flags
261            True,  # stop_at_entry
262            lldb.SBError(),
263        )  # error
264        self.assertTrue(process, PROCESS_IS_VALID)
265        self.assertEqual(process.GetProcessID(), 16)
266
267        self.assertPacketLogContains(
268            [
269                "A%d,0,%s,8,1,61726731,8,2,61726732,8,3,61726733"
270                % (len(exe_hex), exe_hex),
271            ]
272        )
273
274    def test_launch_vRun(self):
275        class MyResponder(MockGDBServerResponder):
276            def __init__(self, *args, **kwargs):
277                self.started = False
278                return super().__init__(*args, **kwargs)
279
280            def qC(self):
281                if self.started:
282                    return "QCp10.10"
283                else:
284                    return "E42"
285
286            def qfThreadInfo(self):
287                if self.started:
288                    return "mp10.10"
289                else:
290                    return "E42"
291
292            def qsThreadInfo(self):
293                return "l"
294
295            def vRun(self, packet):
296                self.started = True
297                return "T13"
298
299            def A(self, packet):
300                return "E28"
301
302        self.server.responder = MyResponder()
303
304        target = self.createTarget("a.yaml")
305        # NB: apparently GDB packets are using "/" on Windows too
306        exe_path = self.getBuildArtifact("a").replace(os.path.sep, "/")
307        exe_hex = binascii.b2a_hex(exe_path.encode()).decode()
308        process = self.connect(target)
309        lldbutil.expect_state_changes(
310            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
311        )
312
313        process = target.Launch(
314            lldb.SBListener(),
315            ["arg1", "arg2", "arg3"],  # argv
316            [],  # envp
317            None,  # stdin_path
318            None,  # stdout_path
319            None,  # stderr_path
320            None,  # working_directory
321            0,  # launch_flags
322            True,  # stop_at_entry
323            lldb.SBError(),
324        )  # error
325        self.assertTrue(process, PROCESS_IS_VALID)
326        self.assertEqual(process.GetProcessID(), 16)
327
328        self.assertPacketLogContains(
329            ["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]
330        )
331
332    def test_launch_QEnvironment(self):
333        class MyResponder(MockGDBServerResponder):
334            def qC(self):
335                return "E42"
336
337            def qfThreadInfo(self):
338                return "E42"
339
340            def vRun(self, packet):
341                self.started = True
342                return "E28"
343
344        self.server.responder = MyResponder()
345
346        target = self.createTarget("a.yaml")
347        process = self.connect(target)
348        lldbutil.expect_state_changes(
349            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
350        )
351
352        target.Launch(
353            lldb.SBListener(),
354            [],  # argv
355            [
356                "PLAIN=foo",
357                "NEEDSENC=frob$",
358                "NEEDSENC2=fr*ob",
359                "NEEDSENC3=fro}b",
360                "NEEDSENC4=f#rob",
361                "EQUALS=foo=bar",
362            ],  # envp
363            None,  # stdin_path
364            None,  # stdout_path
365            None,  # stderr_path
366            None,  # working_directory
367            0,  # launch_flags
368            True,  # stop_at_entry
369            lldb.SBError(),
370        )  # error
371
372        self.assertPacketLogContains(
373            [
374                "QEnvironmentHexEncoded:4e45454453454e43333d66726f7d62",
375                "QEnvironmentHexEncoded:4e45454453454e43343d6623726f62",
376                "QEnvironment:PLAIN=foo",
377                "QEnvironmentHexEncoded:4e45454453454e43323d66722a6f62",
378                "QEnvironmentHexEncoded:4e45454453454e433d66726f6224",
379                "QEnvironment:EQUALS=foo=bar",
380            ]
381        )
382
383    def test_launch_QEnvironmentHexEncoded_only(self):
384        class MyResponder(MockGDBServerResponder):
385            def qC(self):
386                return "E42"
387
388            def qfThreadInfo(self):
389                return "E42"
390
391            def vRun(self, packet):
392                self.started = True
393                return "E28"
394
395            def QEnvironment(self, packet):
396                return ""
397
398        self.server.responder = MyResponder()
399
400        target = self.createTarget("a.yaml")
401        process = self.connect(target)
402        lldbutil.expect_state_changes(
403            self, self.dbg.GetListener(), process, [lldb.eStateConnected]
404        )
405
406        target.Launch(
407            lldb.SBListener(),
408            [],  # argv
409            [
410                "PLAIN=foo",
411                "NEEDSENC=frob$",
412                "NEEDSENC2=fr*ob",
413                "NEEDSENC3=fro}b",
414                "NEEDSENC4=f#rob",
415                "EQUALS=foo=bar",
416            ],  # envp
417            None,  # stdin_path
418            None,  # stdout_path
419            None,  # stderr_path
420            None,  # working_directory
421            0,  # launch_flags
422            True,  # stop_at_entry
423            lldb.SBError(),
424        )  # error
425
426        self.assertPacketLogContains(
427            [
428                "QEnvironmentHexEncoded:4e45454453454e43333d66726f7d62",
429                "QEnvironmentHexEncoded:4e45454453454e43343d6623726f62",
430                "QEnvironmentHexEncoded:504c41494e3d666f6f",
431                "QEnvironmentHexEncoded:4e45454453454e43323d66722a6f62",
432                "QEnvironmentHexEncoded:4e45454453454e433d66726f6224",
433                "QEnvironmentHexEncoded:455155414c533d666f6f3d626172",
434            ]
435        )
436
437    def test_detach_no_multiprocess(self):
438        class MyResponder(MockGDBServerResponder):
439            def __init__(self):
440                super().__init__()
441                self.detached = None
442
443            def qfThreadInfo(self):
444                return "10200"
445
446            def D(self, packet):
447                self.detached = packet
448                return "OK"
449
450        self.server.responder = MyResponder()
451        target = self.dbg.CreateTarget("")
452        process = self.connect(target)
453        process.Detach()
454        self.assertEqual(self.server.responder.detached, "D")
455
456    def test_detach_pid(self):
457        class MyResponder(MockGDBServerResponder):
458            def __init__(self, test_case):
459                super().__init__()
460                self.test_case = test_case
461                self.detached = None
462
463            def qSupported(self, client_supported):
464                self.test_case.assertIn("multiprocess+", client_supported)
465                return "multiprocess+;" + super().qSupported(client_supported)
466
467            def qfThreadInfo(self):
468                return "mp400.10200"
469
470            def D(self, packet):
471                self.detached = packet
472                return "OK"
473
474        self.server.responder = MyResponder(self)
475        target = self.dbg.CreateTarget("")
476        process = self.connect(target)
477        process.Detach()
478        self.assertRegex(self.server.responder.detached, r"D;0*400")
479
480    def test_signal_gdb(self):
481        class MyResponder(MockGDBServerResponder):
482            def qSupported(self, client_supported):
483                return "PacketSize=3fff;QStartNoAckMode+"
484
485            def haltReason(self):
486                return "S0a"
487
488            def cont(self):
489                return self.haltReason()
490
491        self.server.responder = MyResponder()
492
493        self.runCmd("platform select remote-linux")
494        target = self.createTarget("a.yaml")
495        process = self.connect(target)
496
497        self.assertEqual(process.threads[0].GetStopReason(), lldb.eStopReasonSignal)
498        self.assertEqual(process.threads[0].GetStopDescription(100), "signal SIGBUS")
499
500    def test_signal_lldb_old(self):
501        class MyResponder(MockGDBServerResponder):
502            def qSupported(self, client_supported):
503                return "PacketSize=3fff;QStartNoAckMode+"
504
505            def qHostInfo(self):
506                return "triple:61726d76372d756e6b6e6f776e2d6c696e75782d676e75;"
507
508            def QThreadSuffixSupported(self):
509                return "OK"
510
511            def haltReason(self):
512                return "S0a"
513
514            def cont(self):
515                return self.haltReason()
516
517        self.server.responder = MyResponder()
518
519        self.runCmd("platform select remote-linux")
520        target = self.createTarget("a.yaml")
521        process = self.connect(target)
522
523        self.assertEqual(process.threads[0].GetStopReason(), lldb.eStopReasonSignal)
524        self.assertEqual(process.threads[0].GetStopDescription(100), "signal SIGUSR1")
525
526    def test_signal_lldb(self):
527        class MyResponder(MockGDBServerResponder):
528            def qSupported(self, client_supported):
529                return "PacketSize=3fff;QStartNoAckMode+;native-signals+"
530
531            def qHostInfo(self):
532                return "triple:61726d76372d756e6b6e6f776e2d6c696e75782d676e75;"
533
534            def haltReason(self):
535                return "S0a"
536
537            def cont(self):
538                return self.haltReason()
539
540        self.server.responder = MyResponder()
541
542        self.runCmd("platform select remote-linux")
543        target = self.createTarget("a.yaml")
544        process = self.connect(target)
545
546        self.assertEqual(process.threads[0].GetStopReason(), lldb.eStopReasonSignal)
547        self.assertEqual(process.threads[0].GetStopDescription(100), "signal SIGUSR1")
548
549    def do_siginfo_test(self, platform, target_yaml, raw_data, expected):
550        class MyResponder(MockGDBServerResponder):
551            def qSupported(self, client_supported):
552                return "PacketSize=3fff;QStartNoAckMode+;qXfer:siginfo:read+"
553
554            def qXferRead(self, obj, annex, offset, length):
555                if obj == "siginfo":
556                    return raw_data, False
557                else:
558                    return None, False
559
560            def haltReason(self):
561                return "T02"
562
563            def cont(self):
564                return self.haltReason()
565
566        self.server.responder = MyResponder()
567
568        self.runCmd("platform select " + platform)
569        target = self.createTarget(target_yaml)
570        process = self.connect(target)
571
572        siginfo = process.threads[0].GetSiginfo()
573        self.assertSuccess(siginfo.GetError())
574
575        for key, value in expected.items():
576            self.assertEqual(
577                siginfo.GetValueForExpressionPath("." + key).GetValueAsUnsigned(), value
578            )
579
580    def test_siginfo_linux_amd64(self):
581        data = (
582            # si_signo         si_errno        si_code
583            "\x11\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
584            # __pad0           si_pid          si_uid
585            "\x00\x00\x00\x00\xbf\xf7\x0b\x00\xe8\x03\x00\x00"
586            # si_status
587            "\x0c\x00\x00\x00"
588            + "\x00" * 100
589        )
590        expected = {
591            "si_signo": 17,  # SIGCHLD
592            "si_errno": 0,
593            "si_code": 1,  # CLD_EXITED
594            "_sifields._sigchld.si_pid": 784319,
595            "_sifields._sigchld.si_uid": 1000,
596            "_sifields._sigchld.si_status": 12,
597            "_sifields._sigchld.si_utime": 0,
598            "_sifields._sigchld.si_stime": 0,
599        }
600        self.do_siginfo_test("remote-linux", "basic_eh_frame.yaml", data, expected)
601
602    def test_siginfo_linux_i386(self):
603        data = (
604            # si_signo         si_errno        si_code
605            "\x11\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
606            # si_pid           si_uid          si_status
607            "\x49\x43\x07\x00\xe8\x03\x00\x00\x0c\x00\x00\x00"
608            + "\x00" * 104
609        )
610        expected = {
611            "si_signo": 17,  # SIGCHLD
612            "si_errno": 0,
613            "si_code": 1,  # CLD_EXITED
614            "_sifields._sigchld.si_pid": 475977,
615            "_sifields._sigchld.si_uid": 1000,
616            "_sifields._sigchld.si_status": 12,
617            "_sifields._sigchld.si_utime": 0,
618            "_sifields._sigchld.si_stime": 0,
619        }
620        self.do_siginfo_test("remote-linux", "basic_eh_frame-i386.yaml", data, expected)
621
622    def test_siginfo_freebsd_amd64(self):
623        data = (
624            # si_signo         si_errno        si_code
625            "\x0b\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
626            # si_pid           si_uid          si_status
627            "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
628            # si_addr
629            "\x76\x98\xba\xdc\xfe\x00\x00\x00"
630            # si_status                        si_trapno
631            "\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00"
632            + "\x00" * 36
633        )
634
635        expected = {
636            "si_signo": 11,  # SIGSEGV
637            "si_errno": 0,
638            "si_code": 1,  # SEGV_MAPERR
639            "si_addr": 0xFEDCBA9876,
640            "_reason._fault._trapno": 12,
641        }
642        self.do_siginfo_test("remote-freebsd", "basic_eh_frame.yaml", data, expected)
643