Android linker可执行程序加载库失败时输出更详细调试信息

Android linker可执行程序加载库失败时输出更详细调试信息

@(Android研究)[android|linker]android


[TOC]c++


设定场景

可执行文件名:the_exeshell

假设执行the_exe时,Android linker加载库会失败,那么一般会输出下面的信息:app

01-30 20:36:10.376 2078-2078/? A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0xc0011c80 in tid 2078 (the_exe)
01-30 20:36:10.377 197-197/? I/DEBUG: property debug.db.uid not set; NOT waiting for gdb.
01-30 20:36:10.377 197-197/? I/DEBUG: HINT: adb shell setprop debug.db.uid 100000
01-30 20:36:10.377 197-197/? I/DEBUG: HINT: adb forward tcp:5039 tcp:5039
01-30 20:36:10.482 197-197/? I/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
01-30 20:36:10.482 197-197/? I/DEBUG: Build fingerprint: 'google/hammerhead/hammerhead:5.1.1/LMY48M/2167285:user/release-keys'
01-30 20:36:10.482 197-197/? I/DEBUG: Revision: '11'
01-30 20:36:10.482 197-197/? I/DEBUG: ABI: 'arm'
01-30 20:36:10.483 197-197/? I/DEBUG: pid: 2078, tid: 2078, name: the_exe  >>> ./the_exe <<<
01-30 20:36:10.483 197-197/? I/DEBUG: signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc0011c80
01-30 20:36:10.491 26449-26682/system_process W/NativeCrashListener: Couldn't find ProcessRecord for pid 2078
01-30 20:36:10.558 197-197/? I/DEBUG:     r0 00000000  r1 b6fa83bf  r2 c0011c81  r3 b6fad714
01-30 20:36:10.558 197-197/? E/DEBUG: AM write failure (32 / Broken pipe)
01-30 20:36:10.558 197-197/? I/DEBUG:     r4 c0011c80  r5 b6fa83bf  r6 b6f9b004  r7 b6fa8486
01-30 20:36:10.558 197-197/? I/DEBUG:     r8 00000010  r9 b6fad714  sl 00000001  fp 6011fdb8
01-30 20:36:10.558 197-197/? I/DEBUG:     ip b6fa8486  sp bec6d7a8  lr b6f9f5b7  pc c0011c80  cpsr 800f0010
01-30 20:36:10.559 197-197/? I/DEBUG:     #00 pc c0011c80  <unknown>
01-30 20:36:10.559 197-197/? I/DEBUG:     #01 pc 000015b5  /system/bin/linker (__dl__ZN6soinfo12CallFunctionEPKcPFvvE+44)
01-30 20:36:10.559 197-197/? I/DEBUG:     #02 pc 00001689  /system/bin/linker (__dl__ZN6soinfo9CallArrayEPKcPPFvvEjb+140)
01-30 20:36:10.559 197-197/? I/DEBUG:     #03 pc 0000185f  /system/bin/linker (__dl__ZN6soinfo16CallConstructorsEv+142)
01-30 20:36:10.559 197-197/? I/DEBUG:     #04 pc 00003ae7  /system/bin/linker (__dl___linker_init+1594)
01-30 20:36:10.559 197-197/? I/DEBUG:     #05 pc 00000a98  /system/bin/linker (_start+4)
01-30 20:36:10.559 197-197/? I/DEBUG:     #06 pc 00000a98  /system/bin/linker (_start+4)
01-30 20:36:10.559 197-197/? I/DEBUG:     #07 pc 00000a98  /system/bin/linker (_start+4)

......

查看上面的信息,能够发现出错点在"#01 pc 000015b5 /system/bin/linker (__dl__ZN6soinfo12CallFunctionEPKcPFvvE+44)","/system/bin/linker"代表这个出错点在linker中。又能够发现"#03 pc 0000185f /system/bin/linker (__dl__ZN6soinfo16CallConstructorsEv+142)"这行错误信息,经过这个错误信息可知当linker调用so中的构造函数时发生了错误。但是一个可执行文件会加载不少动态库,而一个动态库中又能够有多个构造函数,究竟是哪一个库的哪一个构造函数执行时出了问题哪?经过上面的错误信息并不能给出缘由所在。tcp

