这篇博客用来说明如何在 FreeRTOS 中诊断在哪里发生了内存泄漏。
1. 诊断内存泄露的步骤
如果你怀疑内存泄漏,则第一步是弄清楚程序的哪一部分正在泄漏内存。使用 xPortGetFreeHeapSize()
或 heap_caps_get_free_size()
来跟踪在应用程序生命周期里的内存使用。尝试将泄漏范围缩小到单个功能或一系列功能,因为在这些功能或功能序列中,可用内存总是会不断的减少。一旦通过上述 API 确定了您认为正在内存泄漏的代码段后,你需要进行以下操作来诊断内存泄漏:
-
通过
make menuconifg
项目打开配置菜单,进入Component settings
->Heap Memory Debugging
->Heap tracing
并选择Standalone
选项(请参阅 CONFIG_HEAP_TRACING_DEST)。 -
在程序开头调用
heap_trace_init_standalone()
函数,以注册一个缓冲区,该缓冲区可用于记录内存跟踪。 -
调用函数
heap_trace_start()
以开始记录系统中的所有malloc / free
。你可以在你怀疑内存泄漏的代码段之前调用此函数。 -
一旦可疑代码执行完毕,你需要调用
heap_trace_stop()
函数以停止跟踪。 -
调用该函数
heap_trace_dump()
以转储堆跟踪的结果。
2. 使用示例
以下是相关使用示例(假设你怀疑 do_something_you_suspect_is_leaking()
这段代码内存泄漏):
#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();
...
}
堆跟踪的输出 log 如下所示:
2 allocations trace (100 entry buffer)
32 bytes (@ 0x3ffaf214) allocated CPU 0 ccount 0x2e9b7384 caller 0x400d276d:0x400d27c1
0x400d276d: leak_some_memory at /path/to/idf/examples/get-started/blink/main/./blink.c:27
0x400d27c1: blink_task at /path/to/idf/examples/get-started/blink/main/./blink.c:52
8 bytes (@ 0x3ffaf804) allocated CPU 0 ccount 0x2e9b79c0 caller 0x400d2776:0x400d27c1
0x400d2776: leak_some_memory at /path/to/idf/examples/get-started/blink/main/./blink.c:29
0x400d27c1: blink_task at /path/to/idf/examples/get-started/blink/main/./blink.c:52
40 bytes 'leaked' in trace (2 allocations)
total allocations 2 total frees 0
注:以上示例输出使用IDF Monitor自动将PC地址解码为其源文件和行号。)
然后你可以分析堆跟踪的输出 log,第一行 2 allocations trace (100 entry buffer)
指示缓冲区中有多少个分配条目(与其总大小相比)。
此外,在HEAP_TRACE_LEAKS
模式下,对于尚未释放的每个跟踪的内存分配,打印一行:
-
XX bytes
是分配的字节数 -
@ 0x...
是从malloc / calloc
返回的堆地址。 -
CPU x
是分配时运行的 CPU(0 或 1)。 -
ccount 0x...
是分配为 mode 时的 CCOUNT(CPU 周期计数)寄存器值。CPU 0 与CPU 1 有所不同。 -
caller 0x...
给出对malloc()/ free()
的调用的调用堆栈,作为 PC 地址的列表。可以将它们解码为源文件和行号,如上所示。
可以在 make menuconfig
打开的项目配置菜单的 Heap Memory Debugging
-> Enable heap tracing
-> Heap tracing stack depth
下配置为每个跟踪条目记录的调用堆栈的深度。每次分配最多可以记录 10 个堆栈帧(默认为 2)。每个附加的堆栈帧将每个 heap_trace_record_t
记录的内存使用量增加 8 个字节。
最后,40 bytes leaked in trace (2 allocations)
打印 “泄漏” 字节的总数(已分配但未运行跟踪时释放的字节),并以此表示分配的总数。
如果跟踪缓冲区的大小不足以容纳所有发生的分配,则会打印警告。如果看到此警告,请考虑缩短跟踪时间或增加跟踪缓冲区中的记录数。