brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.8 KiB · 4c40299 Raw
741 lines · python
1from abc import ABC, abstractmethod2import ctypes3import errno4import io5import threading6import socket7import traceback8from enum import Enum9from lldbsuite.support import seven10from typing import Optional, List, Tuple, Union, Sequence11 12 13def checksum(message):14    """15    Calculate the GDB server protocol checksum of the message.16 17    The GDB server protocol uses a simple modulo 256 sum.18    """19    check = 020    for c in message:21        check += ord(c)22    return check % 25623 24 25def frame_packet(message):26    """27    Create a framed packet that's ready to send over the GDB connection28    channel.29 30    Framing includes surrounding the message between $ and #, and appending31    a two character hex checksum.32    """33    return "$%s#%02x" % (message, checksum(message))34 35 36def escape_binary(message):37    """38    Escape the binary message using the process described in the GDB server39    protocol documentation.40 41    Most bytes are sent through as-is, but $, #, and { are escaped by writing42    a { followed by the original byte mod 0x20.43    """44    out = ""45    for c in message:46        d = ord(c)47        if d in (0x23, 0x24, 0x7D):48            out += chr(0x7D)49            out += chr(d ^ 0x20)50        else:51            out += c52    return out53 54 55def hex_encode_bytes(message):56    """57    Encode the binary message by converting each byte into a two-character58    hex string.59    """60    out = ""61    for c in message:62        out += "%02x" % ord(c)63    return out64 65 66def hex_decode_bytes(hex_bytes):67    """68    Decode the hex string into a binary message by converting each two-character69    hex string into a single output byte.70    """71    out = ""72    hex_len = len(hex_bytes)73    i = 074    while i < hex_len - 1:75        out += chr(int(hex_bytes[i : i + 2], 16))76        i += 277    return out78 79 80class PacketDirection(Enum):81    RECV = "recv"82    SEND = "send"83 84 85class PacketLog:86    def __init__(self):87        self._packets: list[tuple[PacketDirection, str]] = []88 89    def add_sent(self, packet: str):90        self._packets.append((PacketDirection.SEND, packet))91 92    def add_received(self, packet: str):93        self._packets.append((PacketDirection.RECV, packet))94 95    def get_sent(self):96        return [97            pkt for direction, pkt in self._packets if direction == PacketDirection.SEND98        ]99 100    def get_received(self):101        return [102            pkt for direction, pkt in self._packets if direction == PacketDirection.RECV103        ]104 105    def __iter__(self):106        return iter(self._packets)107 108 109class MockGDBServerResponder:110    """111    A base class for handling client packets and issuing server responses for112    GDB tests.113 114    This handles many typical situations, while still allowing subclasses to115    completely customize their responses.116 117    Most subclasses will be interested in overriding the other() method, which118    handles any packet not recognized in the common packet handling code.119    """120 121    registerCount: int = 40122 123    class SpecialResponse(Enum):124        RESPONSE_DISCONNECT = 0125        RESPONSE_NONE = 1126 127    RESPONSE_DISCONNECT = SpecialResponse.RESPONSE_DISCONNECT128    RESPONSE_NONE = SpecialResponse.RESPONSE_NONE129    Response = Union[str, SpecialResponse]130 131    def __init__(self):132        self.packetLog = PacketLog()133 134    def respond(self, packet: str) -> Sequence[Response]:135        """136        Return the unframed packet data that the server should issue in response137        to the given packet received from the client.138        """139        self.packetLog.add_received(packet)140        response = self._respond_impl(packet)141        if not isinstance(response, list):142            response = [response]143        for part in response:144            if isinstance(part, self.SpecialResponse):145                continue146            self.packetLog.add_sent(part)147        return response148 149    def _respond_impl(self, packet) -> Union[Response, List[Response]]:150        if packet is MockGDBServer.PACKET_INTERRUPT:151            return self.interrupt()152        if packet == "c":153            return self.cont()154        if packet.startswith("vCont;c"):155            return self.vCont(packet)156        if packet[0] == "A":157            return self.A(packet)158        if packet[0] == "D":159            return self.D(packet)160        if packet[0] == "g":161            return self.readRegisters()162        if packet[0] == "G":163            # Gxxxxxxxxxxx164            # Gxxxxxxxxxxx;thread:1234;165            return self.writeRegisters(packet[1:].split(";")[0])166        if packet[0] == "p":167            regnum = packet[1:].split(";")[0]168            return self.readRegister(int(regnum, 16))169        if packet[0] == "P":170            register, value = packet[1:].split("=")171            return self.writeRegister(int(register, 16), value)172        if packet[0] == "m":173            addr, length = [int(x, 16) for x in packet[1:].split(",")]174            return self.readMemory(addr, length)175        if packet[0] == "x":176            addr, length = [int(x, 16) for x in packet[1:].split(",")]177            return self.x(addr, length)178        if packet[0] == "M":179            location, encoded_data = packet[1:].split(":")180            addr, length = [int(x, 16) for x in location.split(",")]181            return self.writeMemory(addr, encoded_data)182        if packet[0:7] == "qSymbol":183            return self.qSymbol(packet[8:])184        if packet[0:10] == "qSupported":185            return self.qSupported(packet[11:].split(";"))186        if packet == "qfThreadInfo":187            return self.qfThreadInfo()188        if packet == "qsThreadInfo":189            return self.qsThreadInfo()190        if packet == "qC":191            return self.qC()192        if packet == "QEnableErrorStrings":193            return self.QEnableErrorStrings()194        if packet == "?":195            return self.haltReason()196        if packet == "s":197            return self.haltReason()198        if packet[0] == "H":199            tid = packet[2:]200            if "." in tid:201                assert tid.startswith("p")202                # TODO: do we want to do anything with PID?203                tid = tid.split(".", 1)[1]204            return self.selectThread(packet[1], int(tid, 16))205        if packet[0:6] == "qXfer:":206            obj, read, annex, location = packet[6:].split(":")207            offset, length = [int(x, 16) for x in location.split(",")]208            data, has_more = self.qXferRead(obj, annex, offset, length)209            if data is not None:210                return self._qXferResponse(data, has_more)211            return ""212        if packet.startswith("vAttach;"):213            pid = packet.partition(";")[2]214            return self.vAttach(int(pid, 16))215        if packet[0] == "Z":216            return self.setBreakpoint(packet)217        if packet.startswith("qThreadStopInfo"):218            threadnum = int(packet[15:], 16)219            return self.threadStopInfo(threadnum)220        if packet == "QThreadSuffixSupported":221            return self.QThreadSuffixSupported()222        if packet == "QListThreadsInStopReply":223            return self.QListThreadsInStopReply()224        if packet.startswith("qMemoryRegionInfo:"):225            return self.qMemoryRegionInfo(int(packet.split(":")[1], 16))226        if packet == "qQueryGDBServer":227            return self.qQueryGDBServer()228        if packet == "qHostInfo":229            return self.qHostInfo()230        if packet.startswith("qEcho"):231            return self.qEcho(int(packet.split(":")[1]))232        if packet == "qGetWorkingDir":233            return self.qGetWorkingDir()234        if packet == "qOffsets":235            return self.qOffsets()236        if packet == "qProcessInfo":237            return self.qProcessInfo()238        if packet == "qsProcessInfo":239            return self.qsProcessInfo()240        if packet.startswith("qfProcessInfo"):241            return self.qfProcessInfo(packet)242        if packet.startswith("jGetLoadedDynamicLibrariesInfos"):243            return self.jGetLoadedDynamicLibrariesInfos(packet)244        if packet.startswith("qPathComplete:"):245            return self.qPathComplete()246        if packet.startswith("vFile:"):247            return self.vFile(packet)248        if packet.startswith("vRun;"):249            return self.vRun(packet)250        if packet.startswith("qLaunchGDBServer;"):251            _, host = packet.partition(";")[2].split(":")252            return self.qLaunchGDBServer(host)253        if packet.startswith("qLaunchSuccess"):254            return self.qLaunchSuccess()255        if packet.startswith("QEnvironment:"):256            return self.QEnvironment(packet)257        if packet.startswith("QEnvironmentHexEncoded:"):258            return self.QEnvironmentHexEncoded(packet)259        if packet.startswith("qRegisterInfo"):260            regnum = int(packet[len("qRegisterInfo") :], 16)261            return self.qRegisterInfo(regnum)262        if packet == "k":263            return self.k()264 265        return self.other(packet)266 267    def qsProcessInfo(self) -> str:268        return "E04"269 270    def qfProcessInfo(self, packet) -> str:271        return "E04"272 273    def jGetLoadedDynamicLibrariesInfos(self, packet) -> str:274        return ""275 276    def qGetWorkingDir(self) -> str:277        return "2f"278 279    def qOffsets(self) -> str:280        return ""281 282    def qProcessInfo(self) -> str:283        return ""284 285    def qHostInfo(self) -> str:286        return "ptrsize:8;endian:little;"287 288    def qEcho(self, num: int) -> str:289        return "E04"290 291    def qQueryGDBServer(self) -> str:292        return "E04"293 294    def interrupt(self):295        raise self.UnexpectedPacketException()296 297    def cont(self):298        raise self.UnexpectedPacketException()299 300    def vCont(self, packet):301        raise self.UnexpectedPacketException()302 303    def A(self, packet) -> str:304        return ""305 306    def D(self, packet) -> str:307        return "OK"308 309    def readRegisters(self) -> str:310        return "00000000" * self.registerCount311 312    def readRegister(self, register: int) -> str:313        return "00000000"314 315    def writeRegisters(self, registers_hex) -> str:316        return "OK"317 318    def writeRegister(self, register, value_hex) -> str:319        return "OK"320 321    def readMemory(self, addr, length) -> str:322        return "00" * length323 324    def x(self, addr, length) -> str:325        return ""326 327    def writeMemory(self, addr, data_hex) -> str:328        return "OK"329 330    def qSymbol(self, symbol_args) -> str:331        return "OK"332 333    def qSupported(self, client_supported) -> str:334        return "qXfer:features:read+;PacketSize=3fff;QStartNoAckMode+"335 336    def qfThreadInfo(self) -> str:337        return "l"338 339    def qsThreadInfo(self) -> str:340        return "l"341 342    def qC(self) -> str:343        return "QC0"344 345    def QEnableErrorStrings(self) -> str:346        return "OK"347 348    def haltReason(self) -> str:349        # SIGINT is 2, return type is 2 digit hex string350        return "S02"351 352    def qXferRead(353        self, obj: str, annex: str, offset: int, length: int354    ) -> Tuple[Optional[str], bool]:355        return None, False356 357    def _qXferResponse(self, data, has_more):358        return "%s%s" % ("m" if has_more else "l", escape_binary(data))359 360    def vAttach(self, pid):361        raise self.UnexpectedPacketException()362 363    def selectThread(self, op, thread_id) -> str:364        return "OK"365 366    def setBreakpoint(self, packet) -> str:367        raise self.UnexpectedPacketException()368 369    def threadStopInfo(self, threadnum) -> str:370        return ""371 372    def other(self, packet) -> str:373        # empty string means unsupported374        return ""375 376    def QThreadSuffixSupported(self) -> str:377        return ""378 379    def QListThreadsInStopReply(self) -> str:380        return ""381 382    def qMemoryRegionInfo(self, addr) -> str:383        return ""384 385    def qPathComplete(self) -> str:386        return ""387 388    def vFile(self, packet) -> str:389        return ""390 391    def vRun(self, packet) -> str:392        return ""393 394    def qLaunchGDBServer(self, host):395        raise self.UnexpectedPacketException()396 397    def qLaunchSuccess(self) -> str:398        return ""399 400    def QEnvironment(self, packet) -> str:401        return "OK"402 403    def QEnvironmentHexEncoded(self, packet) -> str:404        return "OK"405 406    def qRegisterInfo(self, num) -> str:407        return ""408 409    def k(self):410        return ["W01", self.RESPONSE_DISCONNECT]411 412    """413    Raised when we receive a packet for which there is no default action.414    Override the responder class to implement behavior suitable for the test at415    hand.416    """417 418    class UnexpectedPacketException(Exception):419        pass420 421 422class ServerChannel(ABC):423    """424    A wrapper class for TCP or pty-based server.425    """426 427    @abstractmethod428    def get_connect_address(self) -> str:429        """Get address for the client to connect to."""430 431    @abstractmethod432    def get_connect_url(self) -> str:433        """Get URL suitable for process connect command."""434 435    def close_server(self):436        """Close all resources used by the server."""437 438    def accept(self):439        """Accept a single client connection to the server."""440 441    def close_connection(self):442        """Close all resources used by the accepted connection."""443 444    @abstractmethod445    def recv(self) -> bytes:446        """Receive a data packet from the connected client."""447 448    @abstractmethod449    def sendall(self, data: bytes) -> None:450        """Send the data to the connected client."""451 452 453class ServerSocket(ServerChannel):454    def __init__(self, family, type, proto, addr):455        self._server_socket = socket.socket(family, type, proto)456        self._connection = None457 458        self._server_socket.bind(addr)459        self._server_socket.listen(1)460 461    def close_server(self):462        self._server_socket.close()463 464    def accept(self):465        assert self._connection is None466        # accept() is stubborn and won't fail even when the socket is467        # shutdown, so we'll use a timeout468        self._server_socket.settimeout(30.0)469        client, client_addr = self._server_socket.accept()470        # The connected client inherits its timeout from self._socket,471        # but we'll use a blocking socket for the client472        client.settimeout(None)473        self._connection = client474 475    def close_connection(self):476        assert self._connection is not None477        self._connection.close()478        self._connection = None479 480    def recv(self) -> bytes:481        assert self._connection is not None482        return self._connection.recv(4096)483 484    def sendall(self, data: bytes) -> None:485        assert self._connection is not None486        return self._connection.sendall(data)487 488 489class TCPServerSocket(ServerSocket):490    def __init__(self):491        family, type, proto, _, addr = socket.getaddrinfo(492            "localhost", 0, proto=socket.IPPROTO_TCP493        )[0]494        super().__init__(family, type, proto, addr)495 496    def get_connect_address(self) -> str:497        return "[{}]:{}".format(*self._server_socket.getsockname())498 499    def get_connect_url(self) -> str:500        return "connect://" + self.get_connect_address()501 502 503class UnixServerSocket(ServerSocket):504    def __init__(self, addr):505        super().__init__(socket.AF_UNIX, socket.SOCK_STREAM, 0, addr)506 507    def get_connect_address(self) -> str:508        return self._server_socket.getsockname()509 510    def get_connect_url(self) -> str:511        return "unix-connect://" + self.get_connect_address()512 513 514class PtyServerSocket(ServerChannel):515    def __init__(self):516        import pty517        import tty518 519        primary, secondary = pty.openpty()520        tty.setraw(primary)521        self._primary = io.FileIO(primary, "r+b")522        self._secondary = io.FileIO(secondary, "r+b")523 524    def get_connect_address(self) -> str:525        libc = ctypes.CDLL(None)526        libc.ptsname.argtypes = (ctypes.c_int,)527        libc.ptsname.restype = ctypes.c_char_p528        return libc.ptsname(self._primary.fileno()).decode()529 530    def get_connect_url(self):531        return "serial://" + self.get_connect_address()532 533    def close_server(self):534        self._secondary.close()535        self._primary.close()536 537    def recv(self) -> bytes:538        try:539            return self._primary.read(4096)540        except OSError as e:541            # closing the pty results in EIO on Linux, convert it to EOF542            if e.errno == errno.EIO:543                return b""544            raise545 546    def sendall(self, data: bytes) -> None:547        self._primary.write(data)548 549 550class MockGDBServer:551    """552    A simple TCP-based GDB server that can test client behavior by receiving553    commands and issuing custom-tailored responses.554 555    Responses are generated via the .responder property, which should be an556    instance of a class based on MockGDBServerResponder.557    """558 559    responder = None560    _socket = None561    _thread = None562    _receivedData = None563    _receivedDataOffset = None564    _shouldSendAck = True565 566    def __init__(self, socket):567        self._socket = socket568        self.responder = MockGDBServerResponder()569 570    def start(self):571        # Start a thread that waits for a client connection.572        self._thread = threading.Thread(target=self.run)573        self._thread.start()574 575    def stop(self):576        if self._thread is not None:577            self._thread.join()578            self._thread = None579 580    def get_connect_address(self) -> str:581        assert self._socket is not None582        return self._socket.get_connect_address()583 584    def get_connect_url(self) -> str:585        assert self._socket is not None586        return self._socket.get_connect_url()587 588    def run(self):589        assert self._socket is not None590        # For testing purposes, we only need to worry about one client591        # connecting just one time.592        try:593            self._socket.accept()594        except Exception:595            traceback.print_exc()596            return597        self._shouldSendAck = True598        self._receivedData = ""599        self._receivedDataOffset = 0600        data = None601        try:602            while True:603                data = seven.bitcast_to_string(self._socket.recv())604                if data is None or len(data) == 0:605                    break606                self._receive(data)607        except self.TerminateConnectionException:608            pass609        except Exception:610            print(611                "An exception happened when receiving the response from the gdb server. Closing the client..."612            )613            traceback.print_exc()614        finally:615            self._socket.close_connection()616            self._socket.close_server()617 618    def _receive(self, data):619        """620        Collects data, parses and responds to as many packets as exist.621        Any leftover data is kept for parsing the next time around.622        """623        self._receivedData += data624        packet = self._parsePacket()625        while packet is not None:626            self._handlePacket(packet)627            packet = self._parsePacket()628 629    def _parsePacket(self):630        """631        Reads bytes from self._receivedData, returning:632        - a packet's contents if a valid packet is found633        - the PACKET_ACK unique object if we got an ack634        - None if we only have a partial packet635 636        Raises an InvalidPacketException if unexpected data is received637        or if checksums fail.638 639        Once a complete packet is found at the front of self._receivedData,640        its data is removed form self._receivedData.641        """642        assert self._receivedData is not None643        data = self._receivedData644        assert self._receivedDataOffset is not None645        i = self._receivedDataOffset646        data_len = len(data)647        if data_len == 0:648            return None649        if i == 0:650            # If we're looking at the start of the received data, that means651            # we're looking for the start of a new packet, denoted by a $.652            # It's also possible we'll see an ACK here, denoted by a +653            if data[0] == "+":654                self._receivedData = data[1:]655                return self.PACKET_ACK656            if ord(data[0]) == 3:657                self._receivedData = data[1:]658                return self.PACKET_INTERRUPT659            if data[0] == "$":660                i += 1661            else:662                raise self.InvalidPacketException(663                    "Unexpected leading byte: %s" % data[0]664                )665 666        # If we're looking beyond the start of the received data, then we're667        # looking for the end of the packet content, denoted by a #.668        # Note that we pick up searching from where we left off last time669        while i < data_len and data[i] != "#":670            i += 1671 672        # If there isn't enough data left for a checksum, just remember where673        # we left off so we can pick up there the next time around674        if i > data_len - 3:675            self._receivedDataOffset = i676            return None677 678        # If we have enough data remaining for the checksum, extract it and679        # compare to the packet contents680        packet = data[1:i]681        i += 1682        try:683            check = int(data[i : i + 2], 16)684        except ValueError:685            raise self.InvalidPacketException("Checksum is not valid hex")686        i += 2687        if check != checksum(packet):688            raise self.InvalidPacketException(689                "Checksum %02x does not match content %02x" % (check, checksum(packet))690            )691        # remove parsed bytes from _receivedData and reset offset so parsing692        # can start on the next packet the next time around693        self._receivedData = data[i:]694        self._receivedDataOffset = 0695        return packet696 697    def _sendPacket(self, packet: str):698        assert self._socket is not None699        framed_packet = seven.bitcast_to_bytes(frame_packet(packet))700        self._socket.sendall(framed_packet)701 702    def _handlePacket(self, packet):703        assert self._socket is not None704        if packet is self.PACKET_ACK:705            # Ignore ACKs from the client. For the future, we can consider706            # adding validation code to make sure the client only sends ACKs707            # when it's supposed to.708            return709        response = [""]710        # We'll handle the ack stuff here since it's not something any of the711        # tests will be concerned about, and it'll get turned off quickly anyway.712        if self._shouldSendAck:713            self._socket.sendall(seven.bitcast_to_bytes("+"))714        if packet == "QStartNoAckMode":715            self._shouldSendAck = False716            response = ["OK"]717        elif self.responder is not None:718            # Delegate everything else to our responder719            response = self.responder.respond(packet)720        # MockGDBServerResponder no longer returns non-lists but others like721        # ReverseTestBase still do722        if not isinstance(response, list):723            response = [response]724        for part in response:725            if part is MockGDBServerResponder.RESPONSE_NONE:726                continue727            if part is MockGDBServerResponder.RESPONSE_DISCONNECT:728                raise self.TerminateConnectionException()729            # Should have handled the non-str's above730            assert isinstance(part, str)731            self._sendPacket(part)732 733    PACKET_ACK = object()734    PACKET_INTERRUPT = object()735 736    class TerminateConnectionException(Exception):737        pass738 739    class InvalidPacketException(Exception):740        pass741