Skip to content

Commit

Permalink
[tools] Add ability to coredump from GDB directly
Browse files Browse the repository at this point in the history
By using GDB you can dump all volatile memories including heap and
backup memories. This works even without an ELF file in case it is not
at hand right away. To find the right ELF file later, the GNU build ID
can also be extracted from the firmware.
  • Loading branch information
salkinium committed Feb 20, 2023
1 parent 0d2c126 commit eb2748e
Show file tree
Hide file tree
Showing 18 changed files with 262 additions and 56 deletions.
13 changes: 13 additions & 0 deletions ext/adamgreen/catcher.lb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,22 @@ def init(module):
CrashCatcher hooks into the ARM Cortex-M HardFault handler and generates a coredump
that can be used with CrashDebug for post-mortem debugging.
You must place the `CrashDebug` binary in your path or alternatively set the
environment variable `MODM_CRASHDEBUG_PATH` to point to the enclosing folder:
```sh
export MODM_CRASHDEBUG_PATH=/path/to/crashdebug/bin
```
- https://github.com/adamgreen/CrashCatcher
- https://github.com/adamgreen/CrashDebug
!!! tip "The debugger can generate coredumps too"
In case you encounter a hardfault while debugging or you simply want to
store the current system state for later analysis or to share with other
developers, you can simply call the `modm_coredump` function in GDB and it
will generate a `coredump.txt` file. Consult your chosen build system module
for additional integration.
"""

def prepare(module, options):
Expand Down
13 changes: 7 additions & 6 deletions src/modm/platform/core/cortex/linker.macros
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,13 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }};


%% macro section_rom(memory)
/* build id directly after vector table */
.build_id :
{
__build_id = .;
KEEP(*(.note.gnu.build-id))
} >{{memory}}

/* Read-only sections in {{memory}} */
.text :
{
Expand Down Expand Up @@ -223,12 +230,6 @@ MAIN_STACK_SIZE = {{ options[":platform:cortex-m:main_stack_size"] }};
__assertion_table_end = .;
} >{{memory}}

.build_id :
{
__build_id = .;
KEEP(*(.note.gnu.build-id))
} >{{memory}}

/* We do not call static destructors ever */
/DISCARD/ :
{
Expand Down
4 changes: 3 additions & 1 deletion src/modm/platform/core/cortex/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ def common_vector_table(env):
for vector in driver["vector"]:
interrupts[int(vector["position"])] = vector["name"] + "_IRQHandler"

highest_irq = max(interrupts.keys()) + 1
properties = {
"core": core,
"highest_irq": highest_irq,
"vector_table_location": common_vector_table_location(env),
"vector_table": interrupts,
"highest_irq": max(interrupts.keys()) + 1,
"vector_table_size": (16 + highest_irq) * 4,
}
return properties

Expand Down
12 changes: 12 additions & 0 deletions src/modm/platform/fault/crashcatcher/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ int main()
```


## Coredump via GDB

In case you encounter a HardFault while debugging and you did not include this
module or if you simply want to store the current system state for later
analysis or to share with other developers, you can simply call the
`modm_coredump` function inside GDB and it will generate a `coredump.txt` file.
Note that this coredump file contains all volatile memories including the heap,
so this method is strongly recommended if you can attach a debugger.

Consult your chosen build system module for additional integrations.


## Using the Fault Report

The fault report contains a core dump generated by CrashCatcher and is supposed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,21 +207,21 @@ function(modm_targets_create project_name)
add_custom_command(TARGET debug
USES_TERMINAL
COMMAND ${PYTHON3_EXECUTABLE} modm/modm_tools/gdb.py -x modm/gdbinit -x modm/openocd_gdbinit
${PROJECT_BINARY_DIR}/src/${project_name}.elf --ui=${MODM_DBG_UI} openocd -f modm/openocd.cfg
--elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf --ui=${MODM_DBG_UI} openocd -f modm/openocd.cfg
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})

add_custom_target(debug-bmp DEPENDS ${project_name}.elf)
add_custom_command(TARGET debug-bmp
USES_TERMINAL
COMMAND ${PYTHON3_EXECUTABLE} modm/modm_tools/gdb.py -x modm/gdbinit ${PROJECT_BINARY_DIR}/src/${project_name}.elf
COMMAND ${PYTHON3_EXECUTABLE} modm/modm_tools/gdb.py -x modm/gdbinit --elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf
--ui=${MODM_DBG_UI} bmp -p ${MODM_BMP_PORT}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})

add_custom_target(debug-coredump DEPENDS ${project_name}.elf)
add_custom_command(TARGET debug-coredump
USES_TERMINAL
COMMAND ${PYTHON3_EXECUTABLE} modm/modm_tools/gdb.py -x modm/gdbinit ${PROJECT_BINARY_DIR}/src/${project_name}.elf
--ui=${MODM_DBG_UI} crashdebug --binary-path modm/ext/crashcatcher/bins
COMMAND ${PYTHON3_EXECUTABLE} modm/modm_tools/gdb.py -x modm/gdbinit --elf ${PROJECT_BINARY_DIR}/src/${project_name}.elf
--ui=${MODM_DBG_UI} crashdebug
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})

add_custom_target(log-itm DEPENDS ${project_name}.elf)
Expand Down
6 changes: 0 additions & 6 deletions tools/build_script_generator/gdbinit

This file was deleted.

68 changes: 68 additions & 0 deletions tools/build_script_generator/gdbinit.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright (c) 2023, Niklas Hauser
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------

# Do not ask for confirmation when using Ctrl-D or the quit command
define hook-quit
set confirm off
end

# Create a coredump.txt file in CrashDebug format
define modm_coredump
set pagination off
set style enabled off
set logging file coredump.txt
set logging overwrite on
set logging enabled on

# Dump all volatile memories
%% for ram in all_rams
set var $ptr = 0x{{ "%08x" % ram.start }}
while $ptr < 0x{{ "%08x" % (ram.start + ram.size) }}
x/4wx $ptr
set var $ptr += 16
end

%% endfor

# Dump all CPU/FPU registers
info all-registers

set logging enabled off
set logging overwrite off
set logging file gdb.txt
set style enabled on
set pagination on
end

# Print the modm::build_id() content only from the firmware
define print_build_id
# We want to do a coredump without an ELF file, since you may not have it at
# hand, so we locate it manually: it is the first entry after the vector table
set var $__build_id = 0x08000000 + {{ vector_table_size }}

# We want to do `__build_id.data[__build_id.namesz]` but `ElfNoteSection_t`
# may not even be in the ELF file, so we need to do this manually
set var $start = (const unsigned char*)$__build_id + 12 + *(const unsigned int*)$__build_id
set var $end = $start + 20

# Print the 20 bytes of the signatures as hexadecimal
printf "build_id = "
while $start < $end
printf "%02X", *$start
set var $start += 1
end
printf "\n"
end


set print pretty
set print asm-demangle on
set mem inaccessible-by-default off
compare-sections
b main
23 changes: 23 additions & 0 deletions tools/build_script_generator/make/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,29 @@ from the `coredump={filepath}` argument.
See the `modm:platform:fault` module for details how to receive the coredump data.


#### make coredump

```
make coredump
```

Launches GDB via OpenOCD and creates a `coredump.txt` file containing all
volatile memories and prints the GNU build ID of the firmware under debug.
Note that this command does not require an ELF file, so it can be used to
coredump any firmware whose ELF file is currently unavailable.
(\* *only ARM Cortex-M targets*)


#### make coredump-bmp

```
make coredump-bmp
```

Creates a coredump via Black Magic Probe.
(\* *only ARM Cortex-M targets*)


#### make reset

```
Expand Down
23 changes: 18 additions & 5 deletions tools/build_script_generator/make/resources/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -139,22 +139,35 @@ ui?=tui
debug: build
@python3 $(MODM_PATH)/modm_tools/gdb.py $(addprefix -x ,$(MODM_GDBINIT)) \
$(addprefix -x ,$(MODM_OPENOCD_GDBINIT)) \
$(ELF_FILE) --ui=$(ui) \
--elf $(ELF_FILE) --ui=$(ui) \
openocd $(addprefix -f ,$(MODM_OPENOCD_CONFIGFILES))

.PHONY: debug-bmp
debug-bmp: build
@python3 $(MODM_PATH)/modm_tools/gdb.py $(addprefix -x ,$(MODM_GDBINIT)) \
$(ELF_FILE) --ui=$(ui) \
--elf $(ELF_FILE) --ui=$(ui) \
bmp -p $(port)

coredump?=coredump.txt
.PHONY: debug-coredump
debug-coredump: build
@python3 $(MODM_PATH)/modm_tools/gdb.py $(addprefix -x ,$(MODM_GDBINIT)) \
$(ELF_FILE) --ui=$(ui) \
crashdebug --binary-path $(MODM_PATH)/ext/crashcatcher/bins \
--dump $(coredump)
--elf $(ELF_FILE) --ui=$(ui) \
crashdebug --dump $(coredump)

.PHONY: coredump
coredump:
python3 $(MODM_PATH)/modm_tools/gdb.py $(addprefix -x ,$(MODM_GDBINIT)) \
$(addprefix -x ,$(MODM_OPENOCD_GDBINIT)) \
-ex "modm_coredump" -ex "print_build_id" -ex "quit" \
openocd $(addprefix -f ,$(MODM_OPENOCD_CONFIGFILES))

.PHONY: coredump-bmp
coredump-bmp:
python3 $(MODM_PATH)/modm_tools/gdb.py $(addprefix -x ,$(MODM_GDBINIT)) \
$(addprefix -x ,$(MODM_OPENOCD_GDBINIT)) \
-ex "modm_coredump" -ex "print_build_id" -ex "quit" \
bmp -p $(port)

fcpu?=0
.PHONY: log-itm
Expand Down
17 changes: 8 additions & 9 deletions tools/build_script_generator/module.lb
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,6 @@ def build(env):
env.collect("gitignore", "modm/src/info_build.c")
env.copy("info_build.h")

# Copy crashcatcher binaries
if env.has_module(":crashcatcher"):
env.outbasepath = "modm/ext/crashcatcher"
env.copy(repopath("ext/adamgreen/crashcatcher/CrashDebug/bins/"), "bins/")

# Append common search paths to metadata
if env.has_collector(":build:path.openocd"):
env.collect(":build:path.openocd", "modm/openocd")
Expand Down Expand Up @@ -247,16 +242,20 @@ def post_build(env):
[env.relcwdoutpath(path) for path in env.collector_values("path.openocd")]
env.substitutions["openocd_sources"] = env.collector_values("openocd.source")

linkerscript = env.query(":platform:cortex-m:linkerscript", {})
all_rams = [m for m in linkerscript.get("memories") if "w" in m["access"]]
env.substitutions["all_rams"] = all_rams
vector_table = env.query(":platform:cortex-m:vector_table", {"vector_table_size": 16*4})
env.substitutions["vector_table_size"] = vector_table["vector_table_size"]
env.template("gdbinit.in")

has_rtt = env.has_module(":platform:rtt")
env.substitutions["has_rtt"] = has_rtt
if has_rtt:
main_ram = env.query(":platform:cortex-m:linkerscript", {})
main_ram = main_ram.get("cont_ram_regions", [{"start": 0x20000000, "size": 4096}])[0]
env.substitutions["main_ram"] = main_ram
env.substitutions["main_ram"] = linkerscript.get("cont_ram_regions", [{"start": 0x20000000, "size": 4096}])[0]
env.substitutions["rtt_channels"] = len(env.get(":platform:rtt:buffer.tx", []))
env.template("openocd.cfg.in")
env.template("openocd_gdbinit.in")
env.template("gdbinit")


# ============================ Option Descriptions ============================
Expand Down
41 changes: 35 additions & 6 deletions tools/build_script_generator/scons/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,17 @@ This is just a convenience wrapper for the debug functionality defined in the
program profile=debug` and try `scons debug profile=debug` again.


#### scons debug-remote

```
scons debug-remote profile={debug|release} ui={tui|web} [host={ip or hostname}] [firmware={hash or file}]
```

Debugs the executable via a remote OpenOCD process running on your own computer
(localhost is default) or somewhere else.
(\* *only ARM Cortex-M targets*)


#### scons debug-bmp

```
Expand All @@ -348,20 +359,38 @@ scons debug-coredump profile={debug|release} ui={tui|web} \

Launches GDB for post-mortem debugging with the firmware identified by the
(optional) `firmware={hash or filepath}` argument using the data from the
`coredump={filepath}` argument.
`coredump={filepath}` argument. Note that CrashDebug must be in your path, see
the `modm:crashcatcher` module for details.
(\* *only ARM Cortex-M targets*)

See the `modm:platform:fault` module for details how to receive the coredump data.
Use the `scons coredump` method to generate a coredump with a debugger attached,
otherwise see the `modm:platform:fault` module for details how to generate and
receive the coredump data from the device itself.
(\* *only ARM Cortex-M targets*)


#### scons debug-remote
#### scons coredump

```
scons debug-remote profile={debug|release} ui={tui|web} [host={ip or hostname}] [firmware={hash or file}]
scons coredump
```

Debugs the executable via a remote OpenOCD process running on your own computer
(localhost is default) or somewhere else.
Launches GDB via OpenOCD and creates a `coredump.txt` file containing all
volatile memories and prints the GNU build ID of the firmware under debug.
Note that this command does not require an ELF file, so it can be used to
coredump any firmware whose ELF file is currently unavailable.
You can use the GNU build ID to find the corresponding ELF file in your
artifact store (see `scons artifact`).
(\* *only ARM Cortex-M targets*)


#### scons coredump-bmp

```
scons coredump-bmp
```

Creates a coredump via Black Magic Probe.
(\* *only ARM Cortex-M targets*)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,14 @@ def build_target(env, sources):
# Start only OpenOCD to attach a external (remote) debugger
env.Alias("openocd", env.OpenOcd())

env.Alias("coredump-openocd", env.CoredumpOpenOcd())
env.Alias("coredump-bmp", env.CoredumpBMP())

# Default to OpenOCD
env.Alias("program", "program-openocd")
env.Alias("reset", "reset-openocd")
env.Alias("debug", "debug-openocd")
env.Alias("coredump", "coredump-openocd")

%% elif core.startswith("avr")
env.Alias("program-avrdude", env.ProgramAvrdude(chosen_program))
Expand Down
14 changes: 14 additions & 0 deletions tools/build_script_generator/scons/site_tools/bmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ def call_bmp_debug(target, source, env):
action = Action(call_bmp_debug, cmdstr="$BMP_DEBUG_COMSTR")
return env.AlwaysBuild(env.Alias(alias, source, action))

# -----------------------------------------------------------------------------
def black_magic_probe_coredump(env, alias='black_magic_probe_coredump'):
def call_bmp_coredump(target, source, env):
backend = bmp.BlackMagicProbeBackend(port=ARGUMENTS.get("port", "auto"))
commands = map(env.subst, env.Listify(env.get("MODM_GDB_COMMANDS", [])))
commands += ["modm_coredump", "print_build_id", "quit"]
gdb.call(backend=backend,
config=map(env.subst, env.Listify(env.get("MODM_GDBINIT", []))),
commands=commands)

action = Action(call_bmp_coredump, cmdstr="$BMP_DEBUG_COMSTR")
return env.AlwaysBuild(env.Alias(alias, '', action))

# -----------------------------------------------------------------------------
def black_magic_probe_reset(env, alias='black_magic_probe_reset'):
def call_bmp_reset(target, source, env):
Expand All @@ -47,6 +60,7 @@ def generate(env, **kw):
env.AddMethod(black_magic_probe_program, 'ProgramBMP')
env.AddMethod(black_magic_probe_debug, 'DebugBMP')
env.AddMethod(black_magic_probe_reset, 'ResetBMP')
env.AddMethod(black_magic_probe_coredump, 'CoredumpBMP')

def exists(env):
return True
Loading

0 comments on commit eb2748e

Please sign in to comment.