前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Duang~ Android堆栈慘遭毁容?精神哥揭露毁容真相!

Duang~ Android堆栈慘遭毁容?精神哥揭露毁容真相!

作者头像
腾讯Bugly
发布2018-03-22 16:45:41
1.3K0
发布2018-03-22 16:45:41
举报
文章被收录于专栏:腾讯Bugly的专栏腾讯Bugly的专栏

最近精神哥接到多个小伙伴的投诉,说无法看懂Bugly崩溃克星页面上显示的堆栈信息!精神哥赶紧把正研究的Top Crash崩溃和心爱的鸡爪放下,开始着手跟进。经分析发现,大家误会Bugly崩溃克星了,这一切都是Proguard搞的鬼!

下面请容精神哥一一道来!

问题描述

先看看图一中显示的正常堆栈内容,正常堆栈中每一个调用帧(Frame),都会有3个元素组成:

  • 类名(Full Class Name,红线标注);
  • 方法名(Method,蓝线标注);
  • 源文件及行号(SourceFile:LineNum,绿线标注)。

我们再看看图二显示的异常堆栈内容,可以发现栈中的某一调用帧(Frame),一个类名下会存在多个方法名,而且第一个方法尾部的源文件及行号是(Unknown Source),根本看不出源文件和行号。

但,有经验的同学童鞋应该能看出来,这个堆栈是被Proguard还原过的!

怎么看?很简单,出错时JVM生成的堆栈中每一个"方法描述",只有“方法名”而没有“方法返回类型”及“方法参数”。但被Proguard还原过的堆栈,应该有“方法返回类型”及“方法参数”。

那么这里有两个问题:

  1. 为什么堆栈会是Unknown Source?是Bugly崩溃克星没有上报吗?
  2. 为什么com.xx.a.a会被还原成多个方法(图2示例中就被还原了5个方法)?

精神哥继续给大家分析分析!

为什么堆栈会是Unknown Source?

是Bugly崩溃克星忘记上报了吗?不是!那是因为,你代码编译的姿势不对!

有经验的童鞋应该会发现,开发阶段上报Bugly的Crash崩溃堆栈都是有源码及行号的,但发布后就变成了Unknown Source,为什么?

如图3所示,我们发布时源码信息会先经过javac编译,再经过proguard混淆,才被打包进发布的apk中,最终Crash崩溃后Bugly获取到的堆栈中有木有源码及行号就要看这两步了。

所以想让Crash崩溃堆栈不再Unknown Source,需要两个保证:

保证一:javac编译保留源文件名及行号

【TODO】javac编译保留源文件名及行号 源文件名、行号、变量名称,都存在class文件的debug信息中,javac编译时可以选择是否保留debug信息,那么我们肯定是要保留的!下面是使用不同编译方法时保存源文件名及行号的解决方法示例。

  • 命令行javac编译的解决方法
代码语言:javascript
复制
javac -g:{lines,source} XXX.java
  • 使用Ant编译的解决方法
  • 使用Eclipse编译的解决方法

保证二:Proguard混淆中保留原文件名及行号

【TODO】Proguard中keep住源文件及行号

代码语言:javascript
复制
-keepattributes SourceFile,LineNumberTable

为什么com.xx.a.a被还原成多个方法了?

如图六,大家看到Class1_Promoted类下面的两个方法都被混淆成了a方法。

那么问题来了:假如堆栈中有 proguard.retrace.a.a(Unknown Source),应该被还原成神马呢?

因为它可以是proguard.retrace.Class_Promoted类下的方法void doClass1(int),也可以是void doClass1()。所以Proguard还原工具直接把这两个都给你列出来都作为你的还原结果,所以:

代码语言:javascript
复制
proguard.retrace.a.a(Unknown Source) 

被还原成

代码语言:javascript
复制
proguard.retrace.Class_Promoted.
void doClass1(int)
void doClass1()

图二中的5个方法也就是这么来的。

真不能怪Proguard还原工具,谁让Java堆栈中不给出方法的完整描述或签名(返回值,方法名,方法参数),而只有方法名,但Java语法又允许方法名相同。但这是有解决办法的!

Proguard还原工具其实还能根据行号进行区分,如果你的堆栈已经解决了UnknownSource问题,那么你的还原Mapping文件就不一样了。如图7,大家看到mapping文件中多了“数字:数字”这类内容,这个就是行号,也就是说proguard.retrace.a类中24行到26行属于void doClass(int)方法 ,29到31行属于void doClass1()方法。

假如堆栈中有proguard.retrace.a.a(Demo.java:25),就知道要被还原成proguard.retrace.Class_Promoted.void doClass1(int)了。

精神哥总结

Proguard作为非常优秀的工具,让我们安装包体积变小了,代码混淆更安全了,代码裁剪优化速度更快了,但确实也埋下了一些坑,增加了我们定位崩溃时的成本。跟着精神哥的这篇文章把原文件名和行号补上后,绝大部分的堆栈问题都可以被解决了!

那么堆栈问题就没了吗?有人曾问我:堆栈里显示A方法调用了D方法,跟着D方法崩溃了,但实际查看代码A并没有调用D方法啊?

精神哥汗了,不知道Progurad会做代码优化的么?认真看看是不是有A->B->C->D ?如果有!那是因为Proguard大神很喜欢把你没用的->B->C给干掉,直接A->D了!

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2015-03-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯Bugly 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档