ASM Получить точное значение из стекового фрейма

У меня есть какой-то метод, который содержит вложения вроде ILOAD, и я хочу каким-то образом получить значение стека после этой инструкции. Не просто тип, а точное значение. Я знаю, что для этого нужно эмулировать выполнение метода, но я не знаю, как это сделать правильно.
У меня такой метод для теста называется main:

sipush          15649
istore_0        /* c */
getstatic       java/lang/System.out:Ljava/io/PrintStream;
bipush          45
bipush          11
iload_0         /* c */
...

Я хочу получить значение, загруженное iload_0, Я попытался сделать Analyzer и затем увидеть значения фрейма, но они содержат только тип значений, а не то, что я хочу.

ClassReader cr = new ClassReader(new FileInputStream(new File("input.class")));
ClassNode cn = new ClassNode(Opcodes.ASM5);
cr.accept(cn, 0);

Iterator<MethodNode> methods = cn.methods.iterator();
while (methods.hasNext()) {
    MethodNode mn = methods.next();
    if (!mn.name.equals("main")) continue;
    AbstractInsnNode[] nodes = mn.instructions.toArray();
    Analyzer analyzer = new Analyzer(new BasicInterpreter());
    analyzer.analyze(cn.name, mn);
    int i = -1;
    for (Frame frame : analyzer.getFrames()) {
        i++;
        if (frame == null) continue;
        if (nodes[i].getOpcode() != Opcodes.ILOAD) continue;
        System.out.print(frame.getStack(0) + "|" + frame.getStack(1));
        System.out.print(" - " + nodes[i].getOpcode() + "\n");
    }
}

Это показывает мне результат: R|I - 21Как получить значение как 15649? Я пытался гуглить это часами и не могу найти ничего полезного. Заранее спасибо.

1 ответ

Решение

Ваш код почти полностью игнорирует преимущества Java 5. Когда вы обновите его, вы получите

for(MethodNode mn: cn.methods) {
    if(!mn.name.equals("main")) continue;
    Analyzer<BasicValue> analyzer = new Analyzer<>(new BasicInterpreter());
    analyzer.analyze(cn.name, mn);
    int i = -1;
    for (Frame<BasicValue> frame: analyzer.getFrames()) {
        i++;
        if(frame == null) continue;
        int opcode = mn.instructions.get(i).getOpcode();
        if(opcode != Opcodes.ILOAD) continue;
        BasicValue stackValue = frame.getStack(0);
        System.out.print(stackValue + "|" + frame.getStack(1));
        System.out.print(" - " + opcode + "\n");
    }
}

и вы сразу можете увидеть, что вы получаете BasicValue, который подходит для проверки кода или вычисления кадров стековой карты, но не для получения фактических значений.

Это собственность переводчика, здесь BasicInterpreter, только для поддержания BasicValue с (отсюда и название). Альтернативой является SourceInterpreter который позволяет отслеживать, из каких инструкций может исходить значение, которое будет istore_0 в вашем случае, но это все еще не дает вам фактическое значение.

Поэтому, если вы хотите получить фактическое значение (если оно предсказуемо), вам нужен собственный интерпретатор. Довольно простым, только значения отслеживания, которые действительно происходят от нажатия константы:

import static org.objectweb.asm.Opcodes.*;
import java.util.List;
import java.util.Objects;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.objectweb.asm.tree.analysis.*;

public class ConstantTracker extends Interpreter<ConstantTracker.ConstantValue> {
    static final ConstantValue NULL = new ConstantValue(BasicValue.REFERENCE_VALUE, null);
    public static final class ConstantValue implements Value {
        final Object value; // null if unknown or NULL
        final BasicValue type;
        ConstantValue(BasicValue type, Object value) {
            this.value = value;
            this.type = Objects.requireNonNull(type);
        }
        @Override public int getSize() { return type.getSize(); }
        @Override public String toString() {
            Type t = type.getType();
            if(t == null) return "uninitialized";
            String typeName = type==BasicValue.REFERENCE_VALUE? "a reference type": t.getClassName();
            return this == NULL? "null":
                value == null? "unknown value of "+typeName: value+" ("+typeName+")";
        }
        @Override
        public boolean equals(Object obj) {
            if(this == obj) return true;
            if(this == NULL || obj == NULL || !(obj instanceof ConstantValue))
                return false;
            ConstantValue that = (ConstantValue)obj;
            return Objects.equals(this.value, that.value)
                && Objects.equals(this.type, that.type);
        }
        @Override
        public int hashCode() {
            if(this == NULL) return ~0;
            return (value==null? 7: value.hashCode())+type.hashCode()*31;
        }
    }

    BasicInterpreter basic = new BasicInterpreter() {
        @Override public BasicValue newValue(Type type) {
            return type!=null && (type.getSort()==Type.OBJECT || type.getSort()==Type.ARRAY)?
                   new BasicValue(type): super.newValue(type);
        }
        @Override public BasicValue merge(BasicValue a, BasicValue b) {
            if(a.equals(b)) return a;
            if(a.isReference() && b.isReference())
                // this is the place to consider the actual type hierarchy if you want
                return BasicValue.REFERENCE_VALUE;
            return BasicValue.UNINITIALIZED_VALUE;
        }
    };

