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