首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Javassist生成Invokedynamic

使用Javassist生成Invokedynamic
EN

Stack Overflow用户
提问于 2013-03-14 20:31:10
回答 1查看 1.1K关注 0票数 4

我想,我正在试着做一些相对简单的事情。以下面的doSomething(Int)方法的Java字节码为例:

代码语言:javascript
复制
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,但是:

代码语言:javascript
复制
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());

}

非常感谢您的帮助和时间!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-03-17 08:06:17

我必须说,这是一个非常有趣的问题。如果我的答案有点长,我很抱歉,但我已经尽可能多地(我认为是)给您提供了有用的信息,以试图帮助您和其他人解决这个问题。

问题1

是正确的:我不能重复使用CtMethod.instrument()或CtMethod.insertAt()方法,因为这些方法需要一个包含有效Java表达式的字符串,而我不能用Java语法编写invokedynamic?

你是对的。

CtMethod.insertAt()只能与Java代码一起工作,而不能与字节码操作码一起工作。另一方面,CtMethod.instrument(),允许您处理字节码,甚至可以使用ExprEditorCodeConverter以非常有限的方式修改字节码。但正如我所说的,它们允许您更改的内容非常有限,并且对于您试图实现的目标,这两个修饰符都不能帮助您。

问题2

invokevirtual的参数就像

或invokestatic的参数一样被处理,对吗?我的意思是,您在调用动态之前将参数放到堆栈上,就像对调用虚拟一样?

我不知道我是否完全理解了你在这里真正问的是什么(你在第一句话中重复了invokestatic )。我认为你想问的是--如果我错了,请纠正我--如果invokedynamic中的参数被以与invokevirtual和invokestatic中相同的方式处理。使您能够简单地切换invokevirtual和invokestatic作为invokedynamic。在回答这个问题时,我会假设是这样的。

首先要注意的是,在处理堆栈时,invokevirtual和invokestatic本身是不同的。Invokevirtual除了像invokestatic那样将所需的参数推送到堆栈之外,它还推送对象引用,以便可以链接方法调用。

SideNote

你可能已经知道了这一点,但我添加了这个额外的信息,以防其他人进入这个问题,并想知道为什么invokestatic和invokevirtual处理堆栈的方式不同。

  • invokestatic操作码用于调用类中的静态方法,这意味着在编译时JVM确切地知道如何进行方法调用链接。另一方面,
  • 在有对象实例的方法调用时使用调用动态操作码。由于在编译时无法知道将方法调用链接到何处,因此只有当JVM知道正确的对象引用时,才能在运行时链接它。

当我对操作码如何工作有疑问时,我的建议是检查JVM specification中关于JVM instruction set的章节(链接是针对JVM7的,写这篇文章时的当前版本)。

我这样做只是为了检查我们在这里讨论的3个操作码。

两个操作码invokestaticinvokedynamic具有相同的操作数堆栈定义:

代码语言:javascript
复制
..., [arg1, [arg2 ...]] →

...

正如我之前所说的,invokevirtual有一个不同的操作数堆栈定义,即:

代码语言:javascript
复制
..., 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时,您基本上只能靠自己了。

我知道这可能不是你想要的答案,但我希望我已经对这个主题有了一些启发。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15409377

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档