我想,我正在试着做一些相对简单的事情。以下面的doSomething(Int)方法的Java字节码为例:
public java.lang.String doSomething(int i);
0 iload_1 [i]
1 invokestatic MyHelper.doSomething(int) : Java.lang.String
4 areturn这个字节码几乎只将调用转发给静态帮助器。
我现在想做的是使用Javassist将invokestatic替换为invokedynamic。我知道如何使用ASM做到这一点,因此,假设我纯粹出于好奇心想知道它是如何工作的。以下是我的一些问题:
1)以下内容是否正确:我不能使用javassist ()或CtMethod.insertAt()方法,因为这些方法需要一个包含有效Java表达式的字符串,并且我不能用Java语法编写CtMethod.instrument()或CtMethod.insertAt()方法?
2) invokestatic的参数的处理方式就像invokevirtual或invokestatic的参数一样,对吗?我的意思是,您在调用动态之前将参数放到堆栈上,就像对调用虚拟一样?
3)有没有示例代码(或者您能想出一些示例代码)使用Javassist来创建invokedynamic字节码吗?
到目前为止,我所知道的是:您可以创建一个具有addInvokedynamic()方法的字节码对象。但这需要BootstrapMethodsAttribute中BootstrapMethod的索引。反过来,BootstrapMethod需要常量池中的方法句柄信息的索引,常量池需要方法引用,依此类推。所以从本质上说,你必须自己管理整个常量池条目。这是可以的,但我担心我不能正确地理解它,并在以后引入奇怪的问题。有没有更简单的方法来做这件事(助手方法之类)?我的代码大致如下所示(我并没有真正地“重写”上面的invokestatic,但是:
void transform(CtMethod ctmethod, String originalCallDescriptor) {
MethodInfo mInfo = ctmethod.getMethodInfo();
ConstPool pool = ctmethod.getDeclaringClass().getClassFile().getConstPool();
/* add info about the static bootstrap method to the constant pool*/
int mRefIdx = /*somehow create a method reference entry*/
int mHandleIdx = constPool.addMethodHandleInfo(ConstPool.REF_invokeStatic, mRefIdx);
/* create bootstrap methods attribute; there can only be one per class file! */
BootstrapMethodsAttribute.BootstrapMethod[] bms = new BootstrapMethodsAttribute.BootstrapMethod[] {
new BootstrapMethodsAttribute.BootstrapMethod(mHandleIdx, new int[] {})
};
BootstrapMethodsAttribute bmsAttribute = new BootstrapMethodsAttribute(constPool, bms);
mInfo.addAttribute(bmsAttribute);
//... and then later, finally
Bytecode bc = new Bytecode(constPool);
... push parameters ...
bc.addInvokedynamic(0 /*index in bms array*/, mInfo.getName(), originalCallDescriptor);
//replace the original method body with the one containing invokedynamic
mInfo.removeCodeAttribute();
mInfo.setCodeAttribute(bc.toCodeAttribute());
}非常感谢您的帮助和时间!
发布于 2013-03-17 08:06:17
我必须说,这是一个非常有趣的问题。如果我的答案有点长,我很抱歉,但我已经尽可能多地(我认为是)给您提供了有用的信息,以试图帮助您和其他人解决这个问题。
问题1
是正确的:我不能重复使用CtMethod.instrument()或CtMethod.insertAt()方法,因为这些方法需要一个包含有效Java表达式的字符串,而我不能用Java语法编写invokedynamic?
你是对的。
CtMethod.insertAt()只能与Java代码一起工作,而不能与字节码操作码一起工作。另一方面,CtMethod.instrument(),允许您处理字节码,甚至可以使用ExprEditor和CodeConverter以非常有限的方式修改字节码。但正如我所说的,它们允许您更改的内容非常有限,并且对于您试图实现的目标,这两个修饰符都不能帮助您。
问题2
invokevirtual的参数就像
或invokestatic的参数一样被处理,对吗?我的意思是,您在调用动态之前将参数放到堆栈上,就像对调用虚拟一样?
我不知道我是否完全理解了你在这里真正问的是什么(你在第一句话中重复了invokestatic )。我认为你想问的是--如果我错了,请纠正我--如果invokedynamic中的参数被以与invokevirtual和invokestatic中相同的方式处理。使您能够简单地切换invokevirtual和invokestatic作为invokedynamic。在回答这个问题时,我会假设是这样的。
首先要注意的是,在处理堆栈时,invokevirtual和invokestatic本身是不同的。Invokevirtual除了像invokestatic那样将所需的参数推送到堆栈之外,它还推送对象引用,以便可以链接方法调用。
SideNote
你可能已经知道了这一点,但我添加了这个额外的信息,以防其他人进入这个问题,并想知道为什么invokestatic和invokevirtual处理堆栈的方式不同。
当我对操作码如何工作有疑问时,我的建议是检查JVM specification中关于JVM instruction set的章节(链接是针对JVM7的,写这篇文章时的当前版本)。
我这样做只是为了检查我们在这里讨论的3个操作码。
两个操作码invokestatic和invokedynamic具有相同的操作数堆栈定义:
..., [arg1, [arg2 ...]] →
...正如我之前所说的,invokevirtual有一个不同的操作数堆栈定义,即:
..., objectref, [arg1, [arg2 ...]] →
...我在这里的第一个假设(我必须警告您,我还没有深入研究invokedynamic操作码)是,您不能像使用invokestatic那样简单地更改invokedynamic的invokevirtual。我这么说是因为invokedynamic在堆栈中不需要任何对象引用。
为了更好地理解这种情况,我的建议是使用java.lang.invoke包用Java语言编写一个示例,它将允许您创建使用invokedynamic操作码的Java字节码。在编译类之后,使用命令javap -l -c -v -p检查生成的字节码。
问题3
有没有使用Javassist创建调用动态字节码的示例代码(或者你能想出一些)吗?
据我所知没有。我也用谷歌搜索了一下(你可能已经搜索过了),但我什么也没找到。我想你的帖子会给出javassist的第一个代码示例:)
更多注意事项
,所以本质上,你必须自己管理整个常量池条目。这是可以的,但我担心我没有正确地理解它,并在稍后引入奇怪的问题
只要您使用ConstPool类来管理您的常量池,javassist就会为您处理所有事情,而不会产生问题。
此外,如果您创建了一个损坏的contant池,最常见的情况(很可能总是)是,当您试图装入类或调用修改后的方法时,就会出现ClassFormatException错误。我要说的是,这是其中一个案例,它要么工作,要么不工作。
我想不出会有哪种奇怪的bug会被隐藏起来,等着那个令人不快的时刻出现在你的脑海中(注意,我说过我想不到,并不是说它们不存在)。我甚至敢说,只要你能装入类并调用它的方法,而不会导致JVM崩溃,这是相当安全的。
有没有更简单的方法(帮助器方法)?
我不这样认为。Javassist在修改字节码方面对你有很大的帮助,但这是在你使用高级API的时候(比如,编写java代码并注入代码,或者移动/复制CtMethods、Ctclasses等)。当您使用必须处理所有字节码的低级API时,您基本上只能靠自己了。
我知道这可能不是你想要的答案,但我希望我已经对这个主题有了一些启发。
https://stackoverflow.com/questions/15409377
复制相似问题