Heap Memory Debugging - ESP32 (original) (raw)

[中文]

Overview

ESP-IDF integrates tools for requesting heap information, heap corruption detection, and heap tracing. These can help track down memory-related bugs.

For general information about the heap memory allocator, see Heap Memory Allocation.

Heap Information

To obtain information about the state of the heap, call the following functions:

Heap Allocation and Free Function Hooks

Users can use allocation and free detection hooks to be notified of every successful allocation and free operation:

This feature can be enabled by setting the CONFIG_HEAP_USE_HOOKS option. esp_heap_trace_alloc_hook() and esp_heap_trace_free_hook() have weak declarations (e.g., __attribute__((weak))), thus it is not necessary to provide declarations for both hooks. Given that it is technically possible to allocate and free memory from an ISR (though strongly discouraged from doing so), the esp_heap_trace_alloc_hook() and esp_heap_trace_free_hook() can potentially be called from an ISR.

It is not recommended to perform (or call API functions to perform) blocking operations or memory allocation/free operations in the hook functions. In general, the best practice is to keep the implementation concise and leave the heavy computation outside of the hook functions.

The example below shows how to define the allocation and free function hooks:

#include "esp_heap_caps.h"

void esp_heap_trace_alloc_hook(void* ptr, size_t size, uint32_t caps) { ... } void esp_heap_trace_free_hook(void* ptr) { ... }

void app_main() { ... }

Memory Allocation Failed Hook

Users can use heap_caps_register_failed_alloc_callback() to register a callback that is invoked every time an allocation operation fails.

Additionally, users can enable the CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS, which will automatically trigger a system abort if any allocation operation fails.

The example below shows how to register an allocation failure callback:

#include "esp_heap_caps.h"

void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const char *function_name) { printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",function_name, requested_size, caps); }

void app_main() { ... esp_err_t error = heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook); ... void *ptr = heap_caps_malloc(allocation_size, MALLOC_CAP_DEFAULT); ... }

Heap Corruption Detection

Heap corruption detection allows you to detect various types of heap memory errors:

Three levels of corruption detection are available. Each one providing a finer level of detection than the previous:

Assertions

The heap implementation (heap/multi_heap.c, etc.) includes numerous assertions that will fail if the heap memory is corrupted. To detect heap corruption most effectively, ensure that assertions are enabled in the project configuration via the CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL option.

If a heap integrity assertion fails, a line will be printed like CORRUPT HEAP: multi_heap.c:225 detected at 0x3ffbb71c. The memory address printed is the address of the heap structure that has corrupt content.

It is also possible to manually check heap integrity by calling heap_caps_check_integrity_all() or related functions. This function checks all of the requested heap memory for integrity and can be used even if assertions are disabled. If the integrity checks detects an error, it will print the error along with the address(es) of corrupt heap structures.

Finding Heap Corruption

Memory corruption can be one of the hardest classes of bugs to find and fix, as the source of the corruption could be completely unrelated to the symptoms of the corruption. Here are some tips:

Configuration

Temporarily increasing the heap corruption detection level can give more detailed information about heap corruption errors.

In the project configuration menu, under Component config, there is a menu Heap memory debugging. The option CONFIG_HEAP_CORRUPTION_DETECTION can be set to one of the following three levels:

Basic (No Poisoning)

This is the default level. By default, no special heap corruption features are enabled, but the provided assertions are enabled. A heap corruption error will be printed if any of the heap's internal data structures appear overwritten or corrupted. This usually indicates a buffer overrun or out-of-bounds write.

If assertions are enabled, an assertion will also trigger if a double-free occurs (the same memory is freed twice).

Calling heap_caps_check_integrity() in Basic mode checks the integrity of all heap structures, and print errors if any appear to be corrupted.

Light Impact

This level incorporates the "Basic" detection features. Additionally, each block of memory allocated is "poisoned" with head and tail "canary bytes". If an application writes over the "canary bytes", they will be seen as corrupted and integrity checks will fail.

The head canary word is 0xABBA1234 (3412BAAB in byte order), and the tail canary word is 0xBAAD5678 (7856ADBA in byte order).

With basic heap corruption checks, most out-of-bound writes can be detected and the number of overrun bytes before a failure is detected depends on the properties of the heap. However, the Light Impact mode is more precise as even a single-byte overrun can be detected.

Enabling light-impact checking increases the memory usage since each individual allocation uses additional bytes of metadata.

Each time heap_caps_free() is called in Light Impact mode, the head and tail canary bytes of the buffer being freed are checked against the expected values.

When heap_caps_check_integrity() or heap_caps_check_integrity_all() is called, all allocated blocks of heap memory have their canary bytes checked against the expected values.

In both cases, the functions involve checking that the first 4 bytes of an allocated block (before the buffer is returned to the user) should be the word 0xABBA1234, and the last 4 bytes of the allocated block (after the buffer is returned to the user) should be the word 0xBAAD5678.

