From e3e5ed1445440f2e4c0dacb26072fe17b61cb5fa Mon Sep 17 00:00:00 2001 From: houchengzhi Date: Mon, 2 Jun 2025 16:15:27 +0800 Subject: [PATCH 1/4] apply code format --- backtrace/include/PointerData.h | 6 +++--- backtrace/src/PointerData.cpp | 32 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/backtrace/include/PointerData.h b/backtrace/include/PointerData.h index 5169fef..d61480e 100644 --- a/backtrace/include/PointerData.h +++ b/backtrace/include/PointerData.h @@ -117,9 +117,9 @@ class PointerData { backtraces_info_; size_t cur_hash_index_; - size_t current_used, current_host, current_dma; - size_t peak_tot, peak_host, peak_dma; - std::vector peak_list; + size_t current_used_, current_host_, current_dma_; + size_t peak_tot_, peak_host_, peak_dma_; + std::vector peak_list_; BIONIC_DISALLOW_COPY_AND_ASSIGN(PointerData); }; \ No newline at end of file diff --git a/backtrace/src/PointerData.cpp b/backtrace/src/PointerData.cpp index d71faae..6ccc5e1 100644 --- a/backtrace/src/PointerData.cpp +++ b/backtrace/src/PointerData.cpp @@ -37,12 +37,12 @@ bool PointerData::Initialize(const Config& config) { key_to_index_.clear(); frames_.clear(); backtraces_info_.clear(); - peak_list.clear(); + peak_list_.clear(); // A hash index of kBacktraceEmptyIndex indicates that we tried to get // a backtrace, but there was nothing recorded. cur_hash_index_ = kBacktraceEmptyIndex + 1; - current_used = current_host = current_dma = 0; - peak_tot = peak_host = peak_dma = 0; + current_used_ = current_host_ = current_dma_ = 0; + peak_tot_ = peak_host_ = peak_dma_ = 0; return true; } @@ -60,21 +60,21 @@ void PointerData::Add(const void* ptr, size_t pointer_size, MemType type) { gettimeofday(&tv, NULL); uintptr_t mangled_ptr = ManglePointer(reinterpret_cast(ptr)); pointers_[mangled_ptr] = PointerInfoType{pointer_size, hash_index, type, tv}; - current_used += pointer_size; - size_t* current = (type == DMA) ? ¤t_dma : ¤t_host; - size_t* peak = (type == DMA) ? &peak_dma : &peak_host; + current_used_ += pointer_size; + size_t* current = (type == DMA) ? ¤t_dma_ : ¤t_host_; + size_t* peak = (type == DMA) ? &peak_dma_ : &peak_host_; *current += pointer_size; if (*current > *peak) { *peak = *current; } - if (peak_tot < current_used) { - peak_tot = current_used; + if (peak_tot_ < current_used_) { + peak_tot_ = current_used_; if ((g_debug->config().options() & RECORD_MEMORY_PEAK) && - peak_tot > g_debug->config().backtrace_dump_peak_val()) { + peak_tot_ > g_debug->config().backtrace_dump_peak_val()) { std::lock_guard frame_guard(frame_mutex_); - peak_list.clear(); - GetUniqueList(&peak_list, true); + peak_list_.clear(); + GetUniqueList(&peak_list_, true); } } } @@ -135,8 +135,8 @@ void PointerData::Remove(const void* ptr) { // No tracked pointer. return; } - current_used -= entry->second.size; - size_t* target = (entry->second.mem_type == DMA) ? ¤t_dma : ¤t_host; + current_used_ -= entry->second.size; + size_t* target = (entry->second.mem_type == DMA) ? ¤t_dma_ : ¤t_host_; *target -= entry->second.size; hash_index = entry->second.hash_index; pointers_.erase(mangled_ptr); @@ -239,7 +239,7 @@ void PointerData::DumpLiveToFile(int fd) { std::lock_guard pointer_guard(pointer_mutex_); std::lock_guard frame_guard(frame_mutex_); - std::vector list = std::move(peak_list); + std::vector list = std::move(peak_list_); if (!(g_debug->config().options() & RECORD_MEMORY_PEAK)) { list.clear(); // Sort by the time of the allocation. @@ -317,6 +317,6 @@ void PointerData::DumpPeakInfo() { printf("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" "++++++++++++++++\n"); printf("host peak used: %fMB, dma peak used %fMB, total peak used: %fMB\n\n", - peak_host / 1024.0 / 1024.0, peak_dma / 1024.0 / 1024.0, - peak_tot / 1024.0 / 1024.0); + peak_host_ / 1024.0 / 1024.0, peak_dma_ / 1024.0 / 1024.0, + peak_tot_ / 1024.0 / 1024.0); } From 0017ebc207b926de575b6881441dd4f373f37245 Mon Sep 17 00:00:00 2001 From: houchengzhi Date: Sat, 14 Jun 2025 13:44:19 +0800 Subject: [PATCH 2/4] rename configure variable --- README.md | 2 +- backtrace/include/Config.h | 2 +- backtrace/src/Config.cpp | 2 +- backtrace/src/malloc_debug.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5d5afce..8184c02 100755 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ use to get malloc and free backtrace, include dmabuffer by hook `ioctl` and `clo - `backtrace_max_size_bytes_`: 开启 BACKTRACE_SPECIFIC_SIZES 标志时,抓取 alloc size 小于该值的堆栈信息 - `RECORD_MEMORY_PEAK`: 是否抓取峰值时刻的堆栈信息 - `backtrace_dump_peak_val_`: 当峰值大于该值时,记录峰值时刻的堆栈 - - `DUMP_ON_SINGAL`: 开启 checkpoint 信号机制 + - `DUMP_ON_SIGNAL`: 开启 checkpoint 信号机制 - `backtrace_dump_signal_`: checkpoint 信号机制的信号值,默认 33 - `DUMP_PEAK_VALUE_MB`:环境变量,单位: MB,当内存峰值大于该值时记录峰值内存 - `BACKTRACE_MIN_SIZE`:环境变量,单位: Byte,当申请内存的 size 大于该值时,才抓取堆栈信息 diff --git a/backtrace/include/Config.h b/backtrace/include/Config.h index cb2c003..3bc1fd8 100644 --- a/backtrace/include/Config.h +++ b/backtrace/include/Config.h @@ -7,7 +7,7 @@ constexpr uint64_t BACKTRACE = 0x1; // 记录堆栈 constexpr uint64_t TRACK_ALLOCS = 0x2; // 记录内存申请动作 constexpr uint64_t BACKTRACE_SPECIFIC_SIZES = 0x4; // 记录特定大小的内存申请 constexpr uint64_t RECORD_MEMORY_PEAK = 0x8; // 记录内存峰值 -constexpr uint64_t DUMP_ON_SINGAL = 0x80; // 记录内存峰值 +constexpr uint64_t DUMP_ON_SIGNAL = 0x80; // 信号触发dump class Config { public: diff --git a/backtrace/src/Config.cpp b/backtrace/src/Config.cpp index 10d5de6..2f75b47 100644 --- a/backtrace/src/Config.cpp +++ b/backtrace/src/Config.cpp @@ -70,7 +70,7 @@ bool Config::Init() { backtrace_dump_peak_val_ *= 1024 * 1024; // 通过信号插入 check point - options_ |= DUMP_ON_SINGAL; + options_ |= DUMP_ON_SIGNAL; backtrace_dump_signal_ = BIONIC_SIGNAL_BACKTRACE; // BIONIC_SIGNAL_BACKTRACE: 33 return true; diff --git a/backtrace/src/malloc_debug.cpp b/backtrace/src/malloc_debug.cpp index 764dcf2..7fc0641 100644 --- a/backtrace/src/malloc_debug.cpp +++ b/backtrace/src/malloc_debug.cpp @@ -70,7 +70,7 @@ bool debug_initialize(void* init_space[]) { ScopedConcurrentLock::Init(); - if (g_debug->config().options() & DUMP_ON_SINGAL) { + if (g_debug->config().options() & DUMP_ON_SIGNAL) { struct sigaction enable_act = {}; enable_act.sa_handler = singal_dump_heap; enable_act.sa_flags = SA_RESTART | SA_ONSTACK; From a820e17cf867e2e9f31fa3b803411f103043a7dc Mon Sep 17 00:00:00 2001 From: houchengzhi Date: Sat, 14 Jun 2025 13:58:30 +0800 Subject: [PATCH 3/4] update README.md --- README.md | 315 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 165 insertions(+), 150 deletions(-) diff --git a/README.md b/README.md index 8184c02..0768be5 100755 --- a/README.md +++ b/README.md @@ -1,108 +1,120 @@ # liballoc_hook.so -use to get malloc and free backtrace, include dmabuffer by hook `ioctl` and `close` - -* how to build - - * ./build_android.sh [armeabi-v7a] - -* how to use - * adb shell mkdir `path`, where `path` is the output path for the unwindstack, default: `/data/local/tmp/trace/` - * LD_PRELOAD=liballoc_hook.so LD_LIBRARY_PATH=. ls - * then replace `ls` to you real command - * 如果要抓 trace 请先创建 trace 的输出目录 - * 使用该工具导致程序运行过慢时,可以指定环境 `BACKTRACE_MIN_SIZE` 值,不记录小内存的堆栈信息 - -* checkpoint - * 支持在程序指定位置插入检查点,输出当前时刻的未释放的内存的堆栈信息 - * 第一种方式:使用信号的方式触发堆栈输出,默认信号值为 33,可以在上述配置文件 Config.cpp 中修改,trace 文件以当前时间命名 - ``` C++ - #include - #include - #include - #include - #include - - if (kill(getpid(), 33) == -1) { - fprintf(stderr, "Error in file %s at line %d: %s\n", __FILE__, __LINE__, strerror(errno)); - exit(1); - } - ``` - * 第二种方式:在代码中插入调用,支持用户自定义文件名,前提需要用户的可执行程序依赖该项目编译生成的 so, 例如在 cmake 文件中做出下面的修改 - ``` - add_library(hook SHARED IMPORTED) - set(HOOK_IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/liballoc_hook.so") - set_target_properties(hook PROPERTIES IMPORTED_LOCATION "${HOOK_IMPORTED_LOCATION}") - - add_executable(xxxx ./code/xxxxx.cpp) - target_link_libraries(xxxx hook) - ``` - 用户代码需做以下修改 - ``` C++ - extern "C" void checkpoint(const char* file_name); - int main() { - size_t size = 200 * 1024 * 1024; - void* addr_mmap = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); - checkpoint("/data/local/tmp/trace/check_point.1.txt"); - void* addr_malloc = malloc(size * 2); - - return 0; - } - ``` - 如果以 SHARED 方式生成了 liballoc_hook.so 共享库,则需要将 liballoc_hook.so 文件放在可执行程序可读取的目录中,例如 - ``` bash - adb push /path/liballoc_hook.so ${test_dir} - - adb exec-out "cd ${test_dir}; \ - export LD_LIBRARY_PATH=.; \ - ./xxxx;" - ``` - * 第三种方式:使用 `dlsym` 打开 checkpoint 符号,并用 `LD_PRELOAD=liballoc_hook.so LD_LIBRARY_PATH=. ls` 的方式执行 - ```c++ - typedef void (*checkpoint_func)(const char*); - auto checkpoint = (checkpoint_func)dlsym(RTLD_DEFAULT, "checkpoint"); - if (checkpoint) { - checkpoint("/data/local/tmp/trace/check_point.1.txt"); - } - ``` - -* 如何改造自己的被测试程序以便此工具能`有效`采样 - - 另外在采样过程中,也请务必保证程序处于`停止`状态,常见的做法是在被测试的代码适当位置加上 checkpoint() 或者 kill(getpid(), 33) 以便触发采样, - 下面解释一下什么叫做`适当`位置 - - ``` - 比如典型的 pipline: +用于获取 `malloc` 和 `free` 的堆栈信息,包括通过 hook `iottl` 和 `close` 获取 DMABuffer 的堆栈信息。 + +# 如何编译 +```shell +./build_android.sh [armeabi-v7a] +``` +# 如何使用 + 1. adb shell 手动创建 backtrace 输出目录,默认存储路径为 `/data/local/tmp/trace/` + 1. 使用方式:`LD_PRELOAD=liballoc_hook.so LD_LIBRARY_PATH=. ls`,ls 替换成你的测试程序 + 1. 使用该工具导致程序运行过慢时,可以指定环境 `BACKTRACE_MIN_SIZE` 值,不记录小内存的堆栈信息 + +# 在代码指定位置 dump 内存 backtrace 的三种方式 +支持在程序指定位置插入检查点,输出当前时刻的未释放的内存的堆栈信息。 + +## 1.使用信号触发 +使用信号的方式触发堆栈输出,默认信号值为 33,可以在 Config.cpp 配置文件中修改,trace 文件以当前时间命名,保存在默认路径下。 + +```C++ +#include +#include +#include +#include +#include + +if (kill(getpid(), 33) == -1) { + fprintf(stderr, "Error in file %s at line %d: %s\n", __FILE__, __LINE__, strerror(errno)); + exit(1); +} +``` + +## 2.链接 alloc_hook 库并在代码中插入 checkpoint 调用触发 +在代码中插入调用,支持用户自定义文件名,前提需要用户的可执行程序依赖该项目编译生成的 so, 例如在 cmake 文件中做出下面的修改 + +``` +add_library(hook SHARED IMPORTED) +set(HOOK_IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/liballoc_hook.so") +set_target_properties(hook PROPERTIES IMPORTED_LOCATION "${HOOK_IMPORTED_LOCATION}") + +add_executable(xxxx ./code/xxxxx.cpp) +target_link_libraries(xxxx hook) +``` + +用户代码需做以下修改 + +```C++ +extern "C" void checkpoint(const char* file_name); +int main() { + size_t size = 200 * 1024 * 1024; + void* addr_mmap = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0); + checkpoint("/data/local/tmp/trace/check_point.1.txt"); + void* addr_malloc = malloc(size * 2); + + return 0; +} +``` + +如果以 SHARED 方式生成了 liballoc_hook.so 共享库,则需要将 liballoc_hook.so 文件放在可执行程序可读取的目录中,例如 + +```bash +adb push /path/liballoc_hook.so ${test_dir} + +adb exec-out "cd ${test_dir}; \ + export LD_LIBRARY_PATH=.; \ + ./xxxx;" +``` + +## 3.使用 dlsym 打开 checkpoint 符号调用触发 +使用 `dlsym` 打开 checkpoint 符号,并用 `LD_PRELOAD=liballoc_hook.so LD_LIBRARY_PATH=. ls` 的方式执行 + +```c++ + typedef void (*checkpoint_func)(const char*); + auto checkpoint = (checkpoint_func)dlsym(RTLD_DEFAULT, "checkpoint"); + if (checkpoint) { + checkpoint("/data/local/tmp/trace/check_point.1.txt"); + } +``` + +# 如何改造自己的被测试程序以便此工具能**有效**采样 +另外在采样过程中,也请务必保证程序处于`停止`状态,常见的做法是在被测试的代码适当位置加上 checkpoint() 或者 kill(getpid(), 33) 以便触发采样, +下面解释一下什么叫做`适当`位置 + +``` +比如典型的 pipline: +create_ctx() +.... +use_ctx() +.... +free_ctx() +``` + +一般期望是 free_ctx 后应该资源都释放了(除开常驻的外), 我们改造上述 pipline 来加入 checkpoint()/kill, 以便让此工具有采样点 + +``` +for(;;) +{ create_ctx() .... use_ctx() .... free_ctx() - ``` + checkpoint() or kill(getpid(), 33) +} +``` - 一般期望是 free_ctx 后应该资源都释放了(除开常驻的外), 我们改造上述 pipline 来加入 checkpoint()/kill, 以便让此工具有采样点 +典型的,我们需要让整个 pipline 运行几次分别得到不同运行次数的采样 trace, 当然除了 checkpoint/kill 让程序`暂停`外,我们也可以通过 `gdb` 等调试工具来达到相同的目的。关键在于加的位置,一定是你认为这个点应该释放了资源,比如典型的上面的 free_ctx,一定不要在其他点去进行采样,比如 `use_ctx` 阶段,这个时候本身属于内存用量高峰,即使没有释放也不能说明`泄漏`对吧。 - ``` - for(;;) - { - create_ctx() - .... - use_ctx() - .... - free_ctx() - checkpoint() or kill(getpid(), 33) - } - ``` - - 典型的,我们需要让整个 pipline 运行几次分别得到不同运行次数的采样 trace, 当然除了 checkpoint/kill 让程序`暂停`外,我们也可以通过 `gdb` 等调试工具来达到相同的目的。关键在于加的位置,一定是你认为这个点应该释放了资源,比如典型的上面的 free_ctx,一定不要在其他点去进行采样,比如 `use_ctx` 阶段,这个时候本身属于内存用量高峰,即使没有释放也不能说明`泄漏`对吧。 +# 抓取内存峰值步骤 +- 首先运行一次程序,当程序结束时,会输出如下信息 -* 抓取峰值步骤 - - 首先运行一次程序,当程序结束时,会输出如下信息 ``` +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ host peak used: 415MB, dma peak used 206MB, total peak used: 619MB ``` - - 然后,根据 total peak used 的值,通过环境变量 `DUMP_PEAK_VALUE_MB` 设置 backtrace_dump_peak_val_ 的值,通常要小于 total peak used 50MB 左右,设置方式如下 + +- 然后,根据 total peak used 的值,通过环境变量 `DUMP_PEAK_VALUE_MB` 设置 backtrace_dump_peak_val_ 的值,通常要小于 total peak used 50MB 左右,设置方式如下 ``` export DUMP_PEAK_VALUE_MB=xxx @@ -110,66 +122,69 @@ use to get malloc and free backtrace, include dmabuffer by hook `ioctl` and `clo DUMP_PEAK_VALUE_MB=xxx LD_PRELOAD=liballoc_hook.so LD_LIBRARY_PATH=. ls ``` - - DUMP_PEAK_VALUE_MB 的单位默认为 MB - -* 内存泄露分析步骤 - - 利用 cheakpoint 机制执行两次程序,并对两次的内存调用堆栈输出进行对比,分析内存调用的增量,此时的内存调用是以时间排序,可以从后向前对比 - ``` c++ - void test() { - .... - } - - int main() { - for (int i = 0; i < 2; ++i) { - test(); - kill(getpid(), 33); - } - } - ``` +DUMP_PEAK_VALUE_MB 的单位默认为 MB,程序在退出时会 dump trace 文件在默认路径下。 + +# 内存泄露分析步骤 +利用 cheakpoint 机制执行两次程序,并对两次的内存调用堆栈输出进行对比,分析内存调用的增量,此时的内存调用是以时间排序,可以从后向前对比 -* 在线 trace 抓取 - 以相机程序为例: - - 首先打开相机,使用 top / ps / pidof / pgrep 查看相机服务进程 id, 相机服务名称一般为 camerahalserver - - 查看相机服务的 cmdline 信息, `cat /proc//cmdline` - - 查看启动命令 - - 使用 `find -L /system -name "*.rc" | xargs grep "CMDLINE"` 搜索相机服务启动命令, "CMDLINE" 为第二步的输出 - - 该步骤输出为: "/path/xx.rc", 例如, "/system/vendor/etc/init/camerahalserver.rc" - - 使用 `cat /path/xx.rc` 查看启动命令所需的参数,"/path/xx.rc" 为上一步骤的输出, rc 文件的格式如下 - ``` - service [ ]* -