内联方法体中的 invokevirtual 的意外指令和参数

2023-12-10

我按照“3.2.6内联方法”中的示例代码进行操作http://asm.ow2.org/current/asm-transformations.pdf,将 MethodNode 内联到调用站点。

我的问题是,内联后生成的字节码中显示了一些意外的指令(这些字节码与我的代码不一致),并且只有当ifeq在内联方法体之后,堆栈上的变量由 xLoad 加载。

我仍然没有找到问题的根本原因。现在我开始删除所有不必要的代码,旨在用最少的代码重现它。大家有好的建议欢迎提出。

这是我现有的发现之一:问题与框架无关,因为当配置 ClassRewiter 时问题仍然存在COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXSClassReader 的配置和配置ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES

为了简化问题,被调用者的主体是:

public invokeExact(Ljava/lang/String;)Z
           ICONST_0
           IRETURN

调用者是:

public String invokeExact(String a, String b){
         boolean flag = _guard.invokeExact(a);
         if(flag)
         {
            return a;
         }
         return b;
      }

。 MethodWriter上调用者对应的字节码操作轨迹为:

public java.lang.String invokeExact(java.lang.String, java.lang.String)
       ....
         4: aload_1       
         5: astore_3      
         6: astore        4
         8: iconst_0      
         visitJumpInsn  goto    L1029004533
          //visitmax()  empty implementation. 
          //visitEnd() Empty implementation. 
          visitlabel    L1029004533   // This label is newly created once inlining starts, but is visited until the end of inlining as the target of all xReturn instructions in the Callee's method body. 
       visitVarInsn  istore 5
       visitVarInsn  iload  5 
       visitJumpInsn  ifeq  L980604133
       visitVarInsn   aload 1 
       visitInsn        areturn 
       visitLabel      L980604133
       visitVarInsn   aload 2
       visitInsn        areturn

最后生成的class文件为:

 

public java.lang.String invokeExact(java.lang.String, java.lang.String);
    stack=2, locals=6, args_size=3
         0: aload_0       
         1: getfield      #17                 // Field _guard:Ltest/code/jit/asm/simple/MHGuard;
         4: aload_1       
         5: astore_3      
         6: astore        4
         8: iconst_0      
         **9: goto          9
        12: fconst_0      
        13: iconst_2**      
        14: iload         5
        16: ifeq          21
        19: aload_1       
        20: areturn       
        21: aload_2       
        22: areturn       
      StackMapTable: number_of_entries = 2
           frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class test/code/jit/asm/simple/GWTSample, class java/lang/String, class java/lang/String, class java/lang/String, class test/code/jit/asm/simple/MHGuard ]
          stack = [ int ]
           frame_type = 252 /* append */
             offset_delta = 8
        locals = [ int ]

其中 #9、#12 和 #13 是错误的。


我的部分代码是(周末我将继续简化我的代码):

public class MethodCallInliner extends LocalVariablesSorter {

    protected MethodContext _context;

    private IPlugin _plugin;

    public MethodCallInliner(int access, String desc, MethodContext context){
        // context.getRawMV() return a Class MethodWriter. 
        super(Opcodes.ASM5, access, desc, context.getRawMV());
        _context = context;
        //_fieldVisitor = new FieldManipulationVisitor(mv, context);
        _plugin = NameMappingService.get().getPlugin();

        //removed some unncessary codes..       
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc, boolean itf) {

        if(opcode != Opcodes.INVOKEVIRTUAL){
            mv.visitMethodInsn(opcode, owner, name, desc, itf);
            return;
        }

        MethodNode mn = _plugin.map(owner, name, desc, _context, this);
        if(mn == null){
            mv.visitMethodInsn(opcode, owner, name, desc, itf);
            return;
        }

        //ASMUtil.debug(mn);  //to double confirm the mn content is correct. 
        performInline(ASMUtil.isStaticMethod(mn)?Opcodes.INVOKESTATIC:Opcodes.INVOKEVIRTUAL, owner, desc, mn);
        _plugin.postProcess(mn, this, _context);

    }

