#yyds干货盘点#Java ASM系列((094)查找相关的指令)

【#yyds干货盘点#Java ASM系列((094)查找相关的指令)】博观而约取,厚积而薄发。这篇文章主要讲述#yyds干货盘点#Java ASM系列:(094)查找相关的指令相关的知识,希望能为你提供帮助。
本文属于Java ASM系列三:Tree API当中的一篇。
在StackOverflow上有这样一个问题:Find all instructions belonging to a specific method-call。在解决方法当中,就用到了SourceInterpreterSourceValue类。在本文当中,我们将这个问题和解决思路简单地进行介绍。
1. 实现思路 1.1. 回顾SourceInterpreter
首先,我们来回顾一下SourceInterpreter的作用:记录指令(instruction)与Frame当中值(SourceValue)的关联关系。

public class HelloWorld { public void test(int a, int b) { int c = a + b; System.out.println(c); } }

那么,借助于SourceInterpreter类查看test方法内某一条instuction将某一个SourceValue加载(入栈)到Frame上:
test:(II)V 000:iload_1{[], [], [], []} | {} 001:iload_2{[], [], [], []} | {[iload_1]} 002:iadd{[], [], [], []} | {[iload_1], [iload_2]} 003:istore_3{[], [], [], []} | {[iadd]} 004:getstatic System.out{[], [], [], [istore_3]} | {} 005:iload_3{[], [], [], [istore_3]} | {[getstatic System.out]} 006:invokevirtual PrintStream.println{[], [], [], [istore_3]} | {[getstatic System.out], [iload_3]} 007:return{[], [], [], [istore_3]} | {} ================================================================

由此,我们可以模仿一下,进一步记录某一条instuction将某一个SourceValue从Frame上消耗掉(出栈):
test:(II)V 000:iload_1{[], [], [], []} | {} 001:iload_2{[], [], [], []} | {[iadd]} 002:iadd{[], [], [], []} | {[iadd], [iadd]} 003:istore_3{[], [], [], []} | {[istore_3]} 004:getstatic System.out{[], [], [], []} | {} 005:iload_3{[], [], [], []} | {[invokevirtual PrintStream.println]} 006:invokevirtual PrintStream.println{[], [], [], []} | {[invokevirtual PrintStream.println], [invokevirtual PrintStream.println]} 007:return{[], [], [], []} | {} ================================================================