Different values usually indicate buffer underrun or overrun. Overrun indicates that when writing to memory, the data written exceeds the size of the allocated memory, resulting in writing to an unallocated memory area; underrun indicates that when reading memory, the data read exceeds the allocated memory and reads data from an unallocated memory area.

Comprehensive

This level incorporates the "Light Impact" detection features. Additionally, it checks for uninitialized-access and use-after-free bugs. In this mode, all freshly allocated memory is filled with the pattern 0xCE, and all freed memory is filled with the pattern 0xFE.

Enabling Comprehensive mode has a substantial impact on runtime performance, as all memory needs to be set to the allocation patterns each time a heap_caps_malloc() or heap_caps_free() completes, and the memory also needs to be checked each time. However, this mode allows easier detection of memory corruptions which are much more subtle to find otherwise. It is recommended to only enable this mode when debugging, not in production.

The checks for allocated and free patterns (0xCE and 0xFE, respectively) are also done when calling heap_caps_check_integrity() or heap_caps_check_integrity_all().

Crashes in Comprehensive Mode

If an application crashes when reading or writing an address related to 0xCECECECE in Comprehensive mode, it indicates that it has read uninitialized memory. The application should be changed to either use heap_caps_calloc() (which zeroes memory), or initialize the memory before using it. The value 0xCECECECE may also be seen in stack-allocated automatic variables, because, in ESP-IDF, most task stacks are originally allocated from the heap, and in C, stack memory is uninitialized by default.

If an application crashes, and the exception register dump indicates that some addresses or values were 0xFEFEFEFE, this indicates that it is reading heap memory after it has been freed, i.e., a "use-after-free bug". The application should be changed to not access heap memory after it has been freed.

If a call to heap_caps_malloc() or heap_caps_realloc() causes a crash because it was expected to find the pattern 0xFEFEFEFE in free memory and a different pattern was found, it indicates that the app has a use-after-free bug where it is writing to memory that has already been freed.

Manual Heap Checks in Comprehensive Mode

Calls to heap_caps_check_integrity() or heap_caps_check_integrity_all() may print errors relating to 0xFEFEFEFE, 0xABBA1234, or 0xBAAD5678. In each case the checker is expected to find a given pattern, and will error out if not found:

Heap Task Tracking

The Heap Task Tracking can be enabled via the menuconfig: Component config > Heap memory debugging > Enable heap task tracking (see CONFIG_HEAP_TASK_TRACKING).

The feature allows users to track the heap memory usage of each task created since startup and provides a series of statistics that can be accessed via getter functions or simply dumped into the stream of the user's choosing. This feature is useful for identifying memory usage patterns and potential memory leaks.

An additional configuration can be enabled by the user via the menuconfig: Component config > Heap memory debugging > Keep information about the memory usage of deleted tasks (see CONFIG_HEAP_TRACK_DELETED_TASKS) to keep the statistics collected for a given task even after it is deleted.

Note

Note that the Heap Task Tracking cannot detect the deletion of statically allocated tasks. Therefore, users will have to keep in mind while reading the following section that statically allocated tasks will always be considered alive in the scope of the Heap Task Tracking feature.

It is important to mention that its usage is strongly discouraged for other purposes than debugging for the following reasons:

Note

Note that the memory allocated by the heap task tracking feature will not be visible when dumping or accessing the statistics.

Structure of the statistics and information

For a given task, the heap task tracking feature categorizes statistics on three different levels:

The task level statistics provides the following information:

The heap level statistics provides the following information for each heap used by the given task:

The allocation level statistics provides the following information for each allocation done by the given task on the given heap:

Dumping the statistics and information

The heap_caps_print_single_task_stat_overview() API prints an overview of heap usage for a specific task to the provided output stream.

┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐ │ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │ ├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤ │ task_name │ ALIVE │ 0 │ 7152 │ 1 │ └────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘

heap_caps_print_all_task_stat_overview() prints an overview of heap usage for all tasks (including the deleted tasks if CONFIG_HEAP_TRACK_DELETED_TASKS is enabled).

┌────────────────────┬─────────┬──────────────────────┬───────────────────┬─────────────────┐ │ TASK │ STATUS │ CURRENT MEMORY USAGE │ PEAK MEMORY USAGE │ TOTAL HEAP USED │ ├────────────────────┼─────────┼──────────────────────┼───────────────────┼─────────────────┤ │ task_name │ DELETED │ 11392 │ 11616 │ 1 │ │ other_task_name │ ALIVE │ 0 │ 9408 │ 2 │ │ main │ ALIVE │ 3860 │ 7412 │ 2 │ │ ipc1 │ ALIVE │ 32 │ 44 │ 1 │ │ ipc0 │ ALIVE │ 10080 │ 10092 │ 1 │ │ Pre-scheduler │ ALIVE │ 2236 │ 2236 │ 1 │ └────────────────────┴─────────┴──────────────────────┴───────────────────┴─────────────────┘

