221
|
1 import errno
|
150
|
2 import os
|
|
3 import os.path
|
|
4 import threading
|
|
5 import socket
|
|
6 import lldb
|
|
7 import binascii
|
|
8 import traceback
|
|
9 from lldbsuite.support import seven
|
|
10 from lldbsuite.test.lldbtest import *
|
|
11 from lldbsuite.test import lldbtest_config
|
|
12
|
|
13
|
|
14 def 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
|
|
26 def 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
|
|
37 def 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
|
|
56 def 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
|
|
67 def 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
|
|
80 class 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)
|
173
|
110 if packet[0] == "A":
|
|
111 return self.A(packet)
|
150
|
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:"):
|
223
|
169 return self.qMemoryRegionInfo(int(packet.split(':')[1], 16))
|
150
|
170 if packet == "qQueryGDBServer":
|
|
171 return self.qQueryGDBServer()
|
|
172 if packet == "qHostInfo":
|
|
173 return self.qHostInfo()
|
|
174 if packet == "qGetWorkingDir":
|
|
175 return self.qGetWorkingDir()
|
173
|
176 if packet == "qOffsets":
|
|
177 return self.qOffsets();
|
150
|
178 if packet == "qsProcessInfo":
|
|
179 return self.qsProcessInfo()
|
|
180 if packet.startswith("qfProcessInfo"):
|
|
181 return self.qfProcessInfo(packet)
|
221
|
182 if packet.startswith("qPathComplete:"):
|
|
183 return self.qPathComplete()
|
150
|
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
|
173
|
196 def qOffsets(self):
|
|
197 return ""
|
|
198
|
150
|
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
|
173
|
214 def A(self, packet):
|
|
215 return ""
|
|
216
|
150
|
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
|
223
|
285 def qMemoryRegionInfo(self, addr):
|
150
|
286 return ""
|
|
287
|
221
|
288 def qPathComplete(self):
|
|
289 return ""
|
|
290
|
150
|
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
|
|
300 class 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
|
221
|
317 def __init__(self):
|
150
|
318 self.responder = MockGDBServerResponder()
|
|
319
|
|
320 def start(self):
|
221
|
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
|
150
|
326 self._socket.bind(addr)
|
|
327 self._socket.listen(1)
|
221
|
328
|
|
329 # Start a thread that waits for a client connection.
|
150
|
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
|
221
|
338 def get_connect_address(self):
|
|
339 return "[{}]:{}".format(*self._socket.getsockname())
|
|
340
|
150
|
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
|
173
|
347 self._socket.settimeout(30.0)
|
150
|
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
|
|
478 class 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()
|
221
|
526 process = target.ConnectRemote(listener,
|
|
527 "connect://" + self.server.get_connect_address(), "gdb-remote", error)
|
150
|
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)))
|