稳定性优化

Android Native 内存泄漏检测工具 LeakTrac

2022-06-07  本文已影响0人  沪漂意哥哥

一、LeakTracer介绍

简单来说,该库主要是通过重写libc中的malloc、free、new、delete这些函数和操作符,记录内存申请和释放操作来判断程序是否可能出现了内存泄漏。

二、使用介绍

  /** starts monitoring memory allocations in all threads */
       inline void startMonitoringAllThreads(void);

       /** starts monitoring memory allocations in current thread */
       inline void startMonitoringThisThread(void);

       /** stops monitoring memory allocations (in all threads or in
        *   this thread only, depends on the function used to start
        *   monitoring */
       inline void stopMonitoringAllocations(void);

       /** stops all monitoring - both of allocations and releases */
       inline void stopAllMonitoring(void);

       /** writes report with all memory leaks */
       void writeLeaksToFile(const char *reportFileName);

注意: leak-tracer-helpers目录下的leak-analyze-addr2line工具其实是一堆shell代码,用到了addr2line工具,需要将NDK中的addr2line加到环境变量Path中。

三、源码分析

这里以如下流程为例分析内存泄漏检测流程:

先来看看startMonitoringAllThreads函数,这里主要是调用了leaktracer::MemoryTrace::Setup()进行初始化操作

inline void MemoryTrace::startMonitoringAllThreads(void) {

       leaktracer::MemoryTrace::Setup();

       TRACE((stderr, "LeakTracer: startMonitoringAllThreads\n"));
       if (!__monitoringReleases) {
           MutexLock lock(__allocations_mutex);
           // double-check inside Mutex
           if (!__monitoringReleases) {
               __allocations.clearAllInfo();
               __monitoringReleases = true;
           }
       }
       // 将这个标记位置为true,后续用于判断
       __monitoringAllThreads = true;
       stopMonitoringPerThreadAllocations();
   }

然后在leaktracer::MemoryTrace::Setup()中看到,主要是执行了MemoryTrace::init_no_alloc_allowed()

int MemoryTrace::Setup(void)
{
   pthread_once(&MemoryTrace::_init_no_alloc_allowed_once, MemoryTrace::init_no_alloc_allowed);

   if (!leaktracer::MemoryTrace::GetInstance().AllMonitoringIsDisabled()) {
       pthread_once(&MemoryTrace::_init_full_once, MemoryTrace::init_full_from_once);
   }
#if 0
       else if (!leaktracer::MemoryTrace::GetInstance().__setupDone) {
   }   
#endif
   return 0;
}

在MemoryTrace::init_no_alloc_allowed()函数中做了最关键的两件事:

typedef struct {
 /* Pathname of shared object that contains address. */
 const char* dli_fname;
 /* Address at which shared object is loaded. */
 void* dli_fbase;
 /* Name of nearest symbol with address lower than addr. */
 const char* dli_sname;
 /* Exact address of symbol named in dli_sname. */
 void* dli_saddr;
} Dl_info;
typedef struct {
 const char *symbname;
 void *libcsymbol;
 void **localredirect;
} libc_alloc_func_t;

static Dl_info s_P2pSODlInfo;

static libc_alloc_func_t libc_alloc_funcs[] = {
 { "calloc", (void*)__libc_calloc, (void**)(&lt_calloc) },
 { "malloc", (void*)__libc_malloc, (void**)(&lt_malloc) },
 { "realloc", (void*)__libc_realloc, (void**)(&lt_realloc) },
 { "free", (void*)__libc_free, (void**)(&lt_free) }
};

void MemoryTrace::init_no_alloc_allowed()
{
   libc_alloc_func_t *curfunc;
   unsigned i;
   // 记录libc中需要重写的函数的地址
    for (i=0; i<(sizeof(libc_alloc_funcs)/sizeof(libc_alloc_funcs[0])); ++i) {
       curfunc = &libc_alloc_funcs[i];
       if (!*curfunc->localredirect) {
           if (curfunc->libcsymbol) {
               *curfunc->localredirect = curfunc->libcsymbol;
           } else {
               *curfunc->localredirect = dlsym(RTLD_NEXT, curfunc->symbname);
           }
       }
   } 
   // 调用一次dladdr,用于记录动态库加载时的基础偏移量
    dladdr((const void*)init_no_alloc_allowed, &s_P2pSODlInfo);

   __instance = reinterpret_cast<MemoryTrace*>(&s_memoryTrace_instance);

   // we're using a c++ placement to initialized the MemoryTrace object living in the data section
   new (__instance) MemoryTrace();

   // it seems some implementation of pthread_key_create use malloc() internally (old linuxthreads)
   // these are not supported yet
   pthread_key_create(&__instance->__thread_internal_disabler_key, NULL);
}

