mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
Moved filters out of idf_monitor.py
This commit is contained in:
parent
d3de177c64
commit
252036567c
@ -96,7 +96,7 @@
|
|||||||
- "tools/esp_app_trace/**/*"
|
- "tools/esp_app_trace/**/*"
|
||||||
- "tools/ldgen/**/*"
|
- "tools/ldgen/**/*"
|
||||||
|
|
||||||
- "tools/gdb_panic_server.py"
|
- "tools/idf_monitor_base/*"
|
||||||
- "tools/idf_monitor.py"
|
- "tools/idf_monitor.py"
|
||||||
- "tools/test_idf_monitor/**/*"
|
- "tools/test_idf_monitor/**/*"
|
||||||
|
|
||||||
|
@ -219,7 +219,6 @@ tools/esp_prov/utils/convenience.py
|
|||||||
tools/find_apps.py
|
tools/find_apps.py
|
||||||
tools/find_build_apps/common.py
|
tools/find_build_apps/common.py
|
||||||
tools/find_build_apps/make.py
|
tools/find_build_apps/make.py
|
||||||
tools/gdb_panic_server.py
|
|
||||||
tools/gen_esp_err_to_name.py
|
tools/gen_esp_err_to_name.py
|
||||||
tools/idf.py
|
tools/idf.py
|
||||||
tools/idf_py_actions/core_ext.py
|
tools/idf_py_actions/core_ext.py
|
||||||
|
@ -75,7 +75,7 @@ GDB_REGS_INFO = {
|
|||||||
PanicInfo = namedtuple('PanicInfo', 'core_id regs stack_base_addr stack_data')
|
PanicInfo = namedtuple('PanicInfo', 'core_id regs stack_base_addr stack_data')
|
||||||
|
|
||||||
|
|
||||||
def build_riscv_panic_output_parser(): # type: () -> typing.Type[ParserElement]
|
def build_riscv_panic_output_parser(): # type: () -> typing.Any[typing.Type[ParserElement]]
|
||||||
"""Builds a parser for the panic handler output using pyparsing"""
|
"""Builds a parser for the panic handler output using pyparsing"""
|
||||||
|
|
||||||
# We don't match the first line, since "Guru Meditation" will not be printed in case of an abort:
|
# We don't match the first line, since "Guru Meditation" will not be printed in case of an abort:
|
||||||
@ -258,7 +258,7 @@ class GdbServer(object):
|
|||||||
|
|
||||||
# For any memory address that is not on the stack, pretend the value is 0x00.
|
# For any memory address that is not on the stack, pretend the value is 0x00.
|
||||||
# GDB should never ask us for program memory, it will be obtained from the ELF file.
|
# GDB should never ask us for program memory, it will be obtained from the ELF file.
|
||||||
def in_stack(addr):
|
def in_stack(addr): # type: (int) -> typing.Any[bool]
|
||||||
return stack_addr_min <= addr < stack_addr_max
|
return stack_addr_min <= addr < stack_addr_max
|
||||||
|
|
||||||
result = ''
|
result = ''
|
||||||
@ -271,7 +271,7 @@ class GdbServer(object):
|
|||||||
self._respond(result)
|
self._respond(result)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main(): # type: () -> None
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('input_file', type=argparse.FileType('r'),
|
parser.add_argument('input_file', type=argparse.FileType('r'),
|
||||||
help='File containing the panic handler output')
|
help='File containing the panic handler output')
|
||||||
|
@ -34,32 +34,36 @@ from __future__ import division, print_function, unicode_literals
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import codecs
|
import codecs
|
||||||
import datetime
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from builtins import bytes, object
|
from builtins import bytes, object
|
||||||
from typing import AnyStr, BinaryIO, Callable, List, Optional, Union
|
|
||||||
|
|
||||||
import serial.tools.miniterm as miniterm
|
try:
|
||||||
|
from typing import List, Optional, Union # noqa
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
from idf_monitor_base import (COREDUMP_DECODE_DISABLE, COREDUMP_DECODE_INFO, COREDUMP_DONE, COREDUMP_IDLE,
|
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,
|
COREDUMP_READING, COREDUMP_UART_END, COREDUMP_UART_PROMPT, COREDUMP_UART_START,
|
||||||
DEFAULT_PRINT_FILTER, DEFAULT_TOOLCHAIN_PREFIX, MATCH_PCADDR, PANIC_DECODE_BACKTRACE,
|
DEFAULT_PRINT_FILTER, DEFAULT_TOOLCHAIN_PREFIX, MATCH_PCADDR, PANIC_DECODE_BACKTRACE,
|
||||||
PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE, PANIC_READING, PANIC_STACK_DUMP,
|
PANIC_DECODE_DISABLE, PANIC_END, PANIC_IDLE, PANIC_READING, PANIC_STACK_DUMP,
|
||||||
PANIC_START)
|
PANIC_START, prompt_next_action)
|
||||||
from idf_monitor_base.chip_specific_config import get_chip_config
|
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
|
||||||
from idf_monitor_base.console_reader import ConsoleReader
|
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,
|
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, CTRL_T, TAG_CMD,
|
CMD_STOP, CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_H, TAG_CMD, TAG_KEY,
|
||||||
TAG_KEY, TAG_SERIAL, TAG_SERIAL_FLUSH)
|
TAG_SERIAL, TAG_SERIAL_FLUSH)
|
||||||
from idf_monitor_base.exceptions import SerialStopException
|
from idf_monitor_base.exceptions import SerialStopException
|
||||||
|
from idf_monitor_base.gdbhelper import GDBHelper
|
||||||
from idf_monitor_base.line_matcher import LineMatcher
|
from idf_monitor_base.line_matcher import LineMatcher
|
||||||
from idf_monitor_base.output_helpers import normal_print, red_print, yellow_print
|
from idf_monitor_base.output_helpers import Logger, lookup_pc_address, normal_print, yellow_print
|
||||||
from idf_monitor_base.serial_reader import SerialReader
|
from idf_monitor_base.serial_reader import SerialReader
|
||||||
from idf_monitor_base.web_socket_client import WebSocketClient
|
from idf_monitor_base.web_socket_client import WebSocketClient
|
||||||
|
from serial.tools import miniterm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import queue # noqa
|
import queue # noqa
|
||||||
@ -89,21 +93,21 @@ class Monitor(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
serial_instance, # type: serial.Serial
|
serial_instance, # type: serial.Serial
|
||||||
elf_file, # type: str
|
elf_file, # type: str
|
||||||
print_filter, # type: str
|
print_filter, # type: str
|
||||||
make='make', # type: str
|
make='make', # type: str
|
||||||
encrypted=False, # type: bool
|
encrypted=False, # type: bool
|
||||||
toolchain_prefix=DEFAULT_TOOLCHAIN_PREFIX, # type: str
|
toolchain_prefix=DEFAULT_TOOLCHAIN_PREFIX, # type: str
|
||||||
eol='CRLF', # type: str
|
eol='CRLF', # type: str
|
||||||
decode_coredumps=COREDUMP_DECODE_INFO, # type: str
|
decode_coredumps=COREDUMP_DECODE_INFO, # type: str
|
||||||
decode_panic=PANIC_DECODE_DISABLE, # type: str
|
decode_panic=PANIC_DECODE_DISABLE, # type: str
|
||||||
target='esp32', # type: str
|
target='esp32', # type: str
|
||||||
websocket_client=None, # type: WebSocketClient
|
websocket_client=None, # type: WebSocketClient
|
||||||
enable_address_decoding=True, # type: bool
|
enable_address_decoding=True, # type: bool
|
||||||
timestamps=False, # type: bool
|
timestamps=False, # type: bool
|
||||||
timestamp_format='' # type: str
|
timestamp_format='' # type: str
|
||||||
):
|
):
|
||||||
super(Monitor, self).__init__()
|
super(Monitor, self).__init__()
|
||||||
self.event_queue = queue.Queue() # type: queue.Queue
|
self.event_queue = queue.Queue() # type: queue.Queue
|
||||||
@ -136,24 +140,21 @@ class Monitor(object):
|
|||||||
|
|
||||||
# internal state
|
# internal state
|
||||||
self._last_line_part = b''
|
self._last_line_part = b''
|
||||||
self._gdb_buffer = b''
|
|
||||||
self._pc_address_buffer = b''
|
self._pc_address_buffer = b''
|
||||||
self._line_matcher = LineMatcher(print_filter)
|
self._line_matcher = LineMatcher(print_filter)
|
||||||
self._invoke_processing_last_line_timer = None # type: Optional[threading.Timer]
|
self._invoke_processing_last_line_timer = None # type: Optional[threading.Timer]
|
||||||
self._force_line_print = False
|
self._force_line_print = False
|
||||||
self._output_enabled = True
|
|
||||||
self._serial_check_exit = socket_mode
|
self._serial_check_exit = socket_mode
|
||||||
self._log_file = None # type: Optional[BinaryIO]
|
|
||||||
self._decode_coredumps = decode_coredumps
|
self._decode_coredumps = decode_coredumps
|
||||||
self._reading_coredump = COREDUMP_IDLE
|
self._reading_coredump = COREDUMP_IDLE
|
||||||
self._coredump_buffer = b''
|
self._coredump_buffer = b''
|
||||||
self._decode_panic = decode_panic
|
self._decode_panic = decode_panic
|
||||||
self._reading_panic = PANIC_IDLE
|
self._reading_panic = PANIC_IDLE
|
||||||
self._panic_buffer = b''
|
self._panic_buffer = b''
|
||||||
self.gdb_exit = False
|
|
||||||
self.start_cmd_sent = False
|
self.start_cmd_sent = False
|
||||||
self._timestamps = timestamps
|
self.gdb_helper = GDBHelper(self.toolchain_prefix, self.websocket_client, self.elf_file, self.serial.port,
|
||||||
self._timestamp_format = timestamp_format
|
self.serial.baudrate)
|
||||||
|
self.logger = Logger(self.elf_file, self.console, timestamps, timestamp_format)
|
||||||
|
|
||||||
def invoke_processing_last_line(self):
|
def invoke_processing_last_line(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
@ -163,13 +164,13 @@ class Monitor(object):
|
|||||||
# type: () -> None
|
# type: () -> None
|
||||||
self.console_reader.start()
|
self.console_reader.start()
|
||||||
self.serial_reader.start()
|
self.serial_reader.start()
|
||||||
self.gdb_exit = False
|
self.gdb_helper.gdb_exit = False
|
||||||
self.start_cmd_sent = False
|
self.start_cmd_sent = False
|
||||||
try:
|
try:
|
||||||
while self.console_reader.alive and self.serial_reader.alive:
|
while self.console_reader.alive and self.serial_reader.alive:
|
||||||
try:
|
try:
|
||||||
if self.gdb_exit is True:
|
if self.gdb_helper.gdb_exit:
|
||||||
self.gdb_exit = False
|
self.gdb_helper.gdb_exit = False
|
||||||
|
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
try:
|
try:
|
||||||
@ -204,7 +205,8 @@ class Monitor(object):
|
|||||||
self.handle_serial_input(data)
|
self.handle_serial_input(data)
|
||||||
if self._invoke_processing_last_line_timer is not None:
|
if self._invoke_processing_last_line_timer is not None:
|
||||||
self._invoke_processing_last_line_timer.cancel()
|
self._invoke_processing_last_line_timer.cancel()
|
||||||
self._invoke_processing_last_line_timer = threading.Timer(0.1, self.invoke_processing_last_line)
|
self._invoke_processing_last_line_timer = threading.Timer(0.1,
|
||||||
|
self.invoke_processing_last_line)
|
||||||
self._invoke_processing_last_line_timer.start()
|
self._invoke_processing_last_line_timer.start()
|
||||||
# If no futher data is received in the next short period
|
# If no futher data is received in the next short period
|
||||||
# of time then the _invoke_processing_last_line_timer
|
# of time then the _invoke_processing_last_line_timer
|
||||||
@ -214,7 +216,7 @@ class Monitor(object):
|
|||||||
elif event_tag == TAG_SERIAL_FLUSH:
|
elif event_tag == TAG_SERIAL_FLUSH:
|
||||||
self.handle_serial_input(data, finalize_line=True)
|
self.handle_serial_input(data, finalize_line=True)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Bad event data %r' % ((event_tag,data),))
|
raise RuntimeError('Bad event data %r' % ((event_tag, data),))
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
try:
|
try:
|
||||||
yellow_print('To exit from IDF monitor please use \"Ctrl+]\"')
|
yellow_print('To exit from IDF monitor please use \"Ctrl+]\"')
|
||||||
@ -231,7 +233,7 @@ class Monitor(object):
|
|||||||
try:
|
try:
|
||||||
self.console_reader.stop()
|
self.console_reader.stop()
|
||||||
self.serial_reader.stop()
|
self.serial_reader.stop()
|
||||||
self.stop_logging()
|
self.logger.stop_logging()
|
||||||
# Cancelling _invoke_processing_last_line_timer is not
|
# Cancelling _invoke_processing_last_line_timer is not
|
||||||
# important here because receiving empty data doesn't matter.
|
# important here because receiving empty data doesn't matter.
|
||||||
self._invoke_processing_last_line_timer = None
|
self._invoke_processing_last_line_timer = None
|
||||||
@ -239,6 +241,11 @@ class Monitor(object):
|
|||||||
pass
|
pass
|
||||||
normal_print('\n')
|
normal_print('\n')
|
||||||
|
|
||||||
|
def check_gdb_stub_and_run(self, line): # type: (bytes) -> None
|
||||||
|
if self.gdb_helper.check_gdb_stub_trigger(line):
|
||||||
|
with self: # disable console control
|
||||||
|
self.gdb_helper.run_gdb()
|
||||||
|
|
||||||
def handle_serial_input(self, data, finalize_line=False):
|
def handle_serial_input(self, data, finalize_line=False):
|
||||||
# type: (bytes, bool) -> None
|
# type: (bytes, bool) -> None
|
||||||
# Remove "+" after Continue command
|
# Remove "+" after Continue command
|
||||||
@ -257,47 +264,53 @@ class Monitor(object):
|
|||||||
# last part is not a full line
|
# last part is not a full line
|
||||||
self._last_line_part = sp.pop()
|
self._last_line_part = sp.pop()
|
||||||
for line in sp:
|
for line in sp:
|
||||||
if line != b'':
|
if line == b'':
|
||||||
if self._serial_check_exit and line == self.console_parser.exit_key.encode('latin-1'):
|
continue
|
||||||
raise SerialStopException()
|
if self._serial_check_exit and line == self.console_parser.exit_key.encode('latin-1'):
|
||||||
self.check_panic_decode_trigger(line)
|
raise SerialStopException()
|
||||||
self.check_coredump_trigger_before_print(line)
|
self.check_panic_decode_trigger(line)
|
||||||
if self._force_line_print or self._line_matcher.match(line.decode(errors='ignore')):
|
self.check_coredump_trigger_before_print(line)
|
||||||
self._print(line + b'\n')
|
if self._force_line_print or self._line_matcher.match(line.decode(errors='ignore')):
|
||||||
self.handle_possible_pc_address_in_line(line)
|
self.logger.print(line + b'\n')
|
||||||
self.check_coredump_trigger_after_print()
|
self.handle_possible_pc_address_in_line(line)
|
||||||
self.check_gdbstub_trigger(line)
|
self.check_coredump_trigger_after_print()
|
||||||
self._force_line_print = False
|
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
|
# Now we have the last part (incomplete line) in _last_line_part. By
|
||||||
# default we don't touch it and just wait for the arrival of the rest
|
# default we don't touch it and just wait for the arrival of the rest
|
||||||
# of the line. But after some time when we didn't received it we need
|
# of the line. But after some time when we didn't received it we need
|
||||||
# to make a decision.
|
# to make a decision.
|
||||||
if self._last_line_part != b'':
|
force_print_or_matched = any((
|
||||||
if self._force_line_print or (finalize_line and self._line_matcher.match(self._last_line_part.decode(errors='ignore'))):
|
self._force_line_print,
|
||||||
self._force_line_print = True
|
(finalize_line and self._line_matcher.match(self._last_line_part.decode(errors='ignore')))
|
||||||
self._print(self._last_line_part)
|
))
|
||||||
self.handle_possible_pc_address_in_line(self._last_line_part)
|
if self._last_line_part != b'' and force_print_or_matched:
|
||||||
self.check_gdbstub_trigger(self._last_line_part)
|
self._force_line_print = True
|
||||||
# It is possible that the incomplete line cuts in half the PC
|
self.logger.print(self._last_line_part)
|
||||||
# address. A small buffer is kept and will be used the next time
|
self.handle_possible_pc_address_in_line(self._last_line_part)
|
||||||
# handle_possible_pc_address_in_line is invoked to avoid this problem.
|
self.check_gdb_stub_and_run(self._last_line_part)
|
||||||
# MATCH_PCADDR matches 10 character long addresses. Therefore, we
|
# It is possible that the incomplete line cuts in half the PC
|
||||||
# keep the last 9 characters.
|
# address. A small buffer is kept and will be used the next time
|
||||||
self._pc_address_buffer = self._last_line_part[-9:]
|
# handle_possible_pc_address_in_line is invoked to avoid this problem.
|
||||||
# GDB sequence can be cut in half also. GDB sequence is 7
|
# MATCH_PCADDR matches 10 character long addresses. Therefore, we
|
||||||
# characters long, therefore, we save the last 6 characters.
|
# keep the last 9 characters.
|
||||||
self._gdb_buffer = self._last_line_part[-6:]
|
self._pc_address_buffer = self._last_line_part[-9:]
|
||||||
self._last_line_part = b''
|
# GDB sequence can be cut in half also. GDB sequence is 7
|
||||||
|
# characters long, therefore, we save the last 6 characters.
|
||||||
|
self.gdb_helper.gdb_buffer = self._last_line_part[-6:]
|
||||||
|
self._last_line_part = b''
|
||||||
# else: keeping _last_line_part and it will be processed the next time
|
# else: keeping _last_line_part and it will be processed the next time
|
||||||
# handle_serial_input is invoked
|
# handle_serial_input is invoked
|
||||||
|
|
||||||
def handle_possible_pc_address_in_line(self, line):
|
def handle_possible_pc_address_in_line(self, line): # type: (bytes) -> None
|
||||||
# type: (bytes) -> None
|
|
||||||
line = self._pc_address_buffer + line
|
line = self._pc_address_buffer + line
|
||||||
self._pc_address_buffer = b''
|
self._pc_address_buffer = b''
|
||||||
if self.enable_address_decoding:
|
if not self.enable_address_decoding:
|
||||||
for m in re.finditer(MATCH_PCADDR, line.decode(errors='ignore')):
|
return
|
||||||
self.lookup_pc_address(m.group())
|
for m in re.finditer(MATCH_PCADDR, line.decode(errors='ignore')):
|
||||||
|
translation = lookup_pc_address(m.group(), self.toolchain_prefix, self.elf_file)
|
||||||
|
if translation:
|
||||||
|
self.logger.print(translation, console_printer=yellow_print)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
@ -308,29 +321,9 @@ class Monitor(object):
|
|||||||
def __exit__(self, *args, **kwargs): # type: ignore
|
def __exit__(self, *args, **kwargs): # type: ignore
|
||||||
""" Use 'with self' to temporarily disable monitoring behaviour """
|
""" Use 'with self' to temporarily disable monitoring behaviour """
|
||||||
self.console_reader.start()
|
self.console_reader.start()
|
||||||
self.serial_reader.gdb_exit = self.gdb_exit # write gdb_exit flag
|
self.serial_reader.gdb_exit = self.gdb_helper.gdb_exit # write gdb_exit flag
|
||||||
self.serial_reader.start()
|
self.serial_reader.start()
|
||||||
|
|
||||||
def prompt_next_action(self, reason): # type: (str) -> None
|
|
||||||
self.console.setup() # set up console to trap input characters
|
|
||||||
try:
|
|
||||||
red_print('--- {}'.format(reason))
|
|
||||||
red_print(self.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 = self.console.getkey()
|
|
||||||
finally:
|
|
||||||
self.console.cleanup()
|
|
||||||
ret = self.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
|
|
||||||
self.event_queue.put(ret)
|
|
||||||
else:
|
|
||||||
self.cmd_queue.put(ret)
|
|
||||||
|
|
||||||
def run_make(self, target): # type: (str) -> None
|
def run_make(self, target): # type: (str) -> None
|
||||||
with self:
|
with self:
|
||||||
if isinstance(self.make, list):
|
if isinstance(self.make, list):
|
||||||
@ -344,43 +337,10 @@ class Monitor(object):
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
p.wait()
|
p.wait()
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
self.prompt_next_action('Build failed')
|
prompt_next_action('Build failed', self.console, self.console_parser, self.event_queue,
|
||||||
|
self.cmd_queue)
|
||||||
else:
|
else:
|
||||||
self.output_enable(True)
|
self.logger.output_enabled = True
|
||||||
|
|
||||||
def lookup_pc_address(self, pc_addr): # type: (str) -> None
|
|
||||||
cmd = ['%saddr2line' % self.toolchain_prefix,
|
|
||||||
'-pfiaC', '-e', self.elf_file, pc_addr]
|
|
||||||
try:
|
|
||||||
translation = subprocess.check_output(cmd, cwd='.')
|
|
||||||
if b'?? ??:0' not in translation:
|
|
||||||
self._print(translation.decode(), console_printer=yellow_print)
|
|
||||||
except OSError as e:
|
|
||||||
red_print('%s: %s' % (' '.join(cmd), e))
|
|
||||||
|
|
||||||
def check_gdbstub_trigger(self, line): # type: (bytes) -> None
|
|
||||||
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:
|
|
||||||
return # payload wasn't valid hex digits
|
|
||||||
if chsum == calc_chsum:
|
|
||||||
if self.websocket_client:
|
|
||||||
yellow_print('Communicating through WebSocket')
|
|
||||||
self.websocket_client.send({'event': 'gdb_stub',
|
|
||||||
'port': self.serial.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:
|
|
||||||
self.run_gdb()
|
|
||||||
else:
|
|
||||||
red_print('Malformed gdb message... calculated checksum %02x received %02x' % (chsum, calc_chsum))
|
|
||||||
|
|
||||||
def check_coredump_trigger_before_print(self, line): # type: (bytes) -> None
|
def check_coredump_trigger_before_print(self, line): # type: (bytes) -> None
|
||||||
if self._decode_coredumps == COREDUMP_DECODE_DISABLE:
|
if self._decode_coredumps == COREDUMP_DECODE_DISABLE:
|
||||||
@ -395,7 +355,7 @@ class Monitor(object):
|
|||||||
yellow_print('Core dump started (further output muted)')
|
yellow_print('Core dump started (further output muted)')
|
||||||
self._reading_coredump = COREDUMP_READING
|
self._reading_coredump = COREDUMP_READING
|
||||||
self._coredump_buffer = b''
|
self._coredump_buffer = b''
|
||||||
self._output_enabled = False
|
self.logger.output_enabled = False
|
||||||
return
|
return
|
||||||
|
|
||||||
if COREDUMP_UART_END in line:
|
if COREDUMP_UART_END in line:
|
||||||
@ -410,16 +370,16 @@ class Monitor(object):
|
|||||||
self._coredump_buffer += line.replace(b'\r', b'') + b'\n'
|
self._coredump_buffer += line.replace(b'\r', b'') + b'\n'
|
||||||
new_buffer_len_kb = len(self._coredump_buffer) // kb
|
new_buffer_len_kb = len(self._coredump_buffer) // kb
|
||||||
if new_buffer_len_kb > buffer_len_kb:
|
if new_buffer_len_kb > buffer_len_kb:
|
||||||
yellow_print('Received %3d kB...' % (new_buffer_len_kb), newline='\r')
|
yellow_print('Received %3d kB...' % new_buffer_len_kb, newline='\r')
|
||||||
|
|
||||||
def check_coredump_trigger_after_print(self): # type: () -> None
|
def check_coredump_trigger_after_print(self): # type: () -> None
|
||||||
if self._decode_coredumps == COREDUMP_DECODE_DISABLE:
|
if self._decode_coredumps == COREDUMP_DECODE_DISABLE:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Re-enable output after the last line of core dump has been consumed
|
# Re-enable output after the last line of core dump has been consumed
|
||||||
if not self._output_enabled and self._reading_coredump == COREDUMP_DONE:
|
if not self.logger.output_enabled and self._reading_coredump == COREDUMP_DONE:
|
||||||
self._reading_coredump = COREDUMP_IDLE
|
self._reading_coredump = COREDUMP_IDLE
|
||||||
self._output_enabled = True
|
self.logger.output_enabled = True
|
||||||
self._coredump_buffer = b''
|
self._coredump_buffer = b''
|
||||||
|
|
||||||
def process_coredump(self): # type: () -> None
|
def process_coredump(self): # type: () -> None
|
||||||
@ -436,7 +396,7 @@ class Monitor(object):
|
|||||||
coredump_file.flush()
|
coredump_file.flush()
|
||||||
|
|
||||||
if self.websocket_client:
|
if self.websocket_client:
|
||||||
self._output_enabled = True
|
self.logger.output_enabled = True
|
||||||
yellow_print('Communicating through WebSocket')
|
yellow_print('Communicating through WebSocket')
|
||||||
self.websocket_client.send({'event': 'coredump',
|
self.websocket_client.send({'event': 'coredump',
|
||||||
'file': coredump_file.name,
|
'file': coredump_file.name,
|
||||||
@ -453,14 +413,14 @@ class Monitor(object):
|
|||||||
self.elf_file
|
self.elf_file
|
||||||
]
|
]
|
||||||
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||||
self._output_enabled = True
|
self.logger.output_enabled = True
|
||||||
self._print(output)
|
self.logger.print(output)
|
||||||
self._output_enabled = False # Will be reenabled in check_coredump_trigger_after_print
|
self.logger.output_enabled = False # Will be reenabled in check_coredump_trigger_after_print
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
yellow_print('Failed to run espcoredump script: {}\n{}\n\n'.format(e, e.output))
|
yellow_print('Failed to run espcoredump script: {}\n{}\n\n'.format(e, e.output))
|
||||||
self._output_enabled = True
|
self.logger.output_enabled = True
|
||||||
self._print(COREDUMP_UART_START + b'\n')
|
self.logger.print(COREDUMP_UART_START + b'\n')
|
||||||
self._print(self._coredump_buffer)
|
self.logger.print(self._coredump_buffer)
|
||||||
# end line will be printed in handle_serial_input
|
# end line will be printed in handle_serial_input
|
||||||
finally:
|
finally:
|
||||||
if coredump_file is not None:
|
if coredump_file is not None:
|
||||||
@ -478,155 +438,17 @@ class Monitor(object):
|
|||||||
yellow_print('Stack dump detected')
|
yellow_print('Stack dump detected')
|
||||||
|
|
||||||
if self._reading_panic == PANIC_READING and PANIC_STACK_DUMP in line:
|
if self._reading_panic == PANIC_READING and PANIC_STACK_DUMP in line:
|
||||||
self._output_enabled = False
|
self.logger.output_enabled = False
|
||||||
|
|
||||||
if self._reading_panic == PANIC_READING:
|
if self._reading_panic == PANIC_READING:
|
||||||
self._panic_buffer += line.replace(b'\r', b'') + b'\n'
|
self._panic_buffer += line.replace(b'\r', b'') + b'\n'
|
||||||
|
|
||||||
if self._reading_panic == PANIC_READING and PANIC_END in line:
|
if self._reading_panic == PANIC_READING and PANIC_END in line:
|
||||||
self._reading_panic = PANIC_IDLE
|
self._reading_panic = PANIC_IDLE
|
||||||
self._output_enabled = True
|
self.logger.output_enabled = True
|
||||||
self.process_panic_output(self._panic_buffer)
|
self.gdb_helper.process_panic_output(self._panic_buffer, self.logger, self.target)
|
||||||
self._panic_buffer = b''
|
self._panic_buffer = b''
|
||||||
|
|
||||||
def process_panic_output(self, panic_output): # type: (bytes) -> None
|
|
||||||
panic_output_decode_script = os.path.join(os.path.dirname(__file__), '..', 'tools', 'gdb_panic_server.py')
|
|
||||||
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,
|
|
||||||
script=panic_output_decode_script,
|
|
||||||
target=self.target,
|
|
||||||
output_file=panic_output_file.name),
|
|
||||||
'-ex', 'bt']
|
|
||||||
|
|
||||||
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
|
||||||
yellow_print('\nBacktrace:\n\n')
|
|
||||||
self._print(output)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
yellow_print('Failed to run gdb_panic_server.py script: {}\n{}\n\n'.format(e, e.output))
|
|
||||||
self._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))
|
|
||||||
|
|
||||||
def run_gdb(self): # type: () -> None
|
|
||||||
with self: # disable console control
|
|
||||||
normal_print('')
|
|
||||||
try:
|
|
||||||
cmd = ['%sgdb' % self.toolchain_prefix,
|
|
||||||
'-ex', 'set serial baud %d' % self.serial.baudrate,
|
|
||||||
'-ex', 'target remote %s' % self.serial.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 responce 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:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
# also on Linux, maybe other OSes, gdb sometimes exits uncleanly and breaks the tty mode
|
|
||||||
subprocess.call(['stty', 'sane'])
|
|
||||||
except Exception:
|
|
||||||
pass # don't care if there's no stty, we tried...
|
|
||||||
|
|
||||||
def output_enable(self, enable): # type: (bool) -> None
|
|
||||||
self._output_enabled = enable
|
|
||||||
|
|
||||||
def output_toggle(self): # type: () -> None
|
|
||||||
self._output_enabled = not self._output_enabled
|
|
||||||
|
|
||||||
yellow_print('\nToggle output display: {}, Type Ctrl-T Ctrl-Y to show/disable output again.'.format(
|
|
||||||
self._output_enabled))
|
|
||||||
|
|
||||||
def toggle_logging(self): # type: () -> None
|
|
||||||
if self._log_file:
|
|
||||||
self.stop_logging()
|
|
||||||
else:
|
|
||||||
self.start_logging()
|
|
||||||
|
|
||||||
def toggle_timestamps(self): # type: () -> None
|
|
||||||
self._timestamps = not self._timestamps
|
|
||||||
|
|
||||||
def start_logging(self): # type: () -> None
|
|
||||||
if not self._log_file:
|
|
||||||
name = 'log.{}.{}.txt'.format(os.path.splitext(os.path.basename(self.elf_file))[0],
|
|
||||||
datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
|
|
||||||
try:
|
|
||||||
self._log_file = open(name, 'wb+')
|
|
||||||
yellow_print('\nLogging is enabled into file {}'.format(name))
|
|
||||||
except Exception as e:
|
|
||||||
red_print('\nLog file {} cannot be created: {}'.format(name, e))
|
|
||||||
|
|
||||||
def stop_logging(self): # type: () -> None
|
|
||||||
if self._log_file:
|
|
||||||
try:
|
|
||||||
name = self._log_file.name
|
|
||||||
self._log_file.close()
|
|
||||||
yellow_print('\nLogging is disabled and file {} has been closed'.format(name))
|
|
||||||
except Exception as e:
|
|
||||||
red_print('\nLog file cannot be closed: {}'.format(e))
|
|
||||||
finally:
|
|
||||||
self._log_file = None
|
|
||||||
|
|
||||||
def _print(self, string, console_printer=None): # type: (AnyStr, Optional[Callable]) -> None
|
|
||||||
if console_printer is None:
|
|
||||||
console_printer = self.console.write_bytes
|
|
||||||
if self._timestamps and (self._output_enabled or self._log_file):
|
|
||||||
t = datetime.datetime.now().strftime(self._timestamp_format)
|
|
||||||
# "string" is not guaranteed to be a full line. Timestamps should be only at the beginning of lines.
|
|
||||||
if isinstance(string, type(u'')):
|
|
||||||
search_patt = '\n'
|
|
||||||
replacement = '\n' + t + ' '
|
|
||||||
else:
|
|
||||||
search_patt = b'\n' # type: ignore
|
|
||||||
replacement = b'\n' + t.encode('ascii') + b' ' # type: ignore
|
|
||||||
string = string.replace(search_patt, replacement)
|
|
||||||
if self._output_enabled:
|
|
||||||
console_printer(string)
|
|
||||||
if self._log_file:
|
|
||||||
try:
|
|
||||||
if isinstance(string, type(u'')):
|
|
||||||
string = string.encode() # type: ignore
|
|
||||||
self._log_file.write(string) # type: ignore
|
|
||||||
except Exception as e:
|
|
||||||
red_print('\nCannot write to file: {}'.format(e))
|
|
||||||
# don't fill-up the screen with the previous errors (probably consequent prints would fail also)
|
|
||||||
self.stop_logging()
|
|
||||||
|
|
||||||
def handle_commands(self, cmd, chip): # type: (int, str) -> None
|
def handle_commands(self, cmd, chip): # type: (int, str) -> None
|
||||||
config = get_chip_config(chip)
|
config = get_chip_config(chip)
|
||||||
reset_delay = config['reset']
|
reset_delay = config['reset']
|
||||||
@ -645,17 +467,18 @@ class Monitor(object):
|
|||||||
time.sleep(reset_delay)
|
time.sleep(reset_delay)
|
||||||
self.serial.setRTS(high)
|
self.serial.setRTS(high)
|
||||||
self.serial.setDTR(self.serial.dtr) # usbser.sys workaround
|
self.serial.setDTR(self.serial.dtr) # usbser.sys workaround
|
||||||
self.output_enable(low)
|
self.logger.output_enabled = True
|
||||||
elif cmd == CMD_MAKE:
|
elif cmd == CMD_MAKE:
|
||||||
self.run_make('encrypted-flash' if self.encrypted else 'flash')
|
self.run_make('encrypted-flash' if self.encrypted else 'flash')
|
||||||
elif cmd == CMD_APP_FLASH:
|
elif cmd == CMD_APP_FLASH:
|
||||||
self.run_make('encrypted-app-flash' if self.encrypted else 'app-flash')
|
self.run_make('encrypted-app-flash' if self.encrypted else 'app-flash')
|
||||||
elif cmd == CMD_OUTPUT_TOGGLE:
|
elif cmd == CMD_OUTPUT_TOGGLE:
|
||||||
self.output_toggle()
|
self.logger.output_toggle()
|
||||||
elif cmd == CMD_TOGGLE_LOGGING:
|
elif cmd == CMD_TOGGLE_LOGGING:
|
||||||
self.toggle_logging()
|
self.logger.toggle_logging()
|
||||||
elif cmd == CMD_TOGGLE_TIMESTAMPS:
|
elif cmd == CMD_TOGGLE_TIMESTAMPS:
|
||||||
self.toggle_timestamps()
|
self.logger.toggle_timestamps()
|
||||||
|
self.logger.toggle_logging()
|
||||||
elif cmd == CMD_ENTER_BOOT:
|
elif cmd == CMD_ENTER_BOOT:
|
||||||
self.serial.setDTR(high) # IO0=HIGH
|
self.serial.setDTR(high) # IO0=HIGH
|
||||||
self.serial.setRTS(low) # EN=LOW, chip in reset
|
self.serial.setRTS(low) # EN=LOW, chip in reset
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
import re
|
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)
|
# regex matches an potential PC value (0x4xxxxxxx)
|
||||||
MATCH_PCADDR = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE)
|
MATCH_PCADDR = re.compile(r'0x4[0-9a-f]{7}', re.IGNORECASE)
|
||||||
|
|
||||||
@ -33,3 +44,25 @@ PANIC_READING = 1
|
|||||||
# panic handler decoding options
|
# panic handler decoding options
|
||||||
PANIC_DECODE_DISABLE = 'disable'
|
PANIC_DECODE_DISABLE = 'disable'
|
||||||
PANIC_DECODE_BACKTRACE = 'backtrace'
|
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)
|
||||||
|
@ -19,7 +19,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
import serial.tools.miniterm as miniterm
|
from serial.tools import miniterm
|
||||||
|
|
||||||
from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP,
|
from .constants import (CMD_APP_FLASH, CMD_ENTER_BOOT, CMD_MAKE, CMD_OUTPUT_TOGGLE, CMD_RESET, CMD_STOP,
|
||||||
CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_A, CTRL_F, CTRL_H, CTRL_I, CTRL_L, CTRL_P,
|
CMD_TOGGLE_LOGGING, CMD_TOGGLE_TIMESTAMPS, CTRL_A, CTRL_F, CTRL_H, CTRL_I, CTRL_L, CTRL_P,
|
||||||
|
131
tools/idf_monitor_base/gdbhelper.py
Normal file
131
tools/idf_monitor_base/gdbhelper.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
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_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.
|
||||||
|
# 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,
|
||||||
|
script=panic_output_decode_script,
|
||||||
|
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))
|
@ -13,35 +13,137 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from serial.tools import miniterm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from typing import Optional
|
from typing import BinaryIO, Callable, Optional, Union # noqa
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# ANSI terminal codes (if changed, regular expressions in LineMatcher need to be udpated)
|
# ANSI terminal codes (if changed, regular expressions in LineMatcher need to be updated)
|
||||||
ANSI_RED = '\033[1;31m'
|
ANSI_RED = '\033[1;31m'
|
||||||
ANSI_YELLOW = '\033[0;33m'
|
ANSI_YELLOW = '\033[0;33m'
|
||||||
ANSI_NORMAL = '\033[0m'
|
ANSI_NORMAL = '\033[0m'
|
||||||
|
|
||||||
|
|
||||||
def color_print(message, color, newline='\n'):
|
def color_print(message, color, newline='\n'): # type: (str, str, Optional[str]) -> None
|
||||||
# type: (str, str, Optional[str]) -> None
|
|
||||||
""" Print a message to stderr with colored highlighting """
|
""" Print a message to stderr with colored highlighting """
|
||||||
sys.stderr.write('%s%s%s%s' % (color, message, ANSI_NORMAL, newline))
|
sys.stderr.write('%s%s%s%s' % (color, message, ANSI_NORMAL, newline))
|
||||||
|
|
||||||
|
|
||||||
def normal_print(message):
|
def normal_print(message): # type: (str) -> None
|
||||||
# type: (str) -> None
|
|
||||||
sys.stderr.write(ANSI_NORMAL + message)
|
sys.stderr.write(ANSI_NORMAL + message)
|
||||||
|
|
||||||
|
|
||||||
def yellow_print(message, newline='\n'):
|
def yellow_print(message, newline='\n'): # type: (str, Optional[str]) -> None
|
||||||
# type: (str, Optional[str]) -> None
|
|
||||||
color_print(message, ANSI_YELLOW, newline)
|
color_print(message, ANSI_YELLOW, newline)
|
||||||
|
|
||||||
|
|
||||||
def red_print(message, newline='\n'):
|
def red_print(message, newline='\n'): # type: (str, Optional[str]) -> None
|
||||||
# type: (str, Optional[str]) -> None
|
|
||||||
color_print(message, ANSI_RED, newline)
|
color_print(message, ANSI_RED, newline)
|
||||||
|
|
||||||
|
|
||||||
|
def lookup_pc_address(pc_addr, toolchain_prefix, elf_file): # type: (str, str, str) -> Optional[str]
|
||||||
|
cmd = ['%saddr2line' % toolchain_prefix, '-pfiaC', '-e', elf_file, pc_addr]
|
||||||
|
|
||||||
|
try:
|
||||||
|
translation = subprocess.check_output(cmd, cwd='.')
|
||||||
|
if b'?? ??:0' not in translation:
|
||||||
|
return translation.decode()
|
||||||
|
except OSError as e:
|
||||||
|
red_print('%s: %s' % (' '.join(cmd), e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self, elf_file, console, timestamps, timestamp_format):
|
||||||
|
# type: (str, miniterm.Console, bool, str) -> None
|
||||||
|
self.log_file = None # type: Optional[BinaryIO]
|
||||||
|
self._output_enabled = True # type: bool
|
||||||
|
self.elf_file = elf_file
|
||||||
|
self.console = console
|
||||||
|
self.timestamps = timestamps
|
||||||
|
self.timestamp_format = timestamp_format
|
||||||
|
|
||||||
|
@property
|
||||||
|
def output_enabled(self): # type: () -> bool
|
||||||
|
return self._output_enabled
|
||||||
|
|
||||||
|
@output_enabled.setter
|
||||||
|
def output_enabled(self, value): # type: (bool) -> None
|
||||||
|
self._output_enabled = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log_file(self): # type: () -> Optional[BinaryIO]
|
||||||
|
return self._log_file
|
||||||
|
|
||||||
|
@log_file.setter
|
||||||
|
def log_file(self, value): # type: (Optional[BinaryIO]) -> None
|
||||||
|
self._log_file = value
|
||||||
|
|
||||||
|
def toggle_logging(self): # type: () -> None
|
||||||
|
if self._log_file:
|
||||||
|
self.stop_logging()
|
||||||
|
else:
|
||||||
|
self.start_logging()
|
||||||
|
|
||||||
|
def toggle_timestamps(self): # type: () -> None
|
||||||
|
self.timestamps = not self.timestamps
|
||||||
|
|
||||||
|
def start_logging(self): # type: () -> None
|
||||||
|
if not self._log_file:
|
||||||
|
name = 'log.{}.{}.txt'.format(os.path.splitext(os.path.basename(self.elf_file))[0],
|
||||||
|
datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
|
||||||
|
try:
|
||||||
|
self.log_file = open(name, 'wb+')
|
||||||
|
yellow_print('\nLogging is enabled into file {}'.format(name))
|
||||||
|
except Exception as e:
|
||||||
|
red_print('\nLog file {} cannot be created: {}'.format(name, e))
|
||||||
|
|
||||||
|
def stop_logging(self): # type: () -> None
|
||||||
|
if self._log_file:
|
||||||
|
try:
|
||||||
|
name = self._log_file.name
|
||||||
|
self._log_file.close()
|
||||||
|
yellow_print('\nLogging is disabled and file {} has been closed'.format(name))
|
||||||
|
except Exception as e:
|
||||||
|
red_print('\nLog file cannot be closed: {}'.format(e))
|
||||||
|
finally:
|
||||||
|
self._log_file = None
|
||||||
|
|
||||||
|
def print(self, string, console_printer=None): # noqa: E999
|
||||||
|
# type: (Union[str, bytes], Optional[Callable]) -> None
|
||||||
|
if console_printer is None:
|
||||||
|
console_printer = self.console.write_bytes
|
||||||
|
|
||||||
|
if self.timestamps and (self._output_enabled or self._log_file):
|
||||||
|
t = datetime.datetime.now().strftime(self.timestamp_format)
|
||||||
|
# "string" is not guaranteed to be a full line. Timestamps should be only at the beginning of lines.
|
||||||
|
if isinstance(string, type(u'')):
|
||||||
|
search_patt = '\n'
|
||||||
|
replacement = '\n' + t + ' '
|
||||||
|
else:
|
||||||
|
search_patt = b'\n' # type: ignore
|
||||||
|
replacement = b'\n' + t.encode('ascii') + b' ' # type: ignore
|
||||||
|
string = string.replace(search_patt, replacement) # type: ignore
|
||||||
|
if self._output_enabled:
|
||||||
|
console_printer(string)
|
||||||
|
if self._log_file:
|
||||||
|
try:
|
||||||
|
if isinstance(string, type(u'')):
|
||||||
|
string = string.encode() # type: ignore
|
||||||
|
self._log_file.write(string) # type: ignore
|
||||||
|
except Exception as e:
|
||||||
|
red_print('\nCannot write to file: {}'.format(e))
|
||||||
|
# don't fill-up the screen with the previous errors (probably consequent prints would fail also)
|
||||||
|
self.stop_logging()
|
||||||
|
|
||||||
|
def output_toggle(self): # type: () -> None
|
||||||
|
self.output_enabled = not self.output_enabled
|
||||||
|
yellow_print('\nToggle output display: {}, Type Ctrl-T Ctrl-Y to show/disable output again.'.format(
|
||||||
|
self.output_enabled))
|
||||||
|
Loading…
Reference in New Issue
Block a user