133c0f93fSPavel Labathimport ctypes 233c0f93fSPavel Labathimport errno 333c0f93fSPavel Labathimport io 433c0f93fSPavel Labathimport threading 533c0f93fSPavel Labathimport socket 633c0f93fSPavel Labathimport traceback 733c0f93fSPavel Labathfrom lldbsuite.support import seven 833c0f93fSPavel Labath 92238dcc3SJonas Devlieghere 1033c0f93fSPavel Labathdef checksum(message): 1133c0f93fSPavel Labath """ 1233c0f93fSPavel Labath Calculate the GDB server protocol checksum of the message. 1333c0f93fSPavel Labath 1433c0f93fSPavel Labath The GDB server protocol uses a simple modulo 256 sum. 1533c0f93fSPavel Labath """ 1633c0f93fSPavel Labath check = 0 1733c0f93fSPavel Labath for c in message: 1833c0f93fSPavel Labath check += ord(c) 1933c0f93fSPavel Labath return check % 256 2033c0f93fSPavel Labath 2133c0f93fSPavel Labath 2233c0f93fSPavel Labathdef frame_packet(message): 2333c0f93fSPavel Labath """ 2433c0f93fSPavel Labath Create a framed packet that's ready to send over the GDB connection 2533c0f93fSPavel Labath channel. 2633c0f93fSPavel Labath 2733c0f93fSPavel Labath Framing includes surrounding the message between $ and #, and appending 2833c0f93fSPavel Labath a two character hex checksum. 2933c0f93fSPavel Labath """ 3033c0f93fSPavel Labath return "$%s#%02x" % (message, checksum(message)) 3133c0f93fSPavel Labath 3233c0f93fSPavel Labath 3333c0f93fSPavel Labathdef escape_binary(message): 3433c0f93fSPavel Labath """ 3533c0f93fSPavel Labath Escape the binary message using the process described in the GDB server 3633c0f93fSPavel Labath protocol documentation. 3733c0f93fSPavel Labath 3833c0f93fSPavel Labath Most bytes are sent through as-is, but $, #, and { are escaped by writing 3933c0f93fSPavel Labath a { followed by the original byte mod 0x20. 4033c0f93fSPavel Labath """ 4133c0f93fSPavel Labath out = "" 4233c0f93fSPavel Labath for c in message: 4333c0f93fSPavel Labath d = ord(c) 442238dcc3SJonas Devlieghere if d in (0x23, 0x24, 0x7D): 452238dcc3SJonas Devlieghere out += chr(0x7D) 4633c0f93fSPavel Labath out += chr(d ^ 0x20) 4733c0f93fSPavel Labath else: 4833c0f93fSPavel Labath out += c 4933c0f93fSPavel Labath return out 5033c0f93fSPavel Labath 5133c0f93fSPavel Labath 5233c0f93fSPavel Labathdef hex_encode_bytes(message): 5333c0f93fSPavel Labath """ 5433c0f93fSPavel Labath Encode the binary message by converting each byte into a two-character 5533c0f93fSPavel Labath hex string. 5633c0f93fSPavel Labath """ 5733c0f93fSPavel Labath out = "" 5833c0f93fSPavel Labath for c in message: 5933c0f93fSPavel Labath out += "%02x" % ord(c) 6033c0f93fSPavel Labath return out 6133c0f93fSPavel Labath 6233c0f93fSPavel Labath 6333c0f93fSPavel Labathdef hex_decode_bytes(hex_bytes): 6433c0f93fSPavel Labath """ 6533c0f93fSPavel Labath Decode the hex string into a binary message by converting each two-character 6633c0f93fSPavel Labath hex string into a single output byte. 6733c0f93fSPavel Labath """ 6833c0f93fSPavel Labath out = "" 6933c0f93fSPavel Labath hex_len = len(hex_bytes) 70e67cee09SPavel Labath i = 0 7133c0f93fSPavel Labath while i < hex_len - 1: 72e67cee09SPavel Labath out += chr(int(hex_bytes[i : i + 2], 16)) 7333c0f93fSPavel Labath i += 2 7433c0f93fSPavel Labath return out 7533c0f93fSPavel Labath 7633c0f93fSPavel Labath 7733c0f93fSPavel Labathclass MockGDBServerResponder: 7833c0f93fSPavel Labath """ 7933c0f93fSPavel Labath A base class for handling client packets and issuing server responses for 8033c0f93fSPavel Labath GDB tests. 8133c0f93fSPavel Labath 8233c0f93fSPavel Labath This handles many typical situations, while still allowing subclasses to 8333c0f93fSPavel Labath completely customize their responses. 8433c0f93fSPavel Labath 8533c0f93fSPavel Labath Most subclasses will be interested in overriding the other() method, which 8633c0f93fSPavel Labath handles any packet not recognized in the common packet handling code. 8733c0f93fSPavel Labath """ 8833c0f93fSPavel Labath 8933c0f93fSPavel Labath registerCount = 40 9033c0f93fSPavel Labath packetLog = None 912238dcc3SJonas Devlieghere 922238dcc3SJonas Devlieghere class RESPONSE_DISCONNECT: 932238dcc3SJonas Devlieghere pass 9433c0f93fSPavel Labath 9533c0f93fSPavel Labath def __init__(self): 9633c0f93fSPavel Labath self.packetLog = [] 9733c0f93fSPavel Labath 9833c0f93fSPavel Labath def respond(self, packet): 9933c0f93fSPavel Labath """ 10033c0f93fSPavel Labath Return the unframed packet data that the server should issue in response 10133c0f93fSPavel Labath to the given packet received from the client. 10233c0f93fSPavel Labath """ 10333c0f93fSPavel Labath self.packetLog.append(packet) 10433c0f93fSPavel Labath if packet is MockGDBServer.PACKET_INTERRUPT: 10533c0f93fSPavel Labath return self.interrupt() 10633c0f93fSPavel Labath if packet == "c": 10733c0f93fSPavel Labath return self.cont() 10833c0f93fSPavel Labath if packet.startswith("vCont;c"): 10933c0f93fSPavel Labath return self.vCont(packet) 11033c0f93fSPavel Labath if packet[0] == "A": 11133c0f93fSPavel Labath return self.A(packet) 11233c0f93fSPavel Labath if packet[0] == "D": 11333c0f93fSPavel Labath return self.D(packet) 11433c0f93fSPavel Labath if packet[0] == "g": 11533c0f93fSPavel Labath return self.readRegisters() 11633c0f93fSPavel Labath if packet[0] == "G": 11733c0f93fSPavel Labath # Gxxxxxxxxxxx 11833c0f93fSPavel Labath # Gxxxxxxxxxxx;thread:1234; 1192238dcc3SJonas Devlieghere return self.writeRegisters(packet[1:].split(";")[0]) 12033c0f93fSPavel Labath if packet[0] == "p": 1212238dcc3SJonas Devlieghere regnum = packet[1:].split(";")[0] 12233c0f93fSPavel Labath return self.readRegister(int(regnum, 16)) 12333c0f93fSPavel Labath if packet[0] == "P": 12433c0f93fSPavel Labath register, value = packet[1:].split("=") 12533c0f93fSPavel Labath return self.writeRegister(int(register, 16), value) 12633c0f93fSPavel Labath if packet[0] == "m": 1272238dcc3SJonas Devlieghere addr, length = [int(x, 16) for x in packet[1:].split(",")] 12833c0f93fSPavel Labath return self.readMemory(addr, length) 12933c0f93fSPavel Labath if packet[0] == "M": 13033c0f93fSPavel Labath location, encoded_data = packet[1:].split(":") 1312238dcc3SJonas Devlieghere addr, length = [int(x, 16) for x in location.split(",")] 13233c0f93fSPavel Labath return self.writeMemory(addr, encoded_data) 13333c0f93fSPavel Labath if packet[0:7] == "qSymbol": 13433c0f93fSPavel Labath return self.qSymbol(packet[8:]) 13533c0f93fSPavel Labath if packet[0:10] == "qSupported": 13633c0f93fSPavel Labath return self.qSupported(packet[11:].split(";")) 13733c0f93fSPavel Labath if packet == "qfThreadInfo": 13833c0f93fSPavel Labath return self.qfThreadInfo() 13933c0f93fSPavel Labath if packet == "qsThreadInfo": 14033c0f93fSPavel Labath return self.qsThreadInfo() 14133c0f93fSPavel Labath if packet == "qC": 14233c0f93fSPavel Labath return self.qC() 14333c0f93fSPavel Labath if packet == "QEnableErrorStrings": 14433c0f93fSPavel Labath return self.QEnableErrorStrings() 14533c0f93fSPavel Labath if packet == "?": 14633c0f93fSPavel Labath return self.haltReason() 14733c0f93fSPavel Labath if packet == "s": 14833c0f93fSPavel Labath return self.haltReason() 14933c0f93fSPavel Labath if packet[0] == "H": 15033c0f93fSPavel Labath tid = packet[2:] 15133c0f93fSPavel Labath if "." in tid: 15233c0f93fSPavel Labath assert tid.startswith("p") 15333c0f93fSPavel Labath # TODO: do we want to do anything with PID? 15433c0f93fSPavel Labath tid = tid.split(".", 1)[1] 15533c0f93fSPavel Labath return self.selectThread(packet[1], int(tid, 16)) 15633c0f93fSPavel Labath if packet[0:6] == "qXfer:": 15733c0f93fSPavel Labath obj, read, annex, location = packet[6:].split(":") 1582238dcc3SJonas Devlieghere offset, length = [int(x, 16) for x in location.split(",")] 15933c0f93fSPavel Labath data, has_more = self.qXferRead(obj, annex, offset, length) 16033c0f93fSPavel Labath if data is not None: 16133c0f93fSPavel Labath return self._qXferResponse(data, has_more) 16233c0f93fSPavel Labath return "" 16333c0f93fSPavel Labath if packet.startswith("vAttach;"): 1642238dcc3SJonas Devlieghere pid = packet.partition(";")[2] 16533c0f93fSPavel Labath return self.vAttach(int(pid, 16)) 16633c0f93fSPavel Labath if packet[0] == "Z": 16733c0f93fSPavel Labath return self.setBreakpoint(packet) 16833c0f93fSPavel Labath if packet.startswith("qThreadStopInfo"): 16933c0f93fSPavel Labath threadnum = int(packet[15:], 16) 17033c0f93fSPavel Labath return self.threadStopInfo(threadnum) 17133c0f93fSPavel Labath if packet == "QThreadSuffixSupported": 17233c0f93fSPavel Labath return self.QThreadSuffixSupported() 17333c0f93fSPavel Labath if packet == "QListThreadsInStopReply": 17433c0f93fSPavel Labath return self.QListThreadsInStopReply() 17533c0f93fSPavel Labath if packet.startswith("qMemoryRegionInfo:"): 1762238dcc3SJonas Devlieghere return self.qMemoryRegionInfo(int(packet.split(":")[1], 16)) 17733c0f93fSPavel Labath if packet == "qQueryGDBServer": 17833c0f93fSPavel Labath return self.qQueryGDBServer() 17933c0f93fSPavel Labath if packet == "qHostInfo": 18033c0f93fSPavel Labath return self.qHostInfo() 18133c0f93fSPavel Labath if packet == "qGetWorkingDir": 18233c0f93fSPavel Labath return self.qGetWorkingDir() 18333c0f93fSPavel Labath if packet == "qOffsets": 1842238dcc3SJonas Devlieghere return self.qOffsets() 185e67cee09SPavel Labath if packet == "qProcessInfo": 186e67cee09SPavel Labath return self.qProcessInfo() 18733c0f93fSPavel Labath if packet == "qsProcessInfo": 18833c0f93fSPavel Labath return self.qsProcessInfo() 18933c0f93fSPavel Labath if packet.startswith("qfProcessInfo"): 19033c0f93fSPavel Labath return self.qfProcessInfo(packet) 191ced4e000SAdrian Prantl if packet.startswith("jGetLoadedDynamicLibrariesInfos"): 192ced4e000SAdrian Prantl return self.jGetLoadedDynamicLibrariesInfos(packet) 19333c0f93fSPavel Labath if packet.startswith("qPathComplete:"): 19433c0f93fSPavel Labath return self.qPathComplete() 19533c0f93fSPavel Labath if packet.startswith("vFile:"): 19633c0f93fSPavel Labath return self.vFile(packet) 19733c0f93fSPavel Labath if packet.startswith("vRun;"): 19833c0f93fSPavel Labath return self.vRun(packet) 199*2da99a11SJonas Devlieghere if packet.startswith("qLaunchGDBServer;"): 200*2da99a11SJonas Devlieghere _, host = packet.partition(";")[2].split(":") 201*2da99a11SJonas Devlieghere return self.qLaunchGDBServer(host) 20233c0f93fSPavel Labath if packet.startswith("qLaunchSuccess"): 20333c0f93fSPavel Labath return self.qLaunchSuccess() 20433c0f93fSPavel Labath if packet.startswith("QEnvironment:"): 20533c0f93fSPavel Labath return self.QEnvironment(packet) 20633c0f93fSPavel Labath if packet.startswith("QEnvironmentHexEncoded:"): 20733c0f93fSPavel Labath return self.QEnvironmentHexEncoded(packet) 20833c0f93fSPavel Labath if packet.startswith("qRegisterInfo"): 20933c0f93fSPavel Labath regnum = int(packet[len("qRegisterInfo") :], 16) 21033c0f93fSPavel Labath return self.qRegisterInfo(regnum) 21133c0f93fSPavel Labath if packet == "k": 21233c0f93fSPavel Labath return self.k() 21333c0f93fSPavel Labath 21433c0f93fSPavel Labath return self.other(packet) 21533c0f93fSPavel Labath 21633c0f93fSPavel Labath def qsProcessInfo(self): 21733c0f93fSPavel Labath return "E04" 21833c0f93fSPavel Labath 21933c0f93fSPavel Labath def qfProcessInfo(self, packet): 22033c0f93fSPavel Labath return "E04" 22133c0f93fSPavel Labath 222ced4e000SAdrian Prantl def jGetLoadedDynamicLibrariesInfos(self, packet): 223ced4e000SAdrian Prantl return "" 224ced4e000SAdrian Prantl 22533c0f93fSPavel Labath def qGetWorkingDir(self): 22633c0f93fSPavel Labath return "2f" 22733c0f93fSPavel Labath 22833c0f93fSPavel Labath def qOffsets(self): 22933c0f93fSPavel Labath return "" 23033c0f93fSPavel Labath 231e67cee09SPavel Labath def qProcessInfo(self): 232e67cee09SPavel Labath return "" 233e67cee09SPavel Labath 23433c0f93fSPavel Labath def qHostInfo(self): 23533c0f93fSPavel Labath return "ptrsize:8;endian:little;" 23633c0f93fSPavel Labath 23733c0f93fSPavel Labath def qQueryGDBServer(self): 23833c0f93fSPavel Labath return "E04" 23933c0f93fSPavel Labath 24033c0f93fSPavel Labath def interrupt(self): 24133c0f93fSPavel Labath raise self.UnexpectedPacketException() 24233c0f93fSPavel Labath 24333c0f93fSPavel Labath def cont(self): 24433c0f93fSPavel Labath raise self.UnexpectedPacketException() 24533c0f93fSPavel Labath 24633c0f93fSPavel Labath def vCont(self, packet): 24733c0f93fSPavel Labath raise self.UnexpectedPacketException() 24833c0f93fSPavel Labath 24933c0f93fSPavel Labath def A(self, packet): 25033c0f93fSPavel Labath return "" 25133c0f93fSPavel Labath 25233c0f93fSPavel Labath def D(self, packet): 25333c0f93fSPavel Labath return "OK" 25433c0f93fSPavel Labath 25533c0f93fSPavel Labath def readRegisters(self): 25633c0f93fSPavel Labath return "00000000" * self.registerCount 25733c0f93fSPavel Labath 25833c0f93fSPavel Labath def readRegister(self, register): 25933c0f93fSPavel Labath return "00000000" 26033c0f93fSPavel Labath 26133c0f93fSPavel Labath def writeRegisters(self, registers_hex): 26233c0f93fSPavel Labath return "OK" 26333c0f93fSPavel Labath 26433c0f93fSPavel Labath def writeRegister(self, register, value_hex): 26533c0f93fSPavel Labath return "OK" 26633c0f93fSPavel Labath 26733c0f93fSPavel Labath def readMemory(self, addr, length): 26833c0f93fSPavel Labath return "00" * length 26933c0f93fSPavel Labath 27033c0f93fSPavel Labath def writeMemory(self, addr, data_hex): 27133c0f93fSPavel Labath return "OK" 27233c0f93fSPavel Labath 27333c0f93fSPavel Labath def qSymbol(self, symbol_args): 27433c0f93fSPavel Labath return "OK" 27533c0f93fSPavel Labath 27633c0f93fSPavel Labath def qSupported(self, client_supported): 27733c0f93fSPavel Labath return "qXfer:features:read+;PacketSize=3fff;QStartNoAckMode+" 27833c0f93fSPavel Labath 27933c0f93fSPavel Labath def qfThreadInfo(self): 28033c0f93fSPavel Labath return "l" 28133c0f93fSPavel Labath 28233c0f93fSPavel Labath def qsThreadInfo(self): 28333c0f93fSPavel Labath return "l" 28433c0f93fSPavel Labath 28533c0f93fSPavel Labath def qC(self): 28633c0f93fSPavel Labath return "QC0" 28733c0f93fSPavel Labath 28833c0f93fSPavel Labath def QEnableErrorStrings(self): 28933c0f93fSPavel Labath return "OK" 29033c0f93fSPavel Labath 29133c0f93fSPavel Labath def haltReason(self): 29233c0f93fSPavel Labath # SIGINT is 2, return type is 2 digit hex string 29333c0f93fSPavel Labath return "S02" 29433c0f93fSPavel Labath 29533c0f93fSPavel Labath def qXferRead(self, obj, annex, offset, length): 29633c0f93fSPavel Labath return None, False 29733c0f93fSPavel Labath 29833c0f93fSPavel Labath def _qXferResponse(self, data, has_more): 29933c0f93fSPavel Labath return "%s%s" % ("m" if has_more else "l", escape_binary(data)) 30033c0f93fSPavel Labath 30133c0f93fSPavel Labath def vAttach(self, pid): 30233c0f93fSPavel Labath raise self.UnexpectedPacketException() 30333c0f93fSPavel Labath 30433c0f93fSPavel Labath def selectThread(self, op, thread_id): 30533c0f93fSPavel Labath return "OK" 30633c0f93fSPavel Labath 30733c0f93fSPavel Labath def setBreakpoint(self, packet): 30833c0f93fSPavel Labath raise self.UnexpectedPacketException() 30933c0f93fSPavel Labath 31033c0f93fSPavel Labath def threadStopInfo(self, threadnum): 31133c0f93fSPavel Labath return "" 31233c0f93fSPavel Labath 31333c0f93fSPavel Labath def other(self, packet): 31433c0f93fSPavel Labath # empty string means unsupported 31533c0f93fSPavel Labath return "" 31633c0f93fSPavel Labath 31733c0f93fSPavel Labath def QThreadSuffixSupported(self): 31833c0f93fSPavel Labath return "" 31933c0f93fSPavel Labath 32033c0f93fSPavel Labath def QListThreadsInStopReply(self): 32133c0f93fSPavel Labath return "" 32233c0f93fSPavel Labath 32333c0f93fSPavel Labath def qMemoryRegionInfo(self, addr): 32433c0f93fSPavel Labath return "" 32533c0f93fSPavel Labath 32633c0f93fSPavel Labath def qPathComplete(self): 32733c0f93fSPavel Labath return "" 32833c0f93fSPavel Labath 32933c0f93fSPavel Labath def vFile(self, packet): 33033c0f93fSPavel Labath return "" 33133c0f93fSPavel Labath 33233c0f93fSPavel Labath def vRun(self, packet): 33333c0f93fSPavel Labath return "" 33433c0f93fSPavel Labath 335*2da99a11SJonas Devlieghere def qLaunchGDBServer(self, host): 336*2da99a11SJonas Devlieghere raise self.UnexpectedPacketException() 337*2da99a11SJonas Devlieghere 33833c0f93fSPavel Labath def qLaunchSuccess(self): 33933c0f93fSPavel Labath return "" 34033c0f93fSPavel Labath 34133c0f93fSPavel Labath def QEnvironment(self, packet): 34233c0f93fSPavel Labath return "OK" 34333c0f93fSPavel Labath 34433c0f93fSPavel Labath def QEnvironmentHexEncoded(self, packet): 34533c0f93fSPavel Labath return "OK" 34633c0f93fSPavel Labath 34733c0f93fSPavel Labath def qRegisterInfo(self, num): 34833c0f93fSPavel Labath return "" 34933c0f93fSPavel Labath 35033c0f93fSPavel Labath def k(self): 351f3b7cc8bSPavel Labath return ["W01", self.RESPONSE_DISCONNECT] 35233c0f93fSPavel Labath 35333c0f93fSPavel Labath """ 35433c0f93fSPavel Labath Raised when we receive a packet for which there is no default action. 35533c0f93fSPavel Labath Override the responder class to implement behavior suitable for the test at 35633c0f93fSPavel Labath hand. 35733c0f93fSPavel Labath """ 3582238dcc3SJonas Devlieghere 35933c0f93fSPavel Labath class UnexpectedPacketException(Exception): 36033c0f93fSPavel Labath pass 36133c0f93fSPavel Labath 36233c0f93fSPavel Labath 36314086849SPavel Labathclass ServerChannel: 36433c0f93fSPavel Labath """ 36533c0f93fSPavel Labath A wrapper class for TCP or pty-based server. 36633c0f93fSPavel Labath """ 36733c0f93fSPavel Labath 36833c0f93fSPavel Labath def get_connect_address(self): 36933c0f93fSPavel Labath """Get address for the client to connect to.""" 37033c0f93fSPavel Labath 37133c0f93fSPavel Labath def get_connect_url(self): 37233c0f93fSPavel Labath """Get URL suitable for process connect command.""" 37333c0f93fSPavel Labath 37433c0f93fSPavel Labath def close_server(self): 37533c0f93fSPavel Labath """Close all resources used by the server.""" 37633c0f93fSPavel Labath 37733c0f93fSPavel Labath def accept(self): 37833c0f93fSPavel Labath """Accept a single client connection to the server.""" 37933c0f93fSPavel Labath 38033c0f93fSPavel Labath def close_connection(self): 38133c0f93fSPavel Labath """Close all resources used by the accepted connection.""" 38233c0f93fSPavel Labath 38333c0f93fSPavel Labath def recv(self): 38433c0f93fSPavel Labath """Receive a data packet from the connected client.""" 38533c0f93fSPavel Labath 38633c0f93fSPavel Labath def sendall(self, data): 38733c0f93fSPavel Labath """Send the data to the connected client.""" 38833c0f93fSPavel Labath 38933c0f93fSPavel Labath 39014086849SPavel Labathclass ServerSocket(ServerChannel): 39114086849SPavel Labath def __init__(self, family, type, proto, addr): 39233c0f93fSPavel Labath self._server_socket = socket.socket(family, type, proto) 39333c0f93fSPavel Labath self._connection = None 39433c0f93fSPavel Labath 39533c0f93fSPavel Labath self._server_socket.bind(addr) 39633c0f93fSPavel Labath self._server_socket.listen(1) 39733c0f93fSPavel Labath 39833c0f93fSPavel Labath def close_server(self): 39933c0f93fSPavel Labath self._server_socket.close() 40033c0f93fSPavel Labath 40133c0f93fSPavel Labath def accept(self): 40233c0f93fSPavel Labath assert self._connection is None 40333c0f93fSPavel Labath # accept() is stubborn and won't fail even when the socket is 40433c0f93fSPavel Labath # shutdown, so we'll use a timeout 40533c0f93fSPavel Labath self._server_socket.settimeout(30.0) 40633c0f93fSPavel Labath client, client_addr = self._server_socket.accept() 40733c0f93fSPavel Labath # The connected client inherits its timeout from self._socket, 40833c0f93fSPavel Labath # but we'll use a blocking socket for the client 40933c0f93fSPavel Labath client.settimeout(None) 41033c0f93fSPavel Labath self._connection = client 41133c0f93fSPavel Labath 41233c0f93fSPavel Labath def close_connection(self): 41333c0f93fSPavel Labath assert self._connection is not None 41433c0f93fSPavel Labath self._connection.close() 41533c0f93fSPavel Labath self._connection = None 41633c0f93fSPavel Labath 41733c0f93fSPavel Labath def recv(self): 41833c0f93fSPavel Labath assert self._connection is not None 41933c0f93fSPavel Labath return self._connection.recv(4096) 42033c0f93fSPavel Labath 42133c0f93fSPavel Labath def sendall(self, data): 42233c0f93fSPavel Labath assert self._connection is not None 42333c0f93fSPavel Labath return self._connection.sendall(data) 42433c0f93fSPavel Labath 42533c0f93fSPavel Labath 42614086849SPavel Labathclass TCPServerSocket(ServerSocket): 42714086849SPavel Labath def __init__(self): 42814086849SPavel Labath family, type, proto, _, addr = socket.getaddrinfo( 4292238dcc3SJonas Devlieghere "localhost", 0, proto=socket.IPPROTO_TCP 4302238dcc3SJonas Devlieghere )[0] 43114086849SPavel Labath super().__init__(family, type, proto, addr) 43214086849SPavel Labath 43314086849SPavel Labath def get_connect_address(self): 43414086849SPavel Labath return "[{}]:{}".format(*self._server_socket.getsockname()) 43514086849SPavel Labath 43614086849SPavel Labath def get_connect_url(self): 43714086849SPavel Labath return "connect://" + self.get_connect_address() 43814086849SPavel Labath 43914086849SPavel Labath 44014086849SPavel Labathclass UnixServerSocket(ServerSocket): 44114086849SPavel Labath def __init__(self, addr): 44214086849SPavel Labath super().__init__(socket.AF_UNIX, socket.SOCK_STREAM, 0, addr) 44314086849SPavel Labath 44414086849SPavel Labath def get_connect_address(self): 44514086849SPavel Labath return self._server_socket.getsockname() 44614086849SPavel Labath 44714086849SPavel Labath def get_connect_url(self): 44814086849SPavel Labath return "unix-connect://" + self.get_connect_address() 44914086849SPavel Labath 45014086849SPavel Labath 45114086849SPavel Labathclass PtyServerSocket(ServerChannel): 45233c0f93fSPavel Labath def __init__(self): 45333c0f93fSPavel Labath import pty 45433c0f93fSPavel Labath import tty 4552238dcc3SJonas Devlieghere 45633c0f93fSPavel Labath primary, secondary = pty.openpty() 45733c0f93fSPavel Labath tty.setraw(primary) 4582238dcc3SJonas Devlieghere self._primary = io.FileIO(primary, "r+b") 4592238dcc3SJonas Devlieghere self._secondary = io.FileIO(secondary, "r+b") 46033c0f93fSPavel Labath 46133c0f93fSPavel Labath def get_connect_address(self): 46233c0f93fSPavel Labath libc = ctypes.CDLL(None) 46333c0f93fSPavel Labath libc.ptsname.argtypes = (ctypes.c_int,) 46433c0f93fSPavel Labath libc.ptsname.restype = ctypes.c_char_p 46533c0f93fSPavel Labath return libc.ptsname(self._primary.fileno()).decode() 46633c0f93fSPavel Labath 46733c0f93fSPavel Labath def get_connect_url(self): 46833c0f93fSPavel Labath return "serial://" + self.get_connect_address() 46933c0f93fSPavel Labath 47033c0f93fSPavel Labath def close_server(self): 47133c0f93fSPavel Labath self._secondary.close() 47233c0f93fSPavel Labath self._primary.close() 47333c0f93fSPavel Labath 47433c0f93fSPavel Labath def recv(self): 47533c0f93fSPavel Labath try: 47633c0f93fSPavel Labath return self._primary.read(4096) 47733c0f93fSPavel Labath except OSError as e: 47833c0f93fSPavel Labath # closing the pty results in EIO on Linux, convert it to EOF 47933c0f93fSPavel Labath if e.errno == errno.EIO: 4802238dcc3SJonas Devlieghere return b"" 48133c0f93fSPavel Labath raise 48233c0f93fSPavel Labath 48333c0f93fSPavel Labath def sendall(self, data): 48433c0f93fSPavel Labath return self._primary.write(data) 48533c0f93fSPavel Labath 48633c0f93fSPavel Labath 48733c0f93fSPavel Labathclass MockGDBServer: 48833c0f93fSPavel Labath """ 48933c0f93fSPavel Labath A simple TCP-based GDB server that can test client behavior by receiving 49033c0f93fSPavel Labath commands and issuing custom-tailored responses. 49133c0f93fSPavel Labath 49233c0f93fSPavel Labath Responses are generated via the .responder property, which should be an 49333c0f93fSPavel Labath instance of a class based on MockGDBServerResponder. 49433c0f93fSPavel Labath """ 49533c0f93fSPavel Labath 49633c0f93fSPavel Labath responder = None 49733c0f93fSPavel Labath _socket = None 49833c0f93fSPavel Labath _thread = None 49933c0f93fSPavel Labath _receivedData = None 50033c0f93fSPavel Labath _receivedDataOffset = None 50133c0f93fSPavel Labath _shouldSendAck = True 50233c0f93fSPavel Labath 5037c8ae65fSPavel Labath def __init__(self, socket): 5047c8ae65fSPavel Labath self._socket = socket 50533c0f93fSPavel Labath self.responder = MockGDBServerResponder() 50633c0f93fSPavel Labath 50733c0f93fSPavel Labath def start(self): 50833c0f93fSPavel Labath # Start a thread that waits for a client connection. 5097c8ae65fSPavel Labath self._thread = threading.Thread(target=self.run) 51033c0f93fSPavel Labath self._thread.start() 51133c0f93fSPavel Labath 51233c0f93fSPavel Labath def stop(self): 51333c0f93fSPavel Labath self._thread.join() 51433c0f93fSPavel Labath self._thread = None 51533c0f93fSPavel Labath 51633c0f93fSPavel Labath def get_connect_address(self): 51733c0f93fSPavel Labath return self._socket.get_connect_address() 51833c0f93fSPavel Labath 51933c0f93fSPavel Labath def get_connect_url(self): 52033c0f93fSPavel Labath return self._socket.get_connect_url() 52133c0f93fSPavel Labath 5227c8ae65fSPavel Labath def run(self): 52333c0f93fSPavel Labath # For testing purposes, we only need to worry about one client 52433c0f93fSPavel Labath # connecting just one time. 52533c0f93fSPavel Labath try: 52633c0f93fSPavel Labath self._socket.accept() 52733c0f93fSPavel Labath except: 52814086849SPavel Labath traceback.print_exc() 52933c0f93fSPavel Labath return 53033c0f93fSPavel Labath self._shouldSendAck = True 53133c0f93fSPavel Labath self._receivedData = "" 53233c0f93fSPavel Labath self._receivedDataOffset = 0 53333c0f93fSPavel Labath data = None 53433c0f93fSPavel Labath try: 535f3b7cc8bSPavel Labath while True: 53633c0f93fSPavel Labath data = seven.bitcast_to_string(self._socket.recv()) 53733c0f93fSPavel Labath if data is None or len(data) == 0: 53833c0f93fSPavel Labath break 53933c0f93fSPavel Labath self._receive(data) 540f3b7cc8bSPavel Labath except self.TerminateConnectionException: 541f3b7cc8bSPavel Labath pass 54233c0f93fSPavel Labath except Exception as e: 5432238dcc3SJonas Devlieghere print( 5442238dcc3SJonas Devlieghere "An exception happened when receiving the response from the gdb server. Closing the client..." 5452238dcc3SJonas Devlieghere ) 54633c0f93fSPavel Labath traceback.print_exc() 547f3b7cc8bSPavel Labath finally: 54833c0f93fSPavel Labath self._socket.close_connection() 549f3b7cc8bSPavel Labath self._socket.close_server() 55033c0f93fSPavel Labath 55133c0f93fSPavel Labath def _receive(self, data): 55233c0f93fSPavel Labath """ 55333c0f93fSPavel Labath Collects data, parses and responds to as many packets as exist. 55433c0f93fSPavel Labath Any leftover data is kept for parsing the next time around. 55533c0f93fSPavel Labath """ 55633c0f93fSPavel Labath self._receivedData += data 55733c0f93fSPavel Labath packet = self._parsePacket() 55833c0f93fSPavel Labath while packet is not None: 55933c0f93fSPavel Labath self._handlePacket(packet) 56033c0f93fSPavel Labath packet = self._parsePacket() 56133c0f93fSPavel Labath 56233c0f93fSPavel Labath def _parsePacket(self): 56333c0f93fSPavel Labath """ 56433c0f93fSPavel Labath Reads bytes from self._receivedData, returning: 56533c0f93fSPavel Labath - a packet's contents if a valid packet is found 56633c0f93fSPavel Labath - the PACKET_ACK unique object if we got an ack 56733c0f93fSPavel Labath - None if we only have a partial packet 56833c0f93fSPavel Labath 56933c0f93fSPavel Labath Raises an InvalidPacketException if unexpected data is received 57033c0f93fSPavel Labath or if checksums fail. 57133c0f93fSPavel Labath 57233c0f93fSPavel Labath Once a complete packet is found at the front of self._receivedData, 57333c0f93fSPavel Labath its data is removed form self._receivedData. 57433c0f93fSPavel Labath """ 57533c0f93fSPavel Labath data = self._receivedData 57633c0f93fSPavel Labath i = self._receivedDataOffset 57733c0f93fSPavel Labath data_len = len(data) 57833c0f93fSPavel Labath if data_len == 0: 57933c0f93fSPavel Labath return None 58033c0f93fSPavel Labath if i == 0: 58133c0f93fSPavel Labath # If we're looking at the start of the received data, that means 58233c0f93fSPavel Labath # we're looking for the start of a new packet, denoted by a $. 58333c0f93fSPavel Labath # It's also possible we'll see an ACK here, denoted by a + 5842238dcc3SJonas Devlieghere if data[0] == "+": 58533c0f93fSPavel Labath self._receivedData = data[1:] 58633c0f93fSPavel Labath return self.PACKET_ACK 58733c0f93fSPavel Labath if ord(data[0]) == 3: 58833c0f93fSPavel Labath self._receivedData = data[1:] 58933c0f93fSPavel Labath return self.PACKET_INTERRUPT 5902238dcc3SJonas Devlieghere if data[0] == "$": 59133c0f93fSPavel Labath i += 1 59233c0f93fSPavel Labath else: 59333c0f93fSPavel Labath raise self.InvalidPacketException( 5942238dcc3SJonas Devlieghere "Unexpected leading byte: %s" % data[0] 5952238dcc3SJonas Devlieghere ) 59633c0f93fSPavel Labath 59733c0f93fSPavel Labath # If we're looking beyond the start of the received data, then we're 59833c0f93fSPavel Labath # looking for the end of the packet content, denoted by a #. 59933c0f93fSPavel Labath # Note that we pick up searching from where we left off last time 6002238dcc3SJonas Devlieghere while i < data_len and data[i] != "#": 60133c0f93fSPavel Labath i += 1 60233c0f93fSPavel Labath 60333c0f93fSPavel Labath # If there isn't enough data left for a checksum, just remember where 60433c0f93fSPavel Labath # we left off so we can pick up there the next time around 60533c0f93fSPavel Labath if i > data_len - 3: 60633c0f93fSPavel Labath self._receivedDataOffset = i 60733c0f93fSPavel Labath return None 60833c0f93fSPavel Labath 60933c0f93fSPavel Labath # If we have enough data remaining for the checksum, extract it and 61033c0f93fSPavel Labath # compare to the packet contents 61133c0f93fSPavel Labath packet = data[1:i] 61233c0f93fSPavel Labath i += 1 61333c0f93fSPavel Labath try: 61433c0f93fSPavel Labath check = int(data[i : i + 2], 16) 61533c0f93fSPavel Labath except ValueError: 61633c0f93fSPavel Labath raise self.InvalidPacketException("Checksum is not valid hex") 61733c0f93fSPavel Labath i += 2 61833c0f93fSPavel Labath if check != checksum(packet): 61933c0f93fSPavel Labath raise self.InvalidPacketException( 6202238dcc3SJonas Devlieghere "Checksum %02x does not match content %02x" % (check, checksum(packet)) 6212238dcc3SJonas Devlieghere ) 62233c0f93fSPavel Labath # remove parsed bytes from _receivedData and reset offset so parsing 62333c0f93fSPavel Labath # can start on the next packet the next time around 62433c0f93fSPavel Labath self._receivedData = data[i:] 62533c0f93fSPavel Labath self._receivedDataOffset = 0 62633c0f93fSPavel Labath return packet 62733c0f93fSPavel Labath 628f3b7cc8bSPavel Labath def _sendPacket(self, packet): 629f3b7cc8bSPavel Labath self._socket.sendall(seven.bitcast_to_bytes(frame_packet(packet))) 630f3b7cc8bSPavel Labath 63133c0f93fSPavel Labath def _handlePacket(self, packet): 63233c0f93fSPavel Labath if packet is self.PACKET_ACK: 63333c0f93fSPavel Labath # Ignore ACKs from the client. For the future, we can consider 63433c0f93fSPavel Labath # adding validation code to make sure the client only sends ACKs 63533c0f93fSPavel Labath # when it's supposed to. 63633c0f93fSPavel Labath return 63733c0f93fSPavel Labath response = "" 63833c0f93fSPavel Labath # We'll handle the ack stuff here since it's not something any of the 63933c0f93fSPavel Labath # tests will be concerned about, and it'll get turned off quickly anyway. 64033c0f93fSPavel Labath if self._shouldSendAck: 6412238dcc3SJonas Devlieghere self._socket.sendall(seven.bitcast_to_bytes("+")) 64233c0f93fSPavel Labath if packet == "QStartNoAckMode": 64333c0f93fSPavel Labath self._shouldSendAck = False 64433c0f93fSPavel Labath response = "OK" 64533c0f93fSPavel Labath elif self.responder is not None: 64633c0f93fSPavel Labath # Delegate everything else to our responder 64733c0f93fSPavel Labath response = self.responder.respond(packet) 648f3b7cc8bSPavel Labath if not isinstance(response, list): 649f3b7cc8bSPavel Labath response = [response] 650f3b7cc8bSPavel Labath for part in response: 651f3b7cc8bSPavel Labath if part is MockGDBServerResponder.RESPONSE_DISCONNECT: 652f3b7cc8bSPavel Labath raise self.TerminateConnectionException() 653f3b7cc8bSPavel Labath self._sendPacket(part) 65433c0f93fSPavel Labath 65533c0f93fSPavel Labath PACKET_ACK = object() 65633c0f93fSPavel Labath PACKET_INTERRUPT = object() 65733c0f93fSPavel Labath 658f3b7cc8bSPavel Labath class TerminateConnectionException(Exception): 65933c0f93fSPavel Labath pass 66033c0f93fSPavel Labath 661f3b7cc8bSPavel Labath class InvalidPacketException(Exception): 662f3b7cc8bSPavel Labath pass 663