mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
feat(tools): remove gdb_panic_server and use just a wrapper for script
gdb_panic_server is now part of esp-idf-panic-decoder
This commit is contained in:
parent
3b50c716d8
commit
05c883e8f7
@ -87,6 +87,7 @@ tools/esp_app_trace/sysviewtrace_proc.py
|
|||||||
tools/esp_app_trace/test/logtrace/test.sh
|
tools/esp_app_trace/test/logtrace/test.sh
|
||||||
tools/esp_app_trace/test/sysview/test.sh
|
tools/esp_app_trace/test/sysview/test.sh
|
||||||
tools/format.sh
|
tools/format.sh
|
||||||
|
tools/gdb_panic_server.py
|
||||||
tools/gen_esp_err_to_name.py
|
tools/gen_esp_err_to_name.py
|
||||||
tools/gen_soc_caps_kconfig/gen_soc_caps_kconfig.py
|
tools/gen_soc_caps_kconfig/gen_soc_caps_kconfig.py
|
||||||
tools/gen_soc_caps_kconfig/test/test_gen_soc_caps_kconfig.py
|
tools/gen_soc_caps_kconfig/test/test_gen_soc_caps_kconfig.py
|
||||||
|
282
tools/gdb_panic_server.py
Normal file → Executable file
282
tools/gdb_panic_server.py
Normal file → Executable file
@ -1,288 +1,12 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding=utf-8
|
|
||||||
#
|
#
|
||||||
# A script which parses ESP-IDF panic handler output (registers & stack dump),
|
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||||
# and then acts as a GDB server over stdin/stdout, presenting the information
|
|
||||||
# from the panic handler to GDB.
|
|
||||||
# This allows for generating backtraces out of raw stack dumps on architectures
|
|
||||||
# where backtracing on the target side is not possible.
|
|
||||||
#
|
#
|
||||||
# Note that the "act as a GDB server" approach is somewhat a hack.
|
|
||||||
# A much nicer solution would have been to convert the panic handler output
|
|
||||||
# into a core file, and point GDB to the core file.
|
|
||||||
# However, RISC-V baremetal GDB currently lacks core dump support.
|
|
||||||
#
|
|
||||||
# The approach is inspired by Cesanta's ESP8266 GDB server:
|
|
||||||
# https://github.com/cesanta/mongoose-os/blob/27777c8977/platforms/esp8266/tools/serve_core.py
|
|
||||||
#
|
|
||||||
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import subprocess
|
||||||
import argparse
|
|
||||||
import binascii
|
|
||||||
import logging
|
|
||||||
import struct
|
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
|
||||||
|
|
||||||
# Used for type annotations only. Silence linter warnings.
|
|
||||||
from pyparsing import (Combine, Group, Literal, OneOrMore, ParserElement, # noqa: F401 # pylint: disable=unused-import
|
|
||||||
ParseResults, Word, nums, srange)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import typing # noqa: F401 # pylint: disable=unused-import
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# pyparsing helper
|
|
||||||
hexnumber = srange('[0-9a-f]')
|
|
||||||
|
|
||||||
|
|
||||||
# List of registers to be passed to GDB, in the order GDB expects.
|
|
||||||
# The names should match those used in IDF panic handler.
|
|
||||||
# Registers not present in IDF panic handler output (like X0) will be assumed to be 0.
|
|
||||||
GDB_REGS_INFO_RISCV_ILP32 = [
|
|
||||||
'X0', 'RA', 'SP', 'GP',
|
|
||||||
'TP', 'T0', 'T1', 'T2',
|
|
||||||
'S0/FP', 'S1', 'A0', 'A1',
|
|
||||||
'A2', 'A3', 'A4', 'A5',
|
|
||||||
'A6', 'A7', 'S2', 'S3',
|
|
||||||
'S4', 'S5', 'S6', 'S7',
|
|
||||||
'S8', 'S9', 'S10', 'S11',
|
|
||||||
'T3', 'T4', 'T5', 'T6',
|
|
||||||
'MEPC'
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
GDB_REGS_INFO = {
|
|
||||||
'esp32c3': GDB_REGS_INFO_RISCV_ILP32,
|
|
||||||
'esp32c2': GDB_REGS_INFO_RISCV_ILP32,
|
|
||||||
'esp32c6': GDB_REGS_INFO_RISCV_ILP32,
|
|
||||||
'esp32h2': GDB_REGS_INFO_RISCV_ILP32
|
|
||||||
}
|
|
||||||
|
|
||||||
PanicInfo = namedtuple('PanicInfo', 'core_id regs stack_base_addr stack_data')
|
|
||||||
|
|
||||||
|
|
||||||
def build_riscv_panic_output_parser(): # type: () -> typing.Any[typing.Type[ParserElement]]
|
|
||||||
"""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:
|
|
||||||
# Guru Meditation Error: Core 0 panic'ed (Store access fault). Exception was unhandled.
|
|
||||||
|
|
||||||
# Core 0 register dump:
|
|
||||||
reg_dump_header = Group(Literal('Core') +
|
|
||||||
Word(nums)('core_id') +
|
|
||||||
Literal('register dump:'))('reg_dump_header')
|
|
||||||
|
|
||||||
# MEPC : 0x4200232c RA : 0x42009694 SP : 0x3fc93a80 GP : 0x3fc8b320
|
|
||||||
reg_name = Word(srange('[A-Z_0-9/-]'))('name')
|
|
||||||
hexnumber_with_0x = Combine(Literal('0x') + Word(hexnumber))
|
|
||||||
reg_value = hexnumber_with_0x('value')
|
|
||||||
reg_dump_one_reg = Group(reg_name + Literal(':') + reg_value) # not named because there will be OneOrMore
|
|
||||||
reg_dump_all_regs = Group(OneOrMore(reg_dump_one_reg))('regs')
|
|
||||||
reg_dump = Group(reg_dump_header + reg_dump_all_regs) # not named because there will be OneOrMore
|
|
||||||
reg_dumps = Group(OneOrMore(reg_dump))('reg_dumps')
|
|
||||||
|
|
||||||
# Stack memory:
|
|
||||||
# 3fc93a80: 0x00000030 0x00000021 0x3fc8aedc 0x4200232a 0xa5a5a5a5 0xa5a5a5a5 0x3fc8aedc 0x420099b0
|
|
||||||
stack_line = Group(Word(hexnumber)('base') + Literal(':') +
|
|
||||||
Group(OneOrMore(hexnumber_with_0x))('data'))
|
|
||||||
stack_dump = Group(Literal('Stack memory:') +
|
|
||||||
Group(OneOrMore(stack_line))('lines'))('stack_dump')
|
|
||||||
|
|
||||||
# Parser for the complete panic output:
|
|
||||||
panic_output = reg_dumps + stack_dump
|
|
||||||
return panic_output
|
|
||||||
|
|
||||||
|
|
||||||
def get_stack_addr_and_data(res): # type: (ParseResults) -> typing.Tuple[int, bytes]
|
|
||||||
""" Extract base address and bytes from the parsed stack dump """
|
|
||||||
stack_base_addr = 0 # First reported address in the dump
|
|
||||||
base_addr = 0 # keeps track of the address for the given line of the dump
|
|
||||||
bytes_in_line = 0 # bytes of stack parsed on the previous line; used to validate the next base address
|
|
||||||
stack_data = bytes(b'') # accumulates all the dumped stack data
|
|
||||||
for line in res.stack_dump.lines:
|
|
||||||
# update and validate the base address
|
|
||||||
prev_base_addr = base_addr
|
|
||||||
base_addr = int(line.base, 16)
|
|
||||||
if stack_base_addr == 0:
|
|
||||||
stack_base_addr = base_addr
|
|
||||||
else:
|
|
||||||
assert base_addr == prev_base_addr + bytes_in_line
|
|
||||||
|
|
||||||
# convert little-endian hex words to byte representation
|
|
||||||
words = [int(w, 16) for w in line.data]
|
|
||||||
line_data = bytes(b''.join([struct.pack('<I', w) for w in words]))
|
|
||||||
bytes_in_line = len(line_data)
|
|
||||||
|
|
||||||
# accumulate in the whole stack data
|
|
||||||
stack_data += line_data
|
|
||||||
|
|
||||||
return stack_base_addr, stack_data
|
|
||||||
|
|
||||||
|
|
||||||
def parse_idf_riscv_panic_output(panic_text): # type: (str) -> PanicInfo
|
|
||||||
""" Decode panic handler output from a file """
|
|
||||||
panic_output = build_riscv_panic_output_parser()
|
|
||||||
results = panic_output.searchString(panic_text)
|
|
||||||
if len(results) != 1:
|
|
||||||
raise ValueError("Couldn't parse panic handler output")
|
|
||||||
res = results[0]
|
|
||||||
|
|
||||||
if len(res.reg_dumps) > 1:
|
|
||||||
raise NotImplementedError('Handling of multi-core register dumps not implemented')
|
|
||||||
|
|
||||||
# Build a dict of register names/values
|
|
||||||
rd = res.reg_dumps[0]
|
|
||||||
core_id = int(rd.reg_dump_header.core_id)
|
|
||||||
regs = dict()
|
|
||||||
for reg in rd.regs:
|
|
||||||
reg_value = int(reg.value, 16)
|
|
||||||
regs[reg.name] = reg_value
|
|
||||||
|
|
||||||
stack_base_addr, stack_data = get_stack_addr_and_data(res)
|
|
||||||
|
|
||||||
return PanicInfo(core_id=core_id,
|
|
||||||
regs=regs,
|
|
||||||
stack_base_addr=stack_base_addr,
|
|
||||||
stack_data=stack_data)
|
|
||||||
|
|
||||||
|
|
||||||
PANIC_OUTPUT_PARSERS = {
|
|
||||||
'esp32c3': parse_idf_riscv_panic_output,
|
|
||||||
'esp32c2': parse_idf_riscv_panic_output,
|
|
||||||
'esp32c6': parse_idf_riscv_panic_output,
|
|
||||||
'esp32h2': parse_idf_riscv_panic_output
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GdbServer(object):
|
|
||||||
def __init__(self, panic_info, target, log_file=None): # type: (PanicInfo, str, str) -> None
|
|
||||||
self.panic_info = panic_info
|
|
||||||
self.in_stream = sys.stdin
|
|
||||||
self.out_stream = sys.stdout
|
|
||||||
self.reg_list = GDB_REGS_INFO[target]
|
|
||||||
|
|
||||||
self.logger = logging.getLogger('GdbServer')
|
|
||||||
if log_file:
|
|
||||||
handler = logging.FileHandler(log_file, 'w+')
|
|
||||||
self.logger.setLevel(logging.DEBUG)
|
|
||||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
||||||
handler.setFormatter(formatter)
|
|
||||||
self.logger.addHandler(handler)
|
|
||||||
|
|
||||||
def run(self): # type: () -> None
|
|
||||||
""" Process GDB commands from stdin until GDB tells us to quit """
|
|
||||||
buffer = ''
|
|
||||||
while True:
|
|
||||||
buffer += self.in_stream.read(1)
|
|
||||||
if len(buffer) > 3 and buffer[-3] == '#':
|
|
||||||
self._handle_command(buffer)
|
|
||||||
buffer = ''
|
|
||||||
|
|
||||||
def _handle_command(self, buffer): # type: (str) -> None
|
|
||||||
command = buffer[1:-3] # ignore checksums
|
|
||||||
# Acknowledge the command
|
|
||||||
self.out_stream.write('+')
|
|
||||||
self.out_stream.flush()
|
|
||||||
self.logger.debug('Got command: %s', command)
|
|
||||||
if command == '?':
|
|
||||||
# report sigtrap as the stop reason; the exact reason doesn't matter for backtracing
|
|
||||||
self._respond('T05')
|
|
||||||
elif command.startswith('Hg') or command.startswith('Hc'):
|
|
||||||
# Select thread command
|
|
||||||
self._respond('OK')
|
|
||||||
elif command == 'qfThreadInfo':
|
|
||||||
# Get list of threads.
|
|
||||||
# Only one thread for now, can be extended to show one thread for each core,
|
|
||||||
# if we dump both cores (e.g. on an interrupt watchdog)
|
|
||||||
self._respond('m1')
|
|
||||||
elif command == 'qC':
|
|
||||||
# That single thread is selected.
|
|
||||||
self._respond('QC1')
|
|
||||||
elif command == 'g':
|
|
||||||
# Registers read
|
|
||||||
self._respond_regs()
|
|
||||||
elif command.startswith('m'):
|
|
||||||
# Memory read
|
|
||||||
addr, size = [int(v, 16) for v in command[1:].split(',')]
|
|
||||||
self._respond_mem(addr, size)
|
|
||||||
elif command.startswith('vKill') or command == 'k':
|
|
||||||
# Quit
|
|
||||||
self._respond('OK')
|
|
||||||
raise SystemExit(0)
|
|
||||||
else:
|
|
||||||
# Empty response required for any unknown command
|
|
||||||
self._respond('')
|
|
||||||
|
|
||||||
def _respond(self, data): # type: (str) -> None
|
|
||||||
# calculate checksum
|
|
||||||
data_bytes = bytes(data.encode('ascii')) # bytes() for Py2 compatibility
|
|
||||||
checksum = sum(data_bytes) & 0xff
|
|
||||||
# format and write the response
|
|
||||||
res = '${}#{:02x}'.format(data, checksum)
|
|
||||||
self.logger.debug('Wrote: %s', res)
|
|
||||||
self.out_stream.write(res)
|
|
||||||
self.out_stream.flush()
|
|
||||||
# get the result ('+' or '-')
|
|
||||||
ret = self.in_stream.read(1)
|
|
||||||
self.logger.debug('Response: %s', ret)
|
|
||||||
if ret != '+':
|
|
||||||
sys.stderr.write("GDB responded with '-' to {}".format(res))
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
def _respond_regs(self): # type: () -> None
|
|
||||||
response = ''
|
|
||||||
for reg_name in self.reg_list:
|
|
||||||
# register values are reported as hexadecimal strings
|
|
||||||
# in target byte order (i.e. LSB first for RISC-V)
|
|
||||||
reg_val = self.panic_info.regs.get(reg_name, 0)
|
|
||||||
reg_bytes = struct.pack('<L', reg_val)
|
|
||||||
response += binascii.hexlify(reg_bytes).decode('ascii')
|
|
||||||
self._respond(response)
|
|
||||||
|
|
||||||
def _respond_mem(self, start_addr, size): # type: (int, int) -> None
|
|
||||||
stack_addr_min = self.panic_info.stack_base_addr
|
|
||||||
stack_data = self.panic_info.stack_data
|
|
||||||
stack_len = len(self.panic_info.stack_data)
|
|
||||||
stack_addr_max = stack_addr_min + stack_len
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
def in_stack(addr): # type: (int) -> typing.Any[bool]
|
|
||||||
return stack_addr_min <= addr < stack_addr_max
|
|
||||||
|
|
||||||
result = ''
|
|
||||||
for addr in range(start_addr, start_addr + size):
|
|
||||||
if not in_stack(addr):
|
|
||||||
result += '00'
|
|
||||||
else:
|
|
||||||
result += '{:02x}'.format(stack_data[addr - stack_addr_min])
|
|
||||||
|
|
||||||
self._respond(result)
|
|
||||||
|
|
||||||
|
|
||||||
def main(): # type: () -> None
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('input_file', type=argparse.FileType('r'),
|
|
||||||
help='File containing the panic handler output')
|
|
||||||
parser.add_argument('--target', choices=GDB_REGS_INFO.keys(),
|
|
||||||
help='Chip to use (determines the architecture)')
|
|
||||||
parser.add_argument('--gdb-log', default=None,
|
|
||||||
help='If specified, the file for logging GDB server debug information')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
panic_info = PANIC_OUTPUT_PARSERS[args.target](args.input_file.read())
|
|
||||||
|
|
||||||
server = GdbServer(panic_info, target=args.target, log_file=args.gdb_log)
|
|
||||||
try:
|
|
||||||
server.run()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
sys.exit(subprocess.run([sys.executable, '-m', 'esp_idf_panic_decoder'] + sys.argv[1:]).returncode)
|
||||||
|
@ -15,6 +15,7 @@ esptool
|
|||||||
esp-idf-kconfig
|
esp-idf-kconfig
|
||||||
esp-idf-monitor
|
esp-idf-monitor
|
||||||
esp-idf-size
|
esp-idf-size
|
||||||
|
esp-idf-panic-decoder
|
||||||
|
|
||||||
# gdb extensions dependencies
|
# gdb extensions dependencies
|
||||||
freertos_gdb
|
freertos_gdb
|
||||||
|
Loading…
Reference in New Issue
Block a user