以上便是初始化相关的主要内容,接下来看下申请内存和释放内存时的操作

/*
* underlying allocation, de-allocation used within
* this tool
*/
#define LT_MALLOC  (*lt_malloc)
#define LT_FREE    (*lt_free)
#define LT_REALLOC (*lt_realloc)
#define LT_CALLOC  (*lt_calloc)

在代码中实际调用的malloc将被重写为如下内容,主要是在调用libc的malloc后对内存申请进行记录

void *malloc(size_t size)
{
   void *p;
   leaktracer::MemoryTrace::Setup();

   leaktracer::MemoryTrace::GetInstance().InternalMonitoringDisablerThreadUp();
   p = LT_MALLOC(size);
   leaktracer::MemoryTrace::GetInstance().InternalMonitoringDisablerThreadDown();
   leaktracer::MemoryTrace::GetInstance().registerAllocation(p, size, false);

   return p;
}

将内存申请记录存到__allocations这个Map中,包含内存地址、大小、时间、调用栈信息,其中需要关注下记录调用栈信息的实现

inline void MemoryTrace::registerAllocation(void *p, size_t size, bool is_array) {
       allocation_info_t *info = NULL;
       if (!AllMonitoringIsDisabled() &&
           (__monitoringAllThreads || getThreadOptions().monitoringAllocations) && p != NULL) {
           MutexLock lock(__allocations_mutex);
           info = __allocations.insert(p);
           if (info != NULL) {
               info->size = size;
               info->isArray = is_array;
               storeTimestamp(info->timestamp);
           }
       }
       // we store the stack without locking __allocations_mutex
       // it should be safe enough
       // prevent a deadlock between backtrave function who are now using advanced dl_iterate_phdr function
       // and dl_* function which uses malloc functions
       if (info != NULL) {
           storeAllocationStack(info->allocStack);
       }

       if (p == NULL) {
           InternalMonitoringDisablerThreadUp();
           // WARNING
           InternalMonitoringDisablerThreadDown();
       }
   }

记录调用栈信息的实现如下

void MemoryTrace::storeAllocationStack(void* arr[ALLOCATION_STACK_DEPTH])
{
   unsigned int iIndex = 0;

   TraceHandle traceHandle;
   traceHandle.backtrace = arr;
   traceHandle.pos = 0;
   _Unwind_Backtrace(Unwind_Trace_Fn, &traceHandle);

   // fill remaining spaces
   for (iIndex = traceHandle.pos; iIndex < ALLOCATION_STACK_DEPTH; iIndex++)
       arr[iIndex] = NULL;
}

真正记录调用栈的实现

_Unwind_Reason_Code Unwind_Trace_Fn(_Unwind_Context *context, void *hnd) {
   struct TraceHandle *traceHandle = (struct TraceHandle *) hnd;
   _Unwind_Word ip = _Unwind_GetIP(context);
   if (traceHandle->pos != ALLOCATION_STACK_DEPTH) {
       traceHandle->backtrace[traceHandle->pos] = (void *) (ip - (_Unwind_Word) s_P2pSODlInfo.dli_fbase);
       ++traceHandle->pos;
       return _URC_NO_REASON;
   }
   return _URC_END_OF_STACK;
}
void free(void* ptr)
{
   leaktracer::MemoryTrace::Setup();

   leaktracer::MemoryTrace::GetInstance().registerRelease(ptr, false);
   LT_FREE(ptr);
}
inline void MemoryTrace::registerRelease(void *p, bool is_array) {
       if (!AllMonitoringIsDisabled() && __monitoringReleases && p != NULL) {
           MutexLock lock(__allocations_mutex);
           allocation_info_t *info = __allocations.find(p);
           if (info != NULL) {
               if (info->isArray != is_array) {
                   InternalMonitoringDisablerThreadUp();
                   // WARNING
                   InternalMonitoringDisablerThreadDown();
               }
               __allocations.release(p);
           }
       }
   }
void MemoryTrace::writeLeaksToFile(const char* reportFilename)
{
   MutexLock lock(__allocations_mutex);
   InternalMonitoringDisablerThreadUp();

   std::ofstream oleaks;
   oleaks.open(reportFilename, std::ios_base::out);
   if (oleaks.is_open())
   {
       writeLeaksPrivate(oleaks);
       oleaks.close();
   }
   else
   {
       std::cerr << "Failed to write to \"" << reportFilename << "\"\n";
   }
   InternalMonitoringDisablerThreadDown();
}

__allocations是存放内存申请记录的map,其中的内容就是申请了但是未释放的内存,输出相关信息

