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