Android 中malloc_debug 原理详解

版本基于:Android R

关联博文:

Android 中malloc_debug 使用详解

0. 前言

最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针对整个系统,运行代价还是很高的。而笔者遇到的问题大致有所方向,能指定到某一个进程,针对单个进程是否有检测的功能呢?答案是肯定的,也就是本文需要分析的 malloc_debug。

1. malloc_debug简介

malloc_debug 是调试native 内存问题的一个工具,能够帮助我们检测内存损坏、内存泄漏、释放再使用等问题。详细的细节可以查看:bionic/libc/malloc_debug/README.md 文件,该文件主要总结Android N 及之后版本中的使用方法,而Android N 之前的malloc_debug 使用方法可以查看README_marshmallow_and_earlier.md 这个文件。

2. malloc_debug 初始化

在分析 malloc_debug 初始化之前,需要记住 malloc_debug 在libc 中的几个重要常量:

bionic/libc/bionic/malloc_common_dynamic.cpp

static constexpr char kDebugSharedLib[] = "libc_malloc_debug.so";
static constexpr char kDebugPrefix[] = "debug";
static constexpr char kDebugPropertyOptions[] = "libc.debug.malloc.options";
static constexpr char kDebugPropertyProgram[] = "libc.debug.malloc.program";
static constexpr char kDebugEnvOptions[] = "LIBC_DEBUG_MALLOC_OPTIONS";

这里,指定了动态加载malloc_debug 的so 名称,也指定了 malloc_debug 配置的prop 名称和环境变量名称。 

2.1 加载lib.so 时进行preinit

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

bionic/libc/bionic/libc_init_dynamic.cpp

// We flag the __libc_preinit function as a constructor to ensure that
// its address is listed in libc.so's .init_array section.
// This ensures that the function is called by the dynamic linker as
// soon as the shared library is loaded.
// We give this constructor priority 1 because we want libc's constructor
// to run before any others (such as the jemalloc constructor), and lower
// is better (http://b/68046352).
__attribute__((constructor(1))) static void __libc_preinit() {
  // The linker has initialized its copy of the global stack_chk_guard, and filled in the main
  // thread's TLS slot with that value. Initialize the local global stack guard with its value.
  __stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]);

  __libc_preinit_impl();
}

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

该函数的核心处理函数是 __libc_preinit_impl():

bionic/libc/bionic/libc_init_dynamic.cpp

// We need a helper function for __libc_preinit because compiling with LTO may
// inline functions requiring a stack protector check, but __stack_chk_guard is
// not initialized at the start of __libc_preinit. __libc_preinit_impl will run
// after __stack_chk_guard is initialized and therefore can safely have a stack
// protector.
__attribute__((noinline))
static void __libc_preinit_impl() {
#if defined(__i386__)
  __libc_init_sysinfo();
#endif

  // Register libc.so's copy of the TLS generation variable so the linker can
  // update it when it loads or unloads a shared object.
  TlsModules& tls_modules = __libc_shared_globals()->tls_modules;
  tls_modules.generation_libc_so = &__libc_tls_generation_copy;
  __libc_tls_generation_copy = tls_modules.generation;

  __libc_init_globals();
  __libc_init_common();

  // Hooks for various libraries to let them know that we're starting up.
  __libc_globals.mutate(__libc_init_malloc);

  // Install reserved signal handlers for assisting the platform's profilers.
  __libc_init_profiling_handlers();

  __libc_init_fork_handler();

#if __has_feature(hwaddress_sanitizer)
  // Notify the HWASan runtime library whenever a library is loaded or unloaded
  // so that it can update its shadow memory.
  __libc_shared_globals()->load_hook = __hwasan_library_loaded;
  __libc_shared_globals()->unload_hook = __hwasan_library_unloaded;
#endif

  netdClientInit();
}

这里需要注意三点:

  • 全局变量 __libc_globals:
bionic/libc/private/bionic_globals.h

__LIBC_HIDDEN__ extern WriteProtected<libc_globals> __libc_globals;
  • __libc_init_globals() 对__libc_globals 初始化:
  • __libc_globals.mutate(__libc_init_malloc); 为每个进程注册通知