那么,我们怎么实现这样一个功能呢?用一个DestinationInterpreter类来实现。
1.2. DestinationInterpreter
首先,我们编写一个DestinationInterpreter类。对于这个类,我们从两点来把握:
  • 第一点,抽象功能。要实现什么的功能呢?记录operand stack上某一个元素是被哪一个指令(instruction)消耗掉(出栈)的。这个功能正好与SourceInterpreter类的功能相反。
  • 第二点,具体实现。如何进行编码实现呢?DestinationInterpreter类,是模仿着SourceInterpreter类实现的。
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.Interpreter; import org.objectweb.asm.tree.analysis.SourceValue; import java.util.HashSet; import java.util.List; public class DestinationInterpreter extends Interpreter< SourceValue> implements Opcodes { public DestinationInterpreter() { super(ASM9); if (getClass() != DestinationInterpreter.class) { throw new IllegalStateException(); } }protected DestinationInterpreter(final int api) { super(api); }@Override public SourceValue newValue(Type type) { if (type == Type.VOID_TYPE) { return null; } return new SourceValue(type == null ? 1 : type.getSize(), new HashSet< > ()); }@Override public SourceValue newOperation(AbstractInsnNode insn) { int size; switch (insn.getOpcode()) { case LCONST_0: case LCONST_1: case DCONST_0: case DCONST_1: size = 2; break; case LDC: Object value = https://www.songbingjia.com/android/((LdcInsnNode) insn).cst; size = value instanceof Long || value instanceof Double ? 2 : 1; break; case GETSTATIC: size = Type.getType(((FieldInsnNode) insn).desc).getSize(); break; default: size = 1; break; } return new SourceValue(size, new HashSet< > ()); }@Override public SourceValue copyOperation(AbstractInsnNode insn, SourceValue value) throws AnalyzerException { int opcode = insn.getOpcode(); if (opcode > = ISTORE & & opcode < = ASTORE) { value.insns.add(insn); }return new SourceValue(value.getSize(), new HashSet< > ()); }@Override public SourceValue unaryOperation(AbstractInsnNode insn, SourceValue value) throws AnalyzerException { value.insns.add(insn); int size; switch (insn.getOpcode()) { case LNEG: case DNEG: case I2L: case I2D: case L2D: case F2L: case F2D: case D2L: size = 2; break; case GETFIELD: size = Type.getType(((FieldInsnNode) insn).desc).getSize(); break; default: size = 1; break; } return new SourceValue(size, new HashSet< > ()); }@Override public SourceValue binaryOperation(AbstractInsnNode insn, SourceValue value1, SourceValue value2) throws AnalyzerException { value1.insns.add(insn); value2.insns.add(insn); int size; switch (insn.getOpcode()) { case LALOAD: case DALOAD: case LADD: case DADD: case LSUB: case DSUB: case LMUL: case DMUL: case LDIV: case DDIV: case LREM: case DREM: case LSHL: case LSHR: case LUSHR: case LAND: case LOR: case LXOR: size = 2; break; default: size = 1; break; } return new SourceValue(size, new HashSet< > ()); }@Override public SourceValue ternaryOperation(AbstractInsnNode insn, SourceValue value1, SourceValue value2, SourceValue value3) throws AnalyzerException { value1.insns.add(insn); value2.insns.add(insn); value3.insns.add(insn); return new SourceValue(1, new HashSet< > ()); }@Override public SourceValue naryOperation(AbstractInsnNode insn, List< ? extends SourceValue> values) throws AnalyzerException { if (values != null) { for (SourceValue v : values) { v.insns.add(insn); } }int size; int opcode = insn.getOpcode(); if (opcode == MULTIANEWARRAY) { size = 1; } else if (opcode == INVOKEDYNAMIC) { size = Type.getReturnType(((InvokeDynamicInsnNode) insn).desc).getSize(); } else { size = Type.getReturnType(((MethodInsnNode) insn).desc).getSize(); } return new SourceValue(size, new HashSet< > ()); }@Override public void returnOperation(AbstractInsnNode insn, SourceValue value, SourceValue expected) throws AnalyzerException { // Nothing to do. }@Override public SourceValue merge(final SourceValue value1, final SourceValue value2) { return new SourceValue(Math.min(value1.size, value2.size), new HashSet< > ()); } }

2. 示例:查找相关的指令 2.1. 预期目标
假如有一个HelloWorld类:
public class HelloWorld { public void test() { int a = 1; int b = 2; int c = 3; int d = 4; int sum = add(a, b); int diff = sub(c, d); int result = mul(sum, diff); System.out.println(result); }public int add(int a, int b) { return a + b; }public int sub(int a, int b) { return a - b; }public int mul(int a, int b) { return a * b; } }

我们对test方法进行分析,想实现的预期目标:如果想删除对某一个方法的调用,有哪些指令会变得无效呢?
例如,想删除add(a, b)方法调用,直接使用int sum = 10; ,那么ab两个变量就不需要了:
public class HelloWorld { public void test() { int c = 3; int d = 4; int sum = 10; int diff = sub(c, d); int result = mul(sum, diff); System.out.println(result); }// ... }

2.2. 编码实现
import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import org.objectweb.asm.tree.analysis.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class RelatedInstructionDiagnosis { public static int[] diagnose(String className, MethodNode mn, int insnIndex) throws AnalyzerException { // 第一步,判断insnIndex范围是否合理 InsnList instructions = mn.instructions; int size = instructions.size(); if (insnIndex < 0 || insnIndex > = size) { String message = String.format("the insnIndex argument should in range [0, %d]", size - 1); throw new IllegalArgumentException(message); }// 第二步,获取两个Frame Frame< SourceValue> [] sourceFrames = getSourceFrames(className, mn); Frame< SourceValue> [] destinationFrames = getDestinationFrames(className, mn); // 第三步,循环处理,所有结果记录到这个intArrayList变量中 TIntArrayList intArrayList = new TIntArrayList(); // 循环tmpInsnList List< AbstractInsnNode> tmpInsnList = new ArrayList< > (); AbstractInsnNode insnNode = instructions.get(insnIndex); tmpInsnList.add(insnNode); for (int i = 0; i < tmpInsnList.size(); i++) { AbstractInsnNode currentNode = tmpInsnList.get(i); int opcode = currentNode.getOpcode(); int index = instructions.indexOf(currentNode); intArrayList.add(index); // 第一种情况,处理load相关的opcode情况 Frame< SourceValue> srcFrame = sourceFrames[index]; if (opcode > = Opcodes.ILOAD & & opcode < = Opcodes.ALOAD) { VarInsnNode varInsnNode = (VarInsnNode) currentNode; int localIndex = varInsnNode.var; SourceValue value = https://www.songbingjia.com/android/srcFrame.getLocal(localIndex); for (AbstractInsnNode insn : value.insns) { if (!tmpInsnList.contains(insn)) { tmpInsnList.add(insn); } } }// 第二种情况,从dstFrame到srcFrame查找 Frame< SourceValue> dstFrame = destinationFrames[index]; int stackSize = dstFrame.getStackSize(); for (int j = 0; j < stackSize; j++) { SourceValue value = dstFrame.getStack(j); if (value.insns.contains(currentNode)) { for (AbstractInsnNode insn : srcFrame.getStack(j).insns) { if (!tmpInsnList.contains(insn)) { tmpInsnList.add(insn); } } } } }// 第四步,将intArrayList变量转换成int[],并进行排序 int[] array = intArrayList.toNativeArray(); Arrays.sort(array); return array; }private static Frame< SourceValue> [] getSourceFrames(String className, MethodNode mn) throws AnalyzerException { Analyzer< SourceValue> analyzer = new Analyzer< > (new SourceInterpreter()); return analyzer.analyze(className, mn); }private static Frame< SourceValue> [] getDestinationFrames(String className, MethodNode mn) throws AnalyzerException { Analyzer< SourceValue> analyzer = new Analyzer< > (new DestinationInterpreter()); return analyzer.analyze(className, mn); } }

2.3. 进行分析
public class HelloWorldAnalysisTree { public static void main(String[] args) throws Exception { String relative_path = "sample/HelloWorld.class"; String filepath = FileUtils.getFilePath(relative_path); byte[] bytes = FileUtils.readBytes(filepath); //(1)构建ClassReader ClassReader cr = new ClassReader(bytes); //(2)生成ClassNode int api = Opcodes.ASM9; ClassNode cn = new ClassNode(api); int parsingOptions = ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; cr.accept(cn, parsingOptions); //(3)进行分析 List< MethodNode> methods = cn.methods; MethodNode mn = methods.get(1); int[] array = RelatedInstructionDiagnosis.diagnose(cn.name, mn, 11); System.out.println(Arrays.toString(array)); BoxDrawingUtils.printInstructionLinks(mn.instructions, array); } }

输出结果:
[0, 1, 2, 3, 8, 9, 10, 11] ┌──── 000: iconst_1 ├──── 001: istore_1 ├──── 002: iconst_2 ├──── 003: istore_2 │004: iconst_3 │005: istore_3 │006: iconst_4 │007: istore 4 ├──── 008: aload_0 ├──── 009: iload_1 ├──── 010: iload_2 └──── 011: invokevirtual HelloWorld.add 012: istore 5 013: aload_0 014: iload_3 015: iload 4 016: invokevirtual HelloWorld.sub 017: istore 6 018: aload_0 019: iload 5 020: iload 6 021: invokevirtual HelloWorld.mul 022: istore 7 023: getstatic System.out 024: iload 7 025: invokevirtual PrintStream.println 026: return

3. 测试用例 3.1. switch-yes
public class HelloWorld { public void test(int val) { double doubleValue = https://www.songbingjia.com/android/Math.random(); switch (val) { case 10: System.out.println("val = 10"); break; case 20: System.out.println("val = 20"); break; case 30: System.out.println("val = 30"); break; case 40: System.out.println("val = 40"); break; default: System.out.println("val is unknown"); } System.out.println(doubleValue); // 分析这条语句 } }

输出结果:
[0, 1, 29, 30, 31] ┌──── 000: invokestatic Math.random ├──── 001: dstore_2 │002: iload_1 │003: lookupswitch { │10: L0 │20: L1 │30: L2 │40: L3 │default: L4 │} │004: L0 │005: getstatic System.out │006: ldc "val = 10" │007: invokevirtual PrintStream.println │008: goto L5 │009: L1 │010: getstatic System.out │011: ldc "val = 20" │012: invokevirtual PrintStream.println │013: goto L5 │014: L2 │015: getstatic System.out │016: ldc "val = 30" │017: invokevirtual PrintStream.println │018: goto L5 │019: L3 │020: getstatic System.out │021: ldc "val = 40" │022: invokevirtual PrintStream.println │023: goto L5 │024: L4 │025: getstatic System.out │026: ldc "val is unknown" │027: invokevirtual PrintStream.println │028: L5 ├──── 029: getstatic System.out ├──── 030: dload_2 └──── 031: invokevirtual PrintStream.println 032: return

3.2. switch-no
在当前的解决思路中,还不能很好的处理创建对象(new)的情况。
public class HelloWorld { public void test(int val) { Random rand = new Random(); // 注意,这里创建了Random对象 double doubleValue = https://www.songbingjia.com/android/rand.nextDouble(); switch (val) { case 10: System.out.println("val = 10"); break; case 20: System.out.println("val = 20"); break; case 30: System.out.println("val = 30"); break; case 40: System.out.println("val = 40"); break; default: System.out.println("val is unknown"); } System.out.println(doubleValue); // 分析这条语句 } }

输出结果:
[0, 3, 4, 5, 6, 34, 35, 36] ┌──── 000: new Random// new + dup + invokespecial三个指令一起来创建对象 │001: dup// 当前的方法只分析出了new,而没有分析出dup和invokespecial │002: invokespecial Random.< init> ├──── 003: astore_2 ├──── 004: aload_2 ├──── 005: invokevirtual Random.nextDouble ├──── 006: dstore_3 │007: iload_1 │008: lookupswitch { │10: L0 │20: L1 │30: L2 │40: L3 │default: L4 │} │009: L0 │010: getstatic System.out │011: ldc "val = 10" │012: invokevirtual PrintStream.println │013: goto L5 │014: L1 │015: getstatic System.out │016: ldc "val = 20" │017: invokevirtual PrintStream.println │018: goto L5 │019: L2 │020: getstatic System.out │021: ldc "val = 30" │022: invokevirtual PrintStream.println │023: goto L5 │024: L3 │025: getstatic System.out │026: ldc "val = 40" │027: invokevirtual PrintStream.println │028: goto L5 │029: L4 │030: getstatic System.out │031: ldc "val is unknown" │032: invokevirtual PrintStream.println │033: L5 ├──── 034: getstatic System.out ├──── 035: dload_3 └──── 036: invokevirtual PrintStream.println 037: return

4. 总结本文内容总结如下:
  • 第一点,模拟SourceInterpreter类来编写一个DestinationInterpreter类,这两个类的作用是相反的。
  • 第二点,结合SourceInterpreterDestinationInterpreter类,用来查找相关的指令。

    推荐阅读