void MemoryTrace::writeLeaksPrivate(std::ostream &out)
{
   struct timespec mono, utc, diff;
   allocation_info_t *info;
   void *p;
   double d;
   const int precision = 6;
   int maxsecwidth;

   clock_gettime(CLOCK_REALTIME, &utc);
   clock_gettime(CLOCK_MONOTONIC, &mono);

   if (utc.tv_nsec > mono.tv_nsec) {
       diff.tv_nsec = utc.tv_nsec - mono.tv_nsec;
       diff.tv_sec = utc.tv_sec - mono.tv_sec;
   } else {
       diff.tv_nsec = 1000000000 - (mono.tv_nsec - utc.tv_nsec);
       diff.tv_sec = utc.tv_sec - mono.tv_sec -1;
   }

   maxsecwidth = 0;
   while(mono.tv_sec > 0) {
       mono.tv_sec = mono.tv_sec/10;
       maxsecwidth++;
   }
   if (maxsecwidth == 0) maxsecwidth=1;

   out << "# LeakTracer report";
   d = diff.tv_sec + (((double)diff.tv_nsec)/1000000000);
   out << " diff_utc_mono=" << std::fixed << std::left << std::setprecision(precision) << d ;
   out << "\n";

   __allocations.beginIteration();
   while (__allocations.getNextPair(&info, &p)) {
       d = info->timestamp.tv_sec + (((double)info->timestamp.tv_nsec)/1000000000);
       out << "leak, ";
       out << "time="  << std::fixed << std::right << std::setprecision(precision) << std::setfill('0') << std::setw(maxsecwidth+1+precision) << d << ", "; // setw(16) ?
       out << "stack=";
       for (unsigned int i = 0; i < ALLOCATION_STACK_DEPTH; i++) {
           if (info->allocStack[i] == NULL) break;

           if (i > 0) out << ' ';
           out << info->allocStack[i];

       }
       out << ", ";

       out << "size=" << info->size << ", ";

       out << "data=";
       const char *data = reinterpret_cast<const char *>(p);
       for (unsigned int i = 0; i < PRINTED_DATA_BUFFER_SIZE && i < info->size; i++)
           out << (isprint(data[i]) ? data[i] : '.');
       out << '\n';
   }
}
#!/usr/bin/perl
use IO::Handle;

my $exe_name = shift (@ARGV);
my $log_name = shift (@ARGV);

if (!$exe_name || !$log_name) {
  print "Usage: $0 <PROGRAM> <LEAKFILE>\n";
  exit (1);
}

print "Processing \"$log_name\" log for \"$exe_name\"\n";

print "Matching addresses to \"$exe_name\"\n";

my %stacks;
my %addresses;
my $lines = 0;

open (LEAKFILE, $log_name) || die("failed to read from \"$log_name\"");

while (<LEAKFILE>) {
  chomp;
  my $line = $_;
  if ($line =~ /^leak, time=([\d.]*), stack=([\w ]*), size=(\d*), data=.*/) {
     $lines ++;

     my $id = $2;
     $stacks{$id}{COUNTER} ++;
     $stacks{$id}{TIME} = $1;
     $stacks{$id}{SIZE} += $3;

     my @ptrs = split(/ /, $id);
     foreach $ptr (@ptrs) {
        $addresses{$ptr} = "unknown";
     }
  }
}
close (LEAKFILE);
printf "found $lines leak(s)\n";
if ($lines == 0) { exit 0; }

# resolving addresses
my @unique_addresses = keys (%addresses);
my $addr_list = "";
foreach $addr (@unique_addresses) { $addr_list .= " $addr"; }

if (!open(ADDRLIST, "addr2lineArm64 -e $exe_name $addr_list |")) { die "Failed to resolve addresses"; }
my $addr_idx = 0;
while (<ADDRLIST>) {
  chomp;
  $addresses{$unique_addresses[$addr_idx]} = $_;
  $addr_idx++;
}
close (ADDRLIST);

# printing allocations
while (($stack, $info) = each(%stacks)) {
  print $info->{SIZE}." bytes lost in ".$info->{COUNTER}." blocks (one of them allocated at ".$info->{TIME}."), from following call stack:\n";
  @stack = split(/ /, $stack);
  foreach $addr (@stack) { print "\t".$addresses{$addr}."\n"; }
}

PS:为了方便地对各种ABI的动态库进行分析,我将NDK中各种ABI对应的addr2line工具根据ABI分别命名,如arm64-v8a对应的addr2line命名为addr2lineArm64

Demo下载地址

https://github.com/wangshengyang1996/AndroidLeakTracer
https://gitee.com/luisliuyi/android-native-leak-tracer.git
上一篇下一篇

猜你喜欢

热点阅读