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