java - 使用 ASM 的 LocalVariablesSorter 避免变量槽冲突

标签 java instrumentation java-bytecode-asm

我很难看出 ASM 的 LocalVariablesSorter 如何能够防止变量槽冲突的发生。变量可能来自原始源,或者我可能使用 LocalVariablesSorter.newLocal(Type t) 创建变量。稍后,visitVarInsn aise 将加入这些时段。既来自原始代码,也来 self 注入(inject)的代码。 LocalVariablesSorter 如何区分它们。它们都具有相同的槽索引,如何将其移动到正确的槽?我在现实生活中也没有看到这种情况发生。

下面是一个显示该问题的程序。它通过注入(inject)局部变量来检测 Sample.sample 方法,并在方法的开头和结尾使用它。在中间,原始源代码提供了自己的变量。正如您所看到的,ASM 为它们提供了相同的槽号,这是错误的。这就是 LocalVariablesSorter 应该停止的全部要点,但坦率地说,我不明白它是如何做到这一点的。

这是示例:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.util.CheckMethodAdapter;


public class LVSBug {

    public static void main(String[] args) throws IOException {

        try (InputStream is = new BufferedInputStream(LVSBug.class.getResourceAsStream("/Sample.class"))) {
            ClassReader cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
            ClassVisitor cv = new LVCInjector(cw);
            cr.accept(cv, ClassReader.EXPAND_FRAMES);

            try (OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(System.getProperty("user.home"), "Sample.class")))) {
                os.write(cw.toByteArray());
            }

        }
    }
}

class LVCInjector extends ClassVisitor {

    public LVCInjector(ClassWriter cw) {
        super(Opcodes.ASM5, cw);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (name.equals("sample")) {
            CheckMethodAdapter checker = new CheckMethodAdapter(mv);
            return new LVMInjector(checker, access, desc);
        } else {
            return mv;
        }
    }
}

class LVMInjector extends LocalVariablesSorter {

    private int injectedReg;

    public LVMInjector(MethodVisitor mv, int access, String desc) {
        super(Opcodes.ASM5, access, desc, mv);
    }

    @Override
    public void visitCode() {
        super.visitCode();

        injectedReg = newLocal(Type.INT_TYPE);
        super.visitLdcInsn(Integer.valueOf(1));
        super.visitVarInsn(Opcodes.ISTORE, injectedReg);
    }

    @Override
    public void visitInsn(int opcode) {
        if (opcode == Opcodes.IRETURN) {
            super.visitInsn(Opcodes.POP);
            super.visitVarInsn(Opcodes.ILOAD, injectedReg);
        }

        super.visitInsn(opcode);
    }
}

class Sample {
    public static int sample(String s1) {
        Sample s = new Sample();
        return 0;
    }
} 

这是检测后 Sample.sample 的 javap 输出:

  public static int sample(java.lang.String);
    descriptor: (Ljava/lang/String;)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #22                 // int 1
         2: istore_2
         3: new           #1                  // class Sample
         6: dup
         7: invokespecial #16                 // Method "<init>":()V
        10: astore_2
        11: iconst_0
        12: pop
        13: iload_2
        14: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3      12     0    s1   Ljava/lang/String;
           11       4     2     s   LSample;
      LineNumberTable:
        line 85: 3
        line 86: 11

请注意,我注入(inject)的变量获得了插槽 2,但现有变量也被赋予了插槽 2,这是完全错误的。

最佳答案

好的,所以我想出了一个比 LocalvariablesSorter 假装的更简单的解决方案。我相信这个类只是在概念上被破坏了。但以下是如何轻松地将局部变量添加到现有方法中的方法。

1) 找出最后一个参数的槽号。您可以通过查看方法签名来做到这一点。

    int register = ((access & Opcodes.ACC_STATIC) != 0) ? 0 : 1;
    lastParmReg = register - 1;
    List<String> sigs = parseSignature(desc); // my own method (see below*)
    for (String sig : sigs) {
        lastParmReg = register;
        register += ("J".equals(sig) || "D".equals(sig)) ? 2 : 1;
    }

    int myNewInjectedReg = register;
    register += isMyNewRegADoubleOrLong() ? 2 : 1;
    ....
    ....

2) 您知道要注入(inject)多少个局部变量,因此请使用紧邻 lastParmReg 上方的插槽作为局部变量。

3) 在 MethodVisitor 中,重写 VisitVarInsn、visitLocalVariable 和 VisitLocalVariableAnnotation 方法并执行以下操作

if the specified register slot is less than or equal to lastParmReq, just 
call the super method passing the slot number as is.

if it's larger than lastParmReg, then add the number of local variables you 
are injecting to this value, and pass it along to super.

这是一个 VisitVarInsn 的示例

@Override
public void visitVarInsn(int opcode, int var) {
    super.visitVarInsn(opcode, (var <= lastParmReg) ? var : var + numInjectedRegs);
}

请记住,您自己注入(inject)的本地变量不会通过 MethodVisitor 的 VisitXXX 方法。您应该只对局部变量调用 super.XXXX。来自原始来源的那些是唯一通过您的 MethodVisitors 方法的。

*这是我的解析签名

private static Pattern PARM_PATTERN = Pattern.compile("(\\[*(?:[ZCBSIJFD]|(?:L[^;]+;)))");

private static List<String> parseSignature(String signature) {
    List<String> parms = new ArrayList<>();

    int openParenPos = signature.indexOf('(');
    int closeParenPos = signature.indexOf(')', openParenPos+1);

    String args = signature.substring(openParenPos + 1, closeParenPos);
    if (!args.isEmpty()) {
        Matcher m = PARM_PATTERN.matcher(args);
        while (m.find()) {
            parms.add(m.group(1));
        }
    }

    return parms;
}

关于java - 使用 ASM 的 LocalVariablesSorter 避免变量槽冲突,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27073721/

相关文章:

java - 如何创建一个 ASM LdcInsnNode 将当前类静态添加到堆栈中?

java - String 不断调用 NullPointerException,不确定它从哪里获取

java - 使用 JERSEY 设置重复的 REST 服务

java - 我如何使用 AspectJ 访问私有(private)字段?

java - 在 Java 中使用 ASM 监控对象创建

java - ASM 4.1访问LdcInsn常量池中的非法类型

java - 寻找使用 JUnit 和 Ant 的开源 Java 项目

从检测方法调用自己的类时出现 Java NoClassDefFoundError

maven - 如何从 Java (Spring boot) 应用程序向 Prometheus 公开指标

java - 如何使用 ASM 更改静态变量值?