qemu_gdb_proxy.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. GDB server proxy for the QEMU emulator running a Pebble machine.
  16. This proxy sits between gdb and the gdb server implemented in QEMU. Its primary purpose is to
  17. implement support for the "info threads" and related gdb commands. The QEMU gdb server is not thread
  18. aware and doesn't have any FreeRTOS knowledge such that it can figure out the FreeRTOS threads
  19. created in the Pebble.
  20. This proxy talks to the QEMU gdb server using primitive gdb remote commands and inspects the
  21. FreeRTOS task structures to figure out which threads have been created, their saved registers, etc.
  22. and then returns that information to gdb when it asks for thread info from the target system. For
  23. most other requests recevied from gdb, this proxy simply acts as a passive pass thru to the QEMU gdb
  24. server.
  25. This module is designed to be run as a separate process from both QEMU and gdb. It connects to the
  26. gdb socket created by QEMU and accepts connections from gdb. The intent is that this module would
  27. be launched whenever QEMU is launched and likewise taken down whenever QEMU exits. To support this,
  28. we exit this process whenever we detect that the QEMU gdb server connection has closed.
  29. """
  30. import logging, socket
  31. from struct import unpack
  32. from time import sleep
  33. import sys
  34. import time
  35. import argparse
  36. import select
  37. CTRL_C_CHARACTER = b'\3'
  38. ##########################################################################################
  39. class QemuGdbError(Exception):
  40. pass
  41. ##########################################################################################
  42. def byte_swap_uint32(val):
  43. """ Return a byte-swapped 32-bit value """
  44. return ( ((val & 0xFF000000) >> 24)
  45. | ((val & 0x00FF0000) >> 8)
  46. | ((val & 0x0000FF00) << 8)
  47. | ((val & 0x000000FF) << 24))
  48. ##########################################################################################
  49. class PebbleThread(object):
  50. """ This class encapsulates the information about a thread on the Pebble """
  51. # Mapping of register name to register index
  52. reg_name_to_index = {name: num for num, name in enumerate(
  53. 'r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc xpsr'.split())}
  54. # Offset of each register on the thread's stack
  55. # stack_offset -> register_index
  56. stack_offset_to_reg_index_v2 = [ # Used in Snowy, Cortex-M4
  57. (0x28, 0), # r0
  58. (0x2C, 1), # r1
  59. (0x30, 2), # r2
  60. (0x34, 3), # r3
  61. (0x04, 4), # r4
  62. (0x08, 5), # r5
  63. (0x0C, 6), # r6
  64. (0x10, 7), # r7
  65. (0x14, 8), # r8
  66. (0x18, 9), # r9
  67. (0x1C, 10), # r10
  68. (0x20, 11), # r11
  69. (0x38, 12), # r12
  70. (0x3C, 14), # lr
  71. (0x40, 15), # pc
  72. (0x44, 16), # xpsr
  73. ]
  74. thread_state_size_v2 = 0x48
  75. stack_offset_to_reg_index_v1 = [ # Used in Tintin, Cortex-M3
  76. (0x24, 0), # r0
  77. (0x28, 1), # r1
  78. (0x2C, 2), # r2
  79. (0x30, 3), # r3
  80. (0x04, 4), # r4
  81. (0x08, 5), # r5
  82. (0x0C, 6), # r6
  83. (0x10, 7), # r7
  84. (0x14, 8), # r8
  85. (0x18, 9), # r9
  86. (0x1C, 10), # r10
  87. (0x20, 11), # r11
  88. (0x34, 12), # r12
  89. (0x38, 14), # lr
  90. (0x3C, 15), # pc
  91. (0x40, 16), # xpsr
  92. ]
  93. thread_state_size_v1 = 0x44
  94. def __init__(self, id, ptr, running, name, registers):
  95. self.id = id
  96. self.ptr = ptr
  97. self.running = running
  98. self.name = name
  99. self.registers = registers
  100. def set_register(self, reg_index, value):
  101. self.registers[reg_index] = value
  102. def get_register(self, reg_index):
  103. return self.registers[reg_index]
  104. def __repr__(self):
  105. return "<Thread id:%d, ptr:0x%08X, running:%r, name:%s, registers:%r" % (self.id, self.ptr,
  106. self.running, self.name, self.registers)
  107. ##########################################################################################
  108. class QemuGdbProxy(object):
  109. """
  110. This class implements a GDB server listening for a gdb connection on a specific port.
  111. It connects to and acts as a proxy to yet another gdb server running on the target system.
  112. This proxy implements advanced gdb commands like getting thread info by looking up the values of
  113. specific FreeRTOS symbols and querying the FreeRTOS data structures on the target system.
  114. """
  115. ##########################################################################################
  116. def __init__(self, port, target_host, target_port, connect_timeout):
  117. # The "target" is the remote system we are debugging. The target implements a basic
  118. # gdb remote server
  119. self.target_host = target_host
  120. self.target_port = target_port
  121. self.target_socket = None
  122. self.connect_timeout = connect_timeout
  123. # The "client" is gdb
  124. self.client_accept_socket = None
  125. self.client_accept_port = port
  126. self.client_conn_socket = None
  127. self.packet_size = 2048
  128. self.active_thread_id = 0 # Selected by GDB
  129. self.threads = {} # key is the thread id, value is a PebbleThread object
  130. # The QEMU gdb remote server always assigns a thread ID of 1 to it's one and only thread
  131. self.QEMU_MONITOR_CURRENT_THREAD_ID = 1
  132. # Free RTOS symbols we need to look up in order to inspect FreeRTOS threads
  133. symbol_list = [
  134. "uxFreeRTOSRegisterStackingVersion",
  135. "pxCurrentTCB",
  136. "pxReadyTasksLists",
  137. "xDelayedTaskList1",
  138. "xDelayedTaskList2",
  139. "pxDelayedTaskList",
  140. "pxOverflowDelayedTaskList",
  141. "xPendingReadyList",
  142. "xTasksWaitingTermination",
  143. "xSuspendedTaskList",
  144. "uxCurrentNumberOfTasks",
  145. ]
  146. self.symbol_dict = {symbol: None for symbol in symbol_list}
  147. self.got_all_symbols = False
  148. ##########################################################################################
  149. def _fetch_socket_data(self, timeout=None):
  150. """ Fetch available data from our sockets (client and target). Block until any
  151. data is available, or until the target connection is closed. If we detect that the
  152. target connection has closed, we exit this app.
  153. If we detect that the client connection (from gdb) has closed, we wait for a new connection
  154. request from gdb.
  155. retval:
  156. (target_data, client_data)
  157. """
  158. target_data = b''
  159. client_data = b''
  160. while (not target_data and not client_data):
  161. # Form our read list. The target socket is always in the read list. Depending on if we
  162. # are waiting for a client connection or not, we either put the client_accept_socket or
  163. # client_conn_socket in the list.
  164. if self.client_conn_socket is not None:
  165. read_list = [self.target_socket, self.client_conn_socket]
  166. else:
  167. read_list = [self.target_socket, self.client_accept_socket]
  168. readable, writable, errored = select.select(read_list, [], [], timeout)
  169. # If nothing ready, we must have timed out
  170. if not readable:
  171. logging.debug("read timeout")
  172. break
  173. # Data available from target?
  174. if self.target_socket in readable:
  175. target_data = self.target_socket.recv(self.packet_size)
  176. if not target_data:
  177. raise QemuGdbError("target system disconnected")
  178. logging.debug("got target data: '%s' (0x%s) " % (target_data,
  179. target_data.hex()))
  180. # Data available from client?
  181. if self.client_conn_socket is not None:
  182. if self.client_conn_socket in readable:
  183. client_data = self.client_conn_socket.recv(self.packet_size)
  184. if not client_data:
  185. logging.info("client connection closed")
  186. self.client_conn_socket.close()
  187. self.client_conn_socket = None
  188. logging.debug("got client data: '%s' (0x%s) " % (client_data,
  189. client_data.hex()))
  190. # Connection request from client?
  191. else:
  192. if self.client_accept_socket in readable:
  193. self.client_conn_socket, _ = self.client_accept_socket.accept()
  194. logging.info("Connected to client")
  195. return (target_data, client_data)
  196. ##########################################################################################
  197. def _create_packet(self, data):
  198. checksum = sum(data) % 256
  199. packet = b"$%s#%02X" % (data, checksum)
  200. logging.debug('--<<<<<<<<<<<< GDB packet: %s', packet)
  201. return packet
  202. ##########################################################################################
  203. def _target_read_register(self, reg_index):
  204. """ Fetch the value of the given register index from the active thread """
  205. try:
  206. thread = self.threads[self.active_thread_id]
  207. except KeyError:
  208. raise QemuGdbError("Unknown thread id")
  209. return thread.get_register(reg_index)
  210. ##########################################################################################
  211. def _target_write_register(self, reg_index, value):
  212. """ Update the value of the given register index in the active thread """
  213. try:
  214. thread = self.threads[self.active_thread_id]
  215. except KeyError:
  216. raise QemuGdbError("Unknown thread id")
  217. print("TODO: NEED TO WRITE TO THREAD STACK ON TARGET TOO")
  218. thread.set_register(reg_index, value)
  219. ##########################################################################################
  220. def _target_read_memory(self, address, bytes):
  221. request = self._create_packet('m %08X,%08X' % (address, bytes))
  222. self.target_socket.send(request)
  223. # read response
  224. data = ''
  225. while True:
  226. target_data = self.target_socket.recv(self.packet_size)
  227. if not target_data:
  228. raise QemuGdbError("target system disconnected")
  229. data += target_data
  230. if "$" in data and "#" in data:
  231. break
  232. _, data = data.split('$', 1)
  233. logging.debug("Received target response: %s" % (data))
  234. resp = data.split('#', 1)[0]
  235. if resp.startswith('E '):
  236. raise QemuGdbError("Error response %s", resp)
  237. return resp
  238. ##########################################################################################
  239. def _target_read_uint32(self, address):
  240. hex = self._target_read_memory(address, 4)
  241. value = int(hex, 16)
  242. return byte_swap_uint32(value)
  243. ##########################################################################################
  244. def _target_read_uint8(self, address):
  245. hex = self._target_read_memory(address, 1)
  246. value = int(hex, 16)
  247. return value
  248. ##########################################################################################
  249. def _target_read_cstr(self, address, max_len):
  250. str_hex = self._target_read_memory(address, max_len)
  251. str = str_hex.decode('hex')
  252. return str.split('\0', 1)[0]
  253. ##########################################################################################
  254. def _target_collect_thread_info(self):
  255. # FreeRTOS params used to collect thread info
  256. FRTOS_LIST_NEXT_OFFSET = 16
  257. FRTOS_LIST_WIDTH = 20
  258. FRTOS_LIST_ELEM_NEXT_OFFSET = 8
  259. FRTOS_LIST_ELEM_CONTENT_OFFSET = 12
  260. FRTOS_THREAD_STACK_OFFSET = 0
  261. FRTOS_THREAD_NAME_OFFSET = 84
  262. FRTOS_MAX_PRIORITIES = 5
  263. num_threads = self._target_read_uint32(self.symbol_dict['uxCurrentNumberOfTasks'])
  264. # Figure out the register stacking
  265. if self.symbol_dict['uxFreeRTOSRegisterStackingVersion']:
  266. reg_stacking_version = self._target_read_uint8(
  267. self.symbol_dict['uxFreeRTOSRegisterStackingVersion'])
  268. else:
  269. reg_stacking_version = 1
  270. if reg_stacking_version == 1:
  271. stack_offset_to_reg_index = PebbleThread.stack_offset_to_reg_index_v1
  272. thread_state_size = PebbleThread.thread_state_size_v1
  273. elif reg_stacking_version == 2:
  274. stack_offset_to_reg_index = PebbleThread.stack_offset_to_reg_index_v2
  275. thread_state_size = PebbleThread.thread_state_size_v2
  276. else:
  277. raise QemuGdbError("Unsupported uxFreeRTOSRegisterStackingVersion of %d" %
  278. reg_stacking_version)
  279. # Get total number of threads and current thread ID
  280. num_threads = self._target_read_uint32(self.symbol_dict['uxCurrentNumberOfTasks'])
  281. current_thread = self._target_read_uint32(self.symbol_dict['pxCurrentTCB'])
  282. self.threads = {}
  283. # Get the address of each list
  284. list_addresses = []
  285. address = self.symbol_dict['pxReadyTasksLists']
  286. for i in range(FRTOS_MAX_PRIORITIES):
  287. list_addresses.append(address + i * FRTOS_LIST_WIDTH)
  288. for name in ['xDelayedTaskList1', 'xDelayedTaskList2', 'xPendingReadyList',
  289. 'xSuspendedTaskList', 'xTasksWaitingTermination']:
  290. list_addresses.append(self.symbol_dict[name])
  291. # Fetch the tasks from each list
  292. for list in list_addresses:
  293. thread_count = self._target_read_uint32(list)
  294. if thread_count == 0:
  295. continue
  296. # Location of first item
  297. elem_ptr = self._target_read_uint32(list + FRTOS_LIST_NEXT_OFFSET)
  298. # Loop through the list
  299. prev_elem_ptr = -1
  300. while (thread_count > 0 and elem_ptr != 0 and elem_ptr != prev_elem_ptr
  301. and len(self.threads) < num_threads):
  302. thread_ptr = self._target_read_uint32(elem_ptr + FRTOS_LIST_ELEM_CONTENT_OFFSET)
  303. thread_running = (thread_ptr == current_thread)
  304. # The QEMU gdb server assigns the active thread a thread ID of 1 and if we change it
  305. # to something else (like the TCB ptr), then things are not ideal. For example, gdb
  306. # will display a "The current thread <Thread ID 1> has terminated" message.
  307. # So, we will preserve 1 for the current thread and assign the TCB ptr for the
  308. # others
  309. if thread_running:
  310. thread_id = self.QEMU_MONITOR_CURRENT_THREAD_ID
  311. else:
  312. thread_id = thread_ptr
  313. thread_name = self._target_read_cstr(thread_ptr + FRTOS_THREAD_NAME_OFFSET, 32)
  314. stack = self._target_read_uint32(thread_ptr + FRTOS_THREAD_STACK_OFFSET)
  315. registers = [0] * len(PebbleThread.reg_name_to_index)
  316. for (offset, reg_index) in stack_offset_to_reg_index:
  317. registers[reg_index] = self._target_read_uint32(stack + offset)
  318. registers[13] = stack + thread_state_size
  319. # Create the thread instance
  320. thread = PebbleThread(id=thread_id, ptr=thread_ptr, running=thread_running,
  321. name=thread_name, registers=registers)
  322. self.threads[thread_id] = thread
  323. logging.debug("Got thread info: %r" % (thread))
  324. # Another thread in this list?
  325. prev_elem_ptr = elem_ptr
  326. elem_ptr = self._target_read_uint32(elem_ptr + FRTOS_LIST_ELEM_NEXT_OFFSET)
  327. thread_count -= 1
  328. ##########################################################################################
  329. def _handle_set_active_thread_req(self, data):
  330. num = int(data, 16)
  331. if (num == -1): # All threads
  332. return
  333. elif (num == 0): # Any thread
  334. num = self.QEMU_MONITOR_CURRENT_THREAD_ID
  335. self.active_thread_id = num
  336. return self._create_packet("OK")
  337. ##########################################################################################
  338. def _handle_continue_req(self, msg):
  339. """ The format of this is: 'vCont[;action[:thread-id]]...'
  340. The QEMU gdb server only understands a thread id of 1, so if we pass it other thread ids,
  341. it will barf.
  342. """
  343. if b';' not in msg:
  344. return None
  345. action_thread_pair = msg.split(b';')[1]
  346. if b':' in action_thread_pair:
  347. action = action_thread_pair.split(b':')[0]
  348. else:
  349. action = action_thread_pair
  350. # Send to target with the thread ID
  351. packet = self._create_packet(b"vCont;%s" % (action))
  352. self.target_socket.send(packet)
  353. # Change back to active thread of 1
  354. self.active_thread_id = self.QEMU_MONITOR_CURRENT_THREAD_ID
  355. return ''
  356. ##########################################################################################
  357. def _handle_thread_is_alive_req(self, data):
  358. num = int(data, 16)
  359. if (num == -1 or num == 0): # All threads
  360. return self._create_packet(b"OK")
  361. if num in self.threads:
  362. return self._create_packet(b"OK")
  363. return self._create_packet(b"E22")
  364. ##########################################################################################
  365. def _handle_get_all_registers_req(self):
  366. """ Get all registers for the active thread """
  367. resp = b''
  368. for i in range(len(PebbleThread.reg_name_to_index)):
  369. value = self._target_read_register(i)
  370. resp += b"%08X" % (byte_swap_uint32(value))
  371. return self._create_packet(resp)
  372. ##########################################################################################
  373. def _handle_query_req(self, msg):
  374. msg = msg.split(b'#')[0]
  375. query = msg.split(b':')
  376. logging.debug('GDB received query: %s', query)
  377. if query is None:
  378. logging.error('GDB received query packet malformed')
  379. return None
  380. elif query[0] == b'C':
  381. return self._create_packet(b"%d" % (self.active_thread_id))
  382. elif query[0] == b'fThreadInfo':
  383. if not self.got_all_symbols:
  384. # NOTE: When running the 4.9 gcc tool chain, gdb asks for thread info right
  385. # after attaching, before we have a chance to look up symbols, so respond
  386. # with "last thread" if we don't have symbols yet.
  387. return self._create_packet(b"l") # last
  388. self._target_collect_thread_info()
  389. # For some strange reason, if the active thread is first, the first "i thread" gdb
  390. # command only displays that one thread, so reverse sort to put it at the end
  391. id_strs = ("%016x" % id for id in sorted(list(self.threads.keys()), reverse=True))
  392. return self._create_packet(b"m" + b",".join(id_strs))
  393. elif query[0] == b'sThreadInfo':
  394. return self._create_packet(b"l") # last
  395. elif query[0].startswith(b'ThreadExtraInfo'):
  396. id_str = query[0].split(b',')[1]
  397. id = int(id_str, 16)
  398. found_thread = self.threads.get(id, None)
  399. if found_thread is None:
  400. resp = "<INVALID THREAD ID: %d>" % (id)
  401. elif found_thread.running:
  402. resp = "%s 0x%08X: Running" % (found_thread.name, found_thread.ptr)
  403. else:
  404. resp = "%s 0x%08X" % (found_thread.name, found_thread.ptr)
  405. return self._create_packet(resp.encode('hex'))
  406. elif b'Symbol' in query[0]:
  407. if query[2] != b'':
  408. sym_name = query[2].decode('hex')
  409. if query[1] != b'':
  410. sym_value = int(query[1], 16)
  411. logging.debug("Setting value of symbol '%s' to 0x%08x" % (sym_name, sym_value))
  412. self.symbol_dict[sym_name] = sym_value
  413. else:
  414. logging.debug("Could not find value of symbol '%s'" % (sym_name))
  415. self.symbol_dict[sym_name] = ''
  416. # Anymore we need to look up?
  417. symbol = None
  418. for x, y in list(self.symbol_dict.items()):
  419. if y is None:
  420. symbol = x
  421. break
  422. if symbol is not None:
  423. logging.debug("Asking gdb to lookup symbol %s" % (symbol))
  424. return self._create_packet(b'qSymbol:%s' % (symbol.encode('hex')))
  425. else:
  426. self.got_all_symbols = True
  427. return self._create_packet(b'OK')
  428. else:
  429. return None
  430. ##########################################################################################
  431. def _handle_request(self, msg):
  432. """ See if we want to handle a request directly here in the proxy
  433. retval: resp,
  434. resp: Response to return.
  435. if None, proxy doesn't deal with the request directly
  436. """
  437. logging.debug('-->>>>>>>>>>>> GDB req packet: %s', msg)
  438. msg = msg.split(b'#')[0]
  439. # query command
  440. if msg[1] == b'q':
  441. return self._handle_query_req(msg[2:])
  442. elif msg[1] == b'H':
  443. if msg[2] == b'c':
  444. return None
  445. else:
  446. return self._handle_set_active_thread_req(msg[3:])
  447. elif msg[1] == b'T':
  448. return self._handle_thread_is_alive_req(msg[2:])
  449. elif msg[1] == b'g':
  450. if (self.active_thread_id <= 0
  451. or self.active_thread_id == self.QEMU_MONITOR_CURRENT_THREAD_ID):
  452. return None
  453. else:
  454. return self._handle_get_all_registers_req()
  455. elif msg[1] == b'p':
  456. # 'p <n>' : read value of register n
  457. if self.active_thread_id == self.QEMU_MONITOR_CURRENT_THREAD_ID:
  458. return None
  459. else:
  460. msg = msg[2:]
  461. reg_num = int(msg, 16)
  462. value = self._target_read_register(reg_num)
  463. return self._create_packet("%08X" % (byte_swap_uint32(value)))
  464. elif msg[1] == b'P':
  465. # 'P <n>=<r>' : set value of register n to r
  466. if self.active_thread_id == self.QEMU_MONITOR_CURRENT_THREAD_ID:
  467. return None
  468. else:
  469. msg = msg[2:].split(b'=')
  470. reg_num = int(msg[0], 16)
  471. val = int(msg[1], 16)
  472. val = byte_swap_uint32(val)
  473. self._target_write_register(reg_num, val)
  474. return self._create_packet("OK")
  475. elif msg[1:].startswith(b'vCont'):
  476. return self._handle_continue_req(msg[1:])
  477. else:
  478. return None
  479. ##########################################################################################
  480. def run(self):
  481. """ Run the proxy """
  482. # Connect to the target system first
  483. logging.info("Connecting to target system on %s:%s" % (self.target_host, self.target_port))
  484. start_time = time.time()
  485. connected = False
  486. self.target_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  487. while not connected and (time.time() - start_time < self.connect_timeout):
  488. try:
  489. self.target_socket.connect((self.target_host, self.target_port))
  490. connected = True
  491. except socket.error:
  492. self.target_socket.close()
  493. self.target_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  494. time.sleep(0.1)
  495. if not connected:
  496. raise QemuGdbError("Unable to connect to target system on %s:%s. Is the emulator"
  497. " running?" % (self.target_host, self.target_port))
  498. logging.info("Connected to target system on %s:%s" % (self.target_host,
  499. self.target_port))
  500. # Open up our socket to accept connect requests from the client (gdb)
  501. self.client_accept_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  502. self.client_accept_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  503. self.client_accept_socket.bind(('', self.client_accept_port))
  504. self.client_accept_socket.listen(5)
  505. # Empty out any unsolicited data sent from the target
  506. (target_data, client_data) = self._fetch_socket_data(timeout=0.1)
  507. # --------------------------------------------------------------------------------------
  508. # Loop processing requests
  509. data = b''
  510. while True:
  511. # read more data from client until we get at least one packet
  512. while True:
  513. (target_data, client_data) = self._fetch_socket_data()
  514. # Pass through any response from the target back to gdb
  515. if target_data and self.client_conn_socket is not None:
  516. self.client_conn_socket.send(target_data)
  517. # Ctrl-C interrupt?
  518. if CTRL_C_CHARACTER in client_data:
  519. self.target_socket.send(CTRL_C_CHARACTER)
  520. client_data = client_data[client_data.index(CTRL_C_CHARACTER)+1:]
  521. data += client_data
  522. if b"$" in data and b"#" in data:
  523. break
  524. # Process all complete packets we have received from the client
  525. while b"$" in data and b"#" in data:
  526. data = data[data.index(b"$"):]
  527. logging.debug("Processing remaining data: %s" % (data))
  528. end = data.index(b"#") + 3 # 2 bytes of checksum
  529. packet = data[0:end]
  530. data = data[end:]
  531. # decode and prepare resp
  532. logging.debug("Processing packet: %s" % (packet))
  533. resp = self._handle_request(packet)
  534. # If it's nothing we care about, pass to target and return the response back to
  535. # client
  536. if resp is None:
  537. logging.debug("Sending request to target: %s" % (packet))
  538. self.target_socket.send(packet)
  539. # else, we generated our own response that needs to go to the client
  540. elif resp != '':
  541. self.client_conn_socket.send(b'+' + resp)
  542. # wait for ack from the client
  543. (target_data, client_data) = self._fetch_socket_data()
  544. if target_data:
  545. self.client_conn_socket.send(target_data)
  546. if client_data[0] != b'+':
  547. logging.debug('gdb client did not ack')
  548. else:
  549. logging.debug('gdb client acked')
  550. # Add to our accumulated content
  551. data += client_data[1:]
  552. ####################################################################################################
  553. if __name__ == '__main__':
  554. # Collect our command line arguments
  555. parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  556. parser.add_argument('--port', type=int, default=1233,
  557. help="Port to accept incomming connections on")
  558. parser.add_argument('--target', default='localhost:1234',
  559. help="target to connect to ")
  560. parser.add_argument('--connect_timeout', type=float, default=1.0,
  561. help="give up if we can't connect to the target within this timeout (sec)")
  562. parser.add_argument('--debug', action='store_true',
  563. help="Turn on debug logging")
  564. args = parser.parse_args()
  565. level = logging.INFO
  566. if args.debug:
  567. level = logging.DEBUG
  568. logging.basicConfig(level=level)
  569. (target_host, target_port) = args.target.split(':')
  570. proxy = QemuGdbProxy(port=args.port, target_host=target_host, target_port=int(target_port),
  571. connect_timeout=args.connect_timeout)
  572. proxy.run()