    protected void performInline(int opcode, String owner, String desc, MethodNode mn){
        Remapper remapper = Mapper.getMapper(_context, _context.getReceiverFieldName());
        mn.instructions.resetLabels();
        Label end = new Label();
        System.out.println("++"+end.toString());
        _context.beginInline();
        mn.accept(new InliningAdapter(this,
                    opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0, desc,
                    remapper, end, _context));
        _context.endInline();
        super.visitLabel(end);

    }

    public void visitJumpInsn(int opcode, Label label) {
            super.visitJumpInsn(opcode, label);
     }

    @Override
    public void visitVarInsn(final int opcode, final int var){
        super.visitVarInsn(opcode, var);;
    }
    ...
}

[新发现]

我想我现在更接近问题了。

  • 内联访问者MethodCallInliner应该是正确的,因为使用相同的类对该访问者进行的另一次独立测试成功了。
  • 问题在于如何构建MethodVisitor链。 a) 我只想要一次访问方法说明的过程。 2) 的MethodCallInliner布置在链的末端。在此之前,更多的访问者被插入到推理类型信息中,这些信息可以在方法内联期间使用MethodCallInliner.

我的连锁建设者是:

@Override
public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
    .....
    MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
    return new TransformationChain(Opcodes.ASM5, access, name, desc, signature, mv, _context);
    //return new MethodCallInliner(access, desc, context);  //This is OK.
}

public class TransformationChain extends BaseMethodTransform {

    public TransformationChain(int api, int access, String name, String desc,  String signature, MethodVisitor mv, ClassContext classContext) {
        super(api, mv, classContext.getClassName(), name, desc);
        ....        
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS); 
        _visitors.add(new AnalyzerAdapter(Opcodes.ASM5, owner, access, name,desc, cw.visitMethod(access, name, desc, owner, null)){

            @Override  
            public void visitJumpInsn(final int opcode, final Label label){
                super.visitJumpInsn(opcode, label);
            }
        });

        MethodNode node = new MethodNode(access, name, desc, signature, null);
        _visitors.add(node);
        //cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS); 
        //MethodNode node = context.getClassContext().getMethodNode(name, desc);
        //_visitors.add(new TypeInferencer(Opcodes.ASM5, cw.visitMethod(access, name, desc, null, null), node, context));
        _visitors.add(name.equals(Constants.CONSTRUCTOR)?new ConstructorMerge(access, desc, context): 
            new MethodCallInliner(access, desc, context));
    }

}

abstract class BaseMethodTransform extends MethodVisitor {

    protected final List<MethodVisitor> _visitors = new LinkedList<MethodVisitor>();

    public BaseMethodTransform(int api, MethodVisitor mv, String className, String methodName, String methodDesc) {
        super(api, mv);
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc, boolean itf) {
        for (MethodVisitor mv : _visitors) {
            mv.visitMethodInsn(opcode, owner, name, desc, itf);
        }
    }

    @Override
    public void visitIntInsn(int opcode, int operand) {
        for (MethodVisitor mv : _visitors) {
            mv.visitIntInsn(opcode, operand);
        }
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        for (MethodVisitor mv : _visitors) {
            if (mv!= _visitors.get(_visitors.size()-1) || mv instanceof TraceMethodVisitor) {
                continue;
            }
            mv.visitMaxs(maxStack, maxLocals);
        }
    }

     @Override
        public void visitJumpInsn(final int opcode, final Label label) {
            for (MethodVisitor mv : _visitors) {
                mv.visitJumpInsn(opcode, label);
            }
        }
     ......
}

My Finding如果我注释掉,生成的类是正确的_visitors.add(new AnalyzerAdapter..); in the TransformationChain,其 MethodVisitor 是在此处新创建的。看起来一个方法的某些元素是有状态的,这些元素可能会被MethodWriters修改(即使它们都是独立的),并且之前的修改会对后来的访问者产生影响.

我还注意到它是标签:

/**
 * Informations about forward references. Each forward reference is
 * described by two consecutive integers in this array: the first one is the
 * position of the first byte of the bytecode instruction that contains the
 * forward reference, while the second is the position of the first byte of
 * the forward reference itself. In fact the sign of the first integer
 * indicates if this reference uses 2 or 4 bytes, and its absolute value
 * gives the position of the bytecode instruction. This array is also used
 * as a bitset to store the subroutines to which a basic block belongs. This
 * information is needed in {@linked MethodWriter#visitMaxs}, after all
 * forward references have been resolved. Hence the same array can be used
 * for both purposes without problems.
 */
