gdbhelper.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import os
  2. import re
  3. import subprocess
  4. import sys
  5. import tempfile
  6. from .constants import PANIC_OUTPUT_DECODE_SCRIPT
  7. from .logger import Logger
  8. from .output_helpers import normal_print, red_print, yellow_print
  9. from .web_socket_client import WebSocketClient
  10. class GDBHelper:
  11. def __init__(self, toolchain_prefix, websocket_client, elf_file, port, baud_rate):
  12. # type: (str, WebSocketClient, str, int, int) -> None
  13. self._gdb_buffer = b'' # type: bytes
  14. self._gdb_exit = False # type: bool
  15. self.toolchain_prefix = toolchain_prefix
  16. self.websocket_client = websocket_client
  17. self.elf_file = elf_file
  18. self.port = port
  19. self.baud_rate = baud_rate
  20. @property
  21. def gdb_buffer(self): # type: () -> bytes
  22. return self._gdb_buffer
  23. @gdb_buffer.setter
  24. def gdb_buffer(self, value): # type: (bytes) -> None
  25. self._gdb_buffer = value
  26. @property
  27. def gdb_exit(self): # type: () -> bool
  28. return self._gdb_exit
  29. @gdb_exit.setter
  30. def gdb_exit(self, value): # type: (bool) -> None
  31. self._gdb_exit = value
  32. def run_gdb(self):
  33. # type: () -> None
  34. normal_print('')
  35. try:
  36. cmd = ['%sgdb' % self.toolchain_prefix,
  37. '-ex', 'set serial baud %d' % self.baud_rate,
  38. '-ex', 'target remote %s' % self.port,
  39. self.elf_file]
  40. # Here we handling GDB as a process
  41. # Open GDB process
  42. try:
  43. process = subprocess.Popen(cmd, cwd='.')
  44. except KeyboardInterrupt:
  45. pass
  46. # We ignore Ctrl+C interrupt form external process abd wait response util GDB will be finished.
  47. while True:
  48. try:
  49. process.wait()
  50. break
  51. except KeyboardInterrupt:
  52. pass # We ignore the Ctrl+C
  53. self.gdb_exit = True
  54. except OSError as e:
  55. red_print('%s: %s' % (' '.join(cmd), e))
  56. except KeyboardInterrupt:
  57. pass # happens on Windows, maybe other OSes
  58. finally:
  59. try:
  60. # on Linux, maybe other OSes, gdb sometimes seems to be alive even after wait() returns...
  61. process.terminate()
  62. except Exception: # noqa
  63. pass
  64. try:
  65. # also on Linux, maybe other OSes, gdb sometimes exits uncleanly and breaks the tty mode
  66. subprocess.call(['stty', 'sane'])
  67. except Exception: # noqa
  68. pass # don't care if there's no stty, we tried...
  69. def check_gdb_stub_trigger(self, line):
  70. # type: (bytes) -> bool
  71. line = self.gdb_buffer + line
  72. self.gdb_buffer = b''
  73. m = re.search(b'\\$(T..)#(..)', line) # look for a gdb "reason" for a break
  74. if m is not None:
  75. try:
  76. chsum = sum(ord(bytes([p])) for p in m.group(1)) & 0xFF
  77. calc_chsum = int(m.group(2), 16)
  78. except ValueError: # payload wasn't valid hex digits
  79. return False
  80. if chsum == calc_chsum:
  81. if self.websocket_client:
  82. yellow_print('Communicating through WebSocket')
  83. self.websocket_client.send({'event': 'gdb_stub',
  84. 'port': self.port,
  85. 'prog': self.elf_file})
  86. yellow_print('Waiting for debug finished event')
  87. self.websocket_client.wait([('event', 'debug_finished')])
  88. yellow_print('Communications through WebSocket is finished')
  89. else:
  90. return True
  91. else:
  92. red_print('Malformed gdb message... calculated checksum %02x received %02x' % (chsum, calc_chsum))
  93. return False
  94. def process_panic_output(self, panic_output, logger, target): # type: (bytes, Logger, str) -> None
  95. panic_output_file = None
  96. try:
  97. # On Windows, the temporary file can't be read unless it is closed.
  98. # Set delete=False and delete the file manually later.
  99. with tempfile.NamedTemporaryFile(mode='wb', delete=False) as panic_output_file:
  100. panic_output_file.write(panic_output)
  101. panic_output_file.flush()
  102. cmd = [self.toolchain_prefix + 'gdb',
  103. '--batch', '-n',
  104. self.elf_file,
  105. '-ex', "target remote | \"{python}\" \"{script}\" --target {target} \"{output_file}\""
  106. .format(python=sys.executable,
  107. script=PANIC_OUTPUT_DECODE_SCRIPT,
  108. target=target,
  109. output_file=panic_output_file.name),
  110. '-ex', 'bt']
  111. output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
  112. yellow_print('\nBacktrace:\n\n')
  113. logger.print(output) # noqa: E999
  114. except subprocess.CalledProcessError as e:
  115. yellow_print('Failed to run gdb_panic_server.py script: {}\n{}\n\n'.format(e, e.output))
  116. logger.print(panic_output)
  117. finally:
  118. if panic_output_file is not None:
  119. try:
  120. os.unlink(panic_output_file.name)
  121. except OSError as e:
  122. yellow_print('Couldn\'t remove temporary panic output file ({})'.format(e))