Note

Note that the task named “Pre-scheduler” represents allocations that occurred before the scheduler was started. It is not an actual task, so the "status" field (which is shown as "ALIVE") is not meaningful and should be ignored.

Use heap_caps_print_single_task_stat() to dump the complete set of statistics for a specific task, or heap_caps_print_all_task_stat() to dump statistics for all tasks:

[...] ├ ALIVE: main, CURRENT MEMORY USAGE 308, PEAK MEMORY USAGE 7412, TOTAL HEAP USED 2: │ ├ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 220 (0%), PEAK 220 (0%), ALLOC COUNT: 2 │ │ ├ ALLOC 0x3fc99024, SIZE 88 │ │ ├ ALLOC 0x3fc99124, SIZE 132 │ └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 22308, USAGE: CURRENT 88 (0%), PEAK 7192 (32%), ALLOC COUNT: 5 │ ├ ALLOC 0x3fce99f8, SIZE 20 │ ├ ALLOC 0x3fce9a10, SIZE 12 │ ├ ALLOC 0x3fce9a20, SIZE 16 │ ├ ALLOC 0x3fce9a34, SIZE 20 │ ├ ALLOC 0x3fce9a4c, SIZE 20 [...] └ ALIVE: Pre-scheduler, CURRENT MEMORY USAGE 2236, PEAK MEMORY USAGE 2236, TOTAL HEAP USED 1: └ HEAP: RAM, CAPS: 0x0010580e, SIZE: 344400, USAGE: CURRENT 2236 (0%), PEAK 2236 (0%), ALLOC COUNT: 11 ├ ALLOC 0x3fc95cb0, SIZE 164 ├ ALLOC 0x3fc95dd8, SIZE 12 ├ ALLOC 0x3fc95dfc, SIZE 12 ├ ALLOC 0x3fc95e20, SIZE 16 ├ ALLOC 0x3fc95e48, SIZE 24 ├ ALLOC 0x3fc95e78, SIZE 88 ├ ALLOC 0x3fc95ee8, SIZE 88 ├ ALLOC 0x3fc95f58, SIZE 88 ├ ALLOC 0x3fc95fc8, SIZE 88 ├ ALLOC 0x3fc96038, SIZE 1312 ├ ALLOC 0x3fc96570, SIZE 344

Note

The dump shown above has been truncated (see "[...]") for readability reasons and only displays the statistics and information of the main task and the Pre-scheduler. The goal here is only to demonstrate the information displayed when calling the heap_caps_print_all_task_stat() (resp. heap_caps_print_single_task_stat()) API functions.

Getting the statistics and information

heap_caps_get_single_task_stat() allows the user to access information of a specific task. The information retrieved by calling this API is identical to the one dumped using heap_caps_print_single_task_stat().

heap_caps_get_all_task_stat() allows the user to access an overview of the information of all tasks (including the deleted tasks if CONFIG_HEAP_TRACK_DELETED_TASKS is enabled). The information retrieved by calling this API is identical to the one dumped using heap_caps_print_all_task_stat().

Each getter function requires a pointer to the data structure that will be used by the heap task tracking to gather the statistics and information of a given task (or all tasks). This data structure contains pointers to arrays that the user can allocate statically or dynamically.

Due to the difficulty of estimating the size of the arrays used to store information — since it's hard to determine the number of allocations per task, the number of heaps used by each task, and the number of tasks created since startup — the heap task tracking also provides heap_caps_alloc_single_task_stat_arrays() (resp. heap_caps_alloc_all_task_stat_arrays()) to dynamically allocate the required amount of memory for those arrays.

Similarly, the heap task tracking also provides heap_caps_free_single_task_stat_arrays() (resp. heap_caps_free_all_task_stat_arrays()) to free the memory dynamically allocated when calling heap_caps_alloc_single_task_stat_arrays() (resp. heap_caps_alloc_all_task_stat_arrays()).

Heap Tracing

Heap Tracing allows the tracing of code which allocates or frees memory. Two tracing modes are supported:

Heap tracing can perform two functions:

How to Diagnose Memory Leaks

If you suspect a memory leak, the first step is to figure out which part of the program is leaking memory. Use the heap_caps_get_free_size() or related functions in heap information to track memory use over the life of the application. Try to narrow the leak down to a single function or sequence of functions where free memory always decreases and never recovers.

Standalone Mode

Once you have identified the code which you think is leaking:

The following code snippet demonstrates how application code would typically initialize, start, and stop heap tracing:

#include "esp_heap_trace.h"

#define NUM_RECORDS 100 static heap_trace_record_t trace_record[NUM_RECORDS]; // This buffer must be in internal RAM

...

void app_main() { ... ESP_ERROR_CHECK( heap_trace_init_standalone(trace_record, NUM_RECORDS) ); ... }