2.2 入口函数 __libc_init_malloc()

bioni/libc/bionic/malloc_common_dynamic.cpp

// Initializes memory allocation framework.
// This routine is called from __libc_init routines in libc_init_dynamic.cpp.
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
__LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {
  MallocInitImpl(globals);
}

参数 globals 指向的是 __libc_globals 对象的 contents 里的 value,即 libc_globals 对象。这里顺便来看下 libc_globals:

bionic/libc/private/bionic_globals.h

struct libc_globals {
  vdso_entry vdso[VDSO_END];
  long setjmp_cookie;
  uintptr_t heap_pointer_tag;
  _Atomic(const MallocDispatch*) current_dispatch_table;
  _Atomic(const MallocDispatch*) default_dispatch_table;
  MallocDispatch malloc_dispatch_table;
};

malloc_dispatch_table 就是后来用来存放,libc_malloc_debug.so 中的函数 symbol,详细看第 2.4.1 节。

回到函数,该函数最终调用的 MallocInitImpl(),该函数详细看第 2.3 节。

2.3 MallocInitImpl()

bionic/libc/bionic/malloc_common_dynamic.cpp

static void MallocInitImpl(libc_globals* globals) {
  char prop[PROP_VALUE_MAX];
  char* options = prop;

  MaybeInitGwpAsanFromLibc(globals);

  // Prefer malloc debug since it existed first and is a more complete
  // malloc interceptor than the hooks.
  bool hook_installed = false;
  if (CheckLoadMallocDebug(&options)) {
    hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);
  } else if (CheckLoadMallocHooks(&options)) {
    hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);
  }

  if (!hook_installed) {
    if (HeapprofdShouldLoad()) {
      HeapprofdInstallHooksAtInit(globals);
    }
  } else {
    // Record the fact that incompatible hooks are active, to skip any later
    // heapprofd signal handler invocations.
    HeapprofdRememberHookConflict();
  }
}

针对 malloc_debug() 会通过 CheckLoadMallocDebug() 来确认该工具是否 enabled:

bionic/libc/bionic/malloc_common_dynamic.cpp

static bool CheckLoadMallocDebug(char** options) {
  // If kDebugMallocEnvOptions is set then it overrides the system properties.
  char* env = getenv(kDebugEnvOptions);
  if (env == nullptr || env[0] == '\0') {
    if (__system_property_get(kDebugPropertyOptions, *options) == 0 || *options[0] == '\0') {
      return false;
    }

    // Check to see if only a specific program should have debug malloc enabled.
    char program[PROP_VALUE_MAX];
    if (__system_property_get(kDebugPropertyProgram, program) != 0 &&
        strstr(getprogname(), program) == nullptr) {
      return false;
    }
  } else {
    *options = env;
  }
  return true;
}

通过该函数主要有两种方式使能 malloc_debug:

  • 通过环境变量 LIBC_DEBUG_MALLOC_OPTIONS 指定options
  • 通过prop libc.debug.malloc.options 指定 options

回到 MallocInitImpl() 函数,当确认 malloc_debug 使能时,会调用 InstallHooks() 进行动态加载 libc_malloc_debug.so,该函数的参数除了传入 globals 和 options,还指定了库的使用前缀和库名,这里分别是“debug” 和 libc_malloc_debug.so。函数 InstallHooks() 详细看 2.4 节。

2.4 InstallHooks()

bionic/libc/bionic/malloc_common_dynamic.cpp

static bool InstallHooks(libc_globals* globals, const char* options, const char* prefix,
                         const char* shared_lib) {
  void* impl_handle = LoadSharedLibrary(shared_lib, prefix, &globals->malloc_dispatch_table);
  if (impl_handle == nullptr) {
    return false;
  }

  if (!FinishInstallHooks(globals, options, prefix)) {
    dlclose(impl_handle);
    return false;
  }
  return true;
}

这里主要做了两件事情:

  • LoadSharedLibrary(),注意这里的第三个参数 malloc_dispatch_table;
  • FinishInstallHooks(),进行malloc_debug 初始化工作,并注册回调函数 MallocFiniImpl() 进行收尾;

