xref: /llvm-project/lldb/packages/Python/lldbsuite/test/gdbclientutils.py (revision 2da99a11196246ab5f9787117f01b2251480607a)
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