避免与ASM的LocalVariablesSorter进行可变时隙冲突

时间:2014-11-22 03:27:25

标签: java instrumentation java-bytecode-asm

我很难看到来自ASM的LocalVariablesSorter如何能够防止可变时隙冲突的发生。变量可能来自原始源,或者我可能使用LocalVariablesSorter.newLocal(Type t)创建变量。稍后,访问VarInsn aise将进入这些插槽。从原始代码和我注入的代码。 LocalVariablesSorter如何区分他们。它们都具有相同的插槽索引,如何将其移动到正确的插槽?我也不会在现实生活中看到它。

以下是一个展示问题的程序。它通过注入局部变量来检测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

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

1 个答案:

答案 0 :(得分:-1)

好的,所以我想出了一个比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)您知道要注入多少局部变量,因此请使用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);
}

请记住,您自己注入的本地人不会通过您的MethodVisitor的visitXXX方法。您应该只在本地变量上调用super.XXXX。来自原始源的那些是唯一通过MethodVisitors方法的。

*这是我的parseSignature

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;
}