7 from lldbsuite
.support
import seven
10 def checksum(message
):
12 Calculate the GDB server protocol checksum of the message.
14 The GDB server protocol uses a simple modulo 256 sum.
22 def frame_packet(message
):
24 Create a framed packet that's ready to send over the GDB connection
27 Framing includes surrounding the message between $ and #, and appending
28 a two character hex checksum.
30 return "$%s#%02x" % (message
, checksum(message
))
33 def escape_binary(message
):
35 Escape the binary message using the process described in the GDB server
36 protocol documentation.
38 Most bytes are sent through as-is, but $, #, and { are escaped by writing
39 a { followed by the original byte mod 0x20.
44 if d
in (0x23, 0x24, 0x7D):
52 def hex_encode_bytes(message
):
54 Encode the binary message by converting each byte into a two-character
59 out
+= "%02x" % ord(c
)
63 def hex_decode_bytes(hex_bytes
):
65 Decode the hex string into a binary message by converting each two-character
66 hex string into a single output byte.
69 hex_len
= len(hex_bytes
)
71 while i
< hex_len
- 1:
72 out
+= chr(int(hex_bytes
[i
: i
+ 2], 16))
77 class MockGDBServerResponder
:
79 A base class for handling client packets and issuing server responses for
82 This handles many typical situations, while still allowing subclasses to
83 completely customize their responses.
85 Most subclasses will be interested in overriding the other() method, which
86 handles any packet not recognized in the common packet handling code.
92 class RESPONSE_DISCONNECT
:
98 def respond(self
, packet
):
100 Return the unframed packet data that the server should issue in response
101 to the given packet received from the client.
103 self
.packetLog
.append(packet
)
104 if packet
is MockGDBServer
.PACKET_INTERRUPT
:
105 return self
.interrupt()
108 if packet
.startswith("vCont;c"):
109 return self
.vCont(packet
)
111 return self
.A(packet
)
113 return self
.D(packet
)
115 return self
.readRegisters()
118 # Gxxxxxxxxxxx;thread:1234;
119 return self
.writeRegisters(packet
[1:].split(";")[0])
121 regnum
= packet
[1:].split(";")[0]
122 return self
.readRegister(int(regnum
, 16))
124 register
, value
= packet
[1:].split("=")
125 return self
.writeRegister(int(register
, 16), value
)
127 addr
, length
= [int(x
, 16) for x
in packet
[1:].split(",")]
128 return self
.readMemory(addr
, length
)
130 location
, encoded_data
= packet
[1:].split(":")
131 addr
, length
= [int(x
, 16) for x
in location
.split(",")]
132 return self
.writeMemory(addr
, encoded_data
)
133 if packet
[0:7] == "qSymbol":
134 return self
.qSymbol(packet
[8:])
135 if packet
[0:10] == "qSupported":
136 return self
.qSupported(packet
[11:].split(";"))
137 if packet
== "qfThreadInfo":
138 return self
.qfThreadInfo()
139 if packet
== "qsThreadInfo":
140 return self
.qsThreadInfo()
143 if packet
== "QEnableErrorStrings":
144 return self
.QEnableErrorStrings()
146 return self
.haltReason()
148 return self
.haltReason()
152 assert tid
.startswith("p")
153 # TODO: do we want to do anything with PID?
154 tid
= tid
.split(".", 1)[1]
155 return self
.selectThread(packet
[1], int(tid
, 16))
156 if packet
[0:6] == "qXfer:":
157 obj
, read
, annex
, location
= packet
[6:].split(":")
158 offset
, length
= [int(x
, 16) for x
in location
.split(",")]
159 data
, has_more
= self
.qXferRead(obj
, annex
, offset
, length
)
161 return self
._qXferResponse
(data
, has_more
)
163 if packet
.startswith("vAttach;"):
164 pid
= packet
.partition(";")[2]
165 return self
.vAttach(int(pid
, 16))
167 return self
.setBreakpoint(packet
)
168 if packet
.startswith("qThreadStopInfo"):
169 threadnum
= int(packet
[15:], 16)
170 return self
.threadStopInfo(threadnum
)
171 if packet
== "QThreadSuffixSupported":
172 return self
.QThreadSuffixSupported()
173 if packet
== "QListThreadsInStopReply":
174 return self
.QListThreadsInStopReply()
175 if packet
.startswith("qMemoryRegionInfo:"):
176 return self
.qMemoryRegionInfo(int(packet
.split(":")[1], 16))
177 if packet
== "qQueryGDBServer":
178 return self
.qQueryGDBServer()
179 if packet
== "qHostInfo":
180 return self
.qHostInfo()
181 if packet
== "qGetWorkingDir":
182 return self
.qGetWorkingDir()
183 if packet
== "qOffsets":
184 return self
.qOffsets()
185 if packet
== "qProcessInfo":
186 return self
.qProcessInfo()
187 if packet
== "qsProcessInfo":
188 return self
.qsProcessInfo()
189 if packet
.startswith("qfProcessInfo"):
190 return self
.qfProcessInfo(packet
)
191 if packet
.startswith("jGetLoadedDynamicLibrariesInfos"):
192 return self
.jGetLoadedDynamicLibrariesInfos(packet
)
193 if packet
.startswith("qPathComplete:"):
194 return self
.qPathComplete()
195 if packet
.startswith("vFile:"):
196 return self
.vFile(packet
)
197 if packet
.startswith("vRun;"):
198 return self
.vRun(packet
)
199 if packet
.startswith("qLaunchGDBServer;"):
200 _
, host
= packet
.partition(";")[2].split(":")
201 return self
.qLaunchGDBServer(host
)
202 if packet
.startswith("qLaunchSuccess"):
203 return self
.qLaunchSuccess()
204 if packet
.startswith("QEnvironment:"):
205 return self
.QEnvironment(packet
)
206 if packet
.startswith("QEnvironmentHexEncoded:"):
207 return self
.QEnvironmentHexEncoded(packet
)
208 if packet
.startswith("qRegisterInfo"):
209 regnum
= int(packet
[len("qRegisterInfo") :], 16)
210 return self
.qRegisterInfo(regnum
)
214 return self
.other(packet
)
216 def qsProcessInfo(self
):
219 def qfProcessInfo(self
, packet
):
222 def jGetLoadedDynamicLibrariesInfos(self
, packet
):
225 def qGetWorkingDir(self
):
231 def qProcessInfo(self
):
235 return "ptrsize:8;endian:little;"
237 def qQueryGDBServer(self
):
241 raise self
.UnexpectedPacketException()
244 raise self
.UnexpectedPacketException()
246 def vCont(self
, packet
):
247 raise self
.UnexpectedPacketException()
255 def readRegisters(self
):
256 return "00000000" * self
.registerCount
258 def readRegister(self
, register
):
261 def writeRegisters(self
, registers_hex
):
264 def writeRegister(self
, register
, value_hex
):
267 def readMemory(self
, addr
, length
):
270 def writeMemory(self
, addr
, data_hex
):
273 def qSymbol(self
, symbol_args
):
276 def qSupported(self
, client_supported
):
277 return "qXfer:features:read+;PacketSize=3fff;QStartNoAckMode+"
279 def qfThreadInfo(self
):
282 def qsThreadInfo(self
):
288 def QEnableErrorStrings(self
):
291 def haltReason(self
):
292 # SIGINT is 2, return type is 2 digit hex string
295 def qXferRead(self
, obj
, annex
, offset
, length
):
298 def _qXferResponse(self
, data
, has_more
):
299 return "%s%s" % ("m" if has_more
else "l", escape_binary(data
))
301 def vAttach(self
, pid
):
302 raise self
.UnexpectedPacketException()
304 def selectThread(self
, op
, thread_id
):
307 def setBreakpoint(self
, packet
):
308 raise self
.UnexpectedPacketException()
310 def threadStopInfo(self
, threadnum
):
313 def other(self
, packet
):
314 # empty string means unsupported
317 def QThreadSuffixSupported(self
):
320 def QListThreadsInStopReply(self
):
323 def qMemoryRegionInfo(self
, addr
):
326 def qPathComplete(self
):
329 def vFile(self
, packet
):
332 def vRun(self
, packet
):
335 def qLaunchGDBServer(self
, host
):
336 raise self
.UnexpectedPacketException()
338 def qLaunchSuccess(self
):
341 def QEnvironment(self
, packet
):
344 def QEnvironmentHexEncoded(self
, packet
):
347 def qRegisterInfo(self
, num
):
351 return ["W01", self
.RESPONSE_DISCONNECT
]
354 Raised when we receive a packet for which there is no default action.
355 Override the responder class to implement behavior suitable for the test at
359 class UnexpectedPacketException(Exception):
365 A wrapper class for TCP or pty-based server.
368 def get_connect_address(self
):
369 """Get address for the client to connect to."""
371 def get_connect_url(self
):
372 """Get URL suitable for process connect command."""
374 def close_server(self
):
375 """Close all resources used by the server."""
378 """Accept a single client connection to the server."""
380 def close_connection(self
):
381 """Close all resources used by the accepted connection."""
384 """Receive a data packet from the connected client."""
386 def sendall(self
, data
):
387 """Send the data to the connected client."""
390 class ServerSocket(ServerChannel
):
391 def __init__(self
, family
, type, proto
, addr
):
392 self
._server
_socket
= socket
.socket(family
, type, proto
)
393 self
._connection
= None
395 self
._server
_socket
.bind(addr
)
396 self
._server
_socket
.listen(1)
398 def close_server(self
):
399 self
._server
_socket
.close()
402 assert self
._connection
is None
403 # accept() is stubborn and won't fail even when the socket is
404 # shutdown, so we'll use a timeout
405 self
._server
_socket
.settimeout(30.0)
406 client
, client_addr
= self
._server
_socket
.accept()
407 # The connected client inherits its timeout from self._socket,
408 # but we'll use a blocking socket for the client
409 client
.settimeout(None)
410 self
._connection
= client
412 def close_connection(self
):
413 assert self
._connection
is not None
414 self
._connection
.close()
415 self
._connection
= None
418 assert self
._connection
is not None
419 return self
._connection
.recv(4096)
421 def sendall(self
, data
):
422 assert self
._connection
is not None
423 return self
._connection
.sendall(data
)
426 class TCPServerSocket(ServerSocket
):
428 family
, type, proto
, _
, addr
= socket
.getaddrinfo(
429 "localhost", 0, proto
=socket
.IPPROTO_TCP
431 super().__init
__(family
, type, proto
, addr
)
433 def get_connect_address(self
):
434 return "[{}]:{}".format(*self
._server
_socket
.getsockname())
436 def get_connect_url(self
):
437 return "connect://" + self
.get_connect_address()
440 class UnixServerSocket(ServerSocket
):
441 def __init__(self
, addr
):
442 super().__init
__(socket
.AF_UNIX
, socket
.SOCK_STREAM
, 0, addr
)
444 def get_connect_address(self
):
445 return self
._server
_socket
.getsockname()
447 def get_connect_url(self
):
448 return "unix-connect://" + self
.get_connect_address()
451 class PtyServerSocket(ServerChannel
):
456 primary
, secondary
= pty
.openpty()
458 self
._primary
= io
.FileIO(primary
, "r+b")
459 self
._secondary
= io
.FileIO(secondary
, "r+b")
461 def get_connect_address(self
):
462 libc
= ctypes
.CDLL(None)
463 libc
.ptsname
.argtypes
= (ctypes
.c_int
,)
464 libc
.ptsname
.restype
= ctypes
.c_char_p
465 return libc
.ptsname(self
._primary
.fileno()).decode()
467 def get_connect_url(self
):
468 return "serial://" + self
.get_connect_address()
470 def close_server(self
):
471 self
._secondary
.close()
472 self
._primary
.close()
476 return self
._primary
.read(4096)
478 # closing the pty results in EIO on Linux, convert it to EOF
479 if e
.errno
== errno
.EIO
:
483 def sendall(self
, data
):
484 return self
._primary
.write(data
)
489 A simple TCP-based GDB server that can test client behavior by receiving
490 commands and issuing custom-tailored responses.
492 Responses are generated via the .responder property, which should be an
493 instance of a class based on MockGDBServerResponder.
500 _receivedDataOffset
= None
501 _shouldSendAck
= True
503 def __init__(self
, socket
):
504 self
._socket
= socket
505 self
.responder
= MockGDBServerResponder()
508 # Start a thread that waits for a client connection.
509 self
._thread
= threading
.Thread(target
=self
.run
)
516 def get_connect_address(self
):
517 return self
._socket
.get_connect_address()
519 def get_connect_url(self
):
520 return self
._socket
.get_connect_url()
523 # For testing purposes, we only need to worry about one client
524 # connecting just one time.
526 self
._socket
.accept()
528 traceback
.print_exc()
530 self
._shouldSendAck
= True
531 self
._receivedData
= ""
532 self
._receivedDataOffset
= 0
536 data
= seven
.bitcast_to_string(self
._socket
.recv())
537 if data
is None or len(data
) == 0:
540 except self
.TerminateConnectionException
:
542 except Exception as e
:
544 "An exception happened when receiving the response from the gdb server. Closing the client..."
546 traceback
.print_exc()
548 self
._socket
.close_connection()
549 self
._socket
.close_server()
551 def _receive(self
, data
):
553 Collects data, parses and responds to as many packets as exist.
554 Any leftover data is kept for parsing the next time around.
556 self
._receivedData
+= data
557 packet
= self
._parsePacket
()
558 while packet
is not None:
559 self
._handlePacket
(packet
)
560 packet
= self
._parsePacket
()
562 def _parsePacket(self
):
564 Reads bytes from self._receivedData, returning:
565 - a packet's contents if a valid packet is found
566 - the PACKET_ACK unique object if we got an ack
567 - None if we only have a partial packet
569 Raises an InvalidPacketException if unexpected data is received
570 or if checksums fail.
572 Once a complete packet is found at the front of self._receivedData,
573 its data is removed form self._receivedData.
575 data
= self
._receivedData
576 i
= self
._receivedDataOffset
581 # If we're looking at the start of the received data, that means
582 # we're looking for the start of a new packet, denoted by a $.
583 # It's also possible we'll see an ACK here, denoted by a +
585 self
._receivedData
= data
[1:]
586 return self
.PACKET_ACK
587 if ord(data
[0]) == 3:
588 self
._receivedData
= data
[1:]
589 return self
.PACKET_INTERRUPT
593 raise self
.InvalidPacketException(
594 "Unexpected leading byte: %s" % data
[0]
597 # If we're looking beyond the start of the received data, then we're
598 # looking for the end of the packet content, denoted by a #.
599 # Note that we pick up searching from where we left off last time
600 while i
< data_len
and data
[i
] != "#":
603 # If there isn't enough data left for a checksum, just remember where
604 # we left off so we can pick up there the next time around
606 self
._receivedDataOffset
= i
609 # If we have enough data remaining for the checksum, extract it and
610 # compare to the packet contents
614 check
= int(data
[i
: i
+ 2], 16)
616 raise self
.InvalidPacketException("Checksum is not valid hex")
618 if check
!= checksum(packet
):
619 raise self
.InvalidPacketException(
620 "Checksum %02x does not match content %02x" % (check
, checksum(packet
))
622 # remove parsed bytes from _receivedData and reset offset so parsing
623 # can start on the next packet the next time around
624 self
._receivedData
= data
[i
:]
625 self
._receivedDataOffset
= 0
628 def _sendPacket(self
, packet
):
629 self
._socket
.sendall(seven
.bitcast_to_bytes(frame_packet(packet
)))
631 def _handlePacket(self
, packet
):
632 if packet
is self
.PACKET_ACK
:
633 # Ignore ACKs from the client. For the future, we can consider
634 # adding validation code to make sure the client only sends ACKs
635 # when it's supposed to.
638 # We'll handle the ack stuff here since it's not something any of the
639 # tests will be concerned about, and it'll get turned off quickly anyway.
640 if self
._shouldSendAck
:
641 self
._socket
.sendall(seven
.bitcast_to_bytes("+"))
642 if packet
== "QStartNoAckMode":
643 self
._shouldSendAck
= False
645 elif self
.responder
is not None:
646 # Delegate everything else to our responder
647 response
= self
.responder
.respond(packet
)
648 if not isinstance(response
, list):
649 response
= [response
]
650 for part
in response
:
651 if part
is MockGDBServerResponder
.RESPONSE_DISCONNECT
:
652 raise self
.TerminateConnectionException()
653 self
._sendPacket
(part
)
655 PACKET_ACK
= object()
656 PACKET_INTERRUPT
= object()
658 class TerminateConnectionException(Exception):
661 class InvalidPacketException(Exception):