2.4.1 LoadSharedLibrary()

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

bionic/libc/bionic/malloc_common_dynamic.cpp

bool InitSharedLibrary(void* impl_handle, const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {
  static constexpr const char* names[] = {
    "initialize",
    "finalize",
    "get_malloc_leak_info",
    "free_malloc_leak_info",
    "malloc_backtrace",
    "write_malloc_leak_info",
  };
  for (size_t i = 0; i < FUNC_LAST; i++) {
    char symbol[128];
    snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);
    gFunctions[i] = dlsym(impl_handle, symbol);
    if (gFunctions[i] == nullptr) {
      error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);
      ClearGlobalFunctions();
      return false;
    }
  }

  if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {
    ClearGlobalFunctions();
    return false;
  }
  return true;
}

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

bionic/libc/malloc_debug/malloc_debug.cpp

debug_free()
debug_calloc()
debug_mallinfo()
debug_malloc()
debug_realloc()
...

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

bionic/libc/private/bionic_malloc_dispatch.h

struct MallocDispatch {
  MallocCalloc calloc;
  MallocFree free;
  MallocMallinfo mallinfo;
  MallocMalloc malloc;
  MallocMallocUsableSize malloc_usable_size;
  MallocMemalign memalign;
  MallocPosixMemalign posix_memalign;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  MallocPvalloc pvalloc;
#endif
  MallocRealloc realloc;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  MallocValloc valloc;
#endif
  MallocIterate malloc_iterate;
  MallocMallocDisable malloc_disable;
  MallocMallocEnable malloc_enable;
  MallocMallopt mallopt;
  MallocAlignedAlloc aligned_alloc;
  MallocMallocInfo malloc_info;
} __attribute__((aligned(32)));

指定这些函数的目的是什么呢?

详细可以查看第 3 节的malloc() 函数调用。 

2.4.2 FinishInstallHooks()

bionic/libc/bionic/malloc_common_dynamic.cpp

bool FinishInstallHooks(libc_globals* globals, const char* options, const char* prefix) {
  init_func_t init_func = reinterpret_cast<init_func_t>(gFunctions[FUNC_INITIALIZE]);

  // If GWP-ASan was initialised, we should use it as the dispatch table for
  // heapprofd/malloc_debug/malloc_debug.
  const MallocDispatch* prev_dispatch = GetDefaultDispatchTable();
  if (prev_dispatch == nullptr) {
    prev_dispatch = NativeAllocatorDispatch();
  }

  if (!init_func(prev_dispatch, &gZygoteChild, options)) {
    error_log("%s: failed to enable malloc %s", getprogname(), prefix);
    ClearGlobalFunctions();
    return false;
  }

  // Do a pointer swap so that all of the functions become valid at once to
  // avoid any initialization order problems.
  atomic_store(&globals->default_dispatch_table, &globals->malloc_dispatch_table);
  if (!MallocLimitInstalled()) {
    atomic_store(&globals->current_dispatch_table, &globals->malloc_dispatch_table);
  }

  // Use atexit to trigger the cleanup function. This avoids a problem
  // where another atexit function is used to cleanup allocated memory,
  // but the finalize function was already called. This particular error
  // seems to be triggered by a zygote spawned process calling exit.
  int ret_value = __cxa_atexit(MallocFiniImpl, nullptr, nullptr);
  if (ret_value != 0) {
    // We don't consider this a fatal error.
    warning_log("failed to set atexit cleanup function: %d", ret_value);
  }
  return true;
}

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

  • 调用 malloc_debug 中的 debug_initialize(),对malloc_debug 内存进行初始化工作,例如其中的关键变量 g_dispatch 和 g_debug,注意参数prev_dispatch 是默认dispatch,最开始默认为NULL,用 NativeAllocatorDispatch() 进行创建;
  • 设置 __libc_globals 对象中的 libc_globals.default_dispatch_tablecurrent_dispatch_table 指向 malloc_dispatch_table,以后在 malloc 库函数里都会通过 GetDispatchTable(),这个函数就是返回的 current_dispatch_table 指针;
  • 通过 __cxa_atexit() 调用,注册 MallocFiniImpl(),注册的此函数将在进程 exit 时(例如调用 exit()函数) 进行回调:
