当前位置:   article > 正文

Android malloc_debug实现原理及使用_libc.debug.malloc.program

libc.debug.malloc.program

一、malloc_debug工具使用

malloc_debug作用:用于调试单个native进程内存问题,如检测内存损坏、内存泄漏、内存访问越界、内存释放再使用等。

1.1 打开malloc_debug开关

  1. adb shell setprop libc.debug.malloc.program app_process(proc name) ---指定 app_process,即malloc_debug 只针对 app_process 这个进程生效
  2. adb shell setprop libc.debug.malloc.options "\"backtrace front_guard=16 rear_guard=16 backtrace_dump_prefix=/sdcard/Download/heap"\"

1.2 malloc_debug 的 options

  1. // bionic/libc/malloc_debug/Config.h
  2. constexpr uint64_t FRONT_GUARD = 0x1; // 设置前置保护区,用于检测内存越界访问
  3. constexpr uint64_t REAR_GUARD = 0x2; // 设置后置保护区,用于检测内存越界访问
  4. constexpr uint64_t BACKTRACE = 0x4; // 打印堆栈信息
  5. constexpr uint64_t FILL_ON_ALLOC = 0x8; // 内存分配时填充0xeb
  6. constexpr uint64_t FILL_ON_FREE = 0x10; // 内存释放时填充0xef
  7. constexpr uint64_t EXPAND_ALLOC = 0x20; // 增加额外的内存
  8. constexpr uint64_t FREE_TRACK = 0x40; // 记录内存释放信息
  9. constexpr uint64_t TRACK_ALLOCS = 0x80; // 记录内存分配信息
  10. constexpr uint64_t LEAK_TRACK = 0x100; // 记录内存泄漏信息
  11. constexpr uint64_t RECORD_ALLOCS = 0x200; // 记录线程的内存分配与释放信息
  12. constexpr uint64_t BACKTRACE_FULL = 0x400; // 以Java frames展开
  13. constexpr uint64_t ABORT_ON_ERROR = 0x800;
  14. constexpr uint64_t VERBOSE = 0x1000;

1.3 内存分配与释放检测

1.3.1 mem leak

内存泄漏检测,如果options选项包含backtrace,在发生mem leak时,会将相关信息输出到logcat.

日志如下:

  1. 04-15 12:35:33.304 7412 7412 E malloc_debug: +++ APP leaked block of size 100 at 0x2be3b0b0 (leak 1 of 2)
  2. 04-15 12:35:33.304 7412 7412 E malloc_debug: Backtrace at time of allocation:
  3. 04-15 12:35:33.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  4. 04-15 12:35:33.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  5. 04-15 12:35:33.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  6. 04-15 12:35:33.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
  7. 04-15 12:35:33.305 7412 7412 E malloc_debug: +++ APP leaked block of size 24 at 0x7be32380 (leak 2 of 2)
  8. 04-15 12:35:33.305 7412 7412 E malloc_debug: Backtrace at time of allocation:
  9. 04-15 12:35:33.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  10. 04-15 12:35:33.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  11. 04-15 12:35:33.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  12. 04-15 12:35:33.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so

mem leak监控实现原理详见2.4.

1.3.2 record_allocs[=TOTAL_ENTRIES]

记录每个线程的每一次 分配 / 释放 的 track,当收到信号 SIGRTMAX - 18时,将这些 track dump 到一个文件中。

TOTAL_ENTRIES 表示 分配 /释放 的记录总数。如果达到了 TOTAL_ENTRIES,后面的 分配 / 释放将不再记录。默认值为 8,000,000,最大值可以设到 50,000,000。当收到信号时,所有的记录都会写到文件中,所有的记录也会被删除。

文件格式,如下:

  1. Threadid: action pointer size
  2. 186: malloc 0xb6038060 20
  3. 186: free 0xb6038060
  4. 186: calloc 0xb609f080 32 4
  5. 186: realloc 0xb609f080 0xb603e9a0 12
  6. 186: memalign 0x85423660 16 104
  7. 186: memalign 0x85423660 4096 112
  8. 186: memalign 0x85423660 4096 8192

1.2.3 record_allocs_file[=FILE_NAME]

只有在 record_allocs 这个option 使用时生效,用以指定记录dump 的文件名。

1.2.4 verify_pointers

检测已释放的内存是否被一个指针指向或使用。

如下:

  1. 04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 UNKNOWN POINTER (free)
  2. 04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure:
  3. 04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  4. 04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  5. 04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  6. 04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so

1.2.5 abort_on_error

当malloc debug 检测到error 时,在发送错误 log 消息之后终止。

注意,如果 lead_track 被使能,当进程退出时检测出泄漏时不会产生 abort。

什么情况下会发生error ?

1.2.6 Use After Free

1) after free 之后指针被用来进行了realloc 操作

    04-15 12:00:31.304  7412  7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE (realloc)

