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