Android R8 mapping.txt文件解读

mapping.txt文件解读

链接

manual

download

github

R8

Android Gradle Plugin 3.4.0之后,默认使用R8替代原来的ProGuard,R8在编译过程中主要执行:

  • Code shrinking (or tree-shaking): 检测及删除无用代码

  • Resource shrinking: 检测及删除无用资源,包括 Code shrinking 删除的代码中引用到的资源

  • Obfuscation: 混淆即使用简单字符替代原来的类名、方法名和变量名,减少(复用)字符串常量

  • Optimization: 代码优化,例如方法内联等

同时R8编译之后也会压缩LineNumberTable,导致我们读取崩溃栈信息的时候异常头疼,因为代码行数有可能完全对不上。好在R8在混淆的时候会另外输出一个mapping.txt文件,记录混淆的信息(包括代码行数变化信息),因此我们可以通过该文件逆向解析崩溃栈。

mapping文件格式

ReTrace.java

MappingPrinter.java

classline
    fieldline * N
    methodline * M

__mapping.txt__文件的格式如上所示:__classline__是类信息,紧接着是__N__个__fieldline__的变量信息,以及__M__个__methodline__的方法信息。

originalclassname -> obfuscatedclassname:

__classline__的格式如上所示:由一个 -> 分隔:前半部分是原始类名,后半部分是混淆之后的类名

 originalfieldtype originalfieldname -> obfuscatedfieldname

__fieldline__的格式如上所示:同样由 -> 分隔:前半部分是原始变量类型和变量名,后半部分是混淆之后的变量名,变量类型的混淆信息在其__classline__信息中。

[startline:endline:]originalreturntype [originalclassname.]originalmethodname(originalargumenttype,...)[:originalstartline[:originalendline]] -> obfuscatedmethodname

__methodline__的格式较为复杂,但同样是通过 -> 分隔:

  • [startline:endline:] 一般表示混淆之后的代码行数(也就是日志中崩溃栈的行数)

  • originalreturntype 表示原始的方法返回值类型

  • [originalclassname.] 表示原始的方法定义类;如果是当前类,则可省略

  • originalmethodname 表示原始的方法名称

  • (originalargumenttype,...) 表示原始的方法参数列表

  • [:originalstartline[:originalendline]] 表示原始的方法行数

  • obfuscatedmethodname 表示混淆之后方法的名称

另外:

  • 如果方法的行数保持不变,则 [:originalstartline[:originalendline]] 信息可省略

  • 如果方法的行数被删除,则省略 [startline:endline:] 信息

  • 如果是__inline__代码块,则只有 [:originalstartline] 信息,表示被调用的(原始)行数

  • 同一个方法的信息可能划分为多个 methodline 输出(压缩了中间冗余行数信息)

  • 不同的方法行数可以相同,这时候 retrace 可以通过方法名称进行识别区分

区分是否是__inline__代码块:

如果__连续两个或以上__ methodline[startline:endline:] 信息一样,而且除了第一行 __[:originalstartline[:originalendline]] __有两个值之外,后面的 __[:originalstartline[:originalendline]] __都只有一个值, 则表示这部分代码是__inline__代码。此时 [startline:endline:] 不再是方法所在行数,而是方法的 原始行数 + 1000 * K(K可为0,这样的取值是为了区分这些__inline__代码块)。在这种情况下,方法所在的行数以及原始调用行数信息是放在 [:originalstartline[:originalendline]] 上的。

例子
// Main类未混淆
com.example.application.Main -> com.example.application.Main:
    // 变量configuration混淆为a
    com.example.application.Configuration configuration -> a
    // 构造函数未混淆,其行数不变,与原始行数一样是50-60
    50:66:void <init>(com.example.application.Configuration) -> <init>
    // execute()方法混淆为a,代码行数不变,与原始行数一样是74-228
    74:228:void execute() -> a
    // 将GPL类的check()方法39-56行内联至execute()方法的76行
    2039:2056:void com.example.application.GPL.check():39:56 -> a
    2039:2056:void execute():76 -> a
    // 将当前类的printConfiguration()方法236-252行内联至execute()方法的80行
    2236:2252:void printConfiguration():236:252 -> a
    2236:2252:void execute():80 -> a
    // 这是一个嵌套内联
    // 将PrintWriter类的createPrintWriterOut方法内联至printConfiguration方法块的243行,然后该方法块又被内联至execute()方法的80行
    // 80和243指的是原始调用行数,40-42是代码的原始行数范围
    3040:3042:java.io.PrintWriter com.example.application.util.PrintWriterUtil.createPrintWriterOut(java.io.File):40:42 -> a
    3040:3042:void printConfiguration():243 -> a
    3040:3042:void execute():80 -> a
    // 将当前类的readInput()方法260-268行内联至execute()方法的97行
    3260:3268:void readInput():260:268 -> a
    3260:3268:void execute():97 -> a

从上面的信息中也可以看出 readInput() 方法和 printConfiguration() 方法均被删除并内联至调用的地方,当然代码删除信息,我们可以通过 usage.txt 文件查询。

另外构造函数可能会存在方法行数跳变的情况,这是因为构造函数中除了显式写在构造函数中的代码,还有隐式调用的代码,如部分实例变量的初始化:

49:    private static class ExtractedDex extends File {
50:        public long crc = NO_VALUE;

52:        public ExtractedDex(File dexDir, String fileName) {
53:            super(dexDir, fileName);
54:        }
55:    }
android.support.multidex.MultiDexExtractor$ExtractedDex -> android.support.multidex.MultiDexExtractor$ExtractedDex:
    1:1:void <init>(java.io.File,java.lang.String):53:53 -> <init>
    2:2:void <init>(java.io.File,java.lang.String):50:50 -> <init>
retrace工具

retrace是官方提供的一款工具,可自动根据 mapping.txt 和 stacktrace.txt 转换崩溃栈信息。

java -jar retrace.jar[options...] mapping_file[stacktrace_file]

命令格式如上,另外:

  • -verbose 指定输出更详细的调用栈信息

  • -regex 指定的日志中特定的崩溃栈格式,默认解析常规的崩溃栈格式

详细信息参考retrace

需要指出的是,该工具不保证能完全解析所有的崩溃栈的信息,对于无法正确识别的崩溃栈信息,该工具会将所有可能的调用信息输出,需要我们自行结合代码逻辑进行判断

作者:薛定谔的程序猫
链接:https://juejin.cn/post/6863089679969812488
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值