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