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