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中。

相關文章
相關標籤/搜索