前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >妖怪般的VerifyError | 奇形怪状的bug

妖怪般的VerifyError | 奇形怪状的bug

作者头像
逮虾户
发布2022-03-06 09:46:01
7680
发布2022-03-06 09:46:01
举报
文章被收录于专栏:逮虾户逮虾户

theme: smartblue

前言

任何的Transform的字节都是危险的,所以如果真的觉得自己不能解决所有线上奇奇怪怪的问题,对这门技术还是要慎重。 出自很菜的虾。

如果你是因为这个bug,不幸点入这篇文章,我想说你运气属实不好,那么让我们掌声欢迎这个受害者。

首先我个人觉得这个问题非常难排查和定位,光从堆栈日志来说,你可能会一头雾水。

VerifyError问题排查

首先先看下这个异常的定义。

java.lang.VerifyError 是说 JVM 在加载一个类时,会去校验类的正确性,只有类文件不合法才会报这个Error,这个异常发生在类的加载过程中。

这个问题发生在类的生命周期的过程中。大体上还是和之前我写的那篇文章一个一年没解决的ClassNotFoundException|类加载机制探索这个问题非常类似。这个也是来b之前和字节大佬面试切磋时的一个问题吧,现在回头看看,其实还是受益匪浅,也大概知道这种问题如何去查看和调试了。

问题的本质还是之前说的androidx的升级。因为升级了fragment的版本到1.4.3,对于基础的fragment构造增加了一个有参的构造函数,之后导致了这个奇怪的问题。

在线上灰度的期间,我们发现了4.4的设备有这么个奇怪的crash异常。而在别的系统的设备则都是正常的。之后我们找了一台4.4的设备,发现在release版本的确会出现这个问题,异常情况如下所示。

代码语言:javascript
复制
FATAL EXCEPTION: main
at java.lang.VerifyError: androidx/fragment/app/DialogFragment
at androidx.fragment.app.Fragment.performCreate(Fragment.java:2949)
at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:278)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1971)
at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:311)
at androidx.fragment.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:249)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1244)
at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:669)
at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:631)
at androidx.viewpager.widget.ViewPager.dataSetChanged(ViewPager.java:1086)
at androidx.viewpager.widget.ViewPager$PagerObserver.onChanged(ViewPager.java:3097)
at androidx.viewpager.widget.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:291)
at android.os.Handler.handleCallback(Handler.java:730)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5162)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:525)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:737)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
at dalvik.system.NativeStart.main(NativeStart.java)

现象很简单,这次我们所有的dialogfragment其实全挂了,在4.4的设备上,只要有这个的构造就会导致这个异常崩溃。

起初我们只是以为是混淆导致的这个异常情况的发生,但是尝试keep了所有androidx的类之后,发现这个问题还是稳定的复现,我有点懵逼了。

而之后简单的分析了下异常,猜测dialogfragment的类本身就出现了问题,所以导致了在类加载的时候,验证字节码安全性失败。但是这部分可是androidx内部的代码啊,这不就是不讲武德吗?

当自己走进死胡同的时候,还是可以尝试问下你周围的同事,也可以是你的大佬,正所谓一人计短。有时候的确是会有奇效的,也算是一个老司机理论了。

我大佬之前的确处理过类似的问题,但是他之前处理的那部分是所有机型都必挂,而且是和混淆相关。但是大佬的大佬也说了下,这种异常在崩溃日志之前就会有类信息校验的错误说明,也就是说dalvikvm在校验代码的时候会把错误的信息打印出来。恰巧就和之前的classnotfound异常对应上了。

代码语言:javascript
复制
06-15 18:11:16.876 13235-13235/? W/dalvikvm: VFY: invoke-direct <init> must be on current class or super
06-15 18:11:16.876 13235-13235/? W/dalvikvm: VFY:  rejecting opcode 0x70 at 0x0000
06-15 18:11:16.876 13235-13235/? W/dalvikvm: VFY:  rejected Landroidx/fragment/app/DialogFragment;.<init> (I)V

从描述上来看,就是构造函数内调用的是当前类的,并没有调用父类的构造。所以导致了这部分是一个异常的字节码。

因为这部分我司做了一部分字节码的父类替换,所以DialogFragment其实已经被修改了一部分了。而在新版本的androidx中,则给Fragment添加了另外一个有参的构造函数,所以这部分就出现了异常。

西内,无能狂怒,问题定位出来之后后续的其实也就相对来说还好了,改造方式则是有另外一个大佬去完成的,有兴趣可以参考下大佬之前写的lancet,功能也比较相似,细节我说出来可能要去趟hr办公室领离职证明了。

借住AS查看Apk的bytecode

另一个大佬顺便教会了我如何从apk中去查看最终产物的bytecode的方式。一个不需要用jadx反编译整个apk就可以快速查看class字节码的方式。

通过这部分就可以快速的查看这部分异常了,方式就和下面我所截图的一样了。

  1. 拖入android studio,点击apk
image.png
image.png
  1. 找到你想看的类,右键 show bytecode
image.png
image.png
  1. 看一看
image.png
image.png

这部分异常参考资料

Android 不想和你说话,抛了个 java.lang.VerifyError 这个是摘自另外一个哔哩哔哩的安卓巨佬了。

因为是在低版本手机上触发的问题,运行的仍然是 dalvik VM,很容易的(google)在对应版本(4.1.1)源码中找到类DexVerify.cpp,和 CodeVerify.cpp

(感兴趣的可以从 dvmVerifyClass() 开始阅读类检查的全过程。)

DexVerify 中的 verifyMethod() 最终会调用 CodeVerify 的 dvmVerifyCodeFlow() 来确保类中的单个方法执行流是合法的。

其中要注意的是,异常处理(Exception Hanler)也是在这个时候被校验的,它的opcode是OP_MOVE_EXCEPTION(0x0d,就是前面日志”rejecting opcode 0x0d”提到的)。

检验方法getCaughtExceptionType() 在找不到catch代码块中指定的异常类(如例子中的ErrnoException)时即会报错:”VFY: unable to resolve exception class 1594 (Landroid/system/ErrnoException;)”,尝试各种可能性之后仍然不知道该如何处理这个异常,接着会认为代码有问题日志报错:”VFY: unable to find exception handler at addr 0xe” 和 “VFY: rejected Lcom/sample/FileUtils;.getUid (Ljava/lang/String;)I”

最终走向方法校验失败的分支”rejecting opcode 0x0d at 0x000e”,于是乎dvmVerifyCodeFlow()方法return false标识着verifyMethod()失败,拒绝加载类:”Verifier rejected class Lcom/sample/FileUtils;”

总结

我个人觉得哦,一个开发同学,其实多和别人交流交流,对于你的技术提升啊问题解决,还有很多思路相关的其实都是有很大帮助的。

也不一定是要学到什么自己完全不会的东西,可能一些思路啊,解决问题的方式都是值得你借鉴学习的。而且去向别的同事学习,也可以满足下他们装杯的欲望,他们说实话真的是不会拒绝你的。

这次文章相对来说很短,但是无奈与作者水平有限啊,只能打这么多字了,多有得罪,你特么也打不到我啊!!!!

还有最后说三句,算上这句,没有了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021/06/20 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • theme: smartblue
  • 前言
  • VerifyError问题排查
    • 借住AS查看Apk的bytecode
      • 这部分异常参考资料
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档