2)after free 之后又进行了 free

  1. 04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE (free)
  2. 04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace of original free:
  3. 04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  4. 04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  5. 04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  6. 04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so
  7. 04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure:
  8. 04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  9. 04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  10. 04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  11. 04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so

1.2.7 Invalid Tag

  1. 04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 HAS INVALID TAG 1ee7d000 (malloc_usable_size)
  2. 04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of failure:
  3. 04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  4. 04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  5. 04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  6. 04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so

malloc_usable_size 这个函数被一个指针调用,而这个指针不是用来分配内存,或指针指向的内存已经损坏。

malloc_usable_size 这个函数被一个指针调用,而这个指针不是用来分配内存,或指针指向的内存已经损坏。

1.4 内存越界访问检测

1.4.1 front_guard[=SIZE_BYTES]

设置前置保护区, front guard 空间会被写上一个特定模式的数据(0xaa)。当分配的内存被释放,front guard 空间会被用来check 是否已经被修改。如果 front guard 空间任意地方被修改,将会在 log 中产生一个 error 来表示哪些 bytes 被修改。

如下:

setprop libc.debug.malloc.options front_guard=16

  1. char *ptr = (char*) malloc(1 * 1024 * 1024); //申请 1M 空间
  2. memset(ptr, 0, 1* 1024* 1024); //给这1M空间初始化
  3. printf("*(ptr-16) = %d\n", *(ptr-16)); //将front guard打印一下,应该是0xaa
  4. *(ptr - 16) = 0xee; //这个时候将值修改了
  5. free(ptr); //free的时候malloc_debug会verify,确认是否损坏
  1. 04-10 12:00:45.621 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 SIZE 100 HAS A CORRUPTED FRONT GUARD
  2. 04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[-32] = 0x00 (expected 0xaa)
  3. 04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[-15] = 0x02 (expected 0xaa)

1.4.2 rear_guard[=SIZE_BYTES]

后置保护区,同 front_guard,不过是在待分配内存的尾部加上一个小的buffer,在rear guard 空间写上特定模式的数据(0xbb)。

日志如下:

  1. 04-10 12:00:45.621 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 SIZE 100 HAS A CORRUPTED REAR GUARD
  2. 04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[130] = 0xbf (expected 0xbb)
  3. 04-10 12:00:45.622 7412 7412 E malloc_debug: allocation[131] = 0x00 (expected 0xbb)

1.4.3 guard[=SIZE_BYTES]

同时分配 front guard 和 rear guard。

1.5 使能调用栈功能

1.5.1 backtrace[=MAX_FRAMES]

使能抓取每一个分配点的调用栈信息的功能。

MAX_FRAMES:表示抓取的调用栈最大数目,默认值为 16,最大值为 256.

当进程收到信号SIGRTMAX-17时会dump 堆数据到一个文件。dump 下来的数据格式与运行 am dumpheap -n 的数据格式相同。默认dump 到文件 /data/local/tmp/backtrace_head.**PID**.txt 中。这对于不是从 zygote 进程fork 出来的native 进程是很有用的。

需要注意的是,当信号收到时,head 数据不是立即dump,而是等到下一次的 malloc/free 发生。

1.5.2 backtrace_enable_on_signal[=MAX_FRAMES]

当进程收到信号 SIGRTMAX-19时触发抓取。当这个 option 单独使用的时候,调用栈抓取默认是不开启的,在接受到 signal 之后才开始。如果与backtrace 选项一起设置,那么调用栈抓取在收到 signal之后使能。

1.5.3 backtrace_dump_on_exit

从 P 版本开始,当backtrace 选项使能,这个option 会导致在进程退出才将dump 的堆信息dump 到一个文件。当backtrace 选项没有使能,这个option 不做任何事情。

dump 的默认文件路径是:/data/local/tmp/backtrace_head.**PID**.exit.txt

文件路径可以使用 backtrace_dump_prefix 选项修改,见第 1.5.4.

1.5.4 backtrace_dump_prefix

从 P 版本开始,当一个 backtrace 的选项使能,这个前缀会在 SIGRTMAX-17 信号收到或backtrace_dump_on_exit 设置时使用。

默认的前缀是:/data/local/tmp/backtrace_head

设置该prefix 路径之后,原来backtrace 的路径将变为:backtrace_dump_prefix.**PID**.txt 或 backtrace_dump_prefix.**PID**.exit.txt

例如,

setprop libc.debug.malloc.options backtrace_dump_prefix=/sdcard/log

那么调用栈将会被存到/sdcard/log.**PID**.txt 或 /sdcard/log.**PID**.exit.txt 中

1.5.5 backtrace_full

从 Q 版本开始,调用栈无论何时抓到,都会用另一个不同的算法能够以Java 帧展开。这将比正常的调用栈更慢。

