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