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