核心代码,如下:

  1. // bionic/libc/malloc_debug/malloc_debug.cpp
  2. void BacktraceAndLog() {
  3. if (g_debug->config().options() & BACKTRACE_FULL) {
  4. // frames 中的每个元素代表一个函数在内存中的地址
  5. std::vector<uintptr_t> frames;
  6. std::vector<unwindstack::LocalFrameData> frames_info;
  7. if (!Unwind(&frames, &frames_info, 256)) {
  8. error_log(" Backtrace failed to get any frames.");
  9. } else {
  10. UnwindLog(frames_info);
  11. }
  12. } else {
  13. std::vector<uintptr_t> frames(256);
  14. size_t num_frames = backtrace_get(frames.data(), frames.size());
  15. if (num_frames == 0) {
  16. error_log(" Backtrace failed to get any frames.");
  17. } else {
  18. backtrace_log(frames.data(), num_frames);
  19. }
  20. }
  21. }

当options 中设置了backtrace_full,会调用unwind()。

1.6 填充分配空间

1.6.1 fill_on_alloc[=MAX_FILLED_BYTES]

除了 calloc函数,其他的常规的分配,都会使用0xeb 填充分配空间。当使用realloc 分配一个更大的空间,扩大的空间也会填充 0xeb。

如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。

1.6.2 fill_on_free[=MAX_FILLED_BYTES]

当分配空间被释放的时候,用 0xef 填充需要释放的空间。

如果 MAX_FILLED_BYTES 指定,那么按照该值只填充指定空间大小。默认填充整个分配空间。

1.6.3 fill[=MAX_FILLED_BYTES]

同时使能 fill_on_alloc 和 fill_on_free

1.6.4 expand_alloc[=EXPADN_BYTES]

为每一次分配扩充额外数量的空间。如果EXPAND_BYTES 设定,将按照该值扩充分配空间,默认值为 16字节,最大为16384 字节。

1.7 释放内存空间

1.7.1 free_track[=ALLOCATION_COUNT]

如果使用该option,当一个指针被释放时,不会立即释放内存,而是将其添加到一个列表,该列表存放已被释放的allocation。除了添加到列表之外,整个分配空间将被 0xef 填充(相当于使能fill_on_free),free 时候的调用栈也会被记录。这里调用栈记录是完全独立于backtrace option,也是进行自动记录。默认会记录最大到 16 frames 的调用找,但这个值可以使用 free_track_backtrace_num_frames 进行更改。

当然也可以将 free_track_backtrace_num_frames 设为0 来禁用该功能。

当列表满了的时候,一个 allocation 将从列表中移除,且会确认该空间的内容从添加到列表时就已经被更改。当进程结束的时候,在列表中所有的分配空间都会被核实。

ALLOCATION_COUNT 如果被设定,代表列表中总的 allocation 的个数,默认记录100 个已释放的 allocation 数,最大值可以设到 16384.

日志如下:

  1. 04-15 12:00:31.304 7412 7412 E malloc_debug: +++ ALLOCATION 0x12345678 USED AFTER FREE
  2. 04-15 12:00:31.305 7412 7412 E malloc_debug: allocation[20] = 0xaf (expected 0xef)
  3. 04-15 12:00:31.305 7412 7412 E malloc_debug: allocation[99] = 0x12 (expected 0xef)
  4. 04-15 12:00:31.305 7412 7412 E malloc_debug: Backtrace at time of free:
  5. 04-15 12:00:31.305 7412 7412 E malloc_debug: #00 pc 00029310 /system/lib/libc.so
  6. 04-15 12:00:31.305 7412 7412 E malloc_debug: #01 pc 00021438 /system/lib/libc.so (newlocale+160)
  7. 04-15 12:00:31.305 7412 7412 E malloc_debug: #02 pc 000a9e38 /system/lib/libc++.so
  8. 04-15 12:00:31.305 7412 7412 E malloc_debug: #03 pc 000a28a8 /system/lib/libc++.so

通过这样的方式,可以检测填充为 0xef 的空间是否在被 freed 之后还在使用。

1.7.2 free_track_backtrace_num_frames[=MAX_FRAMES]

这个option 只针对上面 free_track 被设定时生效。表示当一个分配空间被释放,有多少个 frames 的调用栈可以抓取。MAX_FRAMES 表示被抓取的 frames 的数量,如果设为0, 表示在分配空间被释放时,不会抓取任何调用栈。默认值为16,最大可以设置到 256.

二、malloc_debug实现原理

2.1 malloc_debug 初始化

2.1.1 进程加载lib.so 时进行preinit

