一、带符号so和不带符号so
我们开发中用到的so,一般情况下有两个:带符号表的so和不带符号表的so
- 不带符号的so是strip过的,体积会比原始so小很多,用于发布到apk中。
以我自己编译的libbreakpad-core.so为例
strip之前,占用大小为2.6M
strip之后,占用大小334K
可见,在stip操作对于so的占用空间上优化还是挺大的。
- 带符号的so是没有经过strip的的,用于线上so出现crash后,辅助帮助定位发生crash的具体函数
1.1、如何利用Android studio获取带符号表和不带符号表的同一个so
Android Studio 编译so过程 会自动加入strip操作,如下图所示
- build->intermediates->cmake目录下是编译出的原始so
- build->intermediates->stripped-native_libs 目录下是经过strip的so
1.2、带符号和不带符号so,如何配合查找问题。
举例来说,apk发生natvie crash之后,系统会打
工具目录:/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin
我mac上的目录为:
/Users/feifei/Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin
- 1 标示出那个so出现了crash
- 2 pc 此处的地址 并不是崩溃点的虚拟内存地址绝对值,而是崩溃点相对于so起始虚拟内存地址的相对偏移。利用这个相对地址,结合符号表就可以定位到具体是哪个函数出现了crash
- 3 发生crash的方法名
3 处函数名可能有,也可能符号信息缺失crash信息里面没有,只有1和2。但是有1和2就足够了。
如何解析具体崩溃的函数呢?
需要用到aarch64-linux-android-addr2line工具和带符号的so
aarch64-linux-android-addr2line 位于ndk目录下
我的mac上具体路径如下:
/Users/feifei/Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line
由crash信息中1和2 可以知,是libbreakpad-core.so的偏移位置为18340的地方发生了crash
执行aarch64-linux-android-addr2line -f -C -e libbreakpad-core.so 18340 指令,可以获得18340偏移位置的具体函数符号,如下所示
feifeideMacBook-Pro:cmake feifei$ /Users/feifei/Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line -f -C -e libbreakpad-core.so 18340
testCrash_null_point()
/Users/feifei/Desktop/TM/workspace/TrProbe/breakpad-build/src/main/cpp/breakpadcore.cpp:187
再对照看我们的源码:
调用关系为:go2crash()->call_dangerous_function()->testCrash_null_point()
问题得解!
/**
* 创造一个 natvie crash
* @param env
* @param clazz
*/
JNIEXPORT void JNICALL Java_com_sogou_translate_breakpad_BreakPadCore_go2crash
(JNIEnv * env, jclass clazz){
call_dangerous_function();
// testCrash_divide_zero();
// testCrash_stackoverflow();
}
int call_dangerous_function() {
testCrash_null_point();
return 42;
}
int testCrash_null_point(){
volatile int *a = (int *) (NULL);
*a = 1;
return 1;
}
1.3、排查crash的另一个思路
发生crash时,系统会打印出如下日志:
2021-02-04 11:06:04.621 3131-3131/? E//system/bin/tombstoned: Tombstone written to: /data/tombstones/tombstone_31
tombstone是什么文件呢?对于定制的智能硬件(Android系统),也可以利用这个文件排查问题
adb root
adb remount
adb pull /data/tombstones/tombstone_31
tombstone实际上是android系统在发生crash时,dump出的一份内存堆栈信息。
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Native Crash TIME: 5009246
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Sogou/sl8541e_1h10_oversea/sl8541e_1h10:8.1.0/OPM2.171019.012/29301:userdebug/test-keys'
Revision: '0'
ABI: 'arm64'
pid: 11371, tid: 11371, name: anslate.example >>> com.sogou.translate.example <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
x0 0000006fbf6e8140 x1 0000007fd97bc5d4 x2 0000000000000000 x3 0000006fbf6bea00
x4 0000007fd97bc9e0 x5 0000006fa5bfb268 x6 0000000000000000 x7 0000000000000000
x8 0000000000000000 x9 0000000000000001 x10 0000000000430000 x11 0000006fbf5db7a8
x12 0000007043d46cd0 x13 0965de0ad6551339 x14 0000007043d46000 x15 ffffffffffffffff
x16 0000007042ce3ca8 x17 0000006fa5063338 x18 0000000000000008 x19 0000006fbf6bea00
x20 0000006fbf4f3ea0 x21 0000006fbf6bea00 x22 0000007fd97bc85c x23 0000006fa5bfb268
x24 0000000000000000 x25 0000007044348a40 x26 0000006fbf6beaa0 x27 0000000000000000
x28 0000000000000000 x29 0000007fd97bc688 x30 0000006fa58930b4
sp 0000007fd97bc5c0 pc 0000006fa5063340 pstate 0000000060000000
v0 00000000000000000000000000000000 v1 00000000000000000000007fd97bcbd0
v2 00000000000000004008000000000000 v3 00000000000000000000000000000000
v4 00000000000000000000000000000000 v5 00000000000000004000000000000000
v6 00000000000000000000000000000000 v7 00000000000000008020080280200802
v8 00000000000000000000000000000000 v9 00000000000000000000000000000000
v10 00000000000000000000000000000000 v11 00000000000000000000000000000000
v12 00000000000000000000000000000000 v13 00000000000000000000000000000000
v14 00000000000000000000000000000000 v15 00000000000000000000000000000000
v16 40100401401004014010040140100401 v17 000100010004000001010400aaaaaaaa
v18 00000001000000010000040000000000 v19 00000000000000000000007040509624
v20 000000000000000000000070405099b4 v21 00000000000000000000007040509c84
v22 0000000000000000000000704050c8f0 v23 0000000000000000000000704050c930
v24 0000000000000000000000704050c9dc v25 0000000000000000000000704050bbb4
v26 0000000000000000000000704050c31c v27 0000000000000000000000704050c374
v28 0000000000000000000000704050c6b4 v29 0000000000000000000000704050bd0c
v30 0000000000000000000000704050bf04 v31 0000000000000000000000000000000b
fpsr 00000013 fpcr 00000000
backtrace:
#00 pc 0000000000018340 /data/app/com.sogou.translate.example-Sj02X6XFCw3dgjlkJnlrHw==/lib/arm64/libbreakpad-core.so (Java_com_sogou_translate_breakpad_BreakPadCore_go2crash+8)
#01 pc 00000000000100b0 /data/app/com.sogou.translate.example-Sj02X6XFCw3dgjlkJnlrHw==/oat/arm64/base.odex (offset 0x10000)
stack:
0000007fd97bc540 0000000000000000
0000007fd97bc548 0000006fbf6bea00 [anon:libc_malloc]
0000007fd97bc550 0000007fd97bc9e0 [stack]
0000007fd97bc558 0000006fa5bfb268 /data/app/com.sogou.translate.example-Sj02X6XFCw3dgjlkJnlrHw==/oat/arm64/base.vdex
0000007fd97bc560 0000000000000000
0000007fd97bc568 0000000000000000
0000007fd97bc570 0000000000000000
0000007fd97bc578 0000007fd97bcbd0 [stack]
0000007fd97bc580 4008000000000000
0000007fd97bc588 0000000000000000
0000007fd97bc590 0000000000000000
0000007fd97bc598 4000000000000000
0000007fd97bc5a0 0000000000000000
0000007fd97bc5a8 8020080280200802
0000007fd97bc5b0 0000007fd97bc688 [stack]
0000007fd97bc5b8 0000006fa58930b4 /data/app/com.sogou.translate.example-Sj02X6XFCw3dgjlkJnlrHw==/oat/arm64/base.odex
#00 0000007fd97bc5c0 0000007042500d28 /dev/ashmem/dalvik-LinearAlloc (deleted)
彩蛋
Android studio 执行cmake 参数的配置文件为android.toolchain.cmake
具体位置如下:
/Users/feifei/Library/Android/sdk/cmake/3.6.4111459/android.toolchain.cmake
需要查看或者修改AS cmake参数的可以参照此文件
二、一些必备的ndk工具
工具位置:
/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/
常用工具如下:
feifeideMacBook-Pro:bin feifei$ ls
aarch64-linux-android-addr2line aarch64-linux-android-elfedit aarch64-linux-android-nm aarch64-linux-android-size
aarch64-linux-android-ar aarch64-linux-android-gprof aarch64-linux-android-objcopy aarch64-linux-android-strings
aarch64-linux-android-as aarch64-linux-android-ld aarch64-linux-android-objdump aarch64-linux-android-strip
aarch64-linux-android-c++filt aarch64-linux-android-ld.bfd aarch64-linux-android-ranlib
aarch64-linux-android-dwp aarch64-linux-android-ld.gold aarch64-linux-android-readelf
2.1、 aarch64-linux-android-addr2line
Addr2line 工具(它是标准的 GNU Binutils 中的一部分)是一个可以将指令的地址和可执行映像转换成文件名、函数名和源代码行数的工具
常用使用方式:
aarch64-linux-android-addr2line -f -C -e xxx.so 偏移地址
参数介绍:
-a --addresses:在函数名、文件和行号信息之前,显示地址,以十六进制形式。
-b --target=:指定目标文件的格式为bfdname。
-e --exe=:指定需要转换地址的可执行文件名。
-i --inlines : 如果需要转换的地址是一个内联函数,则输出的信息包括其最近范围内的一个非内联函数的信息。
-j --section=:给出的地址代表指定section的偏移,而非绝对地址。
-p --pretty-print:使得该函数的输出信息更加人性化:每一个地址的信息占一行。
-s --basenames:仅仅显示每个文件名的基址(即不显示文件的具体路径,只显示文件名)。
-f --functions:在显示文件名、行号输出信息的同时显示函数名信息。
-C --demangle[=style]:将低级别的符号名解码为用户级别的名字。
-h --help:输出帮助信息。
-v --version:输出版本号。
2.2、 aarch64-linux-android-nm
nm 是name的缩写,用于读取目标文件中的符号名称(可以理解为一些函数和全局变量)
aarch64-linux-android-nm libbreakpad-core.so
0000000000061090 b _ZZ65Java_com_sogou_translate_breakpad_BreakPadCore_initBreakpadNativeE2eh
00000000000426dc r _ZZN12_GLOBAL__N_114MinidumpWriter18WriteOSInformationEP15MDRawSystemInfoE9separator
000000000004245b r _ZZN12_GLOBAL__N_115MicrodumpWriter9LogAppendItEEvT_E3HEX
0000000000062720 b _ZZN15google_breakpad12_GLOBAL__N_127InstallAlternateStackLockedEvE13kSigStackSize
00000000000421d4 r _ZZN15google_breakpad16ExceptionHandler12GenerateDumpEPNS0_12CrashContextEE11no_pipe_msg
0000000000042204 r _ZZN15google_breakpad16ExceptionHandler12GenerateDumpEPNS0_12CrashContextEE3msg
00000000000422ab r _ZZN15google_breakpad16ExceptionHandler15sendKeyInfoBackEPcE3msg
0000000000042272 r _ZZN15google_breakpad16ExceptionHandler21WaitForContinueSignalEvE3msg
00000000000422de r _ZZN15google_breakpad16ExceptionHandler23readKeyInfoInSubProcessEvE3msg
0000000000042233 r _ZZN15google_breakpad16ExceptionHandler25SendContinueSignalToChildEvE19okToContinueMessage
0000000000042234 r _ZZN15google_breakpad16ExceptionHandler25SendContinueSignalToChildEvE3msg
00000000000455e0 r _ZZNK10__cxxabiv129__pointer_to_member_type_info9can_catchEPKNS_16__shim_type_infoERPvE12null_ptr_rep
00000000000455f0 r _ZZNK10__cxxabiv129__pointer_to_member_type_info9can_catchEPKNS_16__shim_type_infoERPvE12null_ptr_rep_0
00000000000271f8 W _ZdaPv
2.3、 aarch64-linux-android-strip
strip经常用来去除目标文件中的一些符号表、调试符号表信息,以减小程序的大小。
aarch64-linux-android-strip xxx.so
会strip掉 xxx.so中的符号信息和调试信息,覆盖原有的xxx.so
2.4、 aarch64-linux-android-readelf
2.4.1、什么是elf (Executable and Linkable Format)
目标文件有三种类型: 可重定位的对象文件(.o),可执行的对象文件,可被共享的对象文件(.so文件)
现代x86-64Linux和Unix系统使用“可执行可链接格式(Executable and Linkable Format,ELF) 来组织目标文件(如.so或可执行文件)
ELF格式的文件在Linux系统下有.axf、 .bin、 .elf、 .o、 .prx、 .puff、 .ko、 .mod和.so等等
ELF格式由以下组成:
- .text 节里装载了程序的可执行机器码
- .rodata 节里装载了只读数据
- .data 节里面装载了被初始化的数据,包括全局和静态C变量
- .bss 节里面装载了未被初始化的全局和静态C变量(在目标文件中只是占位符,不占空间)
- .symtab 或者 .dynsym 节里面装载了符号信息
- 以 .rel 打头的 节里面装载了重定位条目
- .debug 一个调试符号表,只有使用了-g参数编译时才会有,用于debug
- .line 用于记录C源程序的行号和.text节中机器指令之间的映射,也是只有使用了-g参数编译时才会有
- .strtab 或者 .dynstr 节里面装载了字符串信息(以null结尾的字符串信息)
2.4.2、readelf 读取elf信息
- h 查看使用说明
- all 会打印所有的elf信息
- s 会打印符号表信息
aarch64-linux-android-readelf -all libbreakpad-core.so
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: AArch64
Version: 0x1
Entry point address: 0x17980
Start of program headers: 64 (bytes into file)
Start of section headers: 2641088 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 35
Section header string table index: 32
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .note.gnu.build-i NOTE 0000000000000200 00000200
0000000000000024 0000000000000000 A 0 0 4
[ 2] .hash HASH 0000000000000228 00000228
0000000000001358 0000000000000004 A 4 0 8
[ 3] .gnu.hash GNU_HASH 0000000000001580 00001580
aarch64-linux-android-readelf -s libbreakpad-core.so
Symbol table '.dynsym' contains 715 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000017980 0 SECTION LOCAL DEFAULT 11
2: 000000000005d200 0 SECTION LOCAL DEFAULT 18
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sleep@LIBC (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sem_wait@LIBC (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sigemptyset@LIBC (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_create@LIBC (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND realloc@LIBC (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND gettid@LIBC (2)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND open@LIBC (2)
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_key_create@LIBC (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND sem_post@LIBC (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pthread_once@LIBC (2)
13: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND calloc@LIBC (2)
三、参考文章
https://www.jianshu.com/p/c2e2b8f8ea0d
https://zhuanlan.zhihu.com/p/62039158