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.