private int[] srcAndRefPositions;

当AnalyzerAdapter::visitJmpAdadpter第一次访问它时,两个整数(例如10和11)被插入到数组的开头。然后在下一次迭代“MethodCallInliner::visitJmpInsn”中,在位置 2 和 3 处添加了另外两个新 int。现在数组内容是:

[10, 11, 16, 17, 0, 0] 其中(10,11)对用于AnalyzerAdapter,对(16,17)用于MethodMethodCallInliner.

但这里让我困惑的是:在生成字节码类(或块、堆栈帧计算等)时,ASM 应该能够为正确的 MethodVisitor 区分不同的对?

该代码可以通过以下方式访问https://github.com/xushijie/InlineMethod/tree/typeinference


当标签(类读取器从类文件中读取)被访问时会导致问题MethodVisitor管道。标签有一个字段int [] srcAndRefPositions。一旦 MethodVisitor 访问标签,它的两个连续位置(参见我原始帖子的末尾)就会更新。就我而言,标签中的ifeq label拥有 2 个 MethodVisitor。看来位置不正确srcAndRefPositions生成类文件时使用(使用最后一个MethodVisitor)。

我没有调查根本原因。相反,我的解决方案是克隆标签,然后在 MethodVisitor 访问新标签时使用新标签。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