void some_function() { ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );

do_something_you_suspect_is_leaking();

ESP_ERROR_CHECK( heap_trace_stop() );
heap_trace_dump();
...

}

The output from the heap trace has a similar format to the following example:

====== Heap Trace: 8 records (8 capacity) ====== 6 bytes (@ 0x3fc9f620, Internal) allocated CPU 0 ccount 0x1a31ac84 caller 0x40376321:0x40376379 0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

9 bytes (@ 0x3fc9f630, Internal) allocated CPU 0 ccount 0x1a31b618 caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

12 bytes (@ 0x3fc9f640, Internal) allocated CPU 0 ccount 0x1a31bfac caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

15 bytes (@ 0x3fc9f650, Internal) allocated CPU 0 ccount 0x1a31c940 caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

18 bytes (@ 0x3fc9f664, Internal) allocated CPU 0 ccount 0x1a31d2d4 caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

21 bytes (@ 0x3fc9f67c, Internal) allocated CPU 0 ccount 0x1a31dc68 caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

24 bytes (@ 0x3fc9f698, Internal) allocated CPU 0 ccount 0x1a31e600 caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

freed by 0x403839e4:0x42008096 0x403839e4: free at /path/to/idf/examples/components/newlib/heap.c:40 0x42008096: test_func_74 at /path/to/idf/examples/components/heap/test_apps/heap_tests/main/test_heap_trace.c:104 (discriminator 3)

6 bytes (@ 0x3fc9f6b4, Internal) allocated CPU 0 ccount 0x1a320698 caller 0x40376321:0x40376379

0x40376321: heap_caps_malloc at /path/to/idf/examples/components/heap/heap_caps.c:84 0x40376379: heap_caps_malloc_default at /path/to/idf/examples/components/heap/heap_caps.c:110

====== Heap Trace Summary ====== Mode: Heap Trace All 6 bytes alive in trace (1/8 allocations) records: 8 (8 capacity, 8 high water mark) total allocations: 9 total frees: 8

Note

The above example output uses IDF Monitor to automatically decode PC addresses to their source files and line numbers.

(NB: Internal Buffer has overflowed, so trace data is incomplete.) will be logged if the list of records overflow. If you see this log, consider either shortening the tracing period or increasing the number of records in the trace buffer.

(NB: New entries were traced while dumping, so trace dump may have duplicate entries.) will be logged in the summary if new entries are traced while calling heap_trace_dump() or heap_trace_dump_caps().

In HEAP_TRACE_LEAKS or HEAP_TRACE_ALL mode, for each traced memory allocation that has not already been freed, a line is printed with:

In HEAP_TRACE_LEAKS mode, when memory is freed, the associated record is dropped.

In HEAP_TRACE_ALL:

The depth of the call stack recorded for each trace entry can be configured in the project configuration menu, under Heap Memory Debugging > Enable heap tracing > CONFIG_HEAP_TRACING_STACK_DEPTH. Up to 32 stack frames can be recorded for each allocation (the default is 2). Each additional stack frame increases the memory usage of each heap_trace_record_t record by eight bytes.

Finally, the total number of the 'leaked' bytes (bytes allocated but not freed while the trace is running) is printed together with the total number of allocations it represents.

Using hashmap for increased performance

By default, the heap tracing uses a statically allocated doubly-linked list to store the trace records. This has the disadvantage of causing runtime performance issues as the list gets fuller since the more items are in the list, the more time consuming it is to find a given item. This problem makes the use of the doubly linked list particularly inefficient if the user wishes to store a very large amount of records (to the point where the feature is simply no longer usable as the time it takes to retrieve an item in the list prevents the user application from executing properly).

For this reason, the option to use a hashmap mechanism to store records is available by enabling Component config > Heap Memory Debugging > CONFIG_HEAP_TRACE_HASH_MAP in the project configuration menu, allowing users to track significant amounts of records without suffering from drastic performance loss.

Each hashmap entry is a singly linked list of records sharing the same hash ID.

