android malloc_debug tool
android malloc_debug tool
malloc_debug使用方法
130|console:/ # cat /data/local.prop libc.debug.malloc.options=backtrace=16 guard=8 fill_on_free=16 free_track=2046 leak_track libc.debug.malloc.program=slub_debug_test.bin
在/data/local.prop里写了上述内容后,一般需要重启下系统,重启后看下slub_debug_test.bin进程的maps里是否有链接libc_malloc_debug.so,如果有,说明正常。
上面options里加了leak_track,但是只有在slub_debug_test.bin退出后,才会将mem leak信息打印出来,这有些鸡肋..
打印出来的log在logcat,例如:
console:/ # logcat |grep malloc_debug 09-22 15:24:56.400 4914 4914 E malloc_debug: +++ slub_debug_test.bin leaked block of size 16384 at 0x7b690a17f0 (leak 1 of 2) 09-22 15:24:56.400 4914 4914 E malloc_debug: Backtrace at time of allocation: 09-22 15:24:56.402 4914 4914 E malloc_debug: #00 pc 00000000000011f0 /vendor/bin/slub_debug_test.bin 09-22 15:24:56.402 4914 4914 E malloc_debug: #01 pc 000000000007d844 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+108) 09-22 15:24:56.402 4914 4914 E malloc_debug: #02 pc 0000000000000000 <unknown> 09-22 15:24:56.402 4914 4914 E malloc_debug: +++ slub_debug_test.bin leaked block of size 8 at 0x7b6900a5b0 (leak 2 of 2) 09-22 15:24:56.402 4914 4914 E malloc_debug: Backtrace at time of allocation: 09-22 15:24:56.404 4914 4914 E malloc_debug: #00 pc 00000000000d64e8 /apex/com.android.runtime/lib64/bionic/libc.so 09-22 15:24:56.404 4914 4914 E malloc_debug: #01 pc 000000000008b208 /apex/com.android.runtime/lib64/bionic/libc.so (newlocale+160) 09-22 15:24:56.404 4914 4914 E malloc_debug: #02 pc 000000000009956c /system/lib64/vndk-sp-29/libc++.so 09-22 15:24:56.404 4914 4914 E malloc_debug: #03 pc 000000000009f1b8 /system/lib64/vndk-sp-29/libc++.so (std::__1::locale::__global()+160) 09-22 15:24:56.404 4914 4914 E malloc_debug: #04 pc 000000000009f218 /system/lib64/vndk-sp-29/libc++.so (std::__1::locale::locale()+16) 09-22 15:24:56.404 4914 4914 E malloc_debug: #05 pc 0000000000075c5c /system/lib64/vndk-sp-29/libc++.so (std::__1::basic_streambuf<char, std::__1::char_traits<char>>::basic_streambuf()+36) 09-22 15:24:56.404 4914 4914 E malloc_debug: #06 pc 00000000000815d4 /system/lib64/vndk-sp-29/libc++.so (std::__1::ios_base::Init::Init()+68) 09-22 15:24:56.404 4914 4914 E malloc_debug: #07 pc 0000000000082890 /system/lib64/vndk-sp-29/libc++.so 09-22 15:24:56.404 4914 4914 E malloc_debug: #08 pc 0000000000050d08 /apex/com.android.runtime/bin/linker64 09-22 15:24:56.404 4914 4914 E malloc_debug: #09 pc 0000000000050f20 /apex/com.android.runtime/bin/linker64 09-22 15:24:56.404 4914 4914 E malloc_debug: #10 pc 0000000000050e2c /apex/com.android.runtime/bin/linker64 09-22 15:24:56.404 4914 4914 E malloc_debug: #11 pc 000000000004ce70 /apex/com.android.runtime/bin/linker64 09-22 15:24:56.404 4914 4914 E malloc_debug: #12 pc 000000000004c03c /apex/com.android.runtime/bin/linker64 09-22 15:24:56.404 4914 4914 E malloc_debug: #13 pc 00000000000533f4 /apex/com.android.runtime/bin/linker64 09-22 15:24:56.404 4914 4914 E malloc_debug: #14 pc 0000000000000000 <unknown>
slub_debug_test.bin main函数如下,malloc一个16KB后的buffer后就return了:
... unsigned char *ptr = (unsigned char *)malloc(16*1024); printf("the alloced mem: 0x%lx.\n", (unsigned long)ptr); return 0; }
reference:
https://android.googlesource.com/platform/bionic/+/master/libc/malloc_debug/README.md
malloc_debug原理解析
malloc_debug是通过加载libc_malloc_debug.so注册一系列的malloc debug函数以替换标准的malloc系列函数来实现对进程内存分配释放过程中存在的问题进行debug的工具,malloc_debug可以用来分析进程内存泄漏、use-after-free等问题。
malloc_debug一系类malloc debug函数都在libc_malloc_debug.so里,其实现在/bionic/libc/malloc_debug/malloc_debug.cpp。
下面将从注册和使用两个部分来说明malloc_debug是如何工作的
注册debug_malloc系列函数
1. 入口函数__libc_init_malloc()
此函数由__libc_preinit()调用过来,__libc_globals.mutate(__libc_init_malloc)即是调用__libc_init_malloc((libc_globals* globals),其参数globals指向是__libc_globals对象contents成员里的libc_globals value成员。
2. 判断malloc_debug是否有开启
通过检查kDebugPropertyOptions[] = "libc.debug.malloc.options以及kDebugPropertyProgram[] = "libc.debug.malloc.program"系统属性是否均有设置,如果有设置,表示malloc_debug有开启,调用InstallHooks()
3. 加载动态链接库libc_malloc_debug.so
dlopen libc_malloc_debug.so,在这个so里查找如下names数组中的symbol(加上debug_ prefix),将查找到的symbol保存到gFunctions[]数组里:
259 static constexpr const char* names[] = { 260 "initialize", 261 "finalize", 262 "get_malloc_leak_info", 263 "free_malloc_leak_info", 264 "malloc_backtrace", 265 "write_malloc_leak_info", 266 };
然后通过InitMallocFunctions()在这个so里查找malloc_debug系列函数,这些系列函数例如有debug_free/debug_calloc/debug_mallinfo/debug_mallopt/debug_malloc等等,其前缀都是debug_
查找到后,将对应的symbol赋值给MallocDispatch struct里的对应成员,这个struct的成员是一些malloc相关的函数指针,这个struct即是__libc_globals对象里的libc_globals struct里的MallocDispatch malloc_dispatch_table成员,所以就是设置了__libc_globals对象里的malloc_dispatch_table成员:
58 struct MallocDispatch { 59 MallocCalloc calloc; 60 MallocFree free; 61 MallocMallinfo mallinfo; 62 MallocMalloc malloc; 63 MallocMallocUsableSize malloc_usable_size; 64 MallocMemalign memalign; 65 MallocPosixMemalign posix_memalign; 66 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS) 67 MallocPvalloc pvalloc; 68 #endif 69 MallocRealloc realloc; 70 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS) 71 MallocValloc valloc; 72 #endif 73 MallocIterate malloc_iterate; 74 MallocMallocDisable malloc_disable; 75 MallocMallocEnable malloc_enable; 76 MallocMallopt mallopt; 77 MallocAlignedAlloc aligned_alloc; 78 MallocMallocInfo malloc_info; 79 } __attribute__((aligned(32)));
149 static bool InitMallocFunctions(void* impl_handler, MallocDispatch* table, const char* prefix) { 150 if (!InitMallocFunction<MallocFree>(impl_handler, &table->free, prefix, "free")) { 151 return false; 152 } 153 if (!InitMallocFunction<MallocCalloc>(impl_handler, &table->calloc, prefix, "calloc")) { 154 return false; 155 } 156 if (!InitMallocFunction<MallocMallinfo>(impl_handler, &table->mallinfo, prefix, "mallinfo")) { 157 return false; 158 } 159 if (!InitMallocFunction<MallocMallopt>(impl_handler, &table->mallopt, prefix, "mallopt")) { 160 return false; 161 } 162 if (!InitMallocFunction<MallocMalloc>(impl_handler, &table->malloc, prefix, "malloc")) { 163 return false; 164 } 165 if (!InitMallocFunction<MallocMallocInfo>(impl_handler, &table->malloc_info, prefix, 166 "malloc_info")) { 167 return false; 168 } 169 if (!InitMallocFunction<MallocMallocUsableSize>(impl_handler, &table->malloc_usable_size, prefix, 170 "malloc_usable_size")) { 171 return false; 172 } 173 if (!InitMallocFunction<MallocMemalign>(impl_handler, &table->memalign, prefix, "memalign")) { 174 return false; 175 } 176 if (!InitMallocFunction<MallocPosixMemalign>(impl_handler, &table->posix_memalign, prefix, 177 "posix_memalign")) { 178 return false; 179 } 180 if (!InitMallocFunction<MallocAlignedAlloc>(impl_handler, &table->aligned_alloc, 181 prefix, "aligned_alloc")) { 182 return false; 183 } 184 if (!InitMallocFunction<MallocRealloc>(impl_handler, &table->realloc, prefix, "realloc")) { 185 return false; 186 } 187 if (!InitMallocFunction<MallocIterate>(impl_handler, &table->iterate, prefix, "iterate")) { 188 return false; 189 } 190 if (!InitMallocFunction<MallocMallocDisable>(impl_handler, &table->malloc_disable, prefix, 191 "malloc_disable")) { 192 return false; 193 } 194 if (!InitMallocFunction<MallocMallocEnable>(impl_handler, &table->malloc_enable, prefix, 195 "malloc_enable")) { 196 return false; 197 } 198 #if defined(HAVE_DEPRECATED_MALLOC_FUNCS) 199 if (!InitMallocFunction<MallocPvalloc>(impl_handler, &table->pvalloc, prefix, "pvalloc")) { 200 return false; 201 } 202 if (!InitMallocFunction<MallocValloc>(impl_handler, &table->valloc, prefix, "valloc")) { 203 return false; 204 } 205 #endif 206 207 return true; 208 }
4. 调用FinishInstallHooks()
此函数主要做了3件事
A. 调用malloc_debug的initialize函数即debug_initialize()对malloc_debug内部进行初始化
B. 设置__libc_globals对象里的libc_globals.default_dispatch_table/current_dispatch_table指向malloc_dispatch_table,以后在malloc库函数里都会call GetDispatchTable(),这个函数就是返回的current_dispatch_table指针
C. 调用__cxa_atexit(MallocFiniImpl, nullptr, nullptr)注册MallocFiniImpl(),注册的此函数将在进程exit时(比如调用exit())被回调,在这个回调函数(这个回调函数具体是debug_finalize())里检查比如是否有内存泄漏,如果有,会将alloc callstack打印出来:
(注:这个要进程主动call exit()才会调用debug_finalize()去检查是否存在内存泄漏等问题,如果进程是收到了fatal signal而导致的被kernel强制exit,此时就不会有进程再call exit()的情况,所以此时就不会去检查进程是否存在内存泄漏等问题)
bionic/libc/malloc_debug/malloc_debug.cpp
337 void debug_finalize() { 338 if (g_debug == nullptr) { 339 return; 340 } 341 342 // Make sure that there are no other threads doing debug allocations 343 // before we kill everything. 344 ScopedConcurrentLock::BlockAllOperations(); 345 346 // Turn off capturing allocations calls. 347 DebugDisableSet(true); 348 349 if (g_debug->config().options() & FREE_TRACK) { 350 PointerData::VerifyAllFreed(); 351 } 352 353 if (g_debug->config().options() & LEAK_TRACK) { 354 PointerData::LogLeaks(); 355 } 356 357 if ((g_debug->config().options() & BACKTRACE) && g_debug->config().backtrace_dump_on_exit()) { 358 debug_dump_heap(android::base::StringPrintf("%s.%d.exit.txt", 359 g_debug->config().backtrace_dump_prefix().c_str(), 360 getpid()).c_str()); 361 } 362 363 backtrace_shutdown(); 364 365 // In order to prevent any issues of threads freeing previous pointers 366 // after the main thread calls this code, simply leak the g_debug pointer 367 // and do not destroy the debug disable pthread key. 368 }
477 void PointerData::LogLeaks() { 478 std::vector<ListInfoType> list; 479 480 std::lock_guard<std::mutex> pointer_guard(pointer_mutex_); 481 std::lock_guard<std::mutex> frame_guard(frame_mutex_); 482 GetList(&list, false); 483 484 size_t track_count = 0; 485 for (const auto& list_info : list) { 486 error_log("+++ %s leaked block of size %zu at 0x%" PRIxPTR " (leak %zu of %zu)", getprogname(), 487 list_info.size, list_info.pointer, ++track_count, list.size()); 488 if (list_info.backtrace_info != nullptr) { 489 error_log("Backtrace at time of allocation:"); 490 UnwindLog(*list_info.backtrace_info); 491 } else if (list_info.frame_info != nullptr) { 492 error_log("Backtrace at time of allocation:"); 493 backtrace_log(list_info.frame_info->frames.data(), list_info.frame_info->frames.size()); 494 } 495 // Do not bother to free the pointers, we are about to exit any way. 496 } 497 }
如果没有设置libc.debug.malloc.options & libc.debug.malloc.program属性,将不会去链接libc_malloc_debug.so,所以libc_globals.current_dispatch_table就是nullptr,所以GetDispatchTable()将return nullptr。
malloc系列库函数调用malloc_debug系列malloc函数or原生malloc系列函数
在malloc函数里,如果GetDispatchTable()返回值不等于nullptr,将调用此dispatch table里的malloc函数,而非原生malloc函数。
此dispatch table实际就是加载libc_malloc_debug.so时设置的__libc_globals对象里的libc_globals.current_dispatch_table指针
而如果GetDispatchTable()返回nullptr,表示此进程没有enable malloc_debug,所以此时将使用原生的malloc系列函数:
bionic/libc/bionic/malloc_common.cpp
121 extern "C" void* malloc(size_t bytes) { 122 auto dispatch_table = GetDispatchTable(); 123 void *result; 124 if (__predict_false(dispatch_table != nullptr)) { 125 result = dispatch_table->malloc(bytes); 126 } else { 127 result = Malloc(malloc)(bytes); 128 } 129 if (__predict_false(result == nullptr)) { 130 warning_log("malloc(%zu) failed: returning null pointer", bytes); 131 return nullptr; 132 } 133 return MaybeTagPointer(result); 134 } 135
关于__libc_preinit()的调用时机
__libc_preinit()在main函数执行前执行,因为它有__attribute__((constructor(1)))属性,它是constructor,priority为1,通过这个constructor对此程序所链接的libc library进行pre init:
bionic/libc/bionic/libc_init_dynamic.cpp
97 // We flag the __libc_preinit function as a constructor to ensure that 98 // its address is listed in libc.so's .init_array section. 99 // This ensures that the function is called by the dynamic linker as 100 // soon as the shared library is loaded. 101 // We give this constructor priority 1 because we want libc's constructor 102 // to run before any others (such as the jemalloc constructor), and lower 103 // is better (http://b/68046352). 104 __attribute__((constructor(1))) static void __libc_preinit() { 105 // The linker has initialized its copy of the global stack_chk_guard, and filled in the main 106 // thread's TLS slot with that value. Initialize the local global stack guard with its value. 107 __stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]); 108 109 __libc_preinit_impl(); 110 }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
2020-09-24 linux下区分各种SCSI磁盘类型
2020-09-24 Linux那些事儿之我是SCSI硬盘(3)磁盘磁盘你动起来!
2018-09-24 前复权&后复权
2018-09-24 央行回购、逆回购利率
2018-09-24 一文看懂外汇风险准备金率调整为 20%的含义