xref: /llvm-project/lldb/packages/Python/lldbsuite/test/gdbclientutils.py (revision f3b7cc8bb2ea599e0c344e8ae16639667d9f0eff)
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 ServerSocket:
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 TCPServerSocket(ServerSocket):
370    def __init__(self):
371        family, type, proto, _, addr = socket.getaddrinfo(
372                "localhost", 0, proto=socket.IPPROTO_TCP)[0]
373        self._server_socket = socket.socket(family, type, proto)
374        self._connection = None
375
376        self._server_socket.bind(addr)
377        self._server_socket.listen(1)
378
379    def get_connect_address(self):
380        return "[{}]:{}".format(*self._server_socket.getsockname())
381
382    def get_connect_url(self):
383        return "connect://" + self.get_connect_address()
384
385    def close_server(self):
386        self._server_socket.close()
387
388    def accept(self):
389        assert self._connection is None
390        # accept() is stubborn and won't fail even when the socket is
391        # shutdown, so we'll use a timeout
392        self._server_socket.settimeout(30.0)
393        client, client_addr = self._server_socket.accept()
394        # The connected client inherits its timeout from self._socket,
395        # but we'll use a blocking socket for the client
396        client.settimeout(None)
397        self._connection = client
398
399    def close_connection(self):
400        assert self._connection is not None
401        self._connection.close()
402        self._connection = None
403
404    def recv(self):
405        assert self._connection is not None
406        return self._connection.recv(4096)
407
408    def sendall(self, data):
409        assert self._connection is not None
410        return self._connection.sendall(data)
411
412
413class PtyServerSocket(ServerSocket):
414    def __init__(self):
415        import pty
416        import tty
417        primary, secondary = pty.openpty()
418        tty.setraw(primary)
419        self._primary = io.FileIO(primary, 'r+b')
420        self._secondary = io.FileIO(secondary, 'r+b')
421
422    def get_connect_address(self):
423        libc = ctypes.CDLL(None)
424        libc.ptsname.argtypes = (ctypes.c_int,)
425        libc.ptsname.restype = ctypes.c_char_p
426        return libc.ptsname(self._primary.fileno()).decode()
427
428    def get_connect_url(self):
429        return "serial://" + self.get_connect_address()
430
431    def close_server(self):
432        self._secondary.close()
433        self._primary.close()
434
435    def recv(self):
436        try:
437            return self._primary.read(4096)
438        except OSError as e:
439            # closing the pty results in EIO on Linux, convert it to EOF
440            if e.errno == errno.EIO:
441                return b''
442            raise
443
444    def sendall(self, data):
445        return self._primary.write(data)
446
447
448class MockGDBServer:
449    """
450    A simple TCP-based GDB server that can test client behavior by receiving
451    commands and issuing custom-tailored responses.
452
453    Responses are generated via the .responder property, which should be an
454    instance of a class based on MockGDBServerResponder.
455    """
456
457    responder = None
458    _socket = None
459    _thread = None
460    _receivedData = None
461    _receivedDataOffset = None
462    _shouldSendAck = True
463
464    def __init__(self, socket_class):
465        self._socket_class = socket_class
466        self.responder = MockGDBServerResponder()
467
468    def start(self):
469        self._socket = self._socket_class()
470        # Start a thread that waits for a client connection.
471        self._thread = threading.Thread(target=self._run)
472        self._thread.start()
473
474    def stop(self):
475        self._socket.close_server()
476        self._thread.join()
477        self._thread = None
478
479    def get_connect_address(self):
480        return self._socket.get_connect_address()
481
482    def get_connect_url(self):
483        return self._socket.get_connect_url()
484
485    def _run(self):
486        # For testing purposes, we only need to worry about one client
487        # connecting just one time.
488        try:
489            self._socket.accept()
490        except:
491            return
492        self._shouldSendAck = True
493        self._receivedData = ""
494        self._receivedDataOffset = 0
495        data = None
496        try:
497            while True:
498                data = seven.bitcast_to_string(self._socket.recv())
499                if data is None or len(data) == 0:
500                    break
501                self._receive(data)
502        except self.TerminateConnectionException:
503            pass
504        except Exception as e:
505            print("An exception happened when receiving the response from the gdb server. Closing the client...")
506            traceback.print_exc()
507        finally:
508            self._socket.close_connection()
509            self._socket.close_server()
510
511    def _receive(self, data):
512        """
513        Collects data, parses and responds to as many packets as exist.
514        Any leftover data is kept for parsing the next time around.
515        """
516        self._receivedData += data
517        packet = self._parsePacket()
518        while packet is not None:
519            self._handlePacket(packet)
520            packet = self._parsePacket()
521
522    def _parsePacket(self):
523        """
524        Reads bytes from self._receivedData, returning:
525        - a packet's contents if a valid packet is found
526        - the PACKET_ACK unique object if we got an ack
527        - None if we only have a partial packet
528
529        Raises an InvalidPacketException if unexpected data is received
530        or if checksums fail.
531
532        Once a complete packet is found at the front of self._receivedData,
533        its data is removed form self._receivedData.
534        """
535        data = self._receivedData
536        i = self._receivedDataOffset
537        data_len = len(data)
538        if data_len == 0:
539            return None
540        if i == 0:
541            # If we're looking at the start of the received data, that means
542            # we're looking for the start of a new packet, denoted by a $.
543            # It's also possible we'll see an ACK here, denoted by a +
544            if data[0] == '+':
545                self._receivedData = data[1:]
546                return self.PACKET_ACK
547            if ord(data[0]) == 3:
548                self._receivedData = data[1:]
549                return self.PACKET_INTERRUPT
550            if data[0] == '$':
551                i += 1
552            else:
553                raise self.InvalidPacketException(
554                        "Unexpected leading byte: %s" % data[0])
555
556        # If we're looking beyond the start of the received data, then we're
557        # looking for the end of the packet content, denoted by a #.
558        # Note that we pick up searching from where we left off last time
559        while i < data_len and data[i] != '#':
560            i += 1
561
562        # If there isn't enough data left for a checksum, just remember where
563        # we left off so we can pick up there the next time around
564        if i > data_len - 3:
565            self._receivedDataOffset = i
566            return None
567
568        # If we have enough data remaining for the checksum, extract it and
569        # compare to the packet contents
570        packet = data[1:i]
571        i += 1
572        try:
573            check = int(data[i:i + 2], 16)
574        except ValueError:
575            raise self.InvalidPacketException("Checksum is not valid hex")
576        i += 2
577        if check != checksum(packet):
578            raise self.InvalidPacketException(
579                    "Checksum %02x does not match content %02x" %
580                    (check, checksum(packet)))
581        # remove parsed bytes from _receivedData and reset offset so parsing
582        # can start on the next packet the next time around
583        self._receivedData = data[i:]
584        self._receivedDataOffset = 0
585        return packet
586
587    def _sendPacket(self, packet):
588        self._socket.sendall(seven.bitcast_to_bytes(frame_packet(packet)))
589
590    def _handlePacket(self, packet):
591        if packet is self.PACKET_ACK:
592            # Ignore ACKs from the client. For the future, we can consider
593            # adding validation code to make sure the client only sends ACKs
594            # when it's supposed to.
595            return
596        response = ""
597        # We'll handle the ack stuff here since it's not something any of the
598        # tests will be concerned about, and it'll get turned off quickly anyway.
599        if self._shouldSendAck:
600            self._socket.sendall(seven.bitcast_to_bytes('+'))
601        if packet == "QStartNoAckMode":
602            self._shouldSendAck = False
603            response = "OK"
604        elif self.responder is not None:
605            # Delegate everything else to our responder
606            response = self.responder.respond(packet)
607        if not isinstance(response, list):
608            response = [response]
609        for part in response:
610            if part is MockGDBServerResponder.RESPONSE_DISCONNECT:
611                raise self.TerminateConnectionException()
612            self._sendPacket(part)
613
614    PACKET_ACK = object()
615    PACKET_INTERRUPT = object()
616
617    class TerminateConnectionException(Exception):
618        pass
619
620    class InvalidPacketException(Exception):
621        pass
622