Java栈帧问题

这篇博客是记录工作中遇到的一个问题。

关于字节码动态注入框架ASM,可点击了解详情。

问题由来:

描述: 类A extends 类B, A的do方法里调用了 super.do的方法,即B类中的do方法。

需求: 现在动态的修改字节码,将类A中的super.do去掉,即不调用父类中的do方法。

解决: ASM的MethodAdapter类专门处理方法的字节码的类,ClassAdapter则是处理类字节码的类。因此:当ClassAdapter中处理A类时,遇到do方法时,转到MethodAdapter类处理。在MethodAdapter处理do方法时,visitMethodInsn会在遇到有方法的调用的地方进行回调。那么,遇到super.do(string)这个方法时,直接return即可。便去掉了super.do(string)方法。

if(methodName.equals("do")){
return;
}else{
super.visitMethodInsn(arg0, arg1, arg2, arg3);
}

但,此时却遇到了:

Exception in thread "main" java.lang.VerifyError: Instruction type does not match   stack map
Reason:
Current frame's stack size doesn't match stackmap.
Current Frame:

这里错误的意思就是:栈帧不匹配栈的映射

* android中,会导致编译打包失败。*

解决思路:

第一次遇到这个错误,会非常奇怪,再加上对java的字节码不是非常熟悉,感觉有点无从下手。但,还是需要耐心从最基本的了解说起,在java中,虚拟机会维持一个栈,来保存方法的调用的结果,参数,和恢复相关现场的一些数据。 接着我在 android里也用相同的思路去掉super方法,然后反编译观察smali语法,确实作用生效,super的方法被去除了。但在编译打包却始终失败。这肯定是ASM的动态字节码修改导致的问题。于是,我再从ASM的官方文档中去寻找答案。 却始终没有收获。

这里,推荐Eclipse好用的bytecode插件:

https://marketplace.eclipse.org/content/bytecode-visualizer

在查看字节码中,发现方法调用时,会先将需要的参数load的操作压入栈中。super.do(String),实际需要将 类的对象super,和string压入栈中,再进行调用方法的字节码操作。此时便恍然大悟,smali代码中,没有明显的load操作,仅仅没有了方法调用,但此时的参数依然被转载到栈中。java的字节码中,便能清楚看见这一操作。因此知道了原因解决就很简单。

if(methodName.equals("do")){
visitInsn(Opcodes.POP);
visitInsn(Opcodes.POP);
return;
}else{
super.visitMethodInsn(arg0, arg1, arg2, arg3);
}

在ASM注入的代码中,将栈中两个参数给pop出来即可。虽然是个笨方法,但确实能够解决问题。

总结:

对Java的字节码不够熟悉,所以在写ASM的代码时容易遇到坑。因此对bytecode熟悉,也能对java有更深入的认识。这里仅是使用ASM遇到的一个问题。