xref: /llvm-project/lldb/packages/Python/lldbsuite/test/gdbclientutils.py (revision 7c8ae65f2c3d5c1a6aba2f7ee7588f9f76f94f84)
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):
465        self._socket = socket
466        self.responder = MockGDBServerResponder()
467
468    def start(self):
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._thread.join()
475        self._thread = None
476
477    def get_connect_address(self):
478        return self._socket.get_connect_address()
479
480    def get_connect_url(self):
481        return self._socket.get_connect_url()
482
483    def run(self):
484        # For testing purposes, we only need to worry about one client
485        # connecting just one time.
486        try:
487            self._socket.accept()
488        except:
489            return
490        self._shouldSendAck = True
491        self._receivedData = ""
492        self._receivedDataOffset = 0
493        data = None
494        try:
495            while True:
496                data = seven.bitcast_to_string(self._socket.recv())
497                if data is None or len(data) == 0:
498                    break
499                self._receive(data)
500        except self.TerminateConnectionException:
501            pass
502        except Exception as e:
503            print("An exception happened when receiving the response from the gdb server. Closing the client...")
504            traceback.print_exc()
505        finally:
506            self._socket.close_connection()
507            self._socket.close_server()
508
509    def _receive(self, data):
510        """
511        Collects data, parses and responds to as many packets as exist.
512        Any leftover data is kept for parsing the next time around.
513        """
514        self._receivedData += data
515        packet = self._parsePacket()
516        while packet is not None:
517            self._handlePacket(packet)
518            packet = self._parsePacket()
519
520    def _parsePacket(self):
521        """
522        Reads bytes from self._receivedData, returning:
523        - a packet's contents if a valid packet is found
524        - the PACKET_ACK unique object if we got an ack
525        - None if we only have a partial packet
526
527        Raises an InvalidPacketException if unexpected data is received
528        or if checksums fail.
529
530        Once a complete packet is found at the front of self._receivedData,
531        its data is removed form self._receivedData.
532        """
533        data = self._receivedData
534        i = self._receivedDataOffset
535        data_len = len(data)
536        if data_len == 0:
537            return None
538        if i == 0:
539            # If we're looking at the start of the received data, that means
540            # we're looking for the start of a new packet, denoted by a $.
541            # It's also possible we'll see an ACK here, denoted by a +
542            if data[0] == '+':
543                self._receivedData = data[1:]
544                return self.PACKET_ACK
545            if ord(data[0]) == 3:
546                self._receivedData = data[1:]
547                return self.PACKET_INTERRUPT
548            if data[0] == '$':
549                i += 1
550            else:
551                raise self.InvalidPacketException(
552                        "Unexpected leading byte: %s" % data[0])
553
554        # If we're looking beyond the start of the received data, then we're
555        # looking for the end of the packet content, denoted by a #.
556        # Note that we pick up searching from where we left off last time
557        while i < data_len and data[i] != '#':
558            i += 1
559
560        # If there isn't enough data left for a checksum, just remember where
561        # we left off so we can pick up there the next time around
562        if i > data_len - 3:
563            self._receivedDataOffset = i
564            return None
565
566        # If we have enough data remaining for the checksum, extract it and
567        # compare to the packet contents
568        packet = data[1:i]
569        i += 1
570        try:
571            check = int(data[i:i + 2], 16)
572        except ValueError:
573            raise self.InvalidPacketException("Checksum is not valid hex")
574        i += 2
575        if check != checksum(packet):
576            raise self.InvalidPacketException(
577                    "Checksum %02x does not match content %02x" %
578                    (check, checksum(packet)))
579        # remove parsed bytes from _receivedData and reset offset so parsing
580        # can start on the next packet the next time around
581        self._receivedData = data[i:]
582        self._receivedDataOffset = 0
583        return packet
584
585    def _sendPacket(self, packet):
586        self._socket.sendall(seven.bitcast_to_bytes(frame_packet(packet)))
587
588    def _handlePacket(self, packet):
589        if packet is self.PACKET_ACK:
590            # Ignore ACKs from the client. For the future, we can consider
591            # adding validation code to make sure the client only sends ACKs
592            # when it's supposed to.
593            return
594        response = ""
595        # We'll handle the ack stuff here since it's not something any of the
596        # tests will be concerned about, and it'll get turned off quickly anyway.
597        if self._shouldSendAck:
598            self._socket.sendall(seven.bitcast_to_bytes('+'))
599        if packet == "QStartNoAckMode":
600            self._shouldSendAck = False
601            response = "OK"
602        elif self.responder is not None:
603            # Delegate everything else to our responder
604            response = self.responder.respond(packet)
605        if not isinstance(response, list):
606            response = [response]
607        for part in response:
608            if part is MockGDBServerResponder.RESPONSE_DISCONNECT:
609                raise self.TerminateConnectionException()
610            self._sendPacket(part)
611
612    PACKET_ACK = object()
613    PACKET_INTERRUPT = object()
614
615    class TerminateConnectionException(Exception):
616        pass
617
618    class InvalidPacketException(Exception):
619        pass
620