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  }

 

posted @ 2021-09-24 17:30  aspirs  阅读(1265)  评论(0编辑  收藏  举报