我正在研究一个小的java字节码检测工具。
一般的想法是使用_CONGU
后缀重命名所有类方法,然后创建具有原始方法名称的代理方法,这些方法将调用它们的_CONGU
对应方。
例如,如果某个类C
包含int m() { return 1; }
方法,则已检测的C
类将采用int m_CONGU() { return 1; }
方法和int m() { return m_CONGU(); }
方法。
稍后我会在int m()
上添加一些额外的逻辑,在调用m_CONGU()
之前会进行一些检查。
目前,我正在使用_CONGU
后缀将所有构造函数调用调用检测到我的方法。
Bellow您可以看到inverse()
类的Fraction
方法的已检测和非检测版本。
尝试运行此代码时
Fraction fraction = new Fraction(4, 1);
我得到以下异常,这让我在过去几个小时里感到困惑:
线程“main”中的异常java.lang.VerifyError :(类: jorgeTestes / system / fraction / Fraction,方法:inverse_CONGU签名: ()LjorgeTestes / system / fraction / Fraction;)期待找到 堆栈上的对象/数组 jorgeTestes.system.fraction.XyzTest.main(XyzTest.java:9)
我想这肯定是一个明显的错误,但我无法得到问题所在。它看起来在某种程度上与堆栈中包含错误数量的数据有关,但它在我看来堆栈中的元素数量在原始代码和已检测代码上都是相同的(至少应该发生的是这样) )。有什么想法吗?
1)这里是<init>
和Fraction_CONGU
的描述符(正如人们所期望的那样!):
2)我想知道Bytecode Viewer中[0] Code
的颜色是否不同可能意味着我的检测代码存在其他问题?也许有一些元数据在这个过程中被破坏了,所以这可能是代码看起来不错的原因,并且在尝试运行代码时仍然存在问题?
答案 0 :(得分:3)
看起来第一个屏幕截图上的代码从根本上被打破了。 JVM字节码中的对象构造可以分为两个阶段:在堆上分配内存并针对分配的内存调用构造函数(带有可选参数):
new #1 <Fraction> //allocate memory
dup //duplicate to not loose the object after calling constructor
//push c-tor args onto the stack here
invokespecial <Fraction.<init>> //constructor, second `this` is lost
areturn //returns first `this`
您基本上做的是:在堆上分配一些原始(可能已归零)的内存,并针对该内存块调用虚拟方法Fraction_congu
。你还没有调用构造函数!
此外应该有invokevirtual
,我猜这些生成的方法是private
。
更新:我假设您要转换以下类:
class Fraction {
public Fraction(float den, float num) {
//original constructor code here
}
public int m() {
return 1;
}
}
分为:
class Fraction {
public Fraction(float den, float num) {
//proxy method
//place for extra logic
Fraction_CONGU(den, num);
}
private Fraction_CONGU(float den, float num) {
//original constructor code here
}
public int m() {
//proxy method
//place for extra logic
return m_CONGU
}
private int m_CONGU() {
return 1;
}
}
正如你所看到的那样(如果我能正确理解你的想法)是完全可能的。只需编译此Java代码,看看编译器如何实现它。
这引出了一个问题:你不能只使用AspectJ进行编译时编织吗?
答案 1 :(得分:1)
在进行检测时,需要使用特殊情况构造函数和对构造函数的调用。
注意检测的是如何调用invokevirtual,而非检测的是调用invokespecial。
新操作码返回的堆栈上的对象不能由invokespecial以外的任何操作码处理。