在程序加载 libc.so 的时候会调用 __libc_preinit():

  1. bionic/libc/bionic/libc_init_dynamic.cpp
  2. // We flag the __libc_preinit function as a constructor to ensure that
  3. // its address is listed in libc.so's .init_array section.
  4. // This ensures that the function is called by the dynamic linker as
  5. // soon as the shared library is loaded.
  6. // We give this constructor priority 1 because we want libc's constructor
  7. // to run before any others (such as the jemalloc constructor), and lower
  8. // is better (http://b/68046352).
  9. __attribute__((constructor(1))) static void __libc_preinit() {
  10. // The linker has initialized its copy of the global stack_chk_guard, and filled in the main
  11. // thread's TLS slot with that value. Initialize the local global stack guard with its value.
  12. __stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]);
  13. __libc_preinit_impl();
  14. }
  15. // We need a helper function for __libc_preinit because compiling with LTO may
  16. // inline functions requiring a stack protector check, but __stack_chk_guard is
  17. // not initialized at the start of __libc_preinit. __libc_preinit_impl will run
  18. // after __stack_chk_guard is initialized and therefore can safely have a stack
  19. // protector.
  20. __attribute__((noinline))
  21. static void __libc_preinit_impl() {
  22. #if defined(__i386__)
  23. __libc_init_sysinfo();
  24. #endif
  25. // Register libc.so's copy of the TLS generation variable so the linker can
  26. // update it when it loads or unloads a shared object.
  27. TlsModules& tls_modules = __libc_shared_globals()->tls_modules;
  28. tls_modules.generation_libc_so = &__libc_tls_generation_copy;
  29. __libc_tls_generation_copy = tls_modules.generation;
  30. // _libc_globals 初始化
  31. __libc_init_globals();
  32. __libc_init_common();
  33. // Hooks for various libraries to let them know that we're starting up.
  34. // 为每个进程注册通知
  35. __libc_globals.mutate(__libc_init_malloc);
  36. // Install reserved signal handlers for assisting the platform's profilers.
  37. __libc_init_profiling_handlers();
  38. __libc_init_fork_handler();
  39. #if __has_feature(hwaddress_sanitizer)
  40. // Notify the HWASan runtime library whenever a library is loaded or unloaded
  41. // so that it can update its shadow memory.
  42. // 当loaded或unloaded so库时,通知HWASan运行库更新shadow memory
  43. __libc_shared_globals()->load_hook = __hwasan_library_loaded;
  44. __libc_shared_globals()->unload_hook = __hwasan_library_unloaded;
  45. #endif
  46. netdClientInit();
  47. }

__libc_preinit() 在main 函数执行前执行,因为它有 __attribute__((constructor(1))),通过这个constructor 对此程序所连接的 libc.so 进行 preinit。

( __attribute__((constructor)) 是 GCC 和 Clang 编译器提供的函数属性,用来标记一个函数为构造函数。构造函数是在程序运行时,在 main() 函数执行之前自动执行的函数。而括号中的数字可以用来指定构造函数的优先级,数字越小,优先级越高,默认为0 )

__libc_init_malloc():

  1. // bioni/libc/bionic/malloc_common_dynamic.cpp
  2. // Initializes memory allocation framework.
  3. // This routine is called from __libc_init routines in libc_init_dynamic.cpp.
  4. __BIONIC_WEAK_FOR_NATIVE_BRIDGE
  5. __LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {
  6. MallocInitImpl(globals);
  7. }
  1. // bionic/libc/private/bionic_globals.h
  2. struct libc_globals {
  3. vdso_entry vdso[VDSO_END];
  4. long setjmp_cookie;
  5. uintptr_t heap_pointer_tag;
  6. _Atomic(const MallocDispatch*) current_dispatch_table;
  7. _Atomic(const MallocDispatch*) default_dispatch_table;
  8. MallocDispatch malloc_dispatch_table;
  9. };
  1. // bionic/libc/bionic/malloc_common_dynamic.cpp
  2. static void MallocInitImpl(libc_globals* globals) {
  3. char prop[PROP_VALUE_MAX];
  4. char* options = prop;
  5. MaybeInitGwpAsanFromLibc(globals);
  6. // Prefer malloc debug since it existed first and is a more complete
  7. // malloc interceptor than the hooks.
  8. bool hook_installed = false;
  9. // 检查工具是否enable
  10. if (CheckLoadMallocDebug(&options)) {
  11. hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);
  12. } else if (CheckLoadMallocHooks(&options)) {
  13. hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);
  14. }
  15. if (!hook_installed) {
  16. if (HeapprofdShouldLoad()) {
  17. HeapprofdInstallHooksAtInit(globals);
  18. }
  19. } else {
  20. // Record the fact that incompatible hooks are active, to skip any later
  21. // heapprofd signal handler invocations.
  22. HeapprofdRememberHookConflict();
  23. }
  24. }

CheckLoadMallocDebug():

  1. // bionic/libc/bionic/malloc_common_dynamic.cpp
  2. static bool CheckLoadMallocDebug(char** options) {
  3. // If kDebugMallocEnvOptions is set then it overrides the system properties.
  4. // 如果通过设备环境和系统属性都没有设置options,返回false
  5. char* env = getenv(kDebugEnvOptions);
  6. if (env == nullptr || env[0] == '\0') {
  7. if (__system_property_get(kDebugPropertyOptions, *options) == 0 || *options[0] == '\0') {
  8. return false;
  9. }
  10. // Check to see if only a specific program should have debug malloc enabled.
  11. // 检查 prop libc.debug.malloc.program 是否设定 program name
  12. char program[PROP_VALUE_MAX];
  13. if (__system_property_get(kDebugPropertyProgram, program) != 0 &&
  14. strstr(getprogname(), program) == nullptr) {
  15. return false;
  16. }
  17. } else {
  18. *options = env;
  19. }
  20. return true;
  21. }

