现在不管是前端应用还是移动端应用,生产环境一般都会压缩(shrink)混淆 (obfuscation) 代码,混淆处理的目的是通过缩短应用的类、方法和字段的名称来缩减应用的大小。下面是使用Android R8 进行混淆处理的一个示例:
androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
android.content.Context mContext -> a
int mListItemLayout -> O
int mViewSpacingRight -> l
android.widget.Button mButtonNeutral -> w
int mMultiChoiceItemLayout -> M
boolean mShowTitle -> P
int mViewSpacingLeft -> j
int mButtonPanelSideLayout -> K
Java开源压缩技术 ProGuard 声称能够达到90%的压缩率和20%的性能提升
有些语言的backtrace没有文件名和行号,例如C语言,使用backtrace
方法可以打印调用栈
#include <stdio.h>
#include <execinfo.h>
#include <stdlib.h>
int show = 0;
int fibonacci(int n) {
if (n <= 1) {
if (!show) {
void *callStack[32];
int r = backtrace(callStack, 32);
printf("collect %d callstack\n", r);
char** symbols = backtrace_symbols(callStack, r);
for(int i = 0; i < r; i++) {
printf("%s\n", symbols[i]);
}
free(symbols);
show = 1;
}
return n;
}
return fibonacci(n-1) + fibonacci(n-2);
}
int main() {
fibonacci(5);
return 0;
}
会输出:
collect 8 callstack
./backtrace(+0x11b6) [0x55dda8ff61b6]
./backtrace(+0x1249) [0x55dda8ff6249]
./backtrace(+0x1249) [0x55dda8ff6249]
./backtrace(+0x1249) [0x55dda8ff6249]
./backtrace(+0x1249) [0x55dda8ff6249]
./backtrace(+0x1275) [0x55dda8ff6275]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f514f4e609b]
./backtrace(+0x10ba) [0x55dda8ff60ba]
只有函数名和内存地址,缺少文件名和行号,如果需要定位文件名和行号需要结合类似addr2line工具和包含调试信息的可执行文件来获取
$ gcc -g backtrace.c -o backtrace
$
$ addr2line -e backtrace 0x11b6
/root/backtrace.c:10
$ addr2line -e backtrace 0x1249
/root/backtrace.c:22
$ addr2line -e backtrace 0x1275
/root/backtrace.c:27
什么是符号表?
符号表是内存地址与函数名、文件名、行号的映射表。符号表元素如下所示:
<起始地址> <结束地址> <函数> [<文件名:行号>]
iOS符号还原效果
iOS平台上的 sourcemap 文件是以 .dSYM 为后缀的带有调试信息的符号表文件,一般情况下,项目编译完和 .app 文件在同一个目录下,如下所示:
$ ls -l Build/Products/Debug-iphonesimulator/
total 0
drwxr-xr-x 6 zy staff 192 8 9 15:27 Fishing.app
drwxr-xr-x 3 zy staff 96 8 9 14:02 Fishing.app.dSYM
drwxr-xr-x 15 zy staff 480 8 9 15:27 Fishing.doccarchive
drwxr-xr-x 6 zy staff 192 8 9 13:55 Fishing.swiftmodule
需要注意,XCode Release 编译默认会生成 .dSYM 文件,而Debug 编译默认不会生成,需要对 XCode 做如下相应的设置:
Build Settings -> Code Generation -> Generate Debug Symbols -> Yes
Build Settings -> Build Option -> Debug Information Format -> DWARF with dSYM File
还原工具可使用macOS自带的 atos
atos(1) General Commands Manual atos(1)
NAME
atos – convert numeric addresses to symbols of binary images or processes
SYNOPSIS
atos [-o <binary-image-file> | <dSYM>] [-p <pid> | <partial-executable-name>] [-arch architecture]
[-l <load-address>] [-s <slide>] [-printHeader] [-fullPath] [-i] [-d <delimiter>]
[-f <address-input-file>] [<address> ...]
DESCRIPTION
The atos command converts numeric addresses to their symbolic equivalents. If full debug symbol information is
available, for example in a .app.dSYM sitting beside a .app, then the output of atos will include file name and
source line number information.
...
...
示例:
$ atos -o /BuildProducts/Release/Sketch.app.dSYM -arch x86_64 -l 0x10acde000 0x10acea1d3 0x10ace4bea 0x10ace4b7a
-[SKTGraphicView drawRect:] (in Sketch) (SKTGraphicView.m:445)
-[SKTGraphic drawHandlesInView:] (in Sketch) (NSGeometry.h:110)
-[SKTGraphic drawHandleInView:atPoint:] (in Sketch) (SKTGraphic.m:490)
Android有两种符号表
第一种:原生C/C++代码的 .so符号表
可以使用 Android NDK 中集成的工具 ndk-stack 来还原
$ ndk-stack --help
usage: ndk-stack.py [-h] -sym SYMBOL_DIR [-i INPUT]
Symbolizes Android crashes.
optional arguments:
-h, --help show this help message and exit
-sym SYMBOL_DIR, --sym SYMBOL_DIR
directory containing unstripped .so files
-i INPUT, -dump INPUT, --dump INPUT
input filename
See <https://developer.android.com/ndk/guides/ndk-stack>.
使用示例:
$ ndk-stack --sym ~/software/source_map/Android/C_C++/obj/arm64-v8a/ -i ~/software/source_map/Android/C_C++/crash.txt
********** Crash dump: **********
Build fingerprint: 'Android/sdk_phone64_arm64/emulator64_arm64:12/SE1A.220203.002.A1/8151367:userdebug/test-keys'
Abort message: 'abort message for ftNative internal testing'
#00 0x00000000000057fc /data/app/~~Taci3mQyw7W7iWT7Jxo-ag==/com.ft-Q8m2flQFG1MbGImPiuAZmQ==/lib/arm64/libft_native_exp_lib.so (xc_test_call_4+12)
xc_test_call_4
/Users/Brandon/Documents/workplace/working/StudioPlace/xCrash/xcrash_lib/src/main/cpp/xcrash/xc_test.c:65:9
#01 0x00000000000058a4 /data/app/~~Taci3mQyw7W7iWT7Jxo-ag==/com.ft-Q8m2flQFG1MbGImPiuAZmQ==/lib/arm64/libft_native_exp_lib.so (xc_test_call_3+8)
xc_test_call_3
/Users/Brandon/Documents/workplace/working/StudioPlace/xCrash/xcrash_lib/src/main/cpp/xcrash/xc_test.c:73:13
#02 0x00000000000058b4 /data/app/~~Taci3mQyw7W7iWT7Jxo-ag==/com.ft-Q8m2flQFG1MbGImPiuAZmQ==/lib/arm64/libft_native_exp_lib.so (xc_test_call_2+12)
第二种:Java/Kotlin 语言 shrink后生成的mapping文件
Java 目前主要有两种压缩混淆技术,ProGuard 和 Android R8
还原调用栈 两者都提供了 retrace
(retrace.sh
) 脚本
ProGuard
$ /usr/local/proguard-7.2.2/bin/retrace.sh
Usage: java proguard.retrace.ReTrace [-regex <regex>] [-allclassnames] [-verbose] <mapping_file> [<stacktrace_file>]
Default regex: (?:.*?\bat\s+%c\.%m\s*(?:\+\s+[0-9]+)?(?:\(\))?(?:\((?:%s)?(?::?%l)?(?::\d+)?\))?\s*(?:~\[.*\])?)|(?:.*?\bjava\.lang\.ClassCastException: %c cannot be cast to .{5,})|(?:.*?\bjava\.lang\.ClassCastException: .* cannot be cast to %c)|(?:.*?\bjava\.lang\.NullPointerException: Attempt to read from field '%t %c\.%f' on a null object reference)|(?:.*?\bjava\.lang\.NullPointerException: Attempt to write to field '%t %c\.%f' on a null object reference)|(?:.*?\bjava\.lang\.NullPointerException: Attempt to invoke (?:virtual|interface) method '%t %c\.%m\(%a\)' on a null object reference)|(?:.*?\bjava\.lang\.NullPointerException: Cannot invoke \".*\" because the return value of \"%c\.%m\(%a\)\" is null)|(?:.*?\bbecause \"%c\.%f\" is null)|(?:(?:.*?[:"]\s+)?%c(?::.*)?)
Android R8可以使用 Android command line tools
$ /usr/local/cmdline-tools/bin/retrace --help
Retrace 3.2.47 (build 00d5d321bbff9d427a16574fe6e1fdd95bcf9378 from go/r8bot (luci-r8-custom-ci-xenial-36-mfdm))
Usage: retrace <proguard-map> [stack-trace-file] [--regex <regexp>, --verbose, --info, --quiet, --verify-mapping-file-hash]
where <proguard-map> is an r8 generated mapping file.
使用示例:
$ /usr/local/cmdline-tools/bin/retrace ~/software/source_map/Android/Java_Kotlin/mapping.txt ~/software/source_map/Android/Java_Kotlin/java_crash.txt
java.lang.ArithmeticException: divide by zero
at prof.wang.activity.TeamInvitationActivity.onClick$lambda-0(TeamInvitationActivity.java:1)
at java.lang.Thread.run(Thread.java:1012)