Agent agentmain+ASM記錄方法執行時引數資訊
阿新 • • 發佈:2018-11-28
agentmain提供了執行時修改指定程序中位元組碼的能力,配合ASM框架,達到隨時修改位元組碼的效果。
agentmain方法:
public static void agentmain(String agentArgs, Instrumentation inst){ System.out.println("Agent Main called"); inst.addTransformer(new TimeTransformer(),true); inst.addTransformer(new LogTransformer(),true); Class[] cs =inst.getAllLoadedClasses(); if (cs!=null){ for (Class c: cs) { if (c.isInterface() ||c.isAnnotation() ||c.isArray() ||c.isEnum()){ continue; } if (c.getName().contains(paths)) { try { System.out.println(c.getName()); inst.retransformClasses(c); } catch (UnmodifiableClassException e) { System.err.println(c.getName()); e.printStackTrace(); } } } } }
啟動方法,用於連線指定程序,Main是這裡測試目標的程序名,如果是tomcat專案,那麼名字可能是Bootstrap,具體名字使用jps工具檢視。
public static void main( String[] args ) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vmd : list) { if (vmd.displayName().endsWith("Main")) { VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); virtualMachine.loadAgent("D:\\專案\\bytecode\\target\\bytecode-1.0-SNAPSHOT.jar"); System.out.println("ok"); virtualMachine.detach(); } } }
轉換器LogTransformer :
public class LogTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { LogAdapter logAdapter=new LogAdapter(); return logAdapter.addLog(classfileBuffer); } }
實際作用類:
這裡使用asm的treeApi修改類。引數資訊直接列印在控制檯的,事實上,可以把它輸出到任意位置。
package com.runtime.bytecode.adapter;
import com.runtime.bytecode.UdAgent;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import static java.lang.System.out;
/**
* Created by lenovo on 2018/11/20.
*/
public class LogAdapter implements Opcodes {
public static boolean debug=false;
public byte[] addLog(byte[] sourceCodes) {
ClassReader cr = new ClassReader(sourceCodes);
ClassNode cn = new ClassNode();
cr.accept(cn, ClassReader.EXPAND_FRAMES);
int access = cn.access;
if (debug)
out.println(cn.name);
if ((access & ACC_INTERFACE) == 0 && cn.name.replace('/','.').contains(UdAgent.paths)) {//不處理介面
if (debug)
out.println(cn.name+"-進入if條件,即將處理methods物件");
//不修改介面
cn.methods.forEach(mn -> {
if ((mn.access & ACC_ABSTRACT) == 0 && !mn.name.equals("<init>") && !mn.name.equals("<clinit>")) {
//不修改抽象方法,靜態初始化方法或者無參構造方法
if (mn.instructions != null && mn.instructions.size() > 0) {
if (debug){
out.println("方法"+mn.name+"指令數為:"+mn.instructions.size());
}
String methodDescriptor = mn.desc;//方法描述符
boolean isStaticMethod = (mn.access & ACC_STATIC) != 0;
//方法本身有指令。不修改空方法
AbstractInsnNode insnNode = mn.instructions.getFirst();
int argumentsSize=0;
//methodnodes尚未發現新增區域性變數的方法,因為新增區域性變數需要遍歷修改後續所有的位元組碼指令
while (insnNode != null) {
int opcode = insnNode.getOpcode();
if ((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) {
InsnList insnList = new InsnList();
insnList.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"));
insnList.add(new TypeInsnNode(NEW, "java/lang/StringBuilder"));
insnList.add(new InsnNode(DUP));
insnList.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false));
int currentOffset = 1;
int currentSlot = 1;
if (isStaticMethod) {
currentSlot = 0;//靜態方法沒有this
}
char currentChar;
for (currentChar = methodDescriptor.charAt(currentOffset); currentChar != 41; currentChar = methodDescriptor.charAt(currentOffset)) {
//判斷當前引數是什麼型別
switch (currentChar) {
case 68:
insnList.add(new VarInsnNode(DLOAD, currentSlot));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(D)Ljava/lang/StringBuilder;", false));
currentSlot += 2;
currentOffset+=1;
break;
case 74:
insnList.add(new VarInsnNode(LLOAD, currentSlot));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false));
currentSlot += 2;
currentOffset+=1;
break;
case 91://陣列型別
case 76://引用型別
while (methodDescriptor.charAt(currentOffset) == 91) {
++currentOffset;
}
if(methodDescriptor.charAt(currentOffset++) == 76) {//以L開頭
while(methodDescriptor.charAt(currentOffset++) != 59) {
}
}
insnList.add(new VarInsnNode(ALOAD, currentSlot));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
currentSlot+=1;
break;
case 65:
case 66:
case 73:
insnList.add(new VarInsnNode(ILOAD, currentSlot));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false));
currentSlot+=1;
currentOffset+=1;
break;
case 70:
insnList.add(new VarInsnNode(FLOAD, currentSlot));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(F)Ljava/lang/StringBuilder;", false));
currentSlot+=1;
currentOffset+=1;
break;
}
++argumentsSize;
insnList.add(new LdcInsnNode(" | "));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false));
}
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false));
insnList.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false));
if (argumentsSize>0) {
mn.instructions.insertBefore(insnNode, insnList);
}
if (debug){
out.println("方法"+mn.name+"指令數為:"+mn.instructions.size()+",成功新增指令");
}
}
insnNode=insnNode.getNext();
}
if (argumentsSize>0)
mn.maxStack+=4;
}
}
});
}
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
}