Android内存优化(二)之malloc debug简单介绍与初始化工作

简单介绍

malloc debug工具,源码中有这么一段解释(在malloc_debug目录下有README.md):

When malloc debug is enabled, it works by adding a shim layer that replaces
the normal allocation calls. The replaced calls are:
* `malloc`
* `free`
* `calloc`
* `realloc`
* `posix_memalign`
* `memalign`
* `malloc_usable_size`

On 32 bit systems, these two deprecated functions are also replaced:

* `pvalloc`
* `valloc`

老版本的malloc_debug需要控制libc.debug.malloc的debug等级,一般会有1,5,10,20
新版本的malloc_debug则是通过options来控制,源码中有一段注释

Malloc debug is controlled by individual options. Each option can be enabled
individually, or in a group of other options. Every single option can be
combined with every other option.

我们在测试native 内存泄露时,使用的backtrace选项,在源码中也有一段解释

### backtrace[=MAX\_FRAMES]
Enable capturing the backtrace of each allocation site.
This option will slow down allocations by an order of magnitude. If the
system runs too slowly with this option enabled, decreasing the maximum number
of frames captured will speed the allocations up.

This option adds a special header to all allocations that contains the
backtrace and information about the original allocation.

其意思就是这个optipns会内存分配的速度减慢一个数量级,如果感觉系统运行太慢了,减少捕获的最大帧数将加快分配。此选项为包含回溯的所有分配以及有关原始分配的信息添加一个特殊标题。

malloc_debug 如果生效的话,需要配置两个属性:

static const char* DEBUG_MALLOC_PROPERTY_OPTIONS = "libc.debug.malloc.options";
static const char* DEBUG_MALLOC_PROPERTY_PROGRAM = "libc.debug.malloc.program";

在malloc_debug文件夹下有个README文件也说明了用法,如

    adb shell stop
    adb shell setprop libc.debug.malloc.program app_process
    adb shell setprop libc.debug.malloc.options backtrace
    adb shell start

初始化

从阅读的源码看,malloc_debug功能其实跟hook过程很像,将常用的内存分配函数做一层包装,在调用的时候可以携带调试信息~看下malloc_debug功能的初始化,在malloc_common.cpp有这么一段

// Initializes memory allocation framework.
// This routine is called from __libc_init routines in libc_init_dynamic.cpp.
__LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {
  malloc_init_impl(globals);
}
#endif  // !LIBC_STATIC

如注释中所讲, __libc_init会调用__libc_init_malloc,进而调到malloc_init_impl进行初始化
看下malloc_init_impl的主要逻辑:

  // If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
  const char* options = getenv(DEBUG_MALLOC_ENV_OPTIONS);
  if (options == nullptr || options[0] == '\0') {
    if (__system_property_get(DEBUG_MALLOC_PROPERTY_OPTIONS, value) == 0 || value[0] == '\0') {
      return;
    }
    options = value;

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

如果系统环境变量DEBUG_MALLOC_ENV_OPTIONS为空,且libc.debug.malloc.options系统属性为空,直接返回了,初始化结束~如果libc.debug.malloc.options属性正常配置,则会继续初始化,初始化一些方法指针~
比如g_debug_get_malloc_leak_info_func(重要),g_debug_free_malloc_leak_info_func,g_debug_free_malloc_leak_info_func
并且替换常用的内存分配方法,主要实现在InitMalloc方法里,将malloc,calloc,free等函数均替换成debug_malloc,debug_calloc,debug_free形式

比如我们设置好了属性后,调用malloc时,会走到debug_malloc函数,在malloc_debug.cpp

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

  void* pointer = internal_malloc(size);

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

  return pointer;
}

其中有个方法比较重要internal_malloc,有个变量比较重要g_debug,先说下g_debug,因为在internal_malloc也使用了这个变量
g_debug是DebugData类型,是大管家,当我们options设置不同的options时,会初始化不同的模块,比如我们使用的backtrace,在DebugData.cpp中DebugData::Initialize方法有这么一段:
(设置options时,会根据options初始化Config对象,逻辑在Config::Set方法)

    if (config_.options & BACKTRACE) {
      backtrace.reset(new BacktraceData(this, config_, &pointer_offset_));
      if (!backtrace->Initialize(config_)) {
        return false;
      }
    }
    if (config_.options & TRACK_ALLOCS) {
      track.reset(new TrackData(this));//这个地方在Android8.0上还有Bug,跟高通沟通后才解决,后面文章会说这个问题的
    }

