本文屬于Java ASM系列三:Tree API當(dāng)中的一篇。
1. 如何反編譯方法參數(shù)
1.1. 提出問題
我們在學(xué)習(xí)Java的過程中,多多少少都會用到Java Decompiler工具,它可以將具體的.class
文件轉(zhuǎn)換成相應(yīng)的Java代碼。
假如有一個HelloWorld
類:
public class HelloWorld { public void test(int a, int b) { int sum = Math.addExact(a, b); int diff = Math.subtractExact(a, b); int result = Math.multiplyExact(sum, diff); System.out.println(result); }}
上面的HelloWorld.java
經(jīng)過編譯之后會生成HelloWorld.class
文件,然后可以查看其包含的instruction內(nèi)容:
$ javap -v sample.HelloWorld Compiled from "HelloWorld.java"public class sample.HelloWorld{... public void test(int, int); descriptor: (II)V flags: ACC_PUBLIC Code: stack=2, locals=6, args_size=3 0: iload_1 1: iload_2 2: invokestatic #2 // Method java/lang/Math.addExact:(II)I 5: istore_3 6: iload_1 7: iload_2 8: invokestatic #3 // Method java/lang/Math.subtractExact:(II)I 11: istore 4 13: iload_3 14: iload 4 16: invokestatic #4 // Method java/lang/Math.multiplyExact:(II)I 19: istore 5 21: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 24: iload 5 26: invokevirtual #6 // Method java/io/PrintStream.println:(I)V 29: return LocalVariableTable: Start Length Slot Name Signature 0 30 0 this Lsample/HelloWorld; 0 30 1 a I 0 30 2 b I 6 24 3 sum I 13 17 4 diff I 21 9 5 result I}
那么,我們能不能利用Java ASM幫助我們做一些反編譯的工作呢?
1.2. 整體思路
我們的整體思路就是,結(jié)合SourceInterpreter
類和LocalVariableTable
來對invoke(方法調(diào)用)相關(guān)的指令進行反編譯。
使用SourceInterpreter
類輸出Frame變化信息:
test:(II)V000: iload_1 {[], [], [], [], [], []} | {}001: iload_2 {[], [], [], [], [], []} | {[iload_1]}002: invokestatic Math.addExact {[], [], [], [], [], []} | {[iload_1], [iload_2]}003: istore_3 {[], [], [], [], [], []} | {[invokestatic Math.addExact]}004: iload_1 {[], [], [], [istore_3], [], []} | {}005: iload_2 {[], [], [], [istore_3], [], []} | {[iload_1]}006: invokestatic Math.subtractExact {[], [], [], [istore_3], [], []} | {[iload_1], [iload_2]}007: istore 4 {[], [], [], [istore_3], [], []} | {[invokestatic Math.subtractExact]}008: iload_3 {[], [], [], [istore_3], [istore 4], []} | {}009: iload 4 {[], [], [], [istore_3], [istore 4], []} | {[iload_3]}010: invokestatic Math.multiplyExact {[], [], [], [istore_3], [istore 4], []} | {[iload_3], [iload 4]}011: istore 5 {[], [], [], [istore_3], [istore 4], []} | {[invokestatic Math.multiplyExact]}012: getstatic System.out {[], [], [], [istore_3], [istore 4], [istore 5]} | {}013: iload 5 {[], [], [], [istore_3], [istore 4], [istore 5]} | {[getstatic System.out]}014: invokevirtual PrintStream.println {[], [], [], [istore_3], [istore 4], [istore 5]} | {[getstatic System.out], [iload 5]}015: return {[], [], [], [istore_3], [istore 4], [istore 5]} | {}================================================================
2. 示例:方法參數(shù)反編譯
2.1. 預(yù)期目標
我們想對HelloWorld.class
中的test
方法內(nèi)的invoke相關(guān)的instruction進行反編譯。
public class HelloWorld { public void test(int a, int b) { int sum = Math.addExact(a, b); int diff = Math.subtractExact(a, b); int result = Math.multiplyExact(sum, diff); System.out.println(result); }}
預(yù)期目標:將方法調(diào)用的參數(shù)進行反編譯。
例如,將下面的instructions反編譯成Math.addExact(a, b)
。
0: iload_11: iload_22: invokestatic #2 // Method java/lang/Math.addExact:(II)I
2.2. 編碼實現(xiàn)
import org.objectweb.asm.Type;import org.objectweb.asm.tree.*;import org.objectweb.asm.tree.analysis.*;import java.util.ArrayList;import java.util.List;public class ReverseEngineerMethodArgumentsDiagnosis { private static final String UNKNOWN_VARIABLE_NAME = "unknown"; public static void diagnose(String className, MethodNode mn) throws AnalyzerException { // 第一步,獲取Frame信息 Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); Frame[] frames = analyzer.analyze(className, mn); // 第二步,獲取LocalVariableTable信息 List localVariables = mn.localVariables; if (localVariables == null || localVariables.size() < 1) { System.out.println("LocalVariableTable is Empty"); return; } // 第三步,獲取instructions,并找到與invoke相關(guān)的指令 InsnList instructions = mn.instructions; int[] methodInsnArray = findMethodInvokes(instructions); // 第四步,對invoke相關(guān)的指令進行反編譯 for (int methodInsn : methodInsnArray) { // (1) 獲取方法的參數(shù) MethodInsnNode methodInsnNode = (MethodInsnNode) instructions.get(methodInsn); Type methodType = Type.getMethodType(methodInsnNode.desc); Type[] argumentTypes = methodType.getArgumentTypes(); int argNum = argumentTypes.length; // (2) 從Frame當(dāng)中獲取指令,并將指令轉(zhuǎn)換LocalVariableTable當(dāng)中的變量名 Frame f = frames[methodInsn]; int stackSize = f.getStackSize(); List argList = new ArrayList<>(); for (int i = 0; i < argNum; i++) { int stackIndex = stackSize - argNum + i; SourceValue stackValue = f.getStack(stackIndex); AbstractInsnNode insn = stackValue.insns.iterator().next(); String argName = getMethodVariableName(insn, localVariables); argList.add(argName); } // (3) 將反編譯的結(jié)果打印出來 String line = String.format("%s.%s(%s)", methodInsnNode.owner, methodInsnNode.name, argList); System.out.println(line); } } public static String getMethodVariableName(AbstractInsnNode insn, List localVariables) { if (insn instanceof VarInsnNode) { VarInsnNode varInsnNode = (VarInsnNode) insn; int localIndex = varInsnNode.var; for (LocalVariableNode node : localVariables) { if (node.index == localIndex) { return node.name; } } return String.format("locals[%d]", localIndex); } return UNKNOWN_VARIABLE_NAME; } public static int[] findMethodInvokes(InsnList instructions) { int size = instructions.size(); boolean[] methodArray = new boolean[size]; for (int i = 0; i < size; i++) { AbstractInsnNode node = instructions.get(i); if (node instanceof MethodInsnNode) { methodArray[i] = true; } } int count = 0; for (boolean flag : methodArray) { if (flag) { count++; } } int[] array = new int[count]; int j = 0; for (int i = 0; i < size; i++) { boolean flag = methodArray[i]; if (flag) { array[j] = i; j++; } } return array; }}
2.3. 進行分析
在HelloWorldAnalysisTree
類當(dāng)中,要注意:不能使用ClassReader.SKIP_DEBUG
,因為我們要使用到MethodNode.localVariables
字段的信息。
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)構(gòu)建ClassReader ClassReader cr = new ClassReader(bytes); //(2)生成ClassNode int api = Opcodes.ASM9; ClassNode cn = new ClassNode(api); int parsingOptions = ClassReader.SKIP_FRAMES; cr.accept(cn, parsingOptions); //(3)進行分析 String className = cn.name; List methods = cn.methods; MethodNode mn = methods.get(1); ReverseEngineerMethodArgumentsDiagnosis.diagnose(className, mn); }}
輸出結(jié)果:
java/lang/Math.addExact([a, b])java/lang/Math.subtractExact([a, b])java/lang/Math.multiplyExact([sum, diff])java/io/PrintStream.println([result])
3. 總結(jié)
本文內(nèi)容總結(jié)如下:
- 第一點,整體的思路,是利用
SourceInterpreter
類和LocalVariableTable來實現(xiàn)的。 - 第二點,代碼示例。如何編碼實現(xiàn)對于方法的參數(shù)進行反編譯。