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