InstallHooks():

  1. // bionic/libc/bionic/malloc_common_dynamic.cpp
  2. static bool InstallHooks(libc_globals* globals, const char* options, const char* prefix,
  3. const char* shared_lib) {
  4. void* impl_handle = LoadSharedLibrary(shared_lib, prefix, &globals->malloc_dispatch_table);
  5. if (impl_handle == nullptr) {
  6. return false;
  7. }
  8. if (!FinishInstallHooks(globals, options, prefix)) {
  9. dlclose(impl_handle);
  10. return false;
  11. }
  12. return true;
  13. }

LoadSharedLibrary():

LoadSharedLibrary() 中通过 dlopen() 动态加载 libc_malloc_debug.so,接着通过 InitSharedLibrary() 函数查找如下names 数组中的 symbol,将查找到的 symbol 保存在全局数组变量 gfunctions 中。注意查找的函数都会加上 debug_ 前缀。

  1. // bionic/libc/bionic/malloc_common_dynamic.cpp
  2. void* LoadSharedLibrary(const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
  3. void* impl_handle = nullptr;
  4. // Try to load the libc_malloc_* libs from the "runtime" namespace and then
  5. // fall back to dlopen() to load them from the default namespace.
  6. //
  7. // The libraries are packaged in the runtime APEX together with libc.so.
  8. // However, since the libc.so is searched via the symlink in the system
  9. // partition (/system/lib/libc.so -> /apex/com.android.runtime/bionic/libc.so)
  10. // libc.so is loaded into the default namespace. If we just dlopen() here, the
  11. // linker will load the libs found in /system/lib which might be incompatible
  12. // with libc.so in the runtime APEX. Use android_dlopen_ext to explicitly load
  13. // the ones in the runtime APEX.
  14. struct android_namespace_t* runtime_ns = android_get_exported_namespace("com_android_runtime");
  15. if (runtime_ns != nullptr) {
  16. const android_dlextinfo dlextinfo = {
  17. .flags = ANDROID_DLEXT_USE_NAMESPACE,
  18. .library_namespace = runtime_ns,
  19. };
  20. impl_handle = android_dlopen_ext(shared_lib, RTLD_NOW | RTLD_LOCAL, &dlextinfo);
  21. }
  22. if (impl_handle == nullptr) {
  23. impl_handle = dlopen(shared_lib, RTLD_NOW | RTLD_LOCAL);
  24. }
  25. if (impl_handle == nullptr) {
  26. error_log("%s: Unable to open shared library %s: %s", getprogname(), shared_lib, dlerror());
  27. return nullptr;
  28. }
  29. if (!InitSharedLibrary(impl_handle, shared_lib, prefix, dispatch_table)) {
  30. dlclose(impl_handle);
  31. impl_handle = nullptr;
  32. }
  33. return impl_handle;
  34. }
  35. bool InitSharedLibrary(void* impl_handle, const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
  36. static constexpr const char* names[] = {
  37. "initialize",
  38. "finalize",
  39. "get_malloc_leak_info",
  40. "free_malloc_leak_info",
  41. "malloc_backtrace",
  42. "write_malloc_leak_info",
  43. };
  44. for (size_t i = 0; i < FUNC_LAST; i++) {
  45. char symbol[128];
  46. snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);
  47. gFunctions[i] = dlsym(impl_handle, symbol);
  48. if (gFunctions[i] == nullptr) {
  49. error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);
  50. ClearGlobalFunctions();
  51. return false;
  52. }
  53. }
  54. if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {
  55. ClearGlobalFunctions();
  56. return false;
  57. }
  58. return true;
  59. }

通过 InitMallocFuntions() 在这个so里继续查找 malloc_debug 其他系列函数:

  1. // bionic/libc/malloc_debug/malloc_debug.cpp
  2. debug_free()
  3. debug_calloc()
  4. debug_mallinfo()
  5. debug_malloc()
  6. debug_realloc()
  7. ...

并将查找到的 symbol 都存放到 MallocDispatch 对应的函数指针,这个 MallocDispatch 就是最开始 LoadSharedLibrary() 的第三个参数,也就是 globals->malloc_dispatch_table:

  1. // bionic/libc/private/bionic_malloc_dispatch.h
  2. struct MallocDispatch {
  3. MallocCalloc calloc;
  4. MallocFree free;
  5. MallocMallinfo mallinfo;
  6. MallocMalloc malloc;
  7. MallocMallocUsableSize malloc_usable_size;
  8. MallocMemalign memalign;
  9. MallocPosixMemalign posix_memalign;
  10. #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  11. MallocPvalloc pvalloc;
  12. #endif
  13. MallocRealloc realloc;
  14. #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  15. MallocValloc valloc;
  16. #endif
  17. MallocIterate malloc_iterate;
  18. MallocMallocDisable malloc_disable;
  19. MallocMallocEnable malloc_enable;
  20. MallocMallopt mallopt;
  21. MallocAlignedAlloc aligned_alloc;
  22. MallocMallocInfo malloc_info;
  23. } __attribute__((aligned(32)));

