1import ctypes 2import errno 3import io 4import threading 5import socket 6import traceback 7from lldbsuite.support import seven 8 9 10def checksum(message): 11 """ 12 Calculate the GDB server protocol checksum of the message. 13 14 The GDB server protocol uses a simple modulo 256 sum. 15 """ 16 check = 0 17 for c in message: 18 check += ord(c) 19 return check % 256 20 21 22def frame_packet(message): 23 """ 24 Create a framed packet that's ready to send over the GDB connection 25 channel. 26 27 Framing includes surrounding the message between $ and #, and appending 28 a two character hex checksum. 29 """ 30 return "$%s#%02x" % (message, checksum(message)) 31 32 33def escape_binary(message): 34 """ 35 Escape the binary message using the process described in the GDB server 36 protocol documentation. 37 38 Most bytes are sent through as-is, but $, #, and { are escaped by writing 39 a { followed by the original byte mod 0x20. 40 """ 41 out = "" 42 for c in message: 43 d = ord(c) 44 if d in (0x23, 0x24, 0x7D): 45 out += chr(0x7D) 46 out += chr(d ^ 0x20) 47 else: 48 out += c 49 return out 50 51 52def hex_encode_bytes(message): 53 """ 54 Encode the binary message by converting each byte into a two-character 55 hex string. 56 """ 57 out = "" 58 for c in message: 59 out += "%02x" % ord(c) 60 return out 61 62 63def hex_decode_bytes(hex_bytes): 64 """ 65 Decode the hex string into a binary message by converting each two-character 66 hex string into a single output byte. 67 """ 68 out = "" 69 hex_len = len(hex_bytes) 70 i = 0 71 while i < hex_len - 1: 72 out += chr(int(hex_bytes[i : i + 2], 16)) 73 i += 2 74 return out 75 76 77class MockGDBServerResponder: 78 """ 79 A base class for handling client packets and issuing server responses for 80 GDB tests. 81 82 This handles many typical situations, while still allowing subclasses to 83 completely customize their responses. 84 85 Most subclasses will be interested in overriding the other() method, which 86 handles any packet not recognized in the common packet handling code. 87 """ 88 89 registerCount = 40 90 packetLog = None 91 92 class RESPONSE_DISCONNECT: 93 pass 94 95 def __init__(self): 96 self.packetLog = [] 97 98 def respond(self, packet): 99 """ 100 Return the unframed packet data that the server should issue in response 101 to the given packet received from the client. 102 """ 103 self.packetLog.append(packet) 104 if packet is MockGDBServer.PACKET_INTERRUPT: 105 return self.interrupt() 106 if packet == "c": 107 return self.cont() 108 if packet.startswith("vCont;c"): 109 return self.vCont(packet) 110 if packet[0] == "A": 111 return self.A(packet) 112 if packet[0] == "D": 113 return self.D(packet) 114 if packet[0] == "g": 115 return self.readRegisters() 116 if packet[0] == "G": 117 # Gxxxxxxxxxxx 118 # Gxxxxxxxxxxx;thread:1234; 119 return self.writeRegisters(packet[1:].split(";")[0]) 120 if packet[0] == "p": 121 regnum = packet[1:].split(";")[0] 122 return self.readRegister(int(regnum, 16)) 123 if packet[0] == "P": 124 register, value = packet[1:].split("=") 125 return self.writeRegister(int(register, 16), value) 126 if packet[0] == "m": 127 addr, length = [int(x, 16) for x in packet[1:].split(",")] 128 return self.readMemory(addr, length) 129 if packet[0] == "M": 130 location, encoded_data = packet[1:].split(":") 131 addr, length = [int(x, 16) for x in location.split(",")] 132 return self.writeMemory(addr, encoded_data) 133 if packet[0:7] == "qSymbol": 134 return self.qSymbol(packet[8:]) 135 if packet[0:10] == "qSupported": 136 return self.qSupported(packet[11:].split(";")) 137 if packet == "qfThreadInfo": 138 return self.qfThreadInfo() 139 if packet == "qsThreadInfo": 140 return self.qsThreadInfo() 141 if packet == "qC": 142 return self.qC() 143 if packet == "QEnableErrorStrings": 144 return self.QEnableErrorStrings() 145 if packet == "?": 146 return self.haltReason() 147 if packet == "s": 148 return self.haltReason() 149 if packet[0] == "H": 150 tid = packet[2:] 151 if "." in tid: 152 assert tid.startswith("p") 153 # TODO: do we want to do anything with PID? 154 tid = tid.split(".", 1)[1] 155 return self.selectThread(packet[1], int(tid, 16)) 156 if packet[0:6] == "qXfer:": 157 obj, read, annex, location = packet[6:].split(":") 158 offset, length = [int(x, 16) for x in location.split(",")] 159 data, has_more = self.qXferRead(obj, annex, offset, length) 160 if data is not None: 161 return self._qXferResponse(data, has_more) 162 return "" 163 if packet.startswith("vAttach;"): 164 pid = packet.partition(";")[2] 165 return self.vAttach(int(pid, 16)) 166 if packet[0] == "Z": 167 return self.setBreakpoint(packet) 168 if packet.startswith("qThreadStopInfo"): 169 threadnum = int(packet[15:], 16) 170 return self.threadStopInfo(threadnum) 171 if packet == "QThreadSuffixSupported": 172 return self.QThreadSuffixSupported() 173 if packet == "QListThreadsInStopReply": 174 return self.QListThreadsInStopReply() 175 if packet.startswith("qMemoryRegionInfo:"): 176 return self.qMemoryRegionInfo(int(packet.split(":")[1], 16)) 177 if packet == "qQueryGDBServer": 178 return self.qQueryGDBServer() 179 if packet == "qHostInfo": 180 return self.qHostInfo() 181 if packet == "qGetWorkingDir": 182 return self.qGetWorkingDir() 183 if packet == "qOffsets": 184 return self.qOffsets() 185 if packet == "qProcessInfo": 186 return self.qProcessInfo() 187 if packet == "qsProcessInfo": 188 return self.qsProcessInfo() 189 if packet.startswith("qfProcessInfo"): 190 return self.qfProcessInfo(packet) 191 if packet.startswith("jGetLoadedDynamicLibrariesInfos"): 192 return self.jGetLoadedDynamicLibrariesInfos(packet) 193 if packet.startswith("qPathComplete:"): 194 return self.qPathComplete() 195 if packet.startswith("vFile:"): 196 return self.vFile(packet) 197 if packet.startswith("vRun;"): 198 return self.vRun(packet) 199 if packet.startswith("qLaunchSuccess"): 200 return self.qLaunchSuccess() 201 if packet.startswith("QEnvironment:"): 202 return self.QEnvironment(packet) 203 if packet.startswith("QEnvironmentHexEncoded:"): 204 return self.QEnvironmentHexEncoded(packet) 205 if packet.startswith("qRegisterInfo"): 206 regnum = int(packet[len("qRegisterInfo") :], 16) 207 return self.qRegisterInfo(regnum) 208 if packet == "k": 209 return self.k() 210 211 return self.other(packet) 212 213 def qsProcessInfo(self): 214 return "E04" 215 216 def qfProcessInfo(self, packet): 217 return "E04" 218 219 def jGetLoadedDynamicLibrariesInfos(self, packet): 220 return "" 221 222 def qGetWorkingDir(self): 223 return "2f" 224 225 def qOffsets(self): 226 return "" 227 228 def qProcessInfo(self): 229 return "" 230 231 def qHostInfo(self): 232 return "ptrsize:8;endian:little;" 233 234 def qQueryGDBServer(self): 235 return "E04" 236 237 def interrupt(self): 238 raise self.UnexpectedPacketException() 239 240 def cont(self): 241 raise self.UnexpectedPacketException() 242 243 def vCont(self, packet): 244 raise self.UnexpectedPacketException() 245 246 def A(self, packet): 247 return "" 248 249 def D(self, packet): 250 return "OK" 251 252 def readRegisters(self): 253 return "00000000" * self.registerCount 254 255 def readRegister(self, register): 256 return "00000000" 257 258 def writeRegisters(self, registers_hex): 259 return "OK" 260 261 def writeRegister(self, register, value_hex): 262 return "OK" 263 264 def readMemory(self, addr, length): 265 return "00" * length 266 267 def writeMemory(self, addr, data_hex): 268 return "OK" 269 270 def qSymbol(self, symbol_args): 271 return "OK" 272 273 def qSupported(self, client_supported): 274 return "qXfer:features:read+;PacketSize=3fff;QStartNoAckMode+" 275 276 def qfThreadInfo(self): 277 return "l" 278 279 def qsThreadInfo(self): 280 return "l" 281 282 def qC(self): 283 return "QC0" 284 285 def QEnableErrorStrings(self): 286 return "OK" 287 288 def haltReason(self): 289 # SIGINT is 2, return type is 2 digit hex string 290 return "S02" 291 292 def qXferRead(self, obj, annex, offset, length): 293 return None, False 294 295 def _qXferResponse(self, data, has_more): 296 return "%s%s" % ("m" if has_more else "l", escape_binary(data)) 297 298 def vAttach(self, pid): 299 raise self.UnexpectedPacketException() 300 301 def selectThread(self, op, thread_id): 302 return "OK" 303 304 def setBreakpoint(self, packet): 305 raise self.UnexpectedPacketException() 306 307 def threadStopInfo(self, threadnum): 308 return "" 309 310 def other(self, packet): 311 # empty string means unsupported 312 return "" 313 314 def QThreadSuffixSupported(self): 315 return "" 316 317 def QListThreadsInStopReply(self): 318 return "" 319 320 def qMemoryRegionInfo(self, addr): 321 return "" 322 323 def qPathComplete(self): 324 return "" 325 326 def vFile(self, packet): 327 return "" 328 329 def vRun(self, packet): 330 return "" 331 332 def qLaunchSuccess(self): 333 return "" 334 335 def QEnvironment(self, packet): 336 return "OK" 337 338 def QEnvironmentHexEncoded(self, packet): 339 return "OK" 340 341 def qRegisterInfo(self, num): 342 return "" 343 344 def k(self): 345 return ["W01", self.RESPONSE_DISCONNECT] 346 347 """ 348 Raised when we receive a packet for which there is no default action. 349 Override the responder class to implement behavior suitable for the test at 350 hand. 351 """ 352 353 class UnexpectedPacketException(Exception): 354 pass 355 356 357class ServerChannel: 358 """ 359 A wrapper class for TCP or pty-based server. 360 """ 361 362 def get_connect_address(self): 363 """Get address for the client to connect to.""" 364 365 def get_connect_url(self): 366 """Get URL suitable for process connect command.""" 367 368 def close_server(self): 369 """Close all resources used by the server.""" 370 371 def accept(self): 372 """Accept a single client connection to the server.""" 373 374 def close_connection(self): 375 """Close all resources used by the accepted connection.""" 376 377 def recv(self): 378 """Receive a data packet from the connected client.""" 379 380 def sendall(self, data): 381 """Send the data to the connected client.""" 382 383 384class ServerSocket(ServerChannel): 385 def __init__(self, family, type, proto, addr): 386 self._server_socket = socket.socket(family, type, proto) 387 self._connection = None 388 389 self._server_socket.bind(addr) 390 self._server_socket.listen(1) 391 392 def close_server(self): 393 self._server_socket.close() 394 395 def accept(self): 396 assert self._connection is None 397 # accept() is stubborn and won't fail even when the socket is 398 # shutdown, so we'll use a timeout 399 self._server_socket.settimeout(30.0) 400 client, client_addr = self._server_socket.accept() 401 # The connected client inherits its timeout from self._socket, 402 # but we'll use a blocking socket for the client 403 client.settimeout(None) 404 self._connection = client 405 406 def close_connection(self): 407 assert self._connection is not None 408 self._connection.close() 409 self._connection = None 410 411 def recv(self): 412 assert self._connection is not None 413 return self._connection.recv(4096) 414 415 def sendall(self, data): 416 assert self._connection is not None 417 return self._connection.sendall(data) 418 419 420class TCPServerSocket(ServerSocket): 421 def __init__(self): 422 family, type, proto, _, addr = socket.getaddrinfo( 423 "localhost", 0, proto=socket.IPPROTO_TCP 424 )[0] 425 super().__init__(family, type, proto, addr) 426 427 def get_connect_address(self): 428 return "[{}]:{}".format(*self._server_socket.getsockname()) 429 430 def get_connect_url(self): 431 return "connect://" + self.get_connect_address() 432 433 434class UnixServerSocket(ServerSocket): 435 def __init__(self, addr): 436 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM, 0, addr) 437 438 def get_connect_address(self): 439 return self._server_socket.getsockname() 440 441 def get_connect_url(self): 442 return "unix-connect://" + self.get_connect_address() 443 444 445class PtyServerSocket(ServerChannel): 446 def __init__(self): 447 import pty 448 import tty 449 450 primary, secondary = pty.openpty() 451 tty.setraw(primary) 452 self._primary = io.FileIO(primary, "r+b") 453 self._secondary = io.FileIO(secondary, "r+b") 454 455 def get_connect_address(self): 456 libc = ctypes.CDLL(None) 457 libc.ptsname.argtypes = (ctypes.c_int,) 458 libc.ptsname.restype = ctypes.c_char_p 459 return libc.ptsname(self._primary.fileno()).decode() 460 461 def get_connect_url(self): 462 return "serial://" + self.get_connect_address() 463 464 def close_server(self): 465 self._secondary.close() 466 self._primary.close() 467 468 def recv(self): 469 try: 470 return self._primary.read(4096) 471 except OSError as e: 472 # closing the pty results in EIO on Linux, convert it to EOF 473 if e.errno == errno.EIO: 474 return b"" 475 raise 476 477 def sendall(self, data): 478 return self._primary.write(data) 479 480 481class MockGDBServer: 482 """ 483 A simple TCP-based GDB server that can test client behavior by receiving 484 commands and issuing custom-tailored responses. 485 486 Responses are generated via the .responder property, which should be an 487 instance of a class based on MockGDBServerResponder. 488 """ 489 490 responder = None 491 _socket = None 492 _thread = None 493 _receivedData = None 494 _receivedDataOffset = None 495 _shouldSendAck = True 496 497 def __init__(self, socket): 498 self._socket = socket 499 self.responder = MockGDBServerResponder() 500 501 def start(self): 502 # Start a thread that waits for a client connection. 503 self._thread = threading.Thread(target=self.run) 504 self._thread.start() 505 506 def stop(self): 507 self._thread.join() 508 self._thread = None 509 510 def get_connect_address(self): 511 return self._socket.get_connect_address() 512 513 def get_connect_url(self): 514 return self._socket.get_connect_url() 515 516 def run(self): 517 # For testing purposes, we only need to worry about one client 518 # connecting just one time. 519 try: 520 self._socket.accept() 521 except: 522 traceback.print_exc() 523 return 524 self._shouldSendAck = True 525 self._receivedData = "" 526 self._receivedDataOffset = 0 527 data = None 528 try: 529 while True: 530 data = seven.bitcast_to_string(self._socket.recv()) 531 if data is None or len(data) == 0: 532 break 533 self._receive(data) 534 except self.TerminateConnectionException: 535 pass 536 except Exception as e: 537 print( 538 "An exception happened when receiving the response from the gdb server. Closing the client..." 539 ) 540 traceback.print_exc() 541 finally: 542 self._socket.close_connection() 543 self._socket.close_server() 544 545 def _receive(self, data): 546 """ 547 Collects data, parses and responds to as many packets as exist. 548 Any leftover data is kept for parsing the next time around. 549 """ 550 self._receivedData += data 551 packet = self._parsePacket() 552 while packet is not None: 553 self._handlePacket(packet) 554 packet = self._parsePacket() 555 556 def _parsePacket(self): 557 """ 558 Reads bytes from self._receivedData, returning: 559 - a packet's contents if a valid packet is found 560 - the PACKET_ACK unique object if we got an ack 561 - None if we only have a partial packet 562 563 Raises an InvalidPacketException if unexpected data is received 564 or if checksums fail. 565 566 Once a complete packet is found at the front of self._receivedData, 567 its data is removed form self._receivedData. 568 """ 569 data = self._receivedData 570 i = self._receivedDataOffset 571 data_len = len(data) 572 if data_len == 0: 573 return None 574 if i == 0: 575 # If we're looking at the start of the received data, that means 576 # we're looking for the start of a new packet, denoted by a $. 577 # It's also possible we'll see an ACK here, denoted by a + 578 if data[0] == "+": 579 self._receivedData = data[1:] 580 return self.PACKET_ACK 581 if ord(data[0]) == 3: 582 self._receivedData = data[1:] 583 return self.PACKET_INTERRUPT 584 if data[0] == "$": 585 i += 1 586 else: 587 raise self.InvalidPacketException( 588 "Unexpected leading byte: %s" % data[0] 589 ) 590 591 # If we're looking beyond the start of the received data, then we're 592 # looking for the end of the packet content, denoted by a #. 593 # Note that we pick up searching from where we left off last time 594 while i < data_len and data[i] != "#": 595 i += 1 596 597 # If there isn't enough data left for a checksum, just remember where 598 # we left off so we can pick up there the next time around 599 if i > data_len - 3: 600 self._receivedDataOffset = i 601 return None 602 603 # If we have enough data remaining for the checksum, extract it and 604 # compare to the packet contents 605 packet = data[1:i] 606 i += 1 607 try: 608 check = int(data[i : i + 2], 16) 609 except ValueError: 610 raise self.InvalidPacketException("Checksum is not valid hex") 611 i += 2 612 if check != checksum(packet): 613 raise self.InvalidPacketException( 614 "Checksum %02x does not match content %02x" % (check, checksum(packet)) 615 ) 616 # remove parsed bytes from _receivedData and reset offset so parsing 617 # can start on the next packet the next time around 618 self._receivedData = data[i:] 619 self._receivedDataOffset = 0 620 return packet 621 622 def _sendPacket(self, packet): 623 self._socket.sendall(seven.bitcast_to_bytes(frame_packet(packet))) 624 625 def _handlePacket(self, packet): 626 if packet is self.PACKET_ACK: 627 # Ignore ACKs from the client. For the future, we can consider 628 # adding validation code to make sure the client only sends ACKs 629 # when it's supposed to. 630 return 631 response = "" 632 # We'll handle the ack stuff here since it's not something any of the 633 # tests will be concerned about, and it'll get turned off quickly anyway. 634 if self._shouldSendAck: 635 self._socket.sendall(seven.bitcast_to_bytes("+")) 636 if packet == "QStartNoAckMode": 637 self._shouldSendAck = False 638 response = "OK" 639 elif self.responder is not None: 640 # Delegate everything else to our responder 641 response = self.responder.respond(packet) 642 if not isinstance(response, list): 643 response = [response] 644 for part in response: 645 if part is MockGDBServerResponder.RESPONSE_DISCONNECT: 646 raise self.TerminateConnectionException() 647 self._sendPacket(part) 648 649 PACKET_ACK = object() 650 PACKET_INTERRUPT = object() 651 652 class TerminateConnectionException(Exception): 653 pass 654 655 class InvalidPacketException(Exception): 656 pass 657