This tutorial will cover integrating the coredump collection functionality of the Memfault Firmware SDK into your system.
With coredumps integrated, variable, register, & task state can all be collected at the time a fault, assert, or unexpected error takes place.
This guide assumes you have already completed the minimal integration of the Memfault SDK. If you have not, check out the appropriate guide in the table below.
|MCU Architecture||Getting Started Guide|
|ARM Cortex-M||ARM Cortex-M Integration Guide|
|nRF Connect SDK||nRF Connect SDK Integration Guide|
|ESP32 ESP-IDF||ESP32 ESP-IDF Integration Guide|
|ESP8266||ESP8266 RTOS Integration Guide|
When a crash takes place, a snapshot of the state needs to be stored in a storage area that persists across a device reboot.
We typically recommend starting with the RAM backed Coredump port from
By default this will only save the top of the stack at the time of crash but it lets you quickly get coredumps up and running and get a feel for how things work.
Coredump data can also be stored to any other backing storage (eMMC, external
NOR flash, internal flash, etc). We have a number of ports available for
different MCUs in the
directory of the memfault-firmware-sdk or you can add your own port by
implementing the required
At the very end of saving a coredump, the
implemented as part of your initial port will be called. From here any final
system cleanup can be performed before restarting the system
For the best debugging experience it is best to store all of RAM to backing storage. However, this is not always possible or desirable. For one, there simply may not be enough storage to preserve all of RAM. In other cases there may be sensitive data on the device that should not be sent off device. In this section you will learn how to choose what to save and how to make this easier for unrelated sections of RAM in the case where you cannot or should not preserve all of RAM.
This tutorial assumes a
gcc toolchain but the concepts are similar with other
Memfault coredumps are designed to be flexible allowing the user to choose only what they want or what they can fit into the coredump data storage. Memfault uses coredump regions to describe what regions of memory are to be gathered up and preserved when a fault exception or user trace occurs.
For supported RTOSs like FreeRTOS or Zephyr Memfault automatically collects task information if you use the Memfault port. But what if you have a special block of data or set of structures in your application that you would like to capture as part of a coredump? What if you want to store all of RAM except a particular set of structures?
To achieve this control we will use the linker to locate named sections within the memory map of your application. Using those section names and special compiler attributes we can assign any object to a given section.
Once the objects are located in named sections we will add those sections as
regions to the coredump saving logic. This last part will require modifying an
existing version of
memfault_platform_coredump_get_regions() or writing your
own implementation to override the default if you prefer.
Locate the linker script file used to layout the memory map for your application. Sometimes this file is generated from a master file so you will need to modify the master file to prevent losing your changes after a clean rebuild.
The linker script file conventionally has an extension of
.ld but that is not
required. You may need to look for the script file callout in the link line of
your applications, e.g.
gcc <object files> -Wl,-T<script-file>. The
may be written out in long form as
Within the linker script file there is a section called
SECTIONS. This is
where the linker is told where to place zero-initialized data (
initialized data (
.data), and code (
.text) and read-only constants
.rodata). There are other sections like stack and heap but we don't allocate
objects to those sections at build time.
For simplicity, allocate your important objects in one file, for example, in an
important_allocations.c source file. These objects must either be declared
static or be defined at file-scope (global). This example assumes you are not
saving all of RAM as part of a coredump.
In the linker script specifically call out the resultant object file
important_allocations.o as shown below (highlighted).
Be sure to add the highlighted lines between the vendor supplied
__xxx_end labels. If your compiler emits
.obj instead of
object files then be sure to change the extension, or wildcard it, in the linker
script. The leading wildcard symbol removes any path information that may be
prepended by the toolchain.
This modification tells the linker to ensure that the every static and
file-scoped variable allocated in
important_allocations.c will be located
between the respective symbols
Next, add these two regions to your
implementation with entries like this.
With these changes in place you should now see these two objects as part of the Globals & Statics display of the Memfault Issues->Coredump page.
This scenario is slightly different from the previous example in that we wish to
capture particular variables, possibly from many different source files, in the
coredump. For this we need to place variables in a named section so they can be
collected by the linker into a single region of memory. Use compiler directives
to place these important variables in the named section. Memfault provides a
MEMFAULT_PUT_IN_SECTION(), to help with this.
In the default
.data sections in the linker script, collect the
names sections as shown below (highlighted). We still need the start and end
labels to add them to the coredump region list.
Like before, add these two regions to your
memfault_platform_coredump_get_regions(). Because we have used the same label
memfault_xxx_yyy, as in the previous example please refer back to the
previous code snippet as it is valid for this example as well.
Now allocate some variables to these sections. For this example, place variables from two source files into the new sections.
Another benefit working with linker scripts is the ability to place objects or files' data at specific locations or addresses. Often microcontrollers (MCUs) have special memory regions that have some unique characteristic you may wish to take advantage of. Also, it is common to share regions of memory between separate applications on one MCU like a bootloader and the production application. Lastly, it is often the case that you need to persist information across reboots.
In these instances the linker script allows control over the placement and allocation of objects in the MCU's address space. The following example demonstrates how to locate DMA buffers in on-chip "fast" RAM and allocate a shared memory area between a bootloader and an application. The linker script below shows the section layout with the addition of memory regions.
This has implications for your startup code. For zero initialized variables you need to explicitly zero them before use, if desired, and for value initialized variables (not used here) you will need to copy the initial values into the variables before use. This is because the default C run-time will not be aware of your special sections.
By using the location macro you can allocate DMA buffers and ensure they are in the correct memory range.
To share a structure between your bootloader and production application the boot
loader would need to ensure that its linker script file had a matching entry for
SHAREDRAM memory region and the
.shared section. After that, the two
separate applications could exchange information in common data structures by
having a shared allocation file linked into both applications.
This last example assumes that you will initialize the values appropriately. The values will persist across MCU resets.