emdbg.analyze.hardfault
PX4 generates a log file if it encounters a hardfault. This tool converts this to a coredump that can be loaded with CrashDebug. To interpret the hardfault location and reason, use the GDB plugins.
The hardfault log only contains a copy of the stack and registers. The
converter fills unknown memory with the 0xfeedc0de
value, which may then
display wrong or incomplete results in some of the GDB plugins. Keep this in
mind when converting a hardfault to a coredump.
Command Line Interface
To convert the hardfault log:
# convert to hardfault_coredump.txt
python3 -m emdbg.analyze.hardfault hardfault.log
# or with an explicit name
python3 -m emdbg.analyze.hardfault hardfault.log -o custom_name.txt
1# Copyright (c) 2023, Auterion AG 2# SPDX-License-Identifier: BSD-3-Clause 3 4""" 5PX4 generates a log file if it encounters a hardfault. 6This tool converts this to a coredump that can be loaded with CrashDebug. 7To interpret the hardfault location and reason, use the GDB plugins. 8 9.. note:: 10 The hardfault log only contains a copy of the stack and registers. The 11 converter fills unknown memory with the `0xfeedc0de` value, which may then 12 display wrong or incomplete results in some of the GDB plugins. Keep this in 13 mind when converting a hardfault to a coredump. 14 15 16## Command Line Interface 17 18To convert the hardfault log: 19 20```sh 21# convert to hardfault_coredump.txt 22python3 -m emdbg.analyze.hardfault hardfault.log 23# or with an explicit name 24python3 -m emdbg.analyze.hardfault hardfault.log -o custom_name.txt 25``` 26""" 27 28from __future__ import annotations 29import re 30from pathlib import Path 31import statistics 32import itertools 33from collections import defaultdict 34 35# FIXME: hardcoded for FMUv6x memory layout (also works for FMUv5x) 36_ADDR_RANGES = [ 37 (0x2000_0000, 0x80000), 38 (0x2400_0000, 0x80000), 39 (0x3000_0000, 0x48000), 40 (0x3800_0000, 0x10000), 41 (0x3880_0000, 0x01000), 42 (0xE004_2000, 4), # DEVID F7 43 (0x5C00_1000, 4), # DEVID H7 44 (0xE000_E000, 0xFFF), # SCS 45] 46 47_UNKNOWN_MEM = 0xfeedc0de 48 49_UNKNOWN_REGS = [ 50 "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc", 51 "xpsr", "msp", "psp", "primask", "basepri", "faultmask", "control", "fpscr", 52 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16", 53 "s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31", 54 "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "d10", "d11", "d12", "d13", "d14", "d15", 55] 56 57def convert(log: str) -> str: 58 """ 59 Convert a hardfault log to 60 61 :param log: The content of a hardfault log file. 62 :return: A formatted string containing the coredump. 63 """ 64 lines = log.splitlines() 65 66 known_mems = {} 67 regs = {r: _UNKNOWN_MEM for r in _UNKNOWN_REGS} 68 fault_regs = {} 69 for line in lines: 70 if len(reg_matches := re.findall(r" (r\d+|sp|lr|pc|xpsr|basepri|control|primask):\[?(0x[0-9a-f]+)]?", line)): 71 for reg, val in reg_matches: 72 regs[reg] = int(val, 16) 73 if reg == "sp": 74 regs["msp"] = regs["sp"] 75 regs["psp"] = regs["sp"] 76 77 if len(fault_matches := re.findall(r" (c|h|d|mm|b|a|ab)fsr:\[?(0x[0-9a-f]+)]?", line)): 78 for reg, val in fault_matches: 79 fault_regs[reg] = int(val, 16) 80 81 m_base = re.match(r"^(0x[0-9a-f]+)\s(0x[0-9a-f]+).*$", line) 82 m_correlated = re.match(r"^(0x[0-9a-f]+)\s0x[0-9a-f]+ -> \[(0x[0-9a-f]+)].*$", line) 83 84 m = m_correlated or m_base 85 86 if m: 87 addr = int(m.group(1), 16) 88 val = int(m.group(2), 16) 89 known_mems[addr] = val 90 91 # FIXME: hardcoded for FMUv5x (STM32F765) 92 known_mems[0xE004_2000] = 0x10030451 93 # FIXME: hardcoded for FMUv6x (STM32H753) 94 known_mems[0x5C00_1000] = 0x10030450 95 96 # CPUID 97 known_mems[0xE000_ED00] = 0x411fc270 98 # dump all the fault registers back into memory 99 known_mems[0xE000_ED28] = fault_regs.get("c", 0) 100 known_mems[0xE000_ED2C] = fault_regs.get("h", 0) 101 known_mems[0xE000_ED30] = fault_regs.get("d", 0) 102 known_mems[0xE000_ED34] = fault_regs.get("mm", 0) 103 known_mems[0xE000_ED38] = fault_regs.get("b", 0) 104 known_mems[0xE000_ED3C] = fault_regs.get("a", 0) 105 known_mems[0xE000_EFA8] = fault_regs.get("ab", 0) 106 107 output = [] 108 for mem_start, mem_size in _ADDR_RANGES: 109 for addr in range(mem_start, mem_start + mem_size, 16): 110 fmt = "{:#8x}:\t{:#8x}\t{:#8x}\t{:#8x}\t{:#8x}" 111 fmt = fmt.format(addr, known_mems.get(addr, _UNKNOWN_MEM), 112 known_mems.get(addr + 4, _UNKNOWN_MEM), 113 known_mems.get(addr + 8, _UNKNOWN_MEM), 114 known_mems.get(addr + 12, _UNKNOWN_MEM)) 115 output.append(fmt) 116 117 for reg, val in regs.items(): 118 output.append(f"{reg:15} {val:#20x} {val:20}") 119 120 return "\n".join(output) 121 122 123 124# ----------------------------------------------------------------------------- 125if __name__ == "__main__": 126 import argparse 127 128 parser = argparse.ArgumentParser(description="Hardfault log converter") 129 parser.add_argument( 130 "log", 131 type=Path, 132 help="The hardfault log.") 133 parser.add_argument( 134 "--output", 135 "-o", 136 type=Path, 137 default=None, 138 help="The GDB log containing the semaphore boost trace.") 139 args = parser.parse_args() 140 141 if (outfile := args.output) is None: 142 outfile = args.log.with_suffix("_coredump.txt") 143 144 outfile.write_text(convert(args.log.read_text()))
def
convert(log: str) -> str:
58def convert(log: str) -> str: 59 """ 60 Convert a hardfault log to 61 62 :param log: The content of a hardfault log file. 63 :return: A formatted string containing the coredump. 64 """ 65 lines = log.splitlines() 66 67 known_mems = {} 68 regs = {r: _UNKNOWN_MEM for r in _UNKNOWN_REGS} 69 fault_regs = {} 70 for line in lines: 71 if len(reg_matches := re.findall(r" (r\d+|sp|lr|pc|xpsr|basepri|control|primask):\[?(0x[0-9a-f]+)]?", line)): 72 for reg, val in reg_matches: 73 regs[reg] = int(val, 16) 74 if reg == "sp": 75 regs["msp"] = regs["sp"] 76 regs["psp"] = regs["sp"] 77 78 if len(fault_matches := re.findall(r" (c|h|d|mm|b|a|ab)fsr:\[?(0x[0-9a-f]+)]?", line)): 79 for reg, val in fault_matches: 80 fault_regs[reg] = int(val, 16) 81 82 m_base = re.match(r"^(0x[0-9a-f]+)\s(0x[0-9a-f]+).*$", line) 83 m_correlated = re.match(r"^(0x[0-9a-f]+)\s0x[0-9a-f]+ -> \[(0x[0-9a-f]+)].*$", line) 84 85 m = m_correlated or m_base 86 87 if m: 88 addr = int(m.group(1), 16) 89 val = int(m.group(2), 16) 90 known_mems[addr] = val 91 92 # FIXME: hardcoded for FMUv5x (STM32F765) 93 known_mems[0xE004_2000] = 0x10030451 94 # FIXME: hardcoded for FMUv6x (STM32H753) 95 known_mems[0x5C00_1000] = 0x10030450 96 97 # CPUID 98 known_mems[0xE000_ED00] = 0x411fc270 99 # dump all the fault registers back into memory 100 known_mems[0xE000_ED28] = fault_regs.get("c", 0) 101 known_mems[0xE000_ED2C] = fault_regs.get("h", 0) 102 known_mems[0xE000_ED30] = fault_regs.get("d", 0) 103 known_mems[0xE000_ED34] = fault_regs.get("mm", 0) 104 known_mems[0xE000_ED38] = fault_regs.get("b", 0) 105 known_mems[0xE000_ED3C] = fault_regs.get("a", 0) 106 known_mems[0xE000_EFA8] = fault_regs.get("ab", 0) 107 108 output = [] 109 for mem_start, mem_size in _ADDR_RANGES: 110 for addr in range(mem_start, mem_start + mem_size, 16): 111 fmt = "{:#8x}:\t{:#8x}\t{:#8x}\t{:#8x}\t{:#8x}" 112 fmt = fmt.format(addr, known_mems.get(addr, _UNKNOWN_MEM), 113 known_mems.get(addr + 4, _UNKNOWN_MEM), 114 known_mems.get(addr + 8, _UNKNOWN_MEM), 115 known_mems.get(addr + 12, _UNKNOWN_MEM)) 116 output.append(fmt) 117 118 for reg, val in regs.items(): 119 output.append(f"{reg:15} {val:#20x} {val:20}") 120 121 return "\n".join(output)
Convert a hardfault log to
Parameters
- log: The content of a hardfault log file.
Returns
A formatted string containing the coredump.