看下internal_malloc函数:

static void *internal_malloc(size_t size) {
  if (size == 0) {
    size = 1;
  }

  size_t real_size = size + g_debug->extra_bytes();
  if (real_size < size) {
    // Overflow.
    errno = ENOMEM;
    return nullptr;
  }

  void* pointer;
  //g_debug为true
  if (g_debug->need_header()) {
    if (size > Header::max_size()) {
      errno = ENOMEM;
      return nullptr;
    }

    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 && g_debug->config().options & FILL_ON_ALLOC) {
    size_t bytes = internal_malloc_usable_size(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;
}

从调用malloc来看,有一句:size_t real_size = size + g_debug->extra_bytes();,多的这部分内存是什么呢?
在DebugData的初始化过程中,有这么一段(只截取options为backtrace时运行的逻辑):

    pointer_offset_ = BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES);    
    extra_bytes_ = pointer_offset_;

会在实际分配的内存大小的基础上,再额外分配一部分内存(Header),经过调试可以发现是176个字节,这个是可以调试并发现native内存泄露的关键,看下数据结构

// Allocations that require a header include a variable length header.
// This is the order that data structures will be found. If an optional
// part of the header does not exist, the other parts of the header
// will still be in this order.
//   Header          (Required)
//   BacktraceHeader (Optional: For the allocation backtrace)
//   uint8_t data    (Optional: Front guard, will be a multiple of MINIMUM_ALIGNMENT_BYTES)
//   allocation data
//   uint8_t data    (Optional: End guard)
//
// If backtracing is enabled, then both BacktraceHeaders will be present.
//
// In the initialization function, offsets into the header will be set
// for each different header location. The offsets are always from the
// beginning of the Header section.
struct Header {
  uint32_t tag;
  void* orig_pointer; 
  size_t size;
  size_t usable_size;
  size_t real_size() const { return size & ~(1U << 31); }
  void set_zygote() { size |= 1U << 31; }
  static size_t max_size() { return (1U << 31) - 1; }
} __attribute__((packed));

struct BacktraceHeader {
  size_t num_frames;
  uintptr_t frames[0];
} __attribute__((packed));

Header的初始化是在malloc_debug.cpp的InitHeader方法中,而debug_malloc正会调用InitHeader方法,来看下跟backtrace相关的具体实现:

  bool backtrace_found = false;
  if (g_debug->config().options & BACKTRACE) {
    BacktraceHeader* back_header = g_debug->GetAllocBacktrace(header);
    if (g_debug->backtrace->enabled()) {
      //走这个分支
      back_header->num_frames = backtrace_get(
          &back_header->frames[0], g_debug->config().backtrace_frames);
      backtrace_found = back_header->num_frames > 0;
    } else {
      back_header->num_frames = 0;
    }
  }

  if (g_debug->config().options & TRACK_ALLOCS) {
    g_debug->track->Add(header, backtrace_found);
  }

  return g_debug->GetPointer(header);

Add方法就是在headers_统计上将header添加上,并将total_backtrace_allocs_++,而我们在输出泄露信息时,靠的就是headers_保存的所有Header对象

void TrackData::Add(const Header* header, bool backtrace_found) {
  pthread_mutex_lock(&mutex_);
  if (backtrace_found) {
    total_backtrace_allocs_++;
  }
  headers_.insert(header);
  pthread_mutex_unlock(&mutex_);
}

在internal_malloc方法中有个g_dispatch,稍微提一句:g_dispatch是MallocDispatch类型,内部保存了libc的正常内存分配的函数,具体可见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(iterate),
    Malloc(malloc_disable),
    Malloc(malloc_enable),
    Malloc(mallopt),
  };

猜你喜欢

转载自blog.csdn.net/longlong2015/article/details/80041574