Android linker可执行程序加载库失败时输出更详细调试信息
@(Android研究)[android|linker]
[TOC]
设定场景
可执行文件名:the_exe
假设执行the_exe时,Android linker加载库会失败,那么通常会输出下面的信息:
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中的构造函数时发生了错误。可是一个可执行文件会加载很多动态库,而一个动态库中又可以有多个构造函数,到底是哪个库的哪个构造函数执行时出了问题哪?通过上面的错误信息并不能给出原因所在。
获得错误详细信息的方法
按照下面的方法可以获得错误的详细信息,设置LD_DEBUG环境变量:
export LD_DEBUG=10
按照上面设置完LD_DEBUG环境变量后,再执行"./the_exe"时将会输出详细的错误信息。
原理
下面以Android5.1.1源码为基础进行解析。
Android linker代码在"android/system/bionic/linker/"目录下。soinfo::CallFunction函数在"android/system/bionic/linker/linker.cpp"文件中,下面是该函数的源码:
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(...)语句,这个语句用来输出一些信息,如调用的函数名、函数地址等。那么为什么上面的日志中没有输出这些信息哪?
找到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中。