feat(hardfault): add SRAM hardfault/watchdog DroneCAN logging#27787
feat(hardfault): add SRAM hardfault/watchdog DroneCAN logging#27787marinauterion wants to merge 3 commits into
Conversation
f9755c4 to
0ea677e
Compare
🔎 FLASH Analysispx4_fmu-v5x [Total VM Diff: 8 byte (0 %)]px4_fmu-v6x [Total VM Diff: 8 byte (0 %)]Updated: 2026-07-09T14:12:50 |
c9bbf65 to
f0bf1d1
Compare
f0bf1d1 to
4ef874c
Compare
|
No broken links found in changed files. |
|
@hamishwillee: Could you also please have a look at the docs part here? Thanks a lot. |
| char line[90]; | ||
|
|
||
| #ifdef BOARD_HAS_RAM_HARDFAULT_DUMP | ||
| wait_for_transport_ready(); |
There was a problem hiding this comment.
This is dangerous! You do a blocking wait here while the TaskWatchdog is in boosted priority. This has to run as quickly as possible and must terminate quickly otherwise you will starve other tasks in case the node for whatever reason does not reach MODE_OPERATIONAL.
This is in general problematic as the whole output will be starved for 30s due to all the log waiting.
Please check the priorities of tasks.
There was a problem hiding this comment.
wait_for_transport_ready() does call sleep(1s)
thus the task does not starve other tasks
- the watchdog loses his higher prio afet 1.5s anyway
- if we would use eg a timeout
- we were able to catch other starving-tasks, but cuz we assume, in that case, that the transport-medium is dead, there is no need to be ready for other starvations. -> instead, if the transport gets possible after time x, we still get the output of the starvation-reason; which is the most valuable
| @@ -0,0 +1,109 @@ | |||
| # Hardfault Logging on Boards Without Non-Volatile Memory | |||
|
|
|||
| This section introduces what is need by a board-config to persist a hardfault over warm-reset (only), on boards without usable non-volatile storage. | |||
There was a problem hiding this comment.
| This section introduces what is need by a board-config to persist a hardfault over warm-reset (only), on boards without usable non-volatile storage. | |
| This section introduces what is needed by a board-config to persist a hardfault over warm-reset (only), on boards without usable non-volatile storage. |
| @@ -0,0 +1,109 @@ | |||
| # Hardfault Logging on Boards Without Non-Volatile Memory | |||
There was a problem hiding this comment.
I'm away for a week or I would subedit this. If you merge next week please prettier the document.
55362ee to
0e5d500
Compare
0e5d500 to
42a3f1f
Compare
| if (up_time && hrt_elapsed_time(&up_time) > 30_s) { | ||
| up_time = 0; | ||
| if (_up_time && hrt_elapsed_time(&_up_time) > 30_s) { | ||
| _up_time = 0; |
There was a problem hiding this comment.
Question: But doesn't this mean that _up_time will never be more than 30s?
There was a problem hiding this comment.
yes: on uacvannode-boards, uavcannode is a watchdogged-application
if the node does run (again after spinOnce) the flag BOARD_RESET_MODE_RTC_BOOT_FWOK is reset
(the idea seams to be, to be able to detect broken flashed-images)
- it needs to reset BOARD_RESET_MODE_RTC_BOOT_FWOK once
BUT this is not evaluated anywhere as far as I can tell
// This is done only once to signify the node has run 30 seconds
if (_up_time && hrt_elapsed_time(&_up_time) > 30_s) {
_up_time = 0;
board_configure_reset(BOARD_RESET_MODE_RTC_BOOT_FWOK, 0);
}
…
BOARD_RESET_MODE_RTC_BOOT_FWOK = 4 /* Set by a a watch dogged application after running > 30 Seconds */| sched_lock(); | ||
|
|
||
| for (int i = 0; i < CONFIG_FS_PROCFS_MAX_TASKS; i++) { | ||
| for (int i = 0; i < MAX_DUMP_TASKS; i++) { |
There was a problem hiding this comment.
This breaks the whole task watchdog. If the task watchdog has a PID > MAX_DUMP_TASKS it does not find its own PID thus never arms!
There was a problem hiding this comment.
yes!
changes it back to CONFIG_FS_PROCFS_MAX_TASKS
now MAX_DUMP_TASKS is only used in capture_and_write_dump()
| # Input: hex extraction | ||
| # --------------------------------------------------------------------------- | ||
| # Search for chunk-pattern " xxxx <hexdata> xxxx… <hexdata>" | ||
| _CHUNK_RE = re.compile(r"(?<![0-9a-fA-F])([0-9a-fA-F]{4})\s+([0-9a-fA-F]+)\s*$") |
There was a problem hiding this comment.
What exactly should this regex detect? Does this really only match the hard fault stream and not also other kind of things logged? I am asking because what it searches for is very general.
There was a problem hiding this comment.
yes, this was very coarse, (hardfault was filtered later)
now changed for more precise pre-selection:
does now search for beginning hardfault_stream-string
leave space for variations eg
hardfault_stream] xxxx DATE_hardfault_stream] xxxx, hardfault_stream xxxx
_CHUNK_RE = re.compile(r"hardfault_stream\W*?([0-9a-fA-F]{4})\s+([0-9a-fA-F]+)\s*$")
_CRC_RE = re.compile(r"hardfault_stream\W*?crc\s+([0-9a-fA-F]{8})\b")
| const uint8_t *ptr = reinterpret_cast<const uint8_t *>(&g_px4_ram_hardfault.context); | ||
| const size_t total = sizeof(fullcontext_s); | ||
|
|
||
| if (_ram_off < total) { |
There was a problem hiding this comment.
nit: For good measure please init _ram_off to 0 after setting total. Just in case someone restarts the state machine in the future.
There was a problem hiding this comment.
(there should be only 1 hardfault per runtime)
There was a problem hiding this comment.
is reset on case State::SearchHardFault:
| ] | ||
|
|
||
|
|
||
| def format_coredump(p: ParsedHardfault, cpuid: int) -> str: |
There was a problem hiding this comment.
Why does this create a log and a core dump? The normal post mortem debugging uses the fault saved on SD, so one file. Why are both required here?
There was a problem hiding this comment.
the hardfault.log - file provide the same output as a console-output on hardfault
(This is also quiet handy to easily get a first overview on registers without starting gdb
or tell if the script has parsing flaws)
Build datetime: unknown unknown
Build url: unknown
Processor registers: from 0x2001b9ec
r0:0x00000000 r1:0x2001bb40 r2:0x00000000 r3:0x00000000 r4:0xffffffef r5:0x200020a4 r6:0x00000003 r7:0x2001b398
…
exe return:0xffffffe9
Fault status registers: from NVIC
CFSR: 00010000 HFSR: 40000000 DFSR: 00000008 BFAR: e000ed38 AFSR: 00000000
IRQ Stack:
sp: 20001550
base: 200012b8
size: 00000300
0x20001540 0x000001b0 0x00000038 0x00000002 0x00000020 0x40000000 0x00000003 0x2001b9ec 0x00010000
…
User Stack:
…while the format_coredump is less human-readable and feed easily into gdb
…neCAN logging
On boards without a usable persistant (filesystem, BBSRAM, PROGMEM, SSARC, …),
a possibility is needed to save the full-hardfault-context (registers +
interrupt/user stack).
This PR introduces a .noinit SRAM region, that survives warm resets.
On the next boot, the hardfault_stream streams the raw hex dump
with a CRC32 via PX4_ERR so it is forwarded over DroneCAN LogMessage.
The same streaming path is used by task_watchdog: when CPU-starvation is
detected the task dump and a cpuload snapshot are emitted via PX4_ERR
(MAVLink on FMU boards, DroneCAN on canBoards) instead of writing to SD.
Changes:
- introduce BOARD_HAS_RAM_HARDFAULT_DUMP to tell the system, that there is
no persistent storage (filesystem, BBSRAM, PROGMEM, SSARC, …) available
- hardfault_log.h: add BOARD_HAS_RAM_HARDFAULT_DUMP branch with
px4_ram_hardfault_dump_s; fix #if HAS_BBSRAM (was #if defined()) so
STM32F4 boards without BBSRAM fall through to the RAM dump path
- nuttx/CMakeLists.txt: add logic to inject custom cmake-files
- board_crashdump.c: save hardfault to SRAM (.noinit) and stamp magic
- hardfault_stream: stream hardfault-hex dump + CRC via DroneCAN if
configured
- task_watchdog: stream dump + cpuload via PX4_ERR if configured
- uavcannode: wait for valid id before draining log_msgs
This status is used by hardfault-streamer and task_watchdog.
If UAVcan-logging is active, we have to wait till an ID is
assigned, otherwise log-msgs get lost
033d2e2 to
feaa826
Compare
…og-msg-style
add: task_watchdog-config now allows setting the amount of logged tasks on starvation
fix: linker-script only include linker_preprocessor if linker is linked
fix: documentation
fix: small pr-reviews
fix: publish task_watchdog dump with 200Hz
fix: make variable magic in RAM-hardfault-dump volatile
feaa826 to
1b362cb
Compare
Solved Problem
On boards without a usable persistant memory (filesystem, BBSRAM, PROGMEM, SSARC, …), there is no possibility to save the full-hardfault-context (registers + interrupt/user stack).
Neither does the task_watchdog log starvation via dronecan.
There is currently no possibility to preprocess the linker-file.
UAVcan-Node does not publish its status.
If a critical task (like hardfault_streamer) does publish its log-msgs to early (before an ID was assigned),
log-msgs get lost
Solution
Hardfault-Dump:
This PR introduces a .noinit SRAM region, that survives warm resets.
On the next boot, the hardfault_stream streams the raw hex dump with a CRC32 via PX4_ERR so it is forwarded over DroneCAN LogMessage.
Task_Watchdog
The same streaming path is used by task_watchdog: when CPU-starvation is
detected the task dump and a cpuload snapshot are emitted via PX4_ERR.
Changes:
UAVcan_node
Does now publish it status via uORB
This status is used by hardfault-streamer and task_watchdog.
Test: