From 5874fceb0df6a835ecedf061ea5b07651cd30801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ga=C5=88o?= Date: Fri, 14 May 2021 11:20:38 +0200 Subject: [PATCH] Moved coredump utils out of idf_monitor --- tools/idf_monitor.py | 119 +++------------------ tools/idf_monitor_base/__init__.py | 68 ------------ tools/idf_monitor_base/console_parser.py | 25 +++++ tools/idf_monitor_base/constants.py | 28 +++++ tools/idf_monitor_base/coredump.py | 127 +++++++++++++++++++++++ tools/idf_monitor_base/gdbhelper.py | 4 +- tools/idf_monitor_base/output_helpers.py | 1 - 7 files changed, 196 insertions(+), 176 deletions(-) create mode 100644 tools/idf_monitor_base/coredump.py diff --git a/tools/idf_monitor.py b/tools/idf_monitor.py index 038dde1726..af90b568bf 100755 --- a/tools/idf_monitor.py +++ b/tools/idf_monitor.py @@ -46,17 +46,16 @@ try: except ImportError: pass -from idf_monitor_base import (COREDUMP_DECODE_DISABLE, COREDUMP_DECODE_INFO, COREDUMP_DONE, COREDUMP_IDLE, - COREDUMP_READING, COREDUMP_UART_END, COREDUMP_UART_PROMPT, COREDUMP_UART_START, - DEFAULT_PRINT_FILTER, DEFAULT_TOOLCHAIN_PREFIX, MATCH_PCADDR, PANIC_DECODE_BACKTRACE, - PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE, PANIC_READING, PANIC_STACK_DUMP, - PANIC_START, prompt_next_action) from idf_monitor_base.chip_specific_config import get_chip_config -from idf_monitor_base.console_parser import ConsoleParser +from idf_monitor_base.console_parser import ConsoleParser, prompt_next_action from idf_monitor_base.console_reader import ConsoleReader from idf_monitor_base.constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, - CMD_STOP, CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_H, TAG_CMD, TAG_KEY, - TAG_SERIAL, TAG_SERIAL_FLUSH) + CMD_STOP, CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_H, + DEFAULT_PRINT_FILTER, DEFAULT_TOOLCHAIN_PREFIX, MATCH_PCADDR, + PANIC_DECODE_BACKTRACE, PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE, + PANIC_READING, PANIC_STACK_DUMP, PANIC_START, TAG_CMD, TAG_KEY, TAG_SERIAL, + TAG_SERIAL_FLUSH) +from idf_monitor_base.coredump import COREDUMP_DECODE_DISABLE, COREDUMP_DECODE_INFO, CoreDump from idf_monitor_base.exceptions import SerialStopException from idf_monitor_base.gdbhelper import GDBHelper from idf_monitor_base.line_matcher import LineMatcher @@ -72,7 +71,6 @@ except ImportError: import shlex import sys -import tempfile import serial import serial.tools.list_ports @@ -145,16 +143,15 @@ class Monitor(object): self._invoke_processing_last_line_timer = None # type: Optional[threading.Timer] self._force_line_print = False self._serial_check_exit = socket_mode - self._decode_coredumps = decode_coredumps - self._reading_coredump = COREDUMP_IDLE - self._coredump_buffer = b'' self._decode_panic = decode_panic self._reading_panic = PANIC_IDLE self._panic_buffer = b'' self.start_cmd_sent = False self.gdb_helper = GDBHelper(self.toolchain_prefix, self.websocket_client, self.elf_file, self.serial.port, self.serial.baudrate) + self.logger = Logger(self.elf_file, self.console, timestamps, timestamp_format) + self.coredump = CoreDump(decode_coredumps, self.event_queue, self.logger, self.websocket_client, self.elf_file) def invoke_processing_last_line(self): # type: () -> None @@ -208,7 +205,7 @@ class Monitor(object): self._invoke_processing_last_line_timer = threading.Timer(0.1, self.invoke_processing_last_line) self._invoke_processing_last_line_timer.start() - # If no futher data is received in the next short period + # If no further data is received in the next short period # of time then the _invoke_processing_last_line_timer # generates an event which will result in the finishing of # the last line. This is fix for handling lines sent @@ -269,11 +266,10 @@ class Monitor(object): if self._serial_check_exit and line == self.console_parser.exit_key.encode('latin-1'): raise SerialStopException() self.check_panic_decode_trigger(line) - self.check_coredump_trigger_before_print(line) - if self._force_line_print or self._line_matcher.match(line.decode(errors='ignore')): - self.logger.print(line + b'\n') - self.handle_possible_pc_address_in_line(line) - self.check_coredump_trigger_after_print() + with self.coredump.check(line): + if self._force_line_print or self._line_matcher.match(line.decode(errors='ignore')): + self.logger.print(line + b'\n') + self.handle_possible_pc_address_in_line(line) self.check_gdb_stub_and_run(line) self._force_line_print = False # Now we have the last part (incomplete line) in _last_line_part. By @@ -342,93 +338,6 @@ class Monitor(object): else: self.logger.output_enabled = True - def check_coredump_trigger_before_print(self, line): # type: (bytes) -> None - if self._decode_coredumps == COREDUMP_DECODE_DISABLE: - return - - if COREDUMP_UART_PROMPT in line: - yellow_print('Initiating core dump!') - self.event_queue.put((TAG_KEY, '\n')) - return - - if COREDUMP_UART_START in line: - yellow_print('Core dump started (further output muted)') - self._reading_coredump = COREDUMP_READING - self._coredump_buffer = b'' - self.logger.output_enabled = False - return - - if COREDUMP_UART_END in line: - self._reading_coredump = COREDUMP_DONE - yellow_print('\nCore dump finished!') - self.process_coredump() - return - - if self._reading_coredump == COREDUMP_READING: - kb = 1024 - buffer_len_kb = len(self._coredump_buffer) // kb - self._coredump_buffer += line.replace(b'\r', b'') + b'\n' - new_buffer_len_kb = len(self._coredump_buffer) // kb - if new_buffer_len_kb > buffer_len_kb: - yellow_print('Received %3d kB...' % new_buffer_len_kb, newline='\r') - - def check_coredump_trigger_after_print(self): # type: () -> None - if self._decode_coredumps == COREDUMP_DECODE_DISABLE: - return - - # Re-enable output after the last line of core dump has been consumed - if not self.logger.output_enabled and self._reading_coredump == COREDUMP_DONE: - self._reading_coredump = COREDUMP_IDLE - self.logger.output_enabled = True - self._coredump_buffer = b'' - - def process_coredump(self): # type: () -> None - if self._decode_coredumps != COREDUMP_DECODE_INFO: - raise NotImplementedError('process_coredump: %s not implemented' % self._decode_coredumps) - - coredump_script = os.path.join(os.path.dirname(__file__), '..', 'components', 'espcoredump', 'espcoredump.py') - coredump_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 coredump_file: - coredump_file.write(self._coredump_buffer) - coredump_file.flush() - - if self.websocket_client: - self.logger.output_enabled = True - yellow_print('Communicating through WebSocket') - self.websocket_client.send({'event': 'coredump', - 'file': coredump_file.name, - '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: - cmd = [sys.executable, - coredump_script, - 'info_corefile', - '--core', coredump_file.name, - '--core-format', 'b64', - self.elf_file - ] - output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) - self.logger.output_enabled = True - self.logger.print(output) - self.logger.output_enabled = False # Will be reenabled in check_coredump_trigger_after_print - except subprocess.CalledProcessError as e: - yellow_print('Failed to run espcoredump script: {}\n{}\n\n'.format(e, e.output)) - self.logger.output_enabled = True - self.logger.print(COREDUMP_UART_START + b'\n') - self.logger.print(self._coredump_buffer) - # end line will be printed in handle_serial_input - finally: - if coredump_file is not None: - try: - os.unlink(coredump_file.name) - except OSError as e: - yellow_print('Couldn\'t remote temporary core dump file ({})'.format(e)) - def check_panic_decode_trigger(self, line): # type: (bytes) -> None if self._decode_panic == PANIC_DECODE_DISABLE: return diff --git a/tools/idf_monitor_base/__init__.py b/tools/idf_monitor_base/__init__.py index 2e5a11856f..e69de29bb2 100644 --- a/tools/idf_monitor_base/__init__.py +++ b/tools/idf_monitor_base/__init__.py @@ -1,68 +0,0 @@ -import re - -from serial.tools import miniterm - -from .console_parser import ConsoleParser -from .constants import CMD_STOP, CTRL_T -from .output_helpers import red_print - -try: - import queue # noqa -except ImportError: - import Queue as queue # type: ignore # noqa - -# regex matches an potential PC value (0x4xxxxxxx) -MATCH_PCADDR = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE) - -DEFAULT_TOOLCHAIN_PREFIX = 'xtensa-esp32-elf-' - -DEFAULT_PRINT_FILTER = '' - -# coredump related messages -COREDUMP_UART_START = b'================= CORE DUMP START =================' -COREDUMP_UART_END = b'================= CORE DUMP END =================' -COREDUMP_UART_PROMPT = b'Press Enter to print core dump to UART...' - -# coredump states -COREDUMP_IDLE = 0 -COREDUMP_READING = 1 -COREDUMP_DONE = 2 - -# coredump decoding options -COREDUMP_DECODE_DISABLE = 'disable' -COREDUMP_DECODE_INFO = 'info' - -# panic handler related messages -PANIC_START = r'Core \s*\d+ register dump:' -PANIC_END = b'ELF file SHA256:' -PANIC_STACK_DUMP = b'Stack memory:' - -# panic handler decoding states -PANIC_IDLE = 0 -PANIC_READING = 1 - -# panic handler decoding options -PANIC_DECODE_DISABLE = 'disable' -PANIC_DECODE_BACKTRACE = 'backtrace' - - -def prompt_next_action(reason, console, console_parser, event_queue, cmd_queue): - # type: (str, miniterm.Console, ConsoleParser, queue.Queue, queue.Queue) -> None - console.setup() # set up console to trap input characters - try: - red_print('--- {}'.format(reason)) - red_print(console_parser.get_next_action_text()) - - k = CTRL_T # ignore CTRL-T here, so people can muscle-memory Ctrl-T Ctrl-F, etc. - while k == CTRL_T: - k = console.getkey() - finally: - console.cleanup() - ret = console_parser.parse_next_action_key(k) - if ret is not None: - cmd = ret[1] - if cmd == CMD_STOP: - # the stop command should be handled last - event_queue.put(ret) - else: - cmd_queue.put(ret) diff --git a/tools/idf_monitor_base/console_parser.py b/tools/idf_monitor_base/console_parser.py index 8db2b71fdd..d1495a8c41 100644 --- a/tools/idf_monitor_base/console_parser.py +++ b/tools/idf_monitor_base/console_parser.py @@ -19,6 +19,9 @@ try: except ImportError: pass + +import queue + from serial.tools import miniterm from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP, @@ -29,6 +32,28 @@ from .output_helpers import red_print, yellow_print key_description = miniterm.key_description +def prompt_next_action(reason, console, console_parser, event_queue, cmd_queue): + # type: (str, miniterm.Console, ConsoleParser, queue.Queue, queue.Queue) -> None + console.setup() # set up console to trap input characters + try: + red_print('--- {}'.format(reason)) + red_print(console_parser.get_next_action_text()) + + k = CTRL_T # ignore CTRL-T here, so people can muscle-memory Ctrl-T Ctrl-F, etc. + while k == CTRL_T: + k = console.getkey() + finally: + console.cleanup() + ret = console_parser.parse_next_action_key(k) + if ret is not None: + cmd = ret[1] + if cmd == CMD_STOP: + # the stop command should be handled last + event_queue.put(ret) + else: + cmd_queue.put(ret) + + class ConsoleParser(object): def __init__(self, eol='CRLF'): # type: (str) -> None diff --git a/tools/idf_monitor_base/constants.py b/tools/idf_monitor_base/constants.py index efc44b71af..63efaddd87 100644 --- a/tools/idf_monitor_base/constants.py +++ b/tools/idf_monitor_base/constants.py @@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os +import re + # Control-key characters CTRL_A = '\x01' CTRL_B = '\x02' @@ -43,3 +46,28 @@ TAG_SERIAL_FLUSH = 2 TAG_CMD = 3 __version__ = '1.1' + + +# paths to scripts +PANIC_OUTPUT_DECODE_SCRIPT = os.path.join(os.path.dirname(__file__), '..', 'gdb_panic_server.py') +COREDUMP_SCRIPT = os.path.join(os.path.dirname(__file__), '..', '..', 'components', 'espcoredump', 'espcoredump.py') + +# regex matches an potential PC value (0x4xxxxxxx) +MATCH_PCADDR = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE) + +DEFAULT_TOOLCHAIN_PREFIX = 'xtensa-esp32-elf-' + +DEFAULT_PRINT_FILTER = '' + +# panic handler related messages +PANIC_START = r'Core \s*\d+ register dump:' +PANIC_END = b'ELF file SHA256:' +PANIC_STACK_DUMP = b'Stack memory:' + +# panic handler decoding states +PANIC_IDLE = 0 +PANIC_READING = 1 + +# panic handler decoding options +PANIC_DECODE_DISABLE = 'disable' +PANIC_DECODE_BACKTRACE = 'backtrace' diff --git a/tools/idf_monitor_base/coredump.py b/tools/idf_monitor_base/coredump.py new file mode 100644 index 0000000000..4120a2f62a --- /dev/null +++ b/tools/idf_monitor_base/coredump.py @@ -0,0 +1,127 @@ +import os +import queue +import subprocess +import sys +import tempfile +from contextlib import contextmanager +from typing import Generator + +from .constants import COREDUMP_SCRIPT, TAG_KEY +from .output_helpers import Logger, yellow_print +from .web_socket_client import WebSocketClient + +# coredump related messages +COREDUMP_UART_START = b'================= CORE DUMP START =================' +COREDUMP_UART_END = b'================= CORE DUMP END =================' +COREDUMP_UART_PROMPT = b'Press Enter to print core dump to UART...' + +# coredump states +COREDUMP_IDLE = 0 +COREDUMP_READING = 1 +COREDUMP_DONE = 2 + +# coredump decoding options +COREDUMP_DECODE_DISABLE = 'disable' +COREDUMP_DECODE_INFO = 'info' + + +class CoreDump: + def __init__(self, decode_coredumps, event_queue, logger, websocket_client, elf_file): + # type: (str, queue.Queue, Logger, WebSocketClient, str) -> None + + self._coredump_buffer = b'' + self._decode_coredumps = decode_coredumps + self.event_queue = event_queue + self._reading_coredump = COREDUMP_IDLE + self.logger = logger + self.websocket_client = websocket_client + self.elf_file = elf_file + + def _process_coredump(self): # type: () -> None + if self._decode_coredumps != COREDUMP_DECODE_INFO: + raise NotImplementedError('process_coredump: %s not implemented' % self._decode_coredumps) + coredump_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 coredump_file: + coredump_file.write(self._coredump_buffer) + coredump_file.flush() + + if self.websocket_client: + self.logger.output_enabled = True + yellow_print('Communicating through WebSocket') + self.websocket_client.send({'event': 'coredump', + 'file': coredump_file.name, + '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: + cmd = [sys.executable, + COREDUMP_SCRIPT, + 'info_corefile', + '--core', coredump_file.name, + '--core-format', 'b64', + self.elf_file + ] + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + self.logger.output_enabled = True + self.logger.print(output) # noqa: E999 + self.logger.output_enabled = False # Will be reenabled in check_coredump_trigger_after_print + except subprocess.CalledProcessError as e: + yellow_print('Failed to run espcoredump script: {}\n{}\n\n'.format(e, e.output)) + self.logger.output_enabled = True + self.logger.print(COREDUMP_UART_START + b'\n') + self.logger.print(self._coredump_buffer) + # end line will be printed in handle_serial_input + finally: + if coredump_file is not None: + try: + os.unlink(coredump_file.name) + except OSError as e: + yellow_print('Couldn\'t remote temporary core dump file ({})'.format(e)) + + def _check_coredump_trigger_before_print(self, line): # type: (bytes) -> None + if self._decode_coredumps == COREDUMP_DECODE_DISABLE: + return + if COREDUMP_UART_PROMPT in line: + yellow_print('Initiating core dump!') + self.event_queue.put((TAG_KEY, '\n')) + return + if COREDUMP_UART_START in line: + yellow_print('Core dump started (further output muted)') + self._reading_coredump = COREDUMP_READING + self._coredump_buffer = b'' + self.logger.output_enabled = False + return + if COREDUMP_UART_END in line: + self._reading_coredump = COREDUMP_DONE + yellow_print('\nCore dump finished!') + self._process_coredump() + return + if self._reading_coredump == COREDUMP_READING: + kb = 1024 + buffer_len_kb = len(self._coredump_buffer) // kb + self._coredump_buffer += line.replace(b'\r', b'') + b'\n' + new_buffer_len_kb = len(self._coredump_buffer) // kb + if new_buffer_len_kb > buffer_len_kb: + yellow_print('Received %3d kB...' % new_buffer_len_kb, newline='\r') + + def _check_coredump_trigger_after_print(self): # type: () -> None + if self._decode_coredumps == COREDUMP_DECODE_DISABLE: + return + + # Re-enable output after the last line of core dump has been consumed + if not self.logger.output_enabled and self._reading_coredump == COREDUMP_DONE: + self._reading_coredump = COREDUMP_IDLE + self.logger.output_enabled = True + self._coredump_buffer = b'' + + @contextmanager + def check(self, line): # type: (bytes) -> Generator + self._check_coredump_trigger_before_print(line) + try: + yield + finally: + self._check_coredump_trigger_after_print() diff --git a/tools/idf_monitor_base/gdbhelper.py b/tools/idf_monitor_base/gdbhelper.py index 1a6140387f..198ff3f390 100644 --- a/tools/idf_monitor_base/gdbhelper.py +++ b/tools/idf_monitor_base/gdbhelper.py @@ -4,6 +4,7 @@ import subprocess import sys import tempfile +from .constants import PANIC_OUTPUT_DECODE_SCRIPT from .output_helpers import Logger, normal_print, red_print, yellow_print from .web_socket_client import WebSocketClient @@ -100,7 +101,6 @@ class GDBHelper: return False def process_panic_output(self, panic_output, logger, target): # type: (bytes, Logger, str) -> None - panic_output_decode_script = os.path.join(os.path.dirname(__file__), '..', 'gdb_panic_server.py') panic_output_file = None try: # On Windows, the temporary file can't be read unless it is closed. @@ -113,7 +113,7 @@ class GDBHelper: self.elf_file, '-ex', "target remote | \"{python}\" \"{script}\" --target {target} \"{output_file}\"" .format(python=sys.executable, - script=panic_output_decode_script, + script=PANIC_OUTPUT_DECODE_SCRIPT, target=target, output_file=panic_output_file.name), '-ex', 'bt'] diff --git a/tools/idf_monitor_base/output_helpers.py b/tools/idf_monitor_base/output_helpers.py index 6454114b42..6a764e8206 100644 --- a/tools/idf_monitor_base/output_helpers.py +++ b/tools/idf_monitor_base/output_helpers.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - import datetime import os import subprocess