得到错误详细信息的方法

按照下面的方法能够得到错误的详细信息,设置LD_DEBUG环境变量:ionic

export LD_DEBUG=10

按照上面设置完LD_DEBUG环境变量后,再执行"./the_exe"时将会输出详细的错误信息。函数

原理

下面以Android5.1.1源码为基础进行解析。post

Android linker代码在"android/system/bionic/linker/"目录下。soinfo::CallFunction函数在"android/system/bionic/linker/linker.cpp"文件中,下面是该函数的源码:ui

void soinfo::CallFunction(const char* function_name __unused, linker_function_t function) {
  if (function == nullptr || reinterpret_cast<uintptr_t>(function) == static_cast<uintptr_t>(-1)) {
    return;
  }

  TRACE("[ Calling %s @ %p for '%s' ]", function_name, function, name);
  function();
  TRACE("[ Done calling %s @ %p for '%s' ]", function_name, function, name);

  // The function may have called dlopen(3) or dlclose(3), so we need to ensure our data structures
  // are still writable. This happens with our debug malloc (see http://b/7941716).
  protect_data(PROT_READ | PROT_WRITE);
}

能够发如今函数代码中有TRACE(...)语句,这个语句用来输出一些信息,如调用的函数名、函数地址等。那么为何上面的日志中没有输出这些信息哪?google

找到TRACE宏定义的地方,"android/system/bionic/linker/linker_debug.h"文件,下面是这个宏的定义:

#define TRACE(x...)          _PRINTVF(1, x)

_PRINTVF宏定义在"android/system/bionic/linker/linker_debug.h"文件中,下面是_PRINTVF宏的定义:

#if LINKER_DEBUG_TO_LOG
#define _PRINTVF(v, x...) \
    do { \
      if (g_ld_debug_verbosity > (v)) __libc_format_log(5-(v), "linker", x); \
    } while (0)
#else /* !LINKER_DEBUG_TO_LOG */
#define _PRINTVF(v, x...) \
    do { \
      if (g_ld_debug_verbosity > (v)) { __libc_format_fd(1, x); write(1, "\n", 1); } \
    } while (0)
#endif /* !LINKER_DEBUG_TO_LOG */

依据对"if (g_ld_debug_verbosity > (v)) ..."语句的分析,我认为_PRINTVF宏的第一个参数"v"表明的是输出级别,当g_ld_debug_verbosity比"v"大时,便会执行后面的语句进行输出。

g_ld_debug_verbosity是个全局变量,它在"android/system/bionic/linker/linker.cpp"文件的ElfW函数中被赋值,下面是ElfW函数的代码片断:

static ElfW(Addr) __linker_init_post_relocation(KernelArgumentBlock& args, ElfW(Addr) linker_base) {

  ......

  debuggerd_init();

  // Get a few environment variables.
  const char* LD_DEBUG = linker_env_get("LD_DEBUG");
  if (LD_DEBUG != nullptr) {
    g_ld_debug_verbosity = atoi(LD_DEBUG);
  }

  ......

}

经过上面代码可知g_ld_debug_verbosity变量的值与环境变量"LD_DEBUG"有关,若是定义了"LD_DEBUG"环境变量那么会将这个环境变量的值赋给g_ld_debug_verbosity。

回到TRACE宏,能够发现当g_ld_debug_verbosity的值大于1,即"LD_DEBUG"环境变量的值大于1时,TRACE宏所表明的代码就会输出日志。

一样,要想让PRINT、INFO、DEBUG这些宏输出日志,作法和原理与TRACE相同。

特别注意

连接器会将日志输出到logcat中,可是不知为什么有时它会丢掉一些连接日志,若是想要不丢日志那么须要修改Android源码,打开"android/system/bionic/linker/linker_debug.h"文件,将LINKER_DEBUG_TO_LOG宏的值从1改成0,而后使用"mmm"命令从新编译linker再将编译好的linker覆盖Android设备上的/system/bin/linker。

新linker的连接日志将会直接输出到终端上,这部分的日志是完整的;不过错误堆栈(“设定场景”一节中列出的日志内容)仍是会输出到logcat中。

相关文章
相关标签/搜索