diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodInfo.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodInfo.java index 007254bae..8188d4d7d 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodInfo.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodInfo.java @@ -1,7 +1,14 @@ package com.taobao.arthas.bytekit.asm; +/** + * + * @author hengyunabc 2019-03-18 + * + */ public class MethodInfo { + private String owner; + private int access; private String name; private String desc; @@ -30,4 +37,12 @@ public class MethodInfo { this.desc = desc; } + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + } diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java index 33dd15926..18a49a9cb 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java @@ -80,7 +80,7 @@ public class MethodProcessor { private static final Method LONG_VALUE = Method.getMethod("long longValue()"); private static final Method DOUBLE_VALUE = Method.getMethod("double doubleValue()"); - + public static final String DEFAULT_INNER_VARIABLE_PREFIX = "_$bytekit$_"; private final LabelNode interceptorVariableStartLabelNode = new LabelNode(); @@ -90,12 +90,12 @@ public class MethodProcessor { // TODO 这里应该直接从 InsnList 里来取?因为插入代码之后,这个会改变的。 // TODO 这个没有被使用到,是不是没用的?? private AbstractInsnNode lastInsnNode; - + /** * 保留中间生成的 variable的名字 */ private boolean keepLocalVariableNames; - + private String innerVariablePrefix; private String returnVariableName; @@ -106,21 +106,21 @@ public class MethodProcessor { private LocalVariableNode throwVariableNode = null; private LocalVariableNode invokeArgsVariableNode = null; private LocalVariableNode monitorVariableNode = null; // for synchronized - + private String invokeReturnVariablePrefix; private Map invokeReturnVariableNodeMap = new HashMap(); - + private TryCatchBlock tryCatchBlock = null; public MethodProcessor(final ClassNode classNode, final MethodNode methodNode) { this(classNode, methodNode, false); } - + public MethodProcessor(final ClassNode classNode, final MethodNode methodNode, boolean keepLocalVariableNames) { this(classNode.name, methodNode, keepLocalVariableNames); this.classNode = classNode; } - + public MethodProcessor(final String owner, final MethodNode methodNode, boolean keepLocalVariableNames) { this.owner = owner; this.methodNode = methodNode; @@ -135,20 +135,20 @@ public class MethodProcessor { } else { this.enterInsnNode = methodNode.instructions.getFirst(); } - + // when the method is empty, both enterInsnNode and lastInsnNode are Opcodes.RETURN ; this.lastInsnNode = methodNode.instructions.getLast(); - + // setup interceptor variables start/end label. this.methodNode.instructions.insertBefore(this.enterInsnNode, this.interceptorVariableStartLabelNode); this.methodNode.instructions.insert(this.lastInsnNode, this.interceptorVariableEndLabelNode); - + initInnerVariablePrefix(); } public MethodProcessor(final String owner, final MethodNode methodNode) { this(owner, methodNode, false); } - + private void initInnerVariablePrefix() { String prefix = DEFAULT_INNER_VARIABLE_PREFIX; int count = 0; @@ -157,15 +157,15 @@ public class MethodProcessor { count++; } this.innerVariablePrefix = prefix; - + returnVariableName = innerVariablePrefix + "_return"; throwVariableName = innerVariablePrefix + "_throw"; invokeArgsVariableName = innerVariablePrefix + "_invokeArgs"; monitorVariableName = innerVariablePrefix + "_monitor"; - + invokeReturnVariablePrefix = innerVariablePrefix + "_invokeReturn_"; } - + private boolean existLocalVariableWithPrefix(String prefix) { for (LocalVariableNode variableNode : this.methodNode.localVariables) { if (variableNode.name.startsWith(prefix)) { @@ -196,16 +196,16 @@ public class MethodProcessor { } return invokeArgsVariableNode; } - + public LocalVariableNode initReturnVariableNode() { if (returnVariableNode == null) { returnVariableNode = this.addInterceptorLocalVariable(returnVariableName, returnType.getDescriptor()); } return returnVariableNode; } - + /** - * + * * @param name * @param type * @return @@ -219,7 +219,7 @@ public class MethodProcessor { } return variableNode; } - + public TryCatchBlock initTryCatchBlock() { return initTryCatchBlock(THROWABLE_TYPE.getInternalName()); } @@ -232,7 +232,7 @@ public class MethodProcessor { InsnList instructions = new InsnList(); AsmOpUtils.throwException(instructions); this.methodNode.instructions.insert(tryCatchBlock.getEndLabelNode(), instructions); - + tryCatchBlock.sort(); } return tryCatchBlock; @@ -649,12 +649,12 @@ public class MethodProcessor { * @param tmpToInlineMethodNode */ public void inline(String owner, MethodNode toInlineMethodNode) { - + ListIterator originMethodIter = this.methodNode.instructions.iterator(); - + while(originMethodIter.hasNext()) { AbstractInsnNode originMethodInsnNode = originMethodIter.next(); - + if (originMethodInsnNode instanceof MethodInsnNode) { MethodInsnNode methodInsnNode = (MethodInsnNode) originMethodInsnNode; if (methodInsnNode.owner.equals(owner) && methodInsnNode.name.equals(toInlineMethodNode.name) @@ -662,16 +662,16 @@ public class MethodProcessor { // 要copy一份,否则inline多次会出问题 MethodNode tmpToInlineMethodNode = AsmUtils.copy(toInlineMethodNode); tmpToInlineMethodNode = AsmUtils.removeLineNumbers(tmpToInlineMethodNode); - + LabelNode end = new LabelNode(); this.methodNode.instructions.insert(methodInsnNode, end); - + InsnList instructions = new InsnList(); - + // 要先记录好当前的 maxLocals ,然后再依次把 栈上的 args保存起来 ,后面调整 VarInsnNode index里,要加上当前的 maxLocals // save args to local vars int currentMaxLocals = this.nextLocals; - + int off = (tmpToInlineMethodNode.access & Opcodes.ACC_STATIC) != 0 ? 0 : 1; Type[] args = Type.getArgumentTypes(tmpToInlineMethodNode.desc); int argsOff = off; @@ -682,12 +682,12 @@ public class MethodProcessor { // 记录新的 maxLocals this.nextLocals += argsOff; methodNode.maxLocals = this.nextLocals; - + for(int i = args.length - 1; i >= 0; --i) { argsOff -= args[i].getSize(); // this.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), argsOff); - + AsmOpUtils.storeVar(instructions, args[i], currentMaxLocals + argsOff); } @@ -696,15 +696,15 @@ public class MethodProcessor { // this.visitVarInsn(Opcodes.ASTORE, 0); AsmOpUtils.storeVar(instructions, OBJECT_TYPE, currentMaxLocals); } - - - ListIterator inlineIterator = tmpToInlineMethodNode.instructions.iterator(); + + + ListIterator inlineIterator = tmpToInlineMethodNode.instructions.iterator(); while(inlineIterator.hasNext()) { AbstractInsnNode abstractInsnNode = inlineIterator.next(); if(abstractInsnNode instanceof FrameNode) { continue; } - + if(abstractInsnNode instanceof VarInsnNode) { VarInsnNode varInsnNode = (VarInsnNode) abstractInsnNode; varInsnNode.var += currentMaxLocals; @@ -716,12 +716,12 @@ public class MethodProcessor { inlineIterator.remove(); instructions.add(new JumpInsnNode(Opcodes.GOTO, end)); continue; - } + } inlineIterator.remove(); instructions.add(abstractInsnNode); } - - + + // 插入inline之后的代码,再删除掉原来的 MethodInsnNode this.methodNode.instructions.insertBefore(methodInsnNode, instructions); originMethodIter.remove(); @@ -735,7 +735,7 @@ public class MethodProcessor { } } - + public void sortTryCatchBlock() { if (this.methodNode.tryCatchBlocks == null) { return; @@ -762,5 +762,5 @@ public class MethodProcessor { this.methodNode.tryCatchBlocks.get(i).updateIndex(i); } } - + } diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/InstrumentImpl.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/InstrumentImpl.java new file mode 100644 index 000000000..8835d5350 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/InstrumentImpl.java @@ -0,0 +1,125 @@ +package com.taobao.arthas.bytekit.asm.inst.impl; + +import java.util.List; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.Method; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +/** + * + * @author hengyunabc 2019-03-15 + * + */ +public class InstrumentImpl { + + public static MethodNode replaceInvokeOrigin(String originOwner, MethodNode originMethodNode, + MethodNode apmMethodNode) { + + // 查找到所有的 InstrumentApi.invokeOrigin() 指令 + List methodInsnNodes = AsmUtils.findMethodInsnNode(apmMethodNode, + "com/taobao/arthas/bytekit/asm/inst/InstrumentApi", "invokeOrigin", "()Ljava/lang/Object;"); + + Type originReturnType = Type.getMethodType(originMethodNode.desc).getReturnType(); + + for (MethodInsnNode methodInsnNode : methodInsnNodes) { + InsnList instructions = new InsnList(); + + AbstractInsnNode secondInsnNode = methodInsnNode.getNext(); + + // 如果是 非 static ,则要 load this + boolean isStatic = AsmUtils.isStatic(originMethodNode); + int opcode = isStatic ? Opcodes.INVOKESTATIC : Opcodes.INVOKEVIRTUAL; + if (!isStatic) { + AsmOpUtils.loadThis(instructions); + } + AsmOpUtils.loadArgs(instructions, originMethodNode); + + MethodInsnNode originMethodInsnNode = new MethodInsnNode(opcode, originOwner, originMethodNode.name, + originMethodNode.desc, false); + // 调用原来的函数 + instructions.add(originMethodInsnNode); + + int sort = originReturnType.getSort(); + if (sort == Type.VOID) { + if (secondInsnNode != null) { + if (secondInsnNode.getOpcode() == Opcodes.POP) { + // TODO 原来的函数没有返回值,这里要把 POP去掉。有没有可能是 POP2 ? + apmMethodNode.instructions.remove(secondInsnNode); + } else { + // TODO 原来函数没有返回值,这里有没有可能要赋值??是否要 push null? + AsmOpUtils.pushNUll(instructions); + } + } + } else if (sort >= Type.BOOLEAN && sort <= Type.DOUBLE) { + if (secondInsnNode.getOpcode() == Opcodes.POP) { + // 原来是 pop掉一个栈,如果函数返回的是 long,则要pop2 + if (originReturnType.getSize() == 2) { + apmMethodNode.instructions.insert(secondInsnNode, new InsnNode(Opcodes.POP2)); + apmMethodNode.instructions.remove(secondInsnNode); + } + } else { + /** + * 需要把下面两条cast和unbox的指令删掉 + * + *
+                     * CHECKCAST java/lang/Integer
+                     * INVOKEVIRTUAL java/lang/Integer.intValue ()I
+                     * 
+ */ + boolean removeCheckCast = false; + if (secondInsnNode.getOpcode() == Opcodes.CHECKCAST) { + TypeInsnNode typeInsnNode = (TypeInsnNode) secondInsnNode; + // 从原始函数的返回值,获取到它对应的自动box的类 + Type boxedType = AsmOpUtils.getBoxedType(originReturnType); + if (Type.getObjectType(typeInsnNode.desc).equals(boxedType)) { + AbstractInsnNode thridInsnNode = secondInsnNode.getNext(); + if (thridInsnNode != null && thridInsnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode valueInsnNode = (MethodInsnNode) thridInsnNode; + Method unBoxMethod = AsmOpUtils.getUnBoxMethod(originReturnType); + if (unBoxMethod.getDescriptor().equals(valueInsnNode.desc) + && valueInsnNode.owner.equals(boxedType.getInternalName())) { + apmMethodNode.instructions.remove(thridInsnNode); + apmMethodNode.instructions.remove(secondInsnNode); + removeCheckCast = true; + } + } + } + } + if (!removeCheckCast) { + // 没有被转换为原始类型,也没有pop,则说明赋值给了一个对象,用类似Long.valudOf转换为Object + AsmOpUtils.box(instructions, originReturnType); + + } + + } + } else {// ARRAY/OBJECT + // 移掉可能有的 check cast + if (secondInsnNode.getOpcode() == Opcodes.CHECKCAST) { + TypeInsnNode typeInsnNode = (TypeInsnNode) secondInsnNode; + if (Type.getObjectType(typeInsnNode.desc).equals(originReturnType)) { + apmMethodNode.instructions.remove(secondInsnNode); + } + } + } + apmMethodNode.instructions.insertBefore(methodInsnNode, instructions); + apmMethodNode.instructions.remove(methodInsnNode); + } + + MethodProcessor methodProcessor = new MethodProcessor(originOwner, apmMethodNode); + methodProcessor.inline(originOwner, originMethodNode); + + return apmMethodNode; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/MethodReplaceResult.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/MethodReplaceResult.java new file mode 100644 index 000000000..dfd07fff7 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/MethodReplaceResult.java @@ -0,0 +1,52 @@ +package com.taobao.arthas.bytekit.asm.inst.impl; + +import org.objectweb.asm.Label; +import org.objectweb.asm.tree.MethodNode; + +/** + * + * @author hengyunabc 2019-03-18 + * + */ +public class MethodReplaceResult { + + private boolean success; + + private Label start; + private Label end; + + private MethodNode methodNode; + + public Label getStart() { + return start; + } + + public void setStart(Label start) { + this.start = start; + } + + public Label getEnd() { + return end; + } + + public void setEnd(Label end) { + this.end = end; + } + + public MethodNode getMethodNode() { + return methodNode; + } + + public void setMethodNode(MethodNode methodNode) { + this.methodNode = methodNode; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java index f339a0be5..ea609aea7 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java @@ -37,11 +37,11 @@ public class AsmOpUtils { private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double"); public static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); - + public static final Type OBJECT_ARRAY_TYPE = Type.getType(Object[].class); public static final Type STRING_TYPE = Type.getObjectType("java/lang/String"); - + public static final Type STRING_ARRAY_TYPE = Type.getType(String[].class); private static final Type NUMBER_TYPE = Type.getObjectType("java/lang/Number"); @@ -50,6 +50,10 @@ public class AsmOpUtils { private static final Method CHAR_VALUE = Method.getMethod("char charValue()"); + private static final Method BYTE_VALUE = Method.getMethod("byte byteValue()"); + + private static final Method SHORT_VALUE = Method.getMethod("short shortValue()"); + private static final Method INT_VALUE = Method.getMethod("int intValue()"); private static final Method FLOAT_VALUE = Method.getMethod("float floatValue()"); @@ -58,7 +62,7 @@ public class AsmOpUtils { private static final Method DOUBLE_VALUE = Method.getMethod("double doubleValue()"); - static Type getBoxedType(final Type type) { + public static Type getBoxedType(final Type type) { switch (type.getSort()) { case Type.BYTE: return BYTE_TYPE; @@ -80,6 +84,28 @@ public class AsmOpUtils { return type; } + public static Method getUnBoxMethod(final Type type) { + switch (type.getSort()) { + case Type.BYTE: + return BYTE_VALUE; + case Type.BOOLEAN: + return BOOLEAN_VALUE; + case Type.SHORT: + return SHORT_VALUE; + case Type.CHAR: + return CHAR_VALUE; + case Type.INT: + return INT_VALUE; + case Type.FLOAT: + return FLOAT_VALUE; + case Type.LONG: + return LONG_VALUE; + case Type.DOUBLE: + return DOUBLE_VALUE; + } + throw new IllegalArgumentException(type + " is not a primitive type."); + } + public static void newInstance(final InsnList instructions, final Type type) { instructions.add(new TypeInsnNode(Opcodes.NEW, type.getInternalName())); } @@ -103,7 +129,11 @@ public class AsmOpUtils { insnList.add(new LdcInsnNode(value)); } } - + + public static void pushNUll(InsnList insnList) { + insnList.add(new InsnNode(Opcodes.ACONST_NULL)); + } + /** * @see org.objectweb.asm.tree.LdcInsnNode#cst * @param value @@ -145,8 +175,8 @@ public class AsmOpUtils { public static void dup2X2(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.DUP2_X2)); } - - + + public static void pop(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.POP)); } @@ -157,14 +187,14 @@ public class AsmOpUtils { public static void pop2(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.POP2)); } - + public static void swap(final InsnList insnList) { insnList.add(new InsnNode(Opcodes.SWAP)); } - + /** * Generates the instructions to swap the top two stack values. - * + * * @param prev * type of the top - 1 stack value. * @param type @@ -226,6 +256,12 @@ public class AsmOpUtils { .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, owner, method.getName(), method.getDescriptor(), false)); } + /** + * + * @param instructions + * @param type + * @see org.objectweb.asm.commons.GeneratorAdapter#unbox(Type) + */ public static void unbox(final InsnList instructions, Type type) { Type t = NUMBER_TYPE; Method sig = null; @@ -262,7 +298,7 @@ public class AsmOpUtils { sig.getDescriptor(), false)); } } - + public static boolean needBox(Type type) { switch (type.getSort()) { case Type.BYTE: @@ -305,6 +341,16 @@ public class AsmOpUtils { instructions.add(new InsnNode(type.getOpcode(Opcodes.IALOAD))); } + + /** + * Generates the instruction to load 'this' on the stack. + * @see org.objectweb.asm.commons.GeneratorAdapter#loadThis() + * @param instructions + */ + public static void loadThis(final InsnList instructions) { + instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); + } + /** * Generates the instructions to load all the method arguments on the stack, * as a single object array. @@ -354,10 +400,10 @@ public class AsmOpUtils { public static void storeVar(final InsnList instructions, Type type, final int index) { instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ISTORE), index)); } - + /** * Generates a type dependent instruction. - * + * * @param opcode * the instruction's opcode. * @param type @@ -366,11 +412,11 @@ public class AsmOpUtils { private static void typeInsn(final InsnList instructions, final int opcode, final Type type) { instructions.add(new TypeInsnNode(opcode, type.getInternalName())); } - + /** * Generates the instruction to check that the top stack value is of the * given type. - * + * * @param type * a class or interface type. */ @@ -379,15 +425,15 @@ public class AsmOpUtils { typeInsn(instructions, Opcodes.CHECKCAST, type); } } - + public static void throwException(final InsnList instructions) { instructions.add(new InsnNode(Opcodes.ATHROW)); } - + public static boolean isReturnCode(final int opcode) { return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; } - + public static List validVariables(List localVariables, AbstractInsnNode currentInsnNode) { List results = new ArrayList(); diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java index 7daed4df1..25296dba3 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java @@ -220,6 +220,22 @@ public class AsmUtils { return result; } + public static List findMethodInsnNode(MethodNode methodNode, String owner, String name, + String desc) { + List result = new ArrayList(); + for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof MethodInsnNode) { + final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.owner.equals(owner) && methodInsnNode.name.equals(name) + && methodInsnNode.desc.equals(desc)) { + result.add(methodInsnNode); + } + } + } + return result; + } + public static boolean isStatic(MethodNode methodNode) { return (methodNode.access & Opcodes.ACC_STATIC) != 0; } diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java index e22ecc14c..9aa44c08c 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java @@ -2,6 +2,7 @@ package com.taobao.arthas.bytekit.utils; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; @@ -11,44 +12,58 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Type; import org.objectweb.asm.util.CheckClassAdapter; +/** + * + * @author hengyunabc + * + */ public class VerifyUtils { - public static void asmVerify(byte[] bytes) throws IOException { - ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); - ClassReader cr = new ClassReader(inputStream); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - ClassVisitor cv = new CheckClassAdapter(cw); + public static void asmVerify(byte[] bytes) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + ClassReader cr = new ClassReader(inputStream); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + ClassVisitor cv = new CheckClassAdapter(cw); - cr.accept(cv, 0); - } + cr.accept(cv, 0); + } - public static Object instanceVerity(byte[] bytes) throws Exception { - String name = Type.getObjectType(AsmUtils.toClassNode(bytes).name).getClassName(); - - URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + public static Object instanceVerity(byte[] bytes) throws Exception { + String name = Type.getObjectType(AsmUtils.toClassNode(bytes).name).getClassName(); - @SuppressWarnings("resource") - ClassbyteClassLoader cl = new ClassbyteClassLoader(systemClassLoader.getURLs(), - ClassLoader.getSystemClassLoader().getParent()); + URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); - cl.addClass(name, bytes); + @SuppressWarnings("resource") + ClassbyteClassLoader cl = new ClassbyteClassLoader(systemClassLoader.getURLs(), + ClassLoader.getSystemClassLoader().getParent()); - Class loadClass = cl.loadClass(name); - return loadClass.newInstance(); + cl.addClass(name, bytes); - } + Class loadClass = cl.loadClass(name); + return loadClass.newInstance(); + } - public static class ClassbyteClassLoader extends URLClassLoader { - public ClassbyteClassLoader(URL[] urls, ClassLoader cl) { - super(urls, cl); - } + public static Object invoke(Object instance, String name, Object... args) throws Exception { + Method[] methods = instance.getClass().getMethods(); + for (Method method : methods) { + if (name.contentEquals(method.getName())) { + return method.invoke(instance, args); + } + } + throw new NoSuchMethodError("name: " + name); + } - public Class addClass(String name, byte[] bytes) throws ClassFormatError { - Class cl = defineClass(name, bytes, 0, bytes.length); - resolveClass(cl); + public static class ClassbyteClassLoader extends URLClassLoader { + public ClassbyteClassLoader(URL[] urls, ClassLoader cl) { + super(urls, cl); + } - return cl; - } - } + public Class addClass(String name, byte[] bytes) throws ClassFormatError { + Class cl = defineClass(name, bytes, 0, bytes.length); + resolveClass(cl); + + return cl; + } + } } diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java index 672ed14fa..2daf425f5 100644 --- a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java @@ -1,21 +1,18 @@ package com.taobao.arthas.bytekit.asm.inst; -import java.util.Date; - public class InstDemo { - public int returnInt(int i) { - return 9998; - } - - public static int returnIntStatic(int i) { - return 9998; - } + public int returnInt(int i) { + System.out.println(new Object[] { i }); + return 9998; + } - public static void main(String[] args) { - Date date = new Date(1551168643); + public static void onEnter(Object[] args) { + System.out.println(args); + } - System.err.println(date.toLocaleString()); + public static int returnIntStatic(int i) { + return 9998; } } diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java index cdfb5f79d..041d26ea0 100644 --- a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java @@ -1,5 +1,77 @@ package com.taobao.arthas.bytekit.asm.inst; +import java.util.Date; + +/** + * @author hengyunabc 2019-03-13 + * + */ public class InvokeOriginDemo { + public void returnVoid() { + } + + public Void returnVoidObject() { + int i = 0; + try { + int parseInt = Integer.parseInt("1000"); + i += parseInt; + } catch (Exception e) { + System.err.println(i + " " + e); + } + + return null; + } + + public int returnInt(int i) { + return 9998; + } + + public int returnIntToObject(int i) { + + return 9998; + } + + public int returnIntToInteger(int i) { + + return 9998; + } + + public static int returnIntStatic(int i) { + return 9998; + } + + public long returnLong() { + return 9998L; + } + + public long returnLongToObject() { + return 9998L; + } + + public String[] returnStrArray() { + String[] result = new String[] {"abc", "xyz" , "ufo"}; + return result; + } + + public String[] returnStrArrayWithArgs(int i, String s, long l) { + String[] result = new String[] {"abc" + i, "xyz" + s , "ufo" + l}; + return result; + } + + public String returnStr() { + return new Date().toString(); + } + + public Object returnObject() { + return InvokeOriginDemo.class; + } + + + public int recursive(int i) { + if (i == 1) { + return 1; + } + return i + recursive(i - 1); + } } diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java index 0b15a7713..a5d41a27d 100644 --- a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java @@ -1,5 +1,85 @@ package com.taobao.arthas.bytekit.asm.inst; +/** + * + * @author hengyunabc 2019-03-18 + * + */ public class InvokeOriginDemo_APM { + public void returnVoid() { + Object o = InstrumentApi.invokeOrigin(); + System.out.println(o); + } + + public Void returnVoidObject() { + Void v = InstrumentApi.invokeOrigin(); + System.out.println(v); + return v; + } + + public int returnInt(int i) { + System.out.println("before"); + int value = InstrumentApi.invokeOrigin(); + System.out.println("after"); + return value + 123; + } + + public int returnIntToObject(int i) { + Object value = InstrumentApi.invokeOrigin(); + return 9998 + (Integer) value; + } + + public int returnIntToInteger(int i) { + + Integer ixx = InstrumentApi.invokeOrigin(); + + return ixx + 9998; + } + + public static int returnIntStatic(int i) { + int result = InstrumentApi.invokeOrigin(); + return 9998 + result; + } + + public long returnLong() { + long result = InstrumentApi.invokeOrigin(); + return 9998L + result; + } + + public long returnLongToObject() { + Long lll = InstrumentApi.invokeOrigin(); + return 9998L + lll; + } + + public String[] returnStrArray() { + String[] result = InstrumentApi.invokeOrigin(); + System.err.println(result); + return result; + } + + public String[] returnStrArrayWithArgs(int i, String s, long l) { + System.out.println(i); + String[] result = InstrumentApi.invokeOrigin(); + result[0] = "fff"; + return result; + } + + public String returnStr() { + System.err.println("ssss"); + Object result = InstrumentApi.invokeOrigin(); + return "hello" + result; + } + + public Object returnObject() { + InstrumentApi.invokeOrigin(); + return InvokeOriginDemo.class; + } + + public int recursive(int i) { + int result = InstrumentApi.invokeOrigin(); + + System.err.println(result); + return result; + } } diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java new file mode 100644 index 000000000..bdf68ba08 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java @@ -0,0 +1,178 @@ +package com.taobao.arthas.bytekit.asm.inst; + +import java.io.IOException; + +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import com.taobao.arthas.bytekit.asm.inst.impl.InstrumentImpl; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +/** + * + * @author hengyunabc 2019-03-18 + * + */ +public class InvokeOriginTest { + + ClassNode apmClassNode; + ClassNode originClassNode; + + ClassNode targetClassNode; + + @Rule + public TestName testName = new TestName(); + + @BeforeClass + public static void beforeClass() throws IOException { + + } + + @Before + public void before() throws IOException { + apmClassNode = AsmUtils.loadClass(InvokeOriginDemo_APM.class); + originClassNode = AsmUtils.loadClass(InvokeOriginDemo.class); + + byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode), + Type.getObjectType(originClassNode.name).getClassName()); + + apmClassNode = AsmUtils.toClassNode(renameClass); + + targetClassNode = AsmUtils.copy(originClassNode); + } + + private Object replace(String methodName) throws Exception { + System.err.println(methodName); + for (MethodNode methodNode : apmClassNode.methods) { + if (methodNode.name.equals(methodName)) { + methodNode = AsmUtils.removeLineNumbers(methodNode); + // 从原来的类里查找对应的函数 + MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode); + if (findMethod != null) { + MethodNode methodNode2 = InstrumentImpl.replaceInvokeOrigin(originClassNode.name, findMethod, + methodNode); + + System.err.println(Decompiler.toString(methodNode2)); + + AsmUtils.replaceMethod(targetClassNode, methodNode2); + + } else { + + } + } + } + + byte[] resutlBytes = AsmUtils.toBytes(targetClassNode); + + System.err.println("================="); + + System.err.println(Decompiler.decompile(resutlBytes)); + + // System.err.println(AsmUtils.toASMCode(resutlBytes)); + + VerifyUtils.asmVerify(resutlBytes); + return VerifyUtils.instanceVerity(resutlBytes); + } + + @Test + public void test_returnVoid() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null); + } + + @Test + public void test_returnVoidObject() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(null); + } + + @Test + public void test_returnInt() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 123); + } + + @Test + public void test_returnIntToObject() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998); + } + + @Test + public void test_returnIntToInteger() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998); + } + + @Test + public void test_returnIntStatic() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123)).isEqualTo(9998 + 9998); + } + + @Test + public void test_returnLong() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998); + } + + @Test + public void test_returnLongToObject() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(9998L + 9998); + } + + @Test + public void test_returnStrArray() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(new String[] { "abc", "xyz", "ufo" }); + } + + @Test + public void test_returnStrArrayWithArgs() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName, 123, "sss", 777L)) + .isEqualTo(new Object[] { "fff", "xyz" + "sss", "ufo" + 777 }); + } + + @Test + public void test_returnStr() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).asString().startsWith("hello"); + } + + @Test + public void test_returnObject() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName)).isEqualTo(object.getClass()); + } + + @Test + public void test_recursive() throws Exception { + String methodName = testName.getMethodName().substring("test_".length()); + Object object = replace(methodName); + Assertions.assertThat(VerifyUtils.invoke(object, methodName, 100)).isEqualTo((100 + 1) * 100 / 2); + } +}