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