Each record hash ID is calculated based on the pointer to the memory they track. The hash function used is based on the Fowler-Noll-Vo hash function modified to ensure an even spread of all records in the range [0, hashmap size[ where hashmap size can be defined by setting Component config > Heap Memory Debugging > CONFIG_HEAP_TRACE_HASH_MAP_SIZE in the project configuration menu.

Note

Host-Based Mode

Once you have identified the code which you think is leaking:

The following code snippet demonstrates how application code would typically initialize, start, and stop host-based mode heap tracing:

#include "esp_heap_trace.h"

...

void app_main() { ... ESP_ERROR_CHECK( heap_trace_init_tohost() ); ... }

void some_function() { ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );

do_something_you_suspect_is_leaking();

ESP_ERROR_CHECK( heap_trace_stop() );
...

}

To gather and analyze heap trace, do the following on the host:

  1. Build the program and download it to the target as described in Step 5. First Steps on ESP-IDF.
  2. Run OpenOCD (see JTAG Debugging).

Note

In order to use this feature, you need OpenOCD version v0.10.0-esp32-20181105 or later.

  1. You can use GDB to start and/or stop tracing automatically. To do this you need to prepare a special gdbinit file:

target remote :3333

mon reset halt maintenance flush register-cache

tb heap_trace_start commands mon esp sysview start file:///tmp/heap.svdat c end

tb heap_trace_stop commands mon esp sysview stop end

c

Using this file GDB can connect to the target, reset it, and start tracing when the program hits breakpoint at heap_trace_start(). Tracing will be stopped when the program hits breakpoint at heap_trace_stop(). Traced data will be saved to /tmp/heap_log.svdat.

  1. Run GDB using xtensa-esp32-elf-gdb -x gdbinit </path/to/program/elf>.
  2. Quit GDB when the program stops at heap_trace_stop(). Traced data are saved in /tmp/heap.svdat.
  3. Run processing script $IDF_PATH/tools/esp_app_trace/sysviewtrace_proc.py -p -b </path/to/program/elf> /tmp/heap_log.svdat.

The output from the heap trace has a similar format to the following example:

Parse trace from '/tmp/heap.svdat'... Stop parsing trace. (Timeout 0.000000 sec while reading 1 bytes!) Process events from '['/tmp/heap.svdat']'... [0.002244575] HEAP: Allocated 1 bytes @ 0x3ffaffd8 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.002258425] HEAP: Allocated 2 bytes @ 0x3ffaffe0 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:48 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.002563725] HEAP: Freed bytes @ 0x3ffaffe0 from task "free" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:31 (discriminator 9) /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.002782950] HEAP: Freed bytes @ 0x3ffb40b8 from task "main" on core 0 by: /home/user/projects/esp/esp-idf/components/freertos/tasks.c:4590 /home/user/projects/esp/esp-idf/components/freertos/tasks.c:4590

[0.002798700] HEAP: Freed bytes @ 0x3ffb50bc from task "main" on core 0 by: /home/user/projects/esp/esp-idf/components/freertos/tasks.c:4590 /home/user/projects/esp/esp-idf/components/freertos/tasks.c:4590

[0.102436025] HEAP: Allocated 2 bytes @ 0x3ffaffe0 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.102449800] HEAP: Allocated 4 bytes @ 0x3ffaffe8 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:48 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.102666150] HEAP: Freed bytes @ 0x3ffaffe8 from task "free" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:31 (discriminator 9) /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.202436200] HEAP: Allocated 3 bytes @ 0x3ffaffe8 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.202451725] HEAP: Allocated 6 bytes @ 0x3ffafff0 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:48 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.202667075] HEAP: Freed bytes @ 0x3ffafff0 from task "free" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:31 (discriminator 9) /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.302436000] HEAP: Allocated 4 bytes @ 0x3ffafff0 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.302451475] HEAP: Allocated 8 bytes @ 0x3ffb40b8 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:48 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.302667500] HEAP: Freed bytes @ 0x3ffb40b8 from task "free" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:31 (discriminator 9) /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

Processing completed.

Processed 1019 events

=============== HEAP TRACE REPORT ===============

Processed 14 heap events.

[0.002244575] HEAP: Allocated 1 bytes @ 0x3ffaffd8 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.102436025] HEAP: Allocated 2 bytes @ 0x3ffaffe0 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.202436200] HEAP: Allocated 3 bytes @ 0x3ffaffe8 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

[0.302436000] HEAP: Allocated 4 bytes @ 0x3ffafff0 from task "alloc" on core 0 by: /home/user/projects/esp/esp-idf/examples/system/sysview_tracing_heap_log/main/sysview_heap_log.c:47 /home/user/projects/esp/esp-idf/components/freertos/port.c:355 (discriminator 1)

Found 10 leaked bytes in 4 blocks.

Heap Tracing To Find Heap Corruption

Heap tracing can also be used to help track down heap corruption. When a region in the heap is corrupted, it may be from some other part of the program that allocated memory at a nearby address.

If you have an approximate idea of when the corruption occurred, enabling heap tracing in HEAP_TRACE_ALL mode allows you to record all the memory allocation functions used and the corresponding allocation addresses.

Using heap tracing in this way is very similar to memory leak detection as described above. For memories that are allocated and not freed, the output is the same. However, records will also be shown for memory that has been freed.

Performance Impact

Enabling heap tracing in menuconfig increases the code size of your program, and has a very small negative impact on the performance of heap allocation or free operations even when heap tracing is not running.

When heap tracing is running, heap allocation or free operations are substantially slower than when heap tracing is stopped. Increasing the depth of stack frames recorded for each allocation (see above) also increases this performance impact.