bionic/libc/bionic/malloc_common_dynamic.cpp

static void MallocFiniImpl(void*) {
  // Our BSD stdio implementation doesn't close the standard streams,
  // it only flushes them. Other unclosed FILE*s will show up as
  // malloc leaks, but to avoid the standard streams showing up in
  // leak reports, close them here.
  fclose(stdin);
  fclose(stdout);
  fclose(stderr);

  reinterpret_cast<finalize_func_t>(gFunctions[FUNC_FINALIZE])();
}

其实最终调用的是 malloc_debug 中的debug_finalize() 进行检查,例如是否有内存泄漏,如果有,会将 callstack 打印出来:

bionic/libc/malloc_debug/malloc_debug.cpp

void debug_finalize() {
  if (g_debug == nullptr) {
    return;
  }

  // Make sure that there are no other threads doing debug allocations
  // before we kill everything.
  ScopedConcurrentLock::BlockAllOperations();

  // Turn off capturing allocations calls.
  DebugDisableSet(true);

  if (g_debug->config().options() & FREE_TRACK) {
    PointerData::VerifyAllFreed();
  }

  if (g_debug->config().options() & LEAK_TRACK) {
    PointerData::LogLeaks();
  }

  if ((g_debug->config().options() & BACKTRACE) && g_debug->config().backtrace_dump_on_exit()) {
    debug_dump_heap(android::base::StringPrintf("%s.%d.exit.txt",
                                                g_debug->config().backtrace_dump_prefix().c_str(),
                                                getpid()).c_str());
  }

  backtrace_shutdown();

  delete g_debug;
  g_debug = nullptr;

  DebugDisableFinalize();
}

例如options 里指定的 FREE_TRACKLEAK_TRACKBACKTRACE,都是在这里进行检查。

注意:

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

3. malloc()

在上面一节将 malloc_debug 初始化的过程基本是分析完了,其中关键点总结有如下几个方面:

  • 通过 dlopen() 动态加载 libc_malloc_debug.so,并将几个重要接口函数保存在 gfunctions 数组中;
  • 通过 InitMallocFuntions() 在这个so里继续查找 malloc_debug 其他系列函数,并保存在全局变量 __libc_globals 对象里 libc_globals.malloc_dispatch_table 变量指定的函数指针;
  • 注册 exit() 的回调函数 MallocFiniImpl(),最终调用 malloc_debug 中的 debug_finalize() 接口进行检查;

本节来看下 malloc() 接口,通过该接口进一步了解 malloc_debug 的使用:

bionic/libc/bionic/malloc_common.cpp

extern "C" void* malloc(size_t bytes) {
  auto dispatch_table = GetDispatchTable();
  void *result;
  if (__predict_false(dispatch_table != nullptr)) {
    result = dispatch_table->malloc(bytes);
  } else {
    result = Malloc(malloc)(bytes);
  }
  if (__predict_false(result == nullptr)) {
    warning_log("malloc(%zu) failed: returning null pointer", bytes);
    return nullptr;
  }
  return MaybeTagPointer(result);
}

如果没有使能 malloc_debug 时,dispatch_table 为 nullptr,则会进入 Malloc() 调用,即原生的 malloc 函数。这里的 dispatch_table 就是加载 libc_malloc_debug.so 之后初始化全局变量 __libc_globals 中的 current_dispatch_table,详细看上面第 2.4.2 节。

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

bionic/libc/malloc_debug/malloc_debug.cpp

void* debug_malloc(size_t size) {
  if (DebugCallsDisabled()) {
    return g_dispatch->malloc(size);
  }
  ScopedConcurrentLock lock;
  ScopedDisableDebugCalls disable;
  ScopedBacktraceSignalBlocker blocked;

  void* pointer = InternalMalloc(size);

  if (g_debug->config().options() & RECORD_ALLOCS) {
    g_debug->record->AddEntry(new MallocEntry(pointer, size));
  }

  return pointer;
}

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