内联方法体中的 invokevirtual 的意外指令和参数 的相关文章

  • 在 Byte Buddy 中缓存生成的类?

    我已经使用 CGLIB 的增强器有一段时间了 但正在考虑切换到 Byte Buddy 这是非常基本的东西 代理多达数百个数据访问接口 按需创建 Enhancer enhancer new Enhancer enhancer setClass
  • ilasm / ildasm 的 Java 字节码等效项

    对于 CIL MSIL 我可以在文本编辑器中编写代码并使用 ilasm ildasm 进行编译 反编译 我可以使用 Reflector 来查看 NET 类生成的 CIL 在Java世界中 javap c显示反汇编的字节代码 如何编译 Jav
  • 如何使用 ASM 4.0 修改 Java 字节码

    我是 ASM 框架的新手 我已经围绕这个 ASM 框架工作了一个星期 我在网上看到了有关解析类和从头开始生成 class 文件的教程 但我无法理解如何修改 ASM 中的现有类 我无法跟踪之间的执行流程ClassVisitor ClassWr
  • 只需添加方法参数即可实现 10% 以上的性能提升(更精简的 jit 代码)

    注意 正确的答案必须超越复制 经过数百万次调用后 快速排序 1 肯定比快速排序 2 更快 除了这 1 个额外参数之外 快速排序 2 具有相同的代码 代码在帖子末尾 剧透 我还发现 jit 代码增加了 224 个字节 即使它实际上应该更简单
  • 如何生成字节码并保存到.class文件?

    我有以下奇怪的要求 我得到 一些方法名称的列表 上述方法的参数名称和类型 上述方法的功能 如下 对于每个参数 该方法使用以下方法将其转换为字符串toString并获得一个字符串数组 对该数组 该方法应用一个函数foo 功能foo将 a 作为
  • 通过Java从.class文件中获取ByteCode(依赖)信息

    我想分析一下 class文件并获取有关哪个类使用哪个其他类的信息 jdeps是一个命令行工具 它允许您在控制台中显示一些信息 但我想避免调用外部工具并抓取命令行输出 所有依赖项都记录在类文件的中心位置 即常量池 因此 为了有效地处理所有依赖
  • 配置项目“:app”时出现问题。在react-native中并给出一些字节代码作为错误

    当我运行react native应用程序时创建项目后 它给了我这个错误 FAILURE Build failed with an exception What went wrong A problem occurred configurin
  • 我很好奇 ldc 在 JVM 中的缩写是什么?

    ByteCode ldc将一个单字常量压入操作数栈 ldc 采用单个参数 它是要推送的值 JVM中的大部分字节码都可以通过代码描述得知它们的名称 然而 ldc 我没有看到任何线索 它是负载常数 它将常量池中的一项加载到堆栈上 可用的类型有
  • Nodejs/V8 是否将编译后的机器代码存储在磁盘上的任何位置?

    Edit Node 从 Node 8 3 开始使用字节码 在此之前 源代码直接编译为机器代码 我进行了大量的 Python 编码 并且 pyc 文件中总是存在字节码 我想知道节点是否将其机器代码存储在类似的文件中 例如 将机器代码表示保留
  • asm编译器中的二进制表达式

    我正在尝试使用逻辑和符号 执行 if 语句 这是我想要做的 asm字节码中的 y堆栈具有值0和1 我想得到结果 在我们的例子中 逻辑与 不会出现在 if 语句中 我已经尝试过 Opcodes IFEQ 和 Opcodes IFNE 指令 但
  • 更改已编译类中的字符串常量

    我需要更改已部署的Java程序中的字符串常量 即编译后的值 class 文件 它可以重新启动 但不容易重新编译 尽管如果这个问题没有答案 这是一个不方便的选择 这可能吗 更新 我刚刚用十六进制编辑器查看了该文件 看起来我可以轻松更改那里的字
  • 如何以编程方式确定当前类的Java字节码版本? [复制]

    这个问题在这里已经有答案了 我遇到的情况是 部署平台是 Java 5 并且开发是在 Java 6 下使用 Eclipse 进行的 我们建立了一个在开始处理给定项目时创建新工作区的过程 因此 所需步骤之一是将编译器级别设置为 Java 5 这
  • 字节码指令和处理器操作之间的关系

    Java 规范保证原始变量赋值始终是原子的 除了long和双types 相反 获取并添加 http en wikipedia org wiki Fetch and add对应著名的操作i 增量操作将是非原子的 因为会导致读取 修改 写入操作
  • 使用 Java 代理将类添加到类路径

    我正在使用 Java Agent 和 Javassist 向某些 JDK 类添加一些日志记录 本质上 当系统加载一些 TLS 类时 Javassist 会向它们添加一些额外的字节码 以帮助我调试一些连接问题 考虑到此类包含在代理 jar 中
  • 将 JVM 字节码往返于文本表示的故障安全方法

    我正在寻找一种在 JVM 类文件和文本表示之间往返的故障安全方法 一项严格的要求是 只要文本表示形式保持不变 生成的往返 JVM 类文件在功能上与原始 JVM 类文件完全相同 此外 文本表示必须是人类可读和可编辑的 应该可以对文本表示进行小
  • Scala 特征如何编译成 Java 字节码?

    我已经使用 Scala 一段时间了 我知道特征可以充当 Scala 中接口和抽象类的等价物 Trait 究竟是如何编译成 Java 字节码的 我发现了一些简短的解释 说明特征在可能的情况下与 Java 接口完全一样地编译 否则与附加类的接口
  • 为什么 JVM 同时具有“invokespecial”和“invokestatic”操作码?

    两条指令都使用静态而不是动态调度 似乎唯一的实质性区别是invokespecial始终将一个对象作为其第一个参数 该对象是分派方法所属类的实例 然而 invokespecial实际上并没有把物体放在那里 编译器负责通过在发出之前发出适当的堆
  • 从内存中获取Java类字节码(经过多次转换)

    我正在为 Minecraft 开发一个 coremod 并在加载许多类时对其进行转换 然而问题是 有多个 coremod 也转换了与我相同的类 并且我遇到了一些我想研究的奇怪行为 那么问题来了 经过多次转换后的字节码如何检查呢 当我转换它时
  • 为什么 Python 在导入脚本时只保存脚本的字节码?

    既然执行Python字节码会比运行原始源代码更快 因为Python不需要重新编译 为什么Python在导入脚本时只保存编译后的字节码呢 为每个执行的脚本保存 pyc 文件不是更好吗 无论如何 Python 解释器的启动时间都需要时间 即使您
  • 如何读取Python字节码?

    我很难理解 Python 的字节码及其dis module import dis def func x 1 dis dis func 上述代码在解释器中输入时会产生以下输出 0 LOAD CONST 1 1 3 STORE FAST 0 x

随机推荐