    public ConstantTracker() {
        super(ASM5);
    }

    @Override
    public ConstantValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
        switch(insn.getOpcode()) {
            case ACONST_NULL: return NULL;
            case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2:
            case ICONST_3: case ICONST_4: case ICONST_5:
                return new ConstantValue(BasicValue.INT_VALUE, insn.getOpcode()-ICONST_0);
            case LCONST_0: case LCONST_1:
                return new ConstantValue(BasicValue.LONG_VALUE, (long)(insn.getOpcode()-LCONST_0));
            case FCONST_0: case FCONST_1: case FCONST_2:
                return new ConstantValue(BasicValue.FLOAT_VALUE, (float)(insn.getOpcode()-FCONST_0));
            case DCONST_0: case DCONST_1:
                return new ConstantValue(BasicValue.DOUBLE_VALUE, (double)(insn.getOpcode()-DCONST_0));
            case BIPUSH: case SIPUSH:
                return new ConstantValue(BasicValue.INT_VALUE, ((IntInsnNode)insn).operand);
            case LDC:
                return new ConstantValue(basic.newOperation(insn), ((LdcInsnNode)insn).cst);
            default:
                BasicValue v = basic.newOperation(insn);
                return v == null? null: new ConstantValue(v, null);
        }
    }

    @Override
    public ConstantValue copyOperation(AbstractInsnNode insn, ConstantValue value) {
        return value;
    }

    @Override
    public ConstantValue newValue(Type type) {
        BasicValue v = basic.newValue(type);
        return v == null? null: new ConstantValue(v, null);
    }

    @Override
    public ConstantValue unaryOperation(AbstractInsnNode insn, ConstantValue value) throws AnalyzerException {
        BasicValue v = basic.unaryOperation(insn, value.type);
        return v == null? null: new ConstantValue(v, insn.getOpcode()==CHECKCAST? value.value: null);
    }

    @Override
    public ConstantValue binaryOperation(AbstractInsnNode insn, ConstantValue a, ConstantValue b) throws AnalyzerException {
        BasicValue v = basic.binaryOperation(insn, a.type, b.type);
        return v == null? null: new ConstantValue(v, null);
    }

    @Override
    public ConstantValue ternaryOperation(AbstractInsnNode insn, ConstantValue a, ConstantValue b, ConstantValue c) {
        return null;
    }

    @Override
    public ConstantValue naryOperation(AbstractInsnNode insn, List<? extends ConstantValue> values) throws AnalyzerException {
        List<BasicValue> unusedByBasicInterpreter = null;
        BasicValue v = basic.naryOperation(insn, unusedByBasicInterpreter);
        return v == null? null: new ConstantValue(v, null);
    }

    @Override
    public void returnOperation(AbstractInsnNode insn, ConstantValue value, ConstantValue expected) {}

    @Override
    public ConstantValue merge(ConstantValue a, ConstantValue b) {
        if(a == b) return a;
        BasicValue t = basic.merge(a.type, b.type);
        return t.equals(a.type) && (a.value==null&&a!=NULL || a.value.equals(b.value))? a:
               t.equals(b.type) &&  b.value==null&&b!=NULL? b: new ConstantValue(t, null);
    }
}

тогда вы можете использовать его как

private static void analyze() throws IOException, AnalyzerException {
    ClassReader cr = new ClassReader(new FileInputStream(new File("input.class")));
    ClassNode cn = new ClassNode(Opcodes.ASM5);
    cr.accept(cn, 0);

    for(MethodNode mn: cn.methods) {
        if(!mn.name.equals("main")) continue;
        Analyzer<ConstantTracker.ConstantValue> analyzer
                = new Analyzer<>(new ConstantTracker());
        analyzer.analyze(cn.name, mn);
        int i = -1;
        for(Frame<ConstantTracker.ConstantValue> frame: analyzer.getFrames()) {
            i++;
            if(frame == null) continue;
            AbstractInsnNode n = mn.instructions.get(i);
            if(n.getOpcode() != Opcodes.ILOAD) continue;
            VarInsnNode vn = (VarInsnNode)n;
            System.out.println("accessing variable # "+vn.var);
            ConstantTracker.ConstantValue var = frame.getLocal(vn.var);
            System.out.println("\tcontains "+var);
        }
    }
}

Это работает со всеми инструкциями загрузки не только ILOAD т.е. ALOAD , LLOAD , FLOAD , а также DLOAD

Конечно, интерпретатор имеет много возможностей для улучшений, например, для отслеживания тривиальных преобразований, таких как приведение int константы для short или же byte или делать простую математику, но я думаю, что картина теперь более ясна, и это зависит от вашего фактического варианта использования, от того, сколько вы хотите отслеживать или интерпретировать.

Другие вопросы по тегам