指定这些函数的目的是什么呢?详细可以查看第 2.2 中的malloc() 函数调用。

FinishInstallHooks():

  1. // bionic/libc/bionic/malloc_common_dynamic.cpp
  2. bool FinishInstallHooks(libc_globals* globals, const char* options, const char* prefix) {
  3. init_func_t init_func = reinterpret_cast<init_func_t>(gFunctions[FUNC_INITIALIZE]);
  4. // If GWP-ASan was initialised, we should use it as the dispatch table for
  5. // heapprofd/malloc_debug/malloc_debug.
  6. const MallocDispatch* prev_dispatch = GetDefaultDispatchTable();
  7. if (prev_dispatch == nullptr) {
  8. prev_dispatch = NativeAllocatorDispatch();
  9. }
  10. if (!init_func(prev_dispatch, &gZygoteChild, options)) {
  11. error_log("%s: failed to enable malloc %s", getprogname(), prefix);
  12. ClearGlobalFunctions();
  13. return false;
  14. }
  15. // Do a pointer swap so that all of the functions become valid at once to
  16. // avoid any initialization order problems.
  17. atomic_store(&globals->default_dispatch_table, &globals->malloc_dispatch_table);
  18. if (!MallocLimitInstalled()) {
  19. atomic_store(&globals->current_dispatch_table, &globals->malloc_dispatch_table);
  20. }
  21. // Use atexit to trigger the cleanup function. This avoids a problem
  22. // where another atexit function is used to cleanup allocated memory,
  23. // but the finalize function was already called. This particular error
  24. // seems to be triggered by a zygote spawned process calling exit.
  25. int ret_value = __cxa_atexit(MallocFiniImpl, nullptr, nullptr);
  26. if (ret_value != 0) {
  27. // We don't consider this a fatal error.
  28. warning_log("failed to set atexit cleanup function: %d", ret_value);
  29. }
  30. return true;
  31. }

该函数大致做了三件事情:

1)调用 malloc_debug 中的 debug_initialize(),对 malloc_debug 内存进行初始化工作,例如其中的关键变量 g_dispatch 和 g_debug,注意参数 prev_dispatch 是默认dispatch,最开始默认为 NULL,用 NativeAllocatorDispatch() 进行创建;

  1. // bionic/libc/malloc_debug/malloc_debug.cpp
  2. bool debug_initialize(const MallocDispatch* malloc_dispatch, bool* zygote_child, const char* options) {
  3. if (zygote_child == nullptr || options == nullptr) {
  4. return false;
  5. }
  6. if (__asan_init != 0) {
  7. error_log("malloc debug cannot be enabled alongside ASAN");
  8. return false;
  9. }
  10. InitAtfork();
  11. g_zygote_child = zygote_child;
  12. g_dispatch = malloc_dispatch;
  13. if (!DebugDisableInitialize()) {
  14. return false;
  15. }
  16. DebugData* debug = new DebugData();
  17. // g_debug 在初始化
  18. if (!debug->Initialize(options) || !Unreachable::Initialize(debug->config())) {
  19. delete debug;
  20. DebugDisableFinalize();
  21. return false;
  22. }
  23. g_debug = debug;
  24. // Always enable the backtrace code since we will use it in a number// of different error cases.backtrace_startup();
  25. if (g_debug->config().options() & VERBOSE) {
  26. info_log("%s: malloc debug enabled", getprogname());
  27. }
  28. ScopedConcurrentLock::Init();
  29. return true;
  30. }
  31. // bionic/libc/malloc_debug/DebugData.cpp
  32. bool DebugData::Initialize(const char* options) {
  33. if (config_.options() & HEADER_OPTIONS) {
  34. // Initialize all of the static header offsets.
  35. pointer_offset_ = __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES);
  36. if (config_.options() & FRONT_GUARD) {
  37. front_guard.reset(new FrontGuardData(this, config_, &pointer_offset_));
  38. }
  39. extra_bytes_ = pointer_offset_;
  40. // Initialize all of the non-header data.
  41. if (config_.options() & REAR_GUARD) {
  42. rear_guard.reset(new RearGuardData(this, config_));
  43. extra_bytes_ += config_.rear_guard_bytes();
  44. }
  45. }
  46. ...
  47. if (config_.options() & EXPAND_ALLOC) {
  48. extra_bytes_ += config_.expand_alloc_bytes();
  49. }
  50. return true;
  51. }

2)设置 __libc_globals 对象中的 libc_globals.default_dispatch_table 和 current_dispatch_table 指向 malloc_dispatch_table,以后在 malloc 库函数里都会通过 GetDispatchTable(),这个函数就是返回的 current_dispatch_table 指针;

  1. // bionic/libc/bionic/malloc_common.h
  2. static inline const MallocDispatch* GetDispatchTable() {
  3. return atomic_load_explicit(&__libc_globals->current_dispatch_table, memory_order_acquire);
  4. }

