• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import errno
2import os
3import os.path
4import threading
5import socket
6import lldb
7import binascii
8import traceback
9from lldbsuite.support import seven
10from lldbsuite.test.lldbtest import *
11from lldbsuite.test import lldbtest_config
12
13
14def checksum(message):
15    """
16    Calculate the GDB server protocol checksum of the message.
17
18    The GDB server protocol uses a simple modulo 256 sum.
19    """
20    check = 0
21    for c in message:
22        check += ord(c)
23    return check % 256
24
25
26def frame_packet(message):
27    """
28    Create a framed packet that's ready to send over the GDB connection
29    channel.
30
31    Framing includes surrounding the message between $ and #, and appending
32    a two character hex checksum.
33    """
34    return "$%s#%02x" % (message, checksum(message))
35
36
37def escape_binary(message):
38    """
39    Escape the binary message using the process described in the GDB server
40    protocol documentation.
41
42    Most bytes are sent through as-is, but $, #, and { are escaped by writing
43    a { followed by the original byte mod 0x20.
44    """
45    out = ""
46    for c in message:
47        d = ord(c)
48        if d in (0x23, 0x24, 0x7d):
49            out += chr(0x7d)
50            out += chr(d ^ 0x20)
51        else:
52            out += c
53    return out
54
55
56def hex_encode_bytes(message):
57    """
58    Encode the binary message by converting each byte into a two-character
59    hex string.
60    """
61    out = ""
62    for c in message:
63        out += "%02x" % ord(c)
64    return out
65
66
67def hex_decode_bytes(hex_bytes):
68    """
69    Decode the hex string into a binary message by converting each two-character
70    hex string into a single output byte.
71    """
72    out = ""
73    hex_len = len(hex_bytes)
74    while i < hex_len - 1:
75        out += chr(int(hex_bytes[i:i + 2]), 16)
76        i += 2
77    return out
78
79
80class MockGDBServerResponder:
81    """
82    A base class for handling client packets and issuing server responses for
83    GDB tests.
84
85    This handles many typical situations, while still allowing subclasses to
86    completely customize their responses.
87
88    Most subclasses will be interested in overriding the other() method, which
89    handles any packet not recognized in the common packet handling code.
90    """
91
92    registerCount = 40
93    packetLog = None
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] == "g":
113            return self.readRegisters()
114        if packet[0] == "G":
115            # Gxxxxxxxxxxx
116            # Gxxxxxxxxxxx;thread:1234;
117            return self.writeRegisters(packet[1:].split(';')[0])
118        if packet[0] == "p":
119            regnum = packet[1:].split(';')[0]
120            return self.readRegister(int(regnum, 16))
121        if packet[0] == "P":
122            register, value = packet[1:].split("=")
123            return self.writeRegister(int(register, 16), value)
124        if packet[0] == "m":
125            addr, length = [int(x, 16) for x in packet[1:].split(',')]
126            return self.readMemory(addr, length)
127        if packet[0] == "M":
128            location, encoded_data = packet[1:].split(":")
129            addr, length = [int(x, 16) for x in location.split(',')]
130            return self.writeMemory(addr, encoded_data)
131        if packet[0:7] == "qSymbol":
132            return self.qSymbol(packet[8:])
133        if packet[0:10] == "qSupported":
134            return self.qSupported(packet[11:].split(";"))
135        if packet == "qfThreadInfo":
136            return self.qfThreadInfo()
137        if packet == "qsThreadInfo":
138            return self.qsThreadInfo()
139        if packet == "qC":
140            return self.qC()
141        if packet == "QEnableErrorStrings":
142            return self.QEnableErrorStrings()
143        if packet == "?":
144            return self.haltReason()
145        if packet == "s":
146            return self.haltReason()
147        if packet[0] == "H":
148            return self.selectThread(packet[1], int(packet[2:], 16))
149        if packet[0:6] == "qXfer:":
150            obj, read, annex, location = packet[6:].split(":")
151            offset, length = [int(x, 16) for x in location.split(',')]
152            data, has_more = self.qXferRead(obj, annex, offset, length)
153            if data is not None:
154                return self._qXferResponse(data, has_more)
155            return ""
156        if packet.startswith("vAttach;"):
157            pid = packet.partition(';')[2]
158            return self.vAttach(int(pid, 16))
159        if packet[0] == "Z":
160            return self.setBreakpoint(packet)
161        if packet.startswith("qThreadStopInfo"):
162            threadnum = int (packet[15:], 16)
163            return self.threadStopInfo(threadnum)
164        if packet == "QThreadSuffixSupported":
165            return self.QThreadSuffixSupported()
166        if packet == "QListThreadsInStopReply":
167            return self.QListThreadsInStopReply()
168        if packet.startswith("qMemoryRegionInfo:"):
169            return self.qMemoryRegionInfo()
170        if packet == "qQueryGDBServer":
171            return self.qQueryGDBServer()
172        if packet == "qHostInfo":
173            return self.qHostInfo()
174        if packet == "qGetWorkingDir":
175            return self.qGetWorkingDir()
176        if packet == "qOffsets":
177            return self.qOffsets();
178        if packet == "qsProcessInfo":
179            return self.qsProcessInfo()
180        if packet.startswith("qfProcessInfo"):
181            return self.qfProcessInfo(packet)
182        if packet.startswith("qPathComplete:"):
183            return self.qPathComplete()
184
185        return self.other(packet)
186
187    def qsProcessInfo(self):
188        return "E04"
189
190    def qfProcessInfo(self, packet):
191        return "E04"
192
193    def qGetWorkingDir(self):
194        return "2f"
195
196    def qOffsets(self):
197        return ""
198
199    def qHostInfo(self):
200        return "ptrsize:8;endian:little;"
201
202    def qQueryGDBServer(self):
203        return "E04"
204
205    def interrupt(self):
206        raise self.UnexpectedPacketException()
207
208    def cont(self):
209        raise self.UnexpectedPacketException()
210
211    def vCont(self, packet):
212        raise self.UnexpectedPacketException()
213
214    def A(self, packet):
215        return ""
216
217    def readRegisters(self):
218        return "00000000" * self.registerCount
219
220    def readRegister(self, register):
221        return "00000000"
222
223    def writeRegisters(self, registers_hex):
224        return "OK"
225
226    def writeRegister(self, register, value_hex):
227        return "OK"
228
229    def readMemory(self, addr, length):
230        return "00" * length
231
232    def writeMemory(self, addr, data_hex):
233        return "OK"
234
235    def qSymbol(self, symbol_args):
236        return "OK"
237
238    def qSupported(self, client_supported):
239        return "qXfer:features:read+;PacketSize=3fff;QStartNoAckMode+"
240
241    def qfThreadInfo(self):
242        return "l"
243
244    def qsThreadInfo(self):
245        return "l"
246
247    def qC(self):
248        return "QC0"
249
250    def QEnableErrorStrings(self):
251        return "OK"
252
253    def haltReason(self):
254        # SIGINT is 2, return type is 2 digit hex string
255        return "S02"
256
257    def qXferRead(self, obj, annex, offset, length):
258        return None, False
259
260    def _qXferResponse(self, data, has_more):
261        return "%s%s" % ("m" if has_more else "l", escape_binary(data))
262
263    def vAttach(self, pid):
264        raise self.UnexpectedPacketException()
265
266    def selectThread(self, op, thread_id):
267        return "OK"
268
269    def setBreakpoint(self, packet):
270        raise self.UnexpectedPacketException()
271
272    def threadStopInfo(self, threadnum):
273        return ""
274
275    def other(self, packet):
276        # empty string means unsupported
277        return ""
278
279    def QThreadSuffixSupported(self):
280        return ""
281
282    def QListThreadsInStopReply(self):
283        return ""
284
285    def qMemoryRegionInfo(self):
286        return ""
287
288    def qPathComplete(self):
289        return ""
290
291    """
292    Raised when we receive a packet for which there is no default action.
293    Override the responder class to implement behavior suitable for the test at
294    hand.
295    """
296    class UnexpectedPacketException(Exception):
297        pass
298
299
300class MockGDBServer:
301    """
302    A simple TCP-based GDB server that can test client behavior by receiving
303    commands and issuing custom-tailored responses.
304
305    Responses are generated via the .responder property, which should be an
306    instance of a class based on MockGDBServerResponder.
307    """
308
309    responder = None
310    _socket = None
311    _client = None
312    _thread = None
313    _receivedData = None
314    _receivedDataOffset = None
315    _shouldSendAck = True
316
317    def __init__(self):
318        self.responder = MockGDBServerResponder()
319
320    def start(self):
321        family, type, proto, _, addr = socket.getaddrinfo("localhost", 0,
322                proto=socket.IPPROTO_TCP)[0]
323        self._socket = socket.socket(family, type, proto)
324
325
326        self._socket.bind(addr)
327        self._socket.listen(1)
328
329        # Start a thread that waits for a client connection.
330        self._thread = threading.Thread(target=self._run)
331        self._thread.start()
332
333    def stop(self):
334        self._socket.close()
335        self._thread.join()
336        self._thread = None
337
338    def get_connect_address(self):
339        return "[{}]:{}".format(*self._socket.getsockname())
340
341    def _run(self):
342        # For testing purposes, we only need to worry about one client
343        # connecting just one time.
344        try:
345            # accept() is stubborn and won't fail even when the socket is
346            # shutdown, so we'll use a timeout
347            self._socket.settimeout(30.0)
348            client, client_addr = self._socket.accept()
349            self._client = client
350            # The connected client inherits its timeout from self._socket,
351            # but we'll use a blocking socket for the client
352            self._client.settimeout(None)
353        except:
354            return
355        self._shouldSendAck = True
356        self._receivedData = ""
357        self._receivedDataOffset = 0
358        data = None
359        while True:
360            try:
361                data = seven.bitcast_to_string(self._client.recv(4096))
362                if data is None or len(data) == 0:
363                    break
364                self._receive(data)
365            except Exception as e:
366                print("An exception happened when receiving the response from the gdb server. Closing the client...")
367                traceback.print_exc()
368                self._client.close()
369                break
370
371    def _receive(self, data):
372        """
373        Collects data, parses and responds to as many packets as exist.
374        Any leftover data is kept for parsing the next time around.
375        """
376        self._receivedData += data
377        try:
378            packet = self._parsePacket()
379            while packet is not None:
380                self._handlePacket(packet)
381                packet = self._parsePacket()
382        except self.InvalidPacketException:
383            self._client.close()
384
385    def _parsePacket(self):
386        """
387        Reads bytes from self._receivedData, returning:
388        - a packet's contents if a valid packet is found
389        - the PACKET_ACK unique object if we got an ack
390        - None if we only have a partial packet
391
392        Raises an InvalidPacketException if unexpected data is received
393        or if checksums fail.
394
395        Once a complete packet is found at the front of self._receivedData,
396        its data is removed form self._receivedData.
397        """
398        data = self._receivedData
399        i = self._receivedDataOffset
400        data_len = len(data)
401        if data_len == 0:
402            return None
403        if i == 0:
404            # If we're looking at the start of the received data, that means
405            # we're looking for the start of a new packet, denoted by a $.
406            # It's also possible we'll see an ACK here, denoted by a +
407            if data[0] == '+':
408                self._receivedData = data[1:]
409                return self.PACKET_ACK
410            if ord(data[0]) == 3:
411                self._receivedData = data[1:]
412                return self.PACKET_INTERRUPT
413            if data[0] == '$':
414                i += 1
415            else:
416                raise self.InvalidPacketException(
417                        "Unexpected leading byte: %s" % data[0])
418
419        # If we're looking beyond the start of the received data, then we're
420        # looking for the end of the packet content, denoted by a #.
421        # Note that we pick up searching from where we left off last time
422        while i < data_len and data[i] != '#':
423            i += 1
424
425        # If there isn't enough data left for a checksum, just remember where
426        # we left off so we can pick up there the next time around
427        if i > data_len - 3:
428            self._receivedDataOffset = i
429            return None
430
431        # If we have enough data remaining for the checksum, extract it and
432        # compare to the packet contents
433        packet = data[1:i]
434        i += 1
435        try:
436            check = int(data[i:i + 2], 16)
437        except ValueError:
438            raise self.InvalidPacketException("Checksum is not valid hex")
439        i += 2
440        if check != checksum(packet):
441            raise self.InvalidPacketException(
442                    "Checksum %02x does not match content %02x" %
443                    (check, checksum(packet)))
444        # remove parsed bytes from _receivedData and reset offset so parsing
445        # can start on the next packet the next time around
446        self._receivedData = data[i:]
447        self._receivedDataOffset = 0
448        return packet
449
450    def _handlePacket(self, packet):
451        if packet is self.PACKET_ACK:
452            # Ignore ACKs from the client. For the future, we can consider
453            # adding validation code to make sure the client only sends ACKs
454            # when it's supposed to.
455            return
456        response = ""
457        # We'll handle the ack stuff here since it's not something any of the
458        # tests will be concerned about, and it'll get turned off quickly anyway.
459        if self._shouldSendAck:
460            self._client.sendall(seven.bitcast_to_bytes('+'))
461        if packet == "QStartNoAckMode":
462            self._shouldSendAck = False
463            response = "OK"
464        elif self.responder is not None:
465            # Delegate everything else to our responder
466            response = self.responder.respond(packet)
467        # Handle packet framing since we don't want to bother tests with it.
468        if response is not None:
469            framed = frame_packet(response)
470            self._client.sendall(seven.bitcast_to_bytes(framed))
471
472    PACKET_ACK = object()
473    PACKET_INTERRUPT = object()
474
475    class InvalidPacketException(Exception):
476        pass
477
478class GDBRemoteTestBase(TestBase):
479    """
480    Base class for GDB client tests.
481
482    This class will setup and start a mock GDB server for the test to use.
483    It also provides assertPacketLogContains, which simplifies the checking
484    of packets sent by the client.
485    """
486
487    NO_DEBUG_INFO_TESTCASE = True
488    mydir = TestBase.compute_mydir(__file__)
489    server = None
490
491    def setUp(self):
492        TestBase.setUp(self)
493        self.server = MockGDBServer()
494        self.server.start()
495
496    def tearDown(self):
497        # TestBase.tearDown will kill the process, but we need to kill it early
498        # so its client connection closes and we can stop the server before
499        # finally calling the base tearDown.
500        if self.process() is not None:
501            self.process().Kill()
502        self.server.stop()
503        TestBase.tearDown(self)
504
505    def createTarget(self, yaml_path):
506        """
507        Create a target by auto-generating the object based on the given yaml
508        instructions.
509
510        This will track the generated object so it can be automatically removed
511        during tearDown.
512        """
513        yaml_base, ext = os.path.splitext(yaml_path)
514        obj_path = self.getBuildArtifact(yaml_base)
515        self.yaml2obj(yaml_path, obj_path)
516        return self.dbg.CreateTarget(obj_path)
517
518    def connect(self, target):
519        """
520        Create a process by connecting to the mock GDB server.
521
522        Includes assertions that the process was successfully created.
523        """
524        listener = self.dbg.GetListener()
525        error = lldb.SBError()
526        process = target.ConnectRemote(listener,
527                "connect://" + self.server.get_connect_address(), "gdb-remote", error)
528        self.assertTrue(error.Success(), error.description)
529        self.assertTrue(process, PROCESS_IS_VALID)
530        return process
531
532    def assertPacketLogContains(self, packets):
533        """
534        Assert that the mock server's packet log contains the given packets.
535
536        The packet log includes all packets sent by the client and received
537        by the server.  This fuction makes it easy to verify that the client
538        sent the expected packets to the server.
539
540        The check does not require that the packets be consecutive, but does
541        require that they are ordered in the log as they ordered in the arg.
542        """
543        i = 0
544        j = 0
545        log = self.server.responder.packetLog
546
547        while i < len(packets) and j < len(log):
548            if log[j] == packets[i]:
549                i += 1
550            j += 1
551        if i < len(packets):
552            self.fail(u"Did not receive: %s\nLast 10 packets:\n\t%s" %
553                    (packets[i], u'\n\t'.join(log)))
554