To mitigate the performance loss when the heap tracing is enabled and active, enable CONFIG_HEAP_TRACE_HASH_MAP. With this configuration enabled, a hash map mechanism will be used to handle the heap trace records, thus considerably decreasing the heap allocation or free execution time. The size of the hash map can be modified by setting the value of CONFIG_HEAP_TRACE_HASH_MAP_SIZE.

By default, the hash map is placed into internal RAM. It can also be placed into external RAM if CONFIG_HEAP_TRACE_HASH_MAP_IN_EXT_RAM is enabled. In order to enable this configuration, make sure to enable CONFIG_SPIRAM and CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY.

False-Positive Memory Leaks

Not everything printed by heap_trace_dump() is necessarily a memory leak. The following cases may also be printed:

One way to differentiate between "real" and "false positive" memory leaks is to call the suspect code multiple times while tracing is running, and look for patterns (multiple matching allocations) in the heap trace output.

Application Examples

API Reference - Heap Task Tracking

Functions

size_t heap_caps_get_per_task_info(heap_task_info_params_t *params)

Return per-task heap allocation totals and lists of blocks.

For each task that has allocated memory from the heap, return totals for allocations within regions matching one or more sets of capabilities.

Optionally also return an array of structs providing details about each block allocated by one or more requested tasks, or by all tasks.

See also

heap_task_block_t).

Parameters:

params -- Structure to hold all the parameters for the function (

Returns:

Number of block detail structs returned (

esp_err_t heap_caps_get_all_task_stat(heap_all_tasks_stat_t *tasks_stat)

Return per-task heap memory usage and associated allocation information on each heap for all tasks.

For each task that has allocated memory from the heap, return information of memory usage and allocation information of the task on each heap the task has used.

See also

heap_all_tasks_stat_t).

Parameters:

tasks_stat -- Structure to hold the memory usage statistics of all tasks (

Returns:

ESP_OK if the information were gathered successfully. ESP_ERR_INVALID_ARG if the user defined field in heap_all_tasks_stat_t are not set properly

esp_err_t heap_caps_get_single_task_stat(heap_single_task_stat_t *task_stat, TaskHandle_t task_handle)

Return heap memory usage and associated allocation information on each heap for a given task.

Parameters:

Returns:

ESP_OK if the information were gathered successfully. ESP_ERR_INVALID_ARG if the user defined field in heap_single_task_stat_t are not set properly

void heap_caps_print_all_task_stat(FILE *stream)

Print heap memory usage and associated allocation information on each heap for all created tasks since startup (running and deleted ones when CONFIG_HEAP_TRACK_DELETED_TASKS is enabled).

Note

This function is an alternative to heap_caps_get_all_task_stat if the goal is just to print information and not manipulate them.

Parameters:

stream -- The stream to dump to, if NULL then stdout is used

void heap_caps_print_all_task_stat_overview(FILE *stream)

Print summary information of all tasks.

Note

The information printed by this function is an array formatted log of task_stat_t content for each running task (and deleted ones if HEAP_TRACK_DELETED_TASKS is enabled)

Parameters:

stream -- The stream to dump to, if NULL then stdout is used

void heap_caps_print_single_task_stat(FILE *stream, TaskHandle_t task_handle)

Print heap memory usage and associated allocation information on each heap for a given task.

Note

This function is an alternative to heap_caps_get_single_task_stat if the goal is just to print information and not manipulate them.

Parameters:

void heap_caps_print_single_task_stat_overview(FILE *stream, TaskHandle_t task_handle)

Print summary information of a given task.

Note

The information printed by this function is an array formatted log of task_stat_t content for the given task. This function will not print the task summary information if the given task is deleted and HEAP_TRACK_DELETED_TASKS is disabled.

Parameters:

esp_err_t heap_caps_alloc_single_task_stat_arrays(heap_single_task_stat_t *task_stat, TaskHandle_t task_handle)

Allocate the memory used to store the heap and alloc statistics and fill task_stat with the pointer to those allocations and the number of heaps and allocs statistics available for the given task.

Note

If NULL is passed as parameter for the task_handle, the information on the currently running task will be returned. This function should be called prior to heap_caps_get_single_task_stat() if the user wishes to use dynamic allocation to store statistics.

Parameters:

Returns:

ESP_OK if the memory necessary to gather the statistics was allocated successfully. ESP_FAIL if not enough memory space is available to store all statistics.

void heap_caps_free_single_task_stat_arrays(heap_single_task_stat_t *task_stat)

Free the memory allocated to store heap and alloc statistics by calling heap_caps_alloc_single_task_stat_arrays.

Parameters:

task_stat -- Structure from which to free the allocated memory used to store statistics

esp_err_t heap_caps_alloc_all_task_stat_arrays(heap_all_tasks_stat_t *tasks_stat)

Allocate the memory used to store the tasks, heaps and allocs statistics and fill tasks_stat with the pointer to those allocations and the number of tasks, heaps and allocs statistics available.

Note

This function should be called prior to heap_caps_get_all_task_stat() if the user wishes to use dynamic allocation to store statistics.

Parameters:

tasks_stat -- Structure containing information filled by this function.

Returns:

ESP_OK if the memory necessary to gather the statistics was allocated successfully. ESP_FAIL if not enough memory space is available to store all statistics.

void heap_caps_free_all_task_stat_arrays(heap_all_tasks_stat_t *tasks_stat)

Free the memory allocated to store task, heap and alloc statistics by calling heap_caps_alloc_all_task_stat_arrays.

Parameters:

tasks_stat -- Structure from which to free the allocated memory used to store statistics

Structures

struct heap_task_totals_t

Structure to collect per-task heap allocation totals partitioned by selected caps.

Public Members

TaskHandle_t task

Task to which these totals belong.

size_t size[NUM_HEAP_TASK_CAPS]

Total allocations partitioned by selected caps.

size_t count[NUM_HEAP_TASK_CAPS]

Number of blocks partitioned by selected caps.

struct heap_task_block_t

Structure providing details about a block allocated by a task.

Public Members

TaskHandle_t task

Task that allocated the block.

void *address

User address of allocated block.

uint32_t size

Size of the allocated block.

struct heap_task_info_params_t

Structure to provide parameters to heap_caps_get_per_task_info.

The 'caps' and 'mask' arrays allow partitioning the per-task heap allocation totals by selected sets of heap region capabilities so that totals for multiple regions can be accumulated in one scan. The capabilities flags for each region ANDed with mask[i] are compared to caps[i] in order; the allocations in that region are added to totals->size[i] and totals->count[i] for the first i that matches. To collect the totals without any partitioning, set mask[0] and caps[0] both to zero. The allocation totals are returned in the 'totals' array of heap_task_totals_t structs. To allow easily comparing the totals array between consecutive calls, that array can be left populated from one call to the next so the order of tasks is the same even if some tasks have freed their blocks or have been deleted. The number of blocks prepopulated is given by num_totals, which is updated upon return. If there are more tasks with allocations than the capacity of the totals array (given by max_totals), information for the excess tasks will be not be collected. The totals array pointer can be NULL if the totals are not desired.

The 'tasks' array holds a list of handles for tasks whose block details are to be returned in the 'blocks' array of heap_task_block_t structs. If the tasks array pointer is NULL, block details for all tasks will be returned up to the capacity of the buffer array, given by max_blocks. The function return value tells the number of blocks filled into the array. The blocks array pointer can be NULL if block details are not desired, or max_blocks can be set to zero.

Public Members

int32_t caps[NUM_HEAP_TASK_CAPS]

Array of caps for partitioning task totals.

int32_t mask[NUM_HEAP_TASK_CAPS]

Array of masks under which caps must match.

TaskHandle_t *tasks

Array of tasks whose block info is returned.

size_t num_tasks

Length of tasks array.

heap_task_totals_t *totals

Array of structs to collect task totals.

size_t *num_totals

Number of task structs currently in array.

size_t max_totals

Capacity of array of task totals structs.

heap_task_block_t *blocks

Array of task block details structs.

size_t max_blocks

Capacity of array of task block info structs.

struct heap_stat_t

Structure providing details about memory usage of a given task on a heap.

Public Members

const char *name

Pointer to the name of the heap defined in soc_memory_types[].

uint32_t caps

All caps supported by the heap (ORED)

size_t size

The available size of the heap.

size_t current_usage

The current usage of a given task on the heap.

size_t peak_usage

The peak usage since startup on a given task on the heap.

size_t alloc_count

The current number of allocation by a given task on the heap.

heap_task_block_t *alloc_stat

Pointer to an array of allocation stats for a given task on the heap.

struct task_stat_t

Structure providing details about a task.

Public Members

char name[configMAX_TASK_NAME_LEN]

Name of the task.

TaskHandle_t handle

Pointer to the task handle.

bool is_alive

Information whether the task is alive (true) or deleted (false)

size_t overall_peak_usage

Information about the memory peak usage across all heaps of a given task.

size_t overall_current_usage

Information about the memory current usage across all heaps of a given task.

size_t heap_count

Number of different heaps the task has used since its creation.

heap_stat_t *heap_stat

Pointer to an array containing statistics of the heaps used by the task.

struct heap_single_task_stat_t

User interface containing the statistics of a given task and the associated memory usage of the task on each heap.

Public Members

task_stat_t stat

Statistics of the task.

size_t heap_count

size of user defined heap_stat array

heap_stat_t *heap_stat_start

Pointer to the start to the user defined heap_stat array.

size_t alloc_count

size of user defined alloc_stat array

heap_task_block_t *alloc_stat_start

Pointer to the start to the user defined alloc_stat array.

struct heap_all_tasks_stat_t

User interface containing the statistics of all tasks and the associated memory usage of those tasks on each heap they use.

Public Members

size_t task_count

user defined size of heap_single_task_stat_t array

task_stat_t *stat_arr

Pointer to the user defined array of heap_single_task_stat_t.

size_t heap_count

size of user defined heap_stat array

heap_stat_t *heap_stat_start

Pointer to the start to the user defined heap_stat array.

size_t alloc_count

size of user defined alloc_stat array

heap_task_block_t *alloc_stat_start

Pointer to the start to the user defined alloc_stat array.

Macros

NUM_HEAP_TASK_CAPS

API Reference - Heap Tracing

Header File

Functions

esp_err_t heap_trace_init_standalone(heap_trace_record_t *record_buffer, size_t num_records)

Initialise heap tracing in standalone mode.

This function must be called before any other heap tracing functions.

To disable heap tracing and allow the buffer to be freed, stop tracing and then call heap_trace_init_standalone(NULL, 0);

Parameters:

Returns:

esp_err_t heap_trace_init_tohost(void)

Initialise heap tracing in host-based mode.

This function must be called before any other heap tracing functions.

Returns:

esp_err_t heap_trace_start(heap_trace_mode_t mode)

Start heap tracing. All heap allocations & frees will be traced, until heap_trace_stop() is called.

Note

heap_trace_init_standalone() must be called to provide a valid buffer, before this function is called.

Note

Calling this function while heap tracing is running will reset the heap trace state and continue tracing.

Parameters:

mode -- Mode for tracing.

Returns:

esp_err_t heap_trace_stop(void)

Stop heap tracing.

Returns:

esp_err_t heap_trace_alloc_pause(void)

Pause heap tracing of allocations.

Note

This function puts the heap tracing in the state where the new allocations will no longer be traced but the free will still be. This can be used to e.g., strategically monitor a set of allocations to make sure each of them will get freed without polluting the list of records with unwanted allocations.

Returns:

esp_err_t heap_trace_resume(void)

Resume heap tracing which was previously stopped.

Unlike heap_trace_start(), this function does not clear the buffer of any pre-existing trace records.

The heap trace mode is the same as when heap_trace_start() was last called (or HEAP_TRACE_ALL if heap_trace_start() was never called).

Returns:

size_t heap_trace_get_count(void)

Return number of records in the heap trace buffer.

It is safe to call this function while heap tracing is running.

esp_err_t heap_trace_get(size_t index, heap_trace_record_t *record)

Return a raw record from the heap trace buffer.

Note

It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode record indexing may skip entries unless heap tracing is stopped first.

Parameters:

Returns:

void heap_trace_dump(void)

Dump heap trace record data to stdout.

Note

It is safe to call this function while heap tracing is running, however in HEAP_TRACE_LEAK mode the dump may skip entries unless heap tracing is stopped first.

void heap_trace_dump_caps(const uint32_t caps)

Dump heap trace from the memory of the capabilities passed as parameter.

Parameters:

caps -- Capability(ies) of the memory from which to dump the trace. Set MALLOC_CAP_INTERNAL to dump heap trace data from internal memory. Set MALLOC_CAP_SPIRAM to dump heap trace data from PSRAM. Set both to dump both heap trace data.

esp_err_t heap_trace_summary(heap_trace_summary_t *summary)

Get summary information about the result of a heap trace.

Note

It is safe to call this function while heap tracing is running.

Structures

struct heap_trace_record_t

Trace record data type. Stores information about an allocated region of memory.

Public Members

uint32_t ccount

CCOUNT of the CPU when the allocation was made. LSB (bit value 1) is the CPU number (0 or 1).

void *address

Address which was allocated. If NULL, then this record is empty.

size_t size

Size of the allocation.

bool freed

State of the allocation (false if not freed, true if freed)

void *alloced_by[CONFIG_HEAP_TRACING_STACK_DEPTH]

Call stack of the caller which allocated the memory.

void *freed_by[CONFIG_HEAP_TRACING_STACK_DEPTH]

Call stack of the caller which freed the memory (all zero if not freed.)

struct heap_trace_summary_t

Stores information about the result of a heap trace.

Public Members

heap_trace_mode_t mode

The heap trace mode we just completed / are running.

size_t total_allocations

The total number of allocations made during tracing.

size_t total_frees

The total number of frees made during tracing.

size_t count

The number of records in the internal buffer.

size_t capacity

The capacity of the internal buffer.

size_t high_water_mark

The maximum value that 'count' got to.

size_t has_overflowed

True if the internal buffer overflowed at some point.

Macros

CONFIG_HEAP_TRACING_STACK_DEPTH

Type Definitions

typedef struct heap_trace_record_t heap_trace_record_t

Trace record data type. Stores information about an allocated region of memory.

Enumerations

enum heap_trace_mode_t

Values:

enumerator HEAP_TRACE_ALL

enumerator HEAP_TRACE_LEAKS