3)通过 __cxa_atexit() 调用,注册 MallocFiniImpl(),注册的此函数将在进程 exit 时(例如调用 exit()函数) 进行回调:

  1. // bionic/libc/bionic/malloc_common_dynamic.cpp
  2. static void MallocFiniImpl(void*) {
  3. // Our BSD stdio implementation doesn't close the standard streams,
  4. // it only flushes them. Other unclosed FILE*s will show up as
  5. // malloc leaks, but to avoid the standard streams showing up in
  6. // leak reports, close them here.
  7. fclose(stdin);
  8. fclose(stdout);
  9. fclose(stderr);
  10. reinterpret_cast<finalize_func_t>(gFunctions[FUNC_FINALIZE])();
  11. }

终调用的是 malloc_debug 中的 debug_finalize() 进行检查,例如是否有内存泄漏,如果有,会将调用栈打印出来。详见2.4.

注:

__cxa_atexit() 注册的 MallocFiniImpl(),是需要进程主动调用 exit() 才会调用 debug_finalize() 去检查,如果进程收到 fatal signal 而导致被 kernel 强制 exit,此时进程不会调用 exit(),也就不会调用 debug_finalize() 进行检查了。

2.2 malloc函数入口

  1. // bionic/libc/bionic/malloc_common.cpp
  2. extern "C" void* malloc(size_t bytes) {
  3. auto dispatch_table = GetDispatchTable();
  4. void *result;
  5. if (__predict_false(dispatch_table != nullptr)) {
  6. result = dispatch_table->malloc(bytes);
  7. } else {
  8. result = Malloc(malloc)(bytes);
  9. }
  10. if (__predict_false(result == nullptr)) {
  11. warning_log("malloc(%zu) failed: returning null pointer", bytes);
  12. return nullptr;
  13. }
  14. return MaybeTagPointer(result);
  15. }

如果没有使能 malloc_debug 时,dispatch_table 为 nullptr,则会进入 Malloc() 调用,即原生的 malloc 函数。

这里的 dispatch_table 就是加载 libc_malloc_debug.so 之后初始化全局变量 __libc_globals 中的 current_dispatch_table.

如果使能了 malloc_debug 时,就会调用 dispatch_table->malloc(),这里的 malloc 函数就是之前MallocDispatch 里面的函数指针。这里最终调用的就是 malloc_debug 中的 debug_malloc() 函数。

2.3 debug_malloc函数入口

  1. // bionic/libc/malloc_debug/malloc_debug.cpp
  2. void* debug_malloc(size_t size) {
  3. if (DebugCallsDisabled()) {
  4. return g_dispatch->malloc(size);
  5. }
  6. ScopedConcurrentLock lock;
  7. ScopedDisableDebugCalls disable;
  8. ScopedBacktraceSignalBlocker blocked;
  9. // malloc_debug内存分配核心函数
  10. void* pointer = InternalMalloc(size);
  11. // 内存首地址存到g_debug
  12. if (g_debug->config().options() & RECORD_ALLOCS) {
  13. g_debug->record->AddEntry(new MallocEntry(pointer, size));
  14. }
  15. return pointer;
  16. }

FinishInstallHooks() 函数会对 malloc_debug 进行初始化,其中就包括 malloc_debug 中的关键变量 g_debug,options 的解析也是在 g_debug 的 Initialize() 中完成。

2.3.1 InternalMalloc函数

  1. // bionic/libc/malloc_debug/malloc_debug.cpp
  2. static void* InternalMalloc(size_t size) {
  3. if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
  4. debug_dump_heap(android::base::StringPrintf(
  5. "%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid())
  6. .c_str());
  7. }
  8. if (size == 0) {
  9. size = 1;
  10. }
  11. //g_debug在初始化的时候,会根据options解析 extra_bytes_
  12. size_t real_size = size + g_debug->extra_bytes();
  13. if (real_size < size) {
  14. // Overflow.
  15. errno = ENOMEM;
  16. return nullptr;
  17. }
  18. if (size > PointerInfoType::MaxSize()) {
  19. errno = ENOMEM;
  20. return nullptr;
  21. }
  22. void* pointer;
  23. //创建 header,real_size 按照16字节或8字节对齐,64位系统按16字节
  24. if (g_debug->HeaderEnabled()) {
  25. Header* header =
  26. reinterpret_cast<Header*>(g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size));
  27. if (header == nullptr) {
  28. return nullptr;
  29. }
  30. pointer = InitHeader(header, header, size);
  31. } else {
  32. // android p之后直接调用原生的系统内存分配机制,一般用scudo
  33. pointer = g_dispatch->malloc(real_size);
  34. }
  35. if (pointer != nullptr) {
  36. // 如果打开track,将内存首地址存入PointerData
  37. if (g_debug->TrackPointers()) {
  38. PointerData::Add(pointer, size);
  39. }
  40. // options的FILL_ON_ALLOC选项打开,在内存分配时进行数据0xeb填充
  41. if (g_debug->config().options() & FILL_ON_ALLOC) {
  42. size_t bytes = InternalMallocUsableSize(pointer);
  43. size_t fill_bytes = g_debug->config().fill_on_alloc_bytes();
  44. bytes = (bytes < fill_bytes) ? bytes : fill_bytes;
  45. memset(pointer, g_debug->config().fill_alloc_value(), bytes);
  46. }
  47. }
  48. return pointer;
  49. }

