FreeRTOS 小技巧(二):如何诊断内存泄漏

这篇博客用来说明如何在 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) 打印 “泄漏” 字节的总数(已分配但未运行跟踪时释放的字节),并以此表示分配的总数。

如果跟踪缓冲区的大小不足以容纳所有发生的分配,则会打印警告。如果看到此警告,请考虑缩短跟踪时间或增加跟踪缓冲区中的记录数。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章