介绍完 g_debug,再来看下 debug_malloc() 的核心处理函数在 InternalMalloc(),参数为需要申请的内存大小。

3.1 InternalMalloc()

bionic/libc/malloc_debug/malloc_debug.cpp

static void* InternalMalloc(size_t size) {
  if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {
    debug_dump_heap(android::base::StringPrintf(
                        "%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid())
                        .c_str());
  }

  if (size == 0) {
    size = 1;
  }

  //g_debug在初始化的时候,会根据options解析 extra_bytes_
  size_t real_size = size + g_debug->extra_bytes();
  if (real_size < size) {
    // Overflow.
    errno = ENOMEM;
    return nullptr;
  }

  if (size > PointerInfoType::MaxSize()) {
    errno = ENOMEM;
    return nullptr;
  }

  void* pointer;
  //创建 header,real_size 按照16字节或8字节对齐,64位系统按16字节
  if (g_debug->HeaderEnabled()) {
    Header* header =
        reinterpret_cast<Header*>(g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size));
    if (header == nullptr) {
      return nullptr;
    }
    pointer = InitHeader(header, header, size);
  } else {
    pointer = g_dispatch->malloc(real_size);
  }

  if (pointer != nullptr) {
    if (g_debug->TrackPointers()) {
      PointerData::Add(pointer, size);
    }

    if (g_debug->config().options() & FILL_ON_ALLOC) {
      size_t bytes = InternalMallocUsableSize(pointer);
      size_t fill_bytes = g_debug->config().fill_on_alloc_bytes();
      bytes = (bytes < fill_bytes) ? bytes : fill_bytes;
      memset(pointer, g_debug->config().fill_alloc_value(), bytes);
    }
  }
  return pointer;
}

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

bionic/libc/malloc_debug/DebugData.cpp

bool DebugData::Initialize(const char* options) {
  if (config_.options() & HEADER_OPTIONS) {
    // Initialize all of the static header offsets.
    pointer_offset_ = __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES);

    if (config_.options() & FRONT_GUARD) {
      front_guard.reset(new FrontGuardData(this, config_, &pointer_offset_));
    }

    extra_bytes_ = pointer_offset_;

    // Initialize all of the non-header data.
    if (config_.options() & REAR_GUARD) {
      rear_guard.reset(new RearGuardData(this, config_));
      extra_bytes_ += config_.rear_guard_bytes();
    }
  }

  ...

  if (config_.options() & EXPAND_ALLOC) {
    extra_bytes_ += config_.expand_alloc_bytes();
  }
  return true;
}

回到 InternalMalloc(), 当 options 设置 HEADER_OPTIONS 时,HeaderEnabled() 返回true,此时创建 header 对象,以real_size 申请空间,并调用 InitHeader() 对header 进行初始化。

否则,通过 g_dispatch->malloc() 通过原生的 malloc() 申请 real_size 空间:

bionic/libc/bionic/malloc_common.cpp

static constexpr MallocDispatch __libc_malloc_default_dispatch __attribute__((unused)) = {
  Malloc(calloc),
  Malloc(free),
  Malloc(mallinfo),
  Malloc(malloc),
  Malloc(malloc_usable_size),
  Malloc(memalign),
  Malloc(posix_memalign),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  Malloc(pvalloc),
#endif
  Malloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)
  Malloc(valloc),
#endif
  Malloc(malloc_iterate),
  Malloc(malloc_disable),
  Malloc(malloc_enable),
  Malloc(mallopt),
  Malloc(aligned_alloc),
  Malloc(malloc_info),
};

说到底,最终会使用原生的 malloc() 进行创建,只不过在原来的 size 基础上多了一些东西,而这些东西就是 malloc_debug 的核心。

至此,malloc_debug 原理已经基本分析完。

有什么遗漏的地方,欢迎留言提醒,笔者会在后面尽快更新!!

具体malloc_debug 的使用可以查看:Android 中malloc_debug 使用详解

猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/129195257