gdbhelper.py 5.5 KB

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