2021-05-14 05:20:38 -04:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import tempfile
|
|
|
|
|
2021-05-14 05:20:38 -04:00
|
|
|
from .constants import PANIC_OUTPUT_DECODE_SCRIPT
|
2021-05-14 05:20:38 -04:00
|
|
|
from .output_helpers import Logger, normal_print, red_print, yellow_print
|
|
|
|
from .web_socket_client import WebSocketClient
|
|
|
|
|
|
|
|
|
|
|
|
class GDBHelper:
|
|
|
|
def __init__(self, toolchain_prefix, websocket_client, elf_file, port, baud_rate):
|
|
|
|
# type: (str, WebSocketClient, str, int, int) -> None
|
|
|
|
self._gdb_buffer = b'' # type: bytes
|
|
|
|
self._gdb_exit = False # type: bool
|
|
|
|
self.toolchain_prefix = toolchain_prefix
|
|
|
|
self.websocket_client = websocket_client
|
|
|
|
self.elf_file = elf_file
|
|
|
|
self.port = port
|
|
|
|
self.baud_rate = baud_rate
|
|
|
|
|
|
|
|
@property
|
|
|
|
def gdb_buffer(self): # type: () -> bytes
|
|
|
|
return self._gdb_buffer
|
|
|
|
|
|
|
|
@gdb_buffer.setter
|
|
|
|
def gdb_buffer(self, value): # type: (bytes) -> None
|
|
|
|
self._gdb_buffer = value
|
|
|
|
|
|
|
|
@property
|
|
|
|
def gdb_exit(self): # type: () -> bool
|
|
|
|
return self._gdb_exit
|
|
|
|
|
|
|
|
@gdb_exit.setter
|
|
|
|
def gdb_exit(self, value): # type: (bool) -> None
|
|
|
|
self._gdb_exit = value
|
|
|
|
|
|
|
|
def run_gdb(self):
|
|
|
|
# type: () -> None
|
|
|
|
normal_print('')
|
|
|
|
try:
|
|
|
|
cmd = ['%sgdb' % self.toolchain_prefix,
|
|
|
|
'-ex', 'set serial baud %d' % self.baud_rate,
|
|
|
|
'-ex', 'target remote %s' % self.port,
|
|
|
|
self.elf_file]
|
|
|
|
# Here we handling GDB as a process
|
|
|
|
# Open GDB process
|
|
|
|
try:
|
|
|
|
process = subprocess.Popen(cmd, cwd='.')
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
|
|
|
# We ignore Ctrl+C interrupt form external process abd wait response util GDB will be finished.
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
process.wait()
|
|
|
|
break
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass # We ignore the Ctrl+C
|
|
|
|
self.gdb_exit = True
|
|
|
|
except OSError as e:
|
|
|
|
red_print('%s: %s' % (' '.join(cmd), e))
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass # happens on Windows, maybe other OSes
|
|
|
|
finally:
|
|
|
|
try:
|
|
|
|
# on Linux, maybe other OSes, gdb sometimes seems to be alive even after wait() returns...
|
|
|
|
process.terminate()
|
|
|
|
except Exception: # noqa
|
|
|
|
pass
|
|
|
|
try:
|
|
|
|
# also on Linux, maybe other OSes, gdb sometimes exits uncleanly and breaks the tty mode
|
|
|
|
subprocess.call(['stty', 'sane'])
|
|
|
|
except Exception: # noqa
|
|
|
|
pass # don't care if there's no stty, we tried...
|
|
|
|
|
|
|
|
def check_gdb_stub_trigger(self, line):
|
|
|
|
# type: (bytes) -> bool
|
|
|
|
line = self.gdb_buffer + line
|
|
|
|
self.gdb_buffer = b''
|
|
|
|
m = re.search(b'\\$(T..)#(..)', line) # look for a gdb "reason" for a break
|
|
|
|
if m is not None:
|
|
|
|
try:
|
|
|
|
chsum = sum(ord(bytes([p])) for p in m.group(1)) & 0xFF
|
|
|
|
calc_chsum = int(m.group(2), 16)
|
|
|
|
except ValueError: # payload wasn't valid hex digits
|
|
|
|
return False
|
|
|
|
if chsum == calc_chsum:
|
|
|
|
if self.websocket_client:
|
|
|
|
yellow_print('Communicating through WebSocket')
|
|
|
|
self.websocket_client.send({'event': 'gdb_stub',
|
|
|
|
'port': self.port,
|
|
|
|
'prog': self.elf_file})
|
|
|
|
yellow_print('Waiting for debug finished event')
|
|
|
|
self.websocket_client.wait([('event', 'debug_finished')])
|
|
|
|
yellow_print('Communications through WebSocket is finished')
|
|
|
|
else:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
red_print('Malformed gdb message... calculated checksum %02x received %02x' % (chsum, calc_chsum))
|
|
|
|
return False
|
|
|
|
|
|
|
|
def process_panic_output(self, panic_output, logger, target): # type: (bytes, Logger, str) -> None
|
|
|
|
panic_output_file = None
|
|
|
|
try:
|
|
|
|
# On Windows, the temporary file can't be read unless it is closed.
|
|
|
|
# Set delete=False and delete the file manually later.
|
|
|
|
with tempfile.NamedTemporaryFile(mode='wb', delete=False) as panic_output_file:
|
|
|
|
panic_output_file.write(panic_output)
|
|
|
|
panic_output_file.flush()
|
|
|
|
cmd = [self.toolchain_prefix + 'gdb',
|
|
|
|
'--batch', '-n',
|
|
|
|
self.elf_file,
|
|
|
|
'-ex', "target remote | \"{python}\" \"{script}\" --target {target} \"{output_file}\""
|
|
|
|
.format(python=sys.executable,
|
2021-05-14 05:20:38 -04:00
|
|
|
script=PANIC_OUTPUT_DECODE_SCRIPT,
|
2021-05-14 05:20:38 -04:00
|
|
|
target=target,
|
|
|
|
output_file=panic_output_file.name),
|
|
|
|
'-ex', 'bt']
|
|
|
|
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
|
|
|
yellow_print('\nBacktrace:\n\n')
|
|
|
|
logger.print(output) # noqa: E999
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
yellow_print('Failed to run gdb_panic_server.py script: {}\n{}\n\n'.format(e, e.output))
|
|
|
|
logger.print(panic_output)
|
|
|
|
finally:
|
|
|
|
if panic_output_file is not None:
|
|
|
|
try:
|
|
|
|
os.unlink(panic_output_file.name)
|
|
|
|
except OSError as e:
|
|
|
|
yellow_print('Couldn\'t remove temporary panic output file ({})'.format(e))
|