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
или делать простую математику, но я думаю, что картина теперь более ясна, и это зависит от вашего фактического варианта использования, от того, сколько вы хотите отслеживать или интерпретировать.