real_size 是在size 基础上又加上了 g_debug->extra_bytes(),在g_debug 在初始化时根据 options 指定计算出增加的空间保存在 g_debug->extra_bytes_ 中,详细可以查看 g_debug->Initialize().

通过 g_dispatch->malloc() 通过原生的 malloc() 流程申请 real_size 空间:

  1. // bionic/libc/bionic/malloc_common.cpp
  2. static constexpr MallocDispatch __libc_malloc_default_dispatch __attribute__((unused)) = {
  3. Malloc(calloc),
  4. Malloc(free),
  5. Malloc(mallinfo),
  6. Malloc(malloc),
  7. Malloc(malloc_usable_size),
  8. Malloc(memalign),
  9. Malloc(posix_memalign),
  10. #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  11. Malloc(pvalloc),
  12. #endif
  13. Malloc(realloc),
  14. #if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  15. Malloc(valloc),
  16. #endif
  17. Malloc(malloc_iterate),
  18. Malloc(malloc_disable),
  19. Malloc(malloc_enable),
  20. Malloc(mallopt),
  21. Malloc(aligned_alloc),
  22. Malloc(malloc_info),
  23. };

小结:在原来size基础上新增加了一些检测相关的东西,最终调用原生的malloc进行内存分配(一般采用scudo内存分配机制),通过mmap syscall方式向内核请求分配内存。

2.4 mem leak监控原理

内存泄漏的检测原理:维护一个记录内存申请和释放的列表,每当申请内存时列表成员+1,内存释放时列表成员-1,程序退出时列表中还存在的成员即内存泄漏的成员。

1)内存申请

在 PointData 里维护了一个全局的 pointers_ map,每次申请内存时调用 Add 函数增加 pointers_ 成员,释放内存时调用 Remove 函数移除 pointers_ 成员。

  1. // malloc_debug.cpp
  2. static TimedResult InternalMalloc(size_t size) {
  3. ......
  4. if (pointer != nullptr) {
  5. if (g_debug->TrackPointers()) {
  6. // 内存申请时调用 Add 函数增加 pointers_ 成员
  7. PointerData::Add(pointer, size);
  8. }
  9. ......
  10. }
  11. return result;
  12. }

2)内存释放

  1. // malloc_debug.cpp
  2. static TimedResult InternalFree(void* pointer) {
  3. ...
  4. if (g_debug->TrackPointers()) {
  5. // 释放内存时调用 Remove 函数移除 pointers_ 成员
  6. PointerData::Remove(pointer);
  7. }
  8. ...
  9. return result;
  10. }

3)内存泄漏信息打印

程序退出时调用 debug_finalize() 打印内存泄漏并保存dump 文件,调用 LogLeaks() 将内存泄漏信息在log 打印,将dump 文件写入设备存储。

  1. // bionic/libc/malloc_debug/malloc_debug.cpp
  2. void debug_finalize() {
  3. ......
  4. if (g_debug->config().options() & LEAK_TRACK) {
  5. PointerData::LogLeaks();
  6. }
  7. ......
  8. }

LogLeaks():

LogLeaks() 内部调用 GetList 函数获得 pointers_ 成员,打印相关backtrace信息。

  1. // bionic/libc/malloc_debug/PointerData.cpp
  2. void PointerData::LogLeaks() {
  3. std::vector<ListInfoType> list;
  4. std::lock_guard<std::mutex> pointer_guard(pointer_mutex_);
  5. std::lock_guard<std::mutex> frame_guard(frame_mutex_);
  6. GetList(&list, false);
  7. size_t track_count = 0;
  8. for (const auto& list_info : list) {
  9. error_log("+++ %s leaked block of size %zu at 0x%" PRIxPTR " (leak %zu of %zu)", getprogname(),
  10. list_info.size, list_info.pointer, ++track_count, list.size());
  11. if (list_info.backtrace_info != nullptr) {
  12. error_log("Backtrace at time of allocation:");
  13. UnwindLog(*list_info.backtrace_info);
  14. } else if (list_info.frame_info != nullptr) {
  15. error_log("Backtrace at time of allocation:");
  16. backtrace_log(list_info.frame_info->frames.data(), list_info.frame_info->frames.size());
  17. }
  18. // Do not bother to free the pointers, we are about to exit any way.
  19. }
  20. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/609226
推荐阅读
相关标签
  

闽ICP备14008679号