730 lines · python
1import lldb2import binascii3import os.path4from lldbsuite.test.lldbtest import *5from lldbsuite.test.decorators import *6from lldbsuite.test.gdbclientutils import *7from lldbsuite.test.lldbgdbclient import GDBRemoteTestBase8 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.assertPacketLogReceived(["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.assertEqual(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, error92 )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, error118 )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 # We want to make sure that the process is using the g packet, but it's136 # not required the "connect" should read all registers. However, it might137 # have... So we need to wait till we explicitly 'read_registers' to do138 # test.139 # Also, even with the use-g-packet-for-reading lldb will sometimes send p0140 # early on to see if the packet is supported. So we can't say that there141 # will be NO p packets.142 # But there certainly should be no p packets after the g packet.143 144 self.read_registers(process)145 received = self.server.responder.packetLog.get_received()146 print(f"\nPACKET LOG:\n{received}\n")147 g_pos = 0148 try:149 g_pos = received.index("g")150 except err:151 self.fail("'g' packet not found after fetching registers")152 153 try:154 second_g = received.index("g", g_pos + 1)155 self.fail("Found more than one 'g' packet")156 except:157 pass158 159 # Make sure there aren't any `p` packets after the `g` packet:160 self.assertEqual(161 0,162 len([p for p in received[g_pos:] if p.startswith("p")]),163 )164 165 def test_read_registers_using_p_packets(self):166 """Test reading registers using 'p' packets"""167 self.dbg.HandleCommand(168 "settings set plugin.process.gdb-remote.use-g-packet-for-reading false"169 )170 self.server.responder = self.gPacketResponder()171 target = self.createTarget("a.yaml")172 process = self.connect(target)173 174 self.read_registers(process)175 received = self.server.responder.packetLog.get_received()176 self.assertNotIn("g", received)177 self.assertGreater(len([p for p in received if p.startswith("p")]), 0)178 179 def test_write_registers_using_P_packets(self):180 """Test writing registers using 'P' packets (default behavior)"""181 self.server.responder = self.gPacketResponder()182 target = self.createTarget("a.yaml")183 process = self.connect(target)184 185 self.write_registers(process)186 received = self.server.responder.packetLog.get_received()187 self.assertEqual(0, len([p for p in received if p.startswith("G")]))188 self.assertGreater(len([p for p in received if p.startswith("P")]), 0)189 190 def test_write_registers_using_G_packets(self):191 """Test writing registers using 'G' packets"""192 193 class MyResponder(self.gPacketResponder):194 def readRegister(self, register):195 # empty string means unsupported196 return ""197 198 self.server.responder = MyResponder()199 target = self.createTarget("a.yaml")200 process = self.connect(target)201 202 self.write_registers(process)203 received = self.server.responder.packetLog.get_received()204 self.assertEqual(0, len([p for p in received if p.startswith("P")]))205 self.assertGreater(len([p for p in received if p.startswith("G")]), 0)206 207 def read_registers(self, process):208 self.for_each_gpr(209 process, lambda r: self.assertEqual("0x0000000000000000", r.GetValue())210 )211 212 def write_registers(self, process):213 self.for_each_gpr(214 process, lambda r: r.SetValueFromCString("0x0000000000000000")215 )216 217 def for_each_gpr(self, process, operation):218 registers = process.GetThreadAtIndex(0).GetFrameAtIndex(0).GetRegisters()219 self.assertGreater(registers.GetSize(), 0)220 regSet = registers[0]221 numChildren = regSet.GetNumChildren()222 self.assertGreater(numChildren, 0)223 for i in range(numChildren):224 operation(regSet.GetChildAtIndex(i))225 226 def test_launch_A(self):227 class MyResponder(MockGDBServerResponder):228 def __init__(self, *args, **kwargs):229 self.started = False230 return super().__init__(*args, **kwargs)231 232 def qC(self):233 if self.started:234 return "QCp10.10"235 else:236 return "E42"237 238 def qfThreadInfo(self):239 if self.started:240 return "mp10.10"241 else:242 return "E42"243 244 def qsThreadInfo(self):245 return "l"246 247 def A(self, packet):248 self.started = True249 return "OK"250 251 def qLaunchSuccess(self):252 if self.started:253 return "OK"254 return "E42"255 256 self.server.responder = MyResponder()257 258 target = self.createTarget("a.yaml")259 # NB: apparently GDB packets are using "/" on Windows too260 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(264 self, self.dbg.GetListener(), process, [lldb.eStateConnected]265 )266 267 target.Launch(268 lldb.SBListener(),269 ["arg1", "arg2", "arg3"], # argv270 [], # envp271 None, # stdin_path272 None, # stdout_path273 None, # stderr_path274 None, # working_directory275 0, # launch_flags276 True, # stop_at_entry277 lldb.SBError(),278 ) # error279 self.assertTrue(process, PROCESS_IS_VALID)280 self.assertEqual(process.GetProcessID(), 16)281 282 self.assertPacketLogReceived(283 [284 "A%d,0,%s,8,1,61726731,8,2,61726732,8,3,61726733"285 % (len(exe_hex), exe_hex),286 ]287 )288 289 def test_launch_vRun(self):290 class MyResponder(MockGDBServerResponder):291 def __init__(self, *args, **kwargs):292 self.started = False293 return super().__init__(*args, **kwargs)294 295 def qC(self):296 if self.started:297 return "QCp10.10"298 else:299 return "E42"300 301 def qfThreadInfo(self):302 if self.started:303 return "mp10.10"304 else:305 return "E42"306 307 def qsThreadInfo(self):308 return "l"309 310 def vRun(self, packet):311 self.started = True312 return "T13"313 314 def A(self, packet):315 return "E28"316 317 self.server.responder = MyResponder()318 319 target = self.createTarget("a.yaml")320 # NB: apparently GDB packets are using "/" on Windows too321 exe_path = self.getBuildArtifact("a").replace(os.path.sep, "/")322 exe_hex = binascii.b2a_hex(exe_path.encode()).decode()323 process = self.connect(target)324 lldbutil.expect_state_changes(325 self, self.dbg.GetListener(), process, [lldb.eStateConnected]326 )327 328 process = target.Launch(329 lldb.SBListener(),330 ["arg1", "arg2", "arg3"], # argv331 [], # envp332 None, # stdin_path333 None, # stdout_path334 None, # stderr_path335 None, # working_directory336 0, # launch_flags337 True, # stop_at_entry338 lldb.SBError(),339 ) # error340 self.assertTrue(process, PROCESS_IS_VALID)341 self.assertEqual(process.GetProcessID(), 16)342 343 self.assertPacketLogReceived(344 ["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]345 )346 347 def test_launch_lengthy_vRun(self):348 class MyResponder(MockGDBServerResponder):349 def __init__(self, *args, **kwargs):350 self.started = False351 return super().__init__(*args, **kwargs)352 353 def qC(self):354 if self.started:355 return "QCp10.10"356 else:357 return "E42"358 359 def qfThreadInfo(self):360 if self.started:361 return "mp10.10"362 else:363 return "E42"364 365 def qsThreadInfo(self):366 return "l"367 368 def qEcho(self, num):369 resp = "qEcho:" + str(num)370 if num >= 2:371 # We have launched our program372 self.started = True373 return [resp, "T13"]374 375 return resp376 377 def qSupported(self, client_supported):378 return "PacketSize=3fff;QStartNoAckMode+;qEcho+;"379 380 def qHostInfo(self):381 return "default_packet_timeout:1;"382 383 def vRun(self, packet):384 return [self.RESPONSE_NONE]385 386 def A(self, packet):387 return "E28"388 389 self.server.responder = MyResponder()390 391 target = self.createTarget("a.yaml")392 # NB: apparently GDB packets are using "/" on Windows too393 exe_path = self.getBuildArtifact("a").replace(os.path.sep, "/")394 exe_hex = binascii.b2a_hex(exe_path.encode()).decode()395 process = self.connect(target)396 lldbutil.expect_state_changes(397 self, self.dbg.GetListener(), process, [lldb.eStateConnected]398 )399 400 process = target.Launch(401 lldb.SBListener(),402 ["arg1", "arg2", "arg3"], # argv403 [], # envp404 None, # stdin_path405 None, # stdout_path406 None, # stderr_path407 None, # working_directory408 0, # launch_flags409 True, # stop_at_entry410 lldb.SBError(),411 ) # error412 self.assertTrue(process, PROCESS_IS_VALID)413 self.assertEqual(process.GetProcessID(), 16)414 415 self.assertPacketLogReceived(416 ["vRun;%s;61726731;61726732;61726733" % (exe_hex,)]417 )418 419 def test_launch_QEnvironment(self):420 class MyResponder(MockGDBServerResponder):421 def qC(self):422 return "E42"423 424 def qfThreadInfo(self):425 return "E42"426 427 def vRun(self, packet):428 self.started = True429 return "E28"430 431 self.server.responder = MyResponder()432 433 target = self.createTarget("a.yaml")434 process = self.connect(target)435 lldbutil.expect_state_changes(436 self, self.dbg.GetListener(), process, [lldb.eStateConnected]437 )438 439 target.Launch(440 lldb.SBListener(),441 [], # argv442 [443 "PLAIN=foo",444 "NEEDSENC=frob$",445 "NEEDSENC2=fr*ob",446 "NEEDSENC3=fro}b",447 "NEEDSENC4=f#rob",448 "EQUALS=foo=bar",449 ], # envp450 None, # stdin_path451 None, # stdout_path452 None, # stderr_path453 None, # working_directory454 0, # launch_flags455 True, # stop_at_entry456 lldb.SBError(),457 ) # error458 459 self.assertPacketLogReceived(460 [461 "QEnvironment:EQUALS=foo=bar",462 "QEnvironmentHexEncoded:4e45454453454e433d66726f6224",463 "QEnvironmentHexEncoded:4e45454453454e43323d66722a6f62",464 "QEnvironmentHexEncoded:4e45454453454e43333d66726f7d62",465 "QEnvironmentHexEncoded:4e45454453454e43343d6623726f62",466 "QEnvironment:PLAIN=foo",467 ]468 )469 470 def test_launch_QEnvironmentHexEncoded_only(self):471 class MyResponder(MockGDBServerResponder):472 def qC(self):473 return "E42"474 475 def qfThreadInfo(self):476 return "E42"477 478 def vRun(self, packet):479 self.started = True480 return "E28"481 482 def QEnvironment(self, packet):483 return ""484 485 self.server.responder = MyResponder()486 487 target = self.createTarget("a.yaml")488 process = self.connect(target)489 lldbutil.expect_state_changes(490 self, self.dbg.GetListener(), process, [lldb.eStateConnected]491 )492 493 target.Launch(494 lldb.SBListener(),495 [], # argv496 [497 "PLAIN=foo",498 "NEEDSENC=frob$",499 "NEEDSENC2=fr*ob",500 "NEEDSENC3=fro}b",501 "NEEDSENC4=f#rob",502 "EQUALS=foo=bar",503 ], # envp504 None, # stdin_path505 None, # stdout_path506 None, # stderr_path507 None, # working_directory508 0, # launch_flags509 True, # stop_at_entry510 lldb.SBError(),511 ) # error512 513 self.assertPacketLogReceived(514 [515 "QEnvironmentHexEncoded:455155414c533d666f6f3d626172",516 "QEnvironmentHexEncoded:4e45454453454e433d66726f6224",517 "QEnvironmentHexEncoded:4e45454453454e43323d66722a6f62",518 "QEnvironmentHexEncoded:4e45454453454e43333d66726f7d62",519 "QEnvironmentHexEncoded:4e45454453454e43343d6623726f62",520 "QEnvironmentHexEncoded:504c41494e3d666f6f",521 ]522 )523 524 def test_detach_no_multiprocess(self):525 class MyResponder(MockGDBServerResponder):526 def __init__(self):527 super().__init__()528 self.detached = None529 530 def qfThreadInfo(self):531 return "10200"532 533 def D(self, packet):534 self.detached = packet535 return "OK"536 537 self.server.responder = MyResponder()538 target = self.dbg.CreateTarget("")539 process = self.connect(target)540 process.Detach()541 self.assertEqual(self.server.responder.detached, "D")542 543 def test_detach_pid(self):544 class MyResponder(MockGDBServerResponder):545 def __init__(self, test_case):546 super().__init__()547 self.test_case = test_case548 self.detached = None549 550 def qSupported(self, client_supported):551 self.test_case.assertIn("multiprocess+", client_supported)552 return "multiprocess+;" + super().qSupported(client_supported)553 554 def qfThreadInfo(self):555 return "mp400.10200"556 557 def D(self, packet):558 self.detached = packet559 return "OK"560 561 self.server.responder = MyResponder(self)562 target = self.dbg.CreateTarget("")563 process = self.connect(target)564 process.Detach()565 self.assertRegex(self.server.responder.detached, r"D;0*400")566 567 def test_signal_gdb(self):568 class MyResponder(MockGDBServerResponder):569 def qSupported(self, client_supported):570 return "PacketSize=3fff;QStartNoAckMode+"571 572 def haltReason(self):573 return "S0a"574 575 def cont(self):576 return self.haltReason()577 578 self.server.responder = MyResponder()579 580 self.runCmd("platform select remote-linux")581 target = self.createTarget("a.yaml")582 process = self.connect(target)583 584 self.assertEqual(process.threads[0].GetStopReason(), lldb.eStopReasonSignal)585 self.assertEqual(process.threads[0].stop_description, "signal SIGBUS")586 587 def test_signal_lldb_old(self):588 class MyResponder(MockGDBServerResponder):589 def qSupported(self, client_supported):590 return "PacketSize=3fff;QStartNoAckMode+"591 592 def qHostInfo(self):593 return "triple:61726d76372d756e6b6e6f776e2d6c696e75782d676e75;"594 595 def QThreadSuffixSupported(self):596 return "OK"597 598 def haltReason(self):599 return "S0a"600 601 def cont(self):602 return self.haltReason()603 604 self.server.responder = MyResponder()605 606 self.runCmd("platform select remote-linux")607 target = self.createTarget("a.yaml")608 process = self.connect(target)609 610 self.assertEqual(process.threads[0].GetStopReason(), lldb.eStopReasonSignal)611 self.assertEqual(process.threads[0].stop_description, "signal SIGUSR1")612 613 def test_signal_lldb(self):614 class MyResponder(MockGDBServerResponder):615 def qSupported(self, client_supported):616 return "PacketSize=3fff;QStartNoAckMode+;native-signals+"617 618 def qHostInfo(self):619 return "triple:61726d76372d756e6b6e6f776e2d6c696e75782d676e75;"620 621 def haltReason(self):622 return "S0a"623 624 def cont(self):625 return self.haltReason()626 627 self.server.responder = MyResponder()628 629 self.runCmd("platform select remote-linux")630 target = self.createTarget("a.yaml")631 process = self.connect(target)632 633 self.assertEqual(process.threads[0].GetStopReason(), lldb.eStopReasonSignal)634 self.assertEqual(process.threads[0].stop_description, "signal SIGUSR1")635 636 def do_siginfo_test(self, platform, target_yaml, raw_data, expected):637 class MyResponder(MockGDBServerResponder):638 def qSupported(self, client_supported):639 return "PacketSize=3fff;QStartNoAckMode+;qXfer:siginfo:read+"640 641 def qXferRead(self, obj, annex, offset, length):642 if obj == "siginfo":643 return raw_data, False644 else:645 return None, False646 647 def haltReason(self):648 return "T02"649 650 def cont(self):651 return self.haltReason()652 653 self.server.responder = MyResponder()654 655 self.runCmd("platform select " + platform)656 target = self.createTarget(target_yaml)657 process = self.connect(target)658 659 siginfo = process.threads[0].GetSiginfo()660 self.assertSuccess(siginfo.GetError())661 662 for key, value in expected.items():663 self.assertEqual(664 siginfo.GetValueForExpressionPath("." + key).GetValueAsUnsigned(), value665 )666 667 def test_siginfo_linux_amd64(self):668 data = (669 # si_signo si_errno si_code670 "\x11\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"671 # __pad0 si_pid si_uid672 "\x00\x00\x00\x00\xbf\xf7\x0b\x00\xe8\x03\x00\x00"673 # si_status674 "\x0c\x00\x00\x00"675 + "\x00" * 100676 )677 expected = {678 "si_signo": 17, # SIGCHLD679 "si_errno": 0,680 "si_code": 1, # CLD_EXITED681 "_sifields._sigchld.si_pid": 784319,682 "_sifields._sigchld.si_uid": 1000,683 "_sifields._sigchld.si_status": 12,684 "_sifields._sigchld.si_utime": 0,685 "_sifields._sigchld.si_stime": 0,686 }687 self.do_siginfo_test("remote-linux", "basic_eh_frame.yaml", data, expected)688 689 def test_siginfo_linux_i386(self):690 data = (691 # si_signo si_errno si_code692 "\x11\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"693 # si_pid si_uid si_status694 "\x49\x43\x07\x00\xe8\x03\x00\x00\x0c\x00\x00\x00"695 + "\x00" * 104696 )697 expected = {698 "si_signo": 17, # SIGCHLD699 "si_errno": 0,700 "si_code": 1, # CLD_EXITED701 "_sifields._sigchld.si_pid": 475977,702 "_sifields._sigchld.si_uid": 1000,703 "_sifields._sigchld.si_status": 12,704 "_sifields._sigchld.si_utime": 0,705 "_sifields._sigchld.si_stime": 0,706 }707 self.do_siginfo_test("remote-linux", "basic_eh_frame-i386.yaml", data, expected)708 709 def test_siginfo_freebsd_amd64(self):710 data = (711 # si_signo si_errno si_code712 "\x0b\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"713 # si_pid si_uid si_status714 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"715 # si_addr716 "\x76\x98\xba\xdc\xfe\x00\x00\x00"717 # si_status si_trapno718 "\x00\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00"719 + "\x00" * 36720 )721 722 expected = {723 "si_signo": 11, # SIGSEGV724 "si_errno": 0,725 "si_code": 1, # SEGV_MAPERR726 "si_addr": 0xFEDCBA9876,727 "_reason._fault._trapno": 12,728 }729 self.do_siginfo_test("remote-freebsd", "basic_eh_frame.yaml", data, expected)730