我正在编写Dalvik字节码的工具,它对各种方法调用条目执行一些日志记录。具体来说,在各种方法调用站点,我将插入一组指令,收集参数,将它们放在Object[]
数组中,然后将其传递给日志记录函数。
这一切都很好,我已经实现了并且已经超越了大多数应用程序的所有kludges。但我遇到了一个特别难以理解的Dalvik验证错误:
java.lang.VerifyError: Verifier rejected class io.a.a.g: void io.a.a.g.r()
failed to verify: void io.a.a.g.r(): [0x570] register v5 has type Reference:
java.lang.Object but expected Precise Reference: java.lang.String
我查看了我的仪器生成的代码,我所做的就是将寄存器v5放在一个对象数组中。
我在这里有几个问题:
[0x570]
指向字节码指令的中间位置,因此它没有明确映射到任何指令:周围的指令不涉及v5
。编辑:
这是我所说的方法的字节码的转储。 https://gist.github.com/kmicinski/c8382f0521b19643bb24379d91c47d36正如您所看到的,0x570不是指令的开头,并且(据我所知),r5与字符串冲突的任何地方都不应该是对象
答案 0 :(得分:3)
如果仔细查看错误,就会告诉您传递Object
,其中String
是预期的。无论如何,除非你发布导致问题的实际字节码,否则没有更多可以说的。
你确定0x570指向指令的中间吗?它不应该。无论如何,你要调试它的方法是查看相关的指令,并找出为什么r5是一个Object,当它应该是一个String时。或者您可以发布字节码,以便我可以看看。
编辑:现在您已经发布了代码,实际上有一条路径导致v5成为Object,但它有点微妙
异常处理程序
.catch JSONException {:5D8 .. :938} :BDE
跳转到:BDE
异常处理程序的代码将捕获的异常存储在v5中,这意味着此时v5不再是String。然后跳转到:162
:BDE
00000BDE move-exception v5
00000BE0 const v0, 0x00488B36
00000BE6 invoke-static Logger->logBasicBlockEntry(I)V, v0
00000BEC goto/16 :162
:162
在另一个异常处理程序的范围内:.catch ClassNotFoundException {:2E .. :594} :BF0
:Bf0
让v5保持不变并跳转到:A28
:BF0
00000BF0 move-exception v6
00000BF2 const v0, 0x00488B3E
00000BF8 invoke-static Logger->logBasicBlockEntry(I)V, v0
00000BFE goto/16 :A28
:A28
是代码块的开头,它假设v5是String。特别是,在指令:AE0
上,v5被传递给一个带字符串的函数。
00000AE0 invoke-virtual StringBuilder->append(String)StringBuilder, v7, v5
0xAE0正好是0x570的两倍,这解释了错误中显示的偏移量,一旦你按照JesusFreke的建议调整代码单位。
请注意,这不一定是唯一损坏的代码路径,它只是我在查看代码时找到的第一个路径。但是,一个错误的路径足以将v5的类型与JSONException统一起来,因此将其转换为Object。
答案 1 :(得分:2)
0x570可能是代码单元的偏移量,每个代码单位为两个字节。所以字节偏移实际上是0xAE0,它与一条指令相对应,并且该指令确实引用了v5。
我希望发生的事情是在某个地方存在一个字符串存储在v5中的字符串,但是还有另一个代码路径合并在字符串存储在v5中和使用它的位置之间,并且该代码路径具有不同的对象类型存储在v5中。当代码路径合并时,它使用两种类型的公共超类作为寄存器的类型。因此,如果这两种类型完全不相关,则java.lang.Object将是超类。
调试此问题的方法是使用--register-info ARGS,DEST,FULLMERGE
选项(以及--code-offsets
运行baksmali,以便轻松找到0xAE0),然后从0xAE0向后看并查看类型将v5设置为Object。
答案 2 :(得分:2)
我想补充一下我的答案,因为其他人一直非常乐于帮助他们回答一个可能无法概括的棘手问题!
正如@Antimony指出的那样,我的代码中有一个控制路径,它始于异常处理程序,将异常存储在v5
中(导致v5
为Object
)并且然后在异常处理程序中goto
' da点。然后,该异常处理程序导致v5
被用作字符串,从而导致验证程序错误。
在应用的原始代码中,goto
目标的唯一精简是return-void
指令。因此,Dalvik验证程序没有将路径传播到异常处理程序。
不幸的是,当我重写这个应用程序时,它导致该异常处理程序的目标包含的不仅仅是这个return-void
指令,通过该块生成验证原因并进入捕获的异常处理程序。特别是,在return-void
之前,我插入了对Logger.logMethodExit
的调用,然后验证者会假设可以将控制权转移回异常处理程序(在这种情况下为:BF0
)并最终到达该位置其中v5
用作字符串。在原始的应用程序中,它被杀死(在gen / kill数据流意义上)。但是在重写时,我包含了这个额外的调用,打破了数据流不变量...... Crud。
我想我知道如何在我的实现中解决这个问题,但确定很难理解!
这里有更多一般性的经验教训:
验证程序错误偏移实际上只是字节码中的2 *索引
与JVM字节码不同,Dalvik字节码考虑了不可抛出的操作码子集,包括return
。这将影响数据流分析
精确引用意味着某个基本块中的某些东西被限制为Object的特定细化,而另一个基本块中的Object
被限制(虽然这个错误对我来说似乎有点深奥......)
当你重写字节码时,你需要认识到你隐含地使用的gen / kill集,特别是return-*
指令会立即杀死东西,而跳到开头try..
中的基本块将继续保持这些内容。