diff --git a/agent/src/main/java/com/taobao/arthas/agent3/AgentBootstrap.java b/agent/src/main/java/com/taobao/arthas/agent3/AgentBootstrap.java index ad83b32ab..9cc1903de 100755 --- a/agent/src/main/java/com/taobao/arthas/agent3/AgentBootstrap.java +++ b/agent/src/main/java/com/taobao/arthas/agent3/AgentBootstrap.java @@ -1,6 +1,5 @@ package com.taobao.arthas.agent3; -import java.arthas.Spy; import java.io.File; import java.io.FileOutputStream; import java.io.PrintStream; @@ -19,7 +18,6 @@ import com.taobao.arthas.agent.ArthasClassloader; * @author vlinux on 15/5/19. */ public class AgentBootstrap { - private static final String RESET = "resetArthasClassLoader"; private static final String ARTHAS_SPY_JAR = "arthas-spy.jar"; private static final String ARTHAS_CORE_JAR = "arthas-core.jar"; private static final String ARTHAS_BOOTSTRAP = "com.taobao.arthas.core.server.ArthasBootstrap"; @@ -79,7 +77,7 @@ public class AgentBootstrap { Class spyClass = null; if (parent != null) { try { - spyClass = parent.loadClass("java.arthas.Spy"); + spyClass =parent.loadClass("java.arthas.SpyAPI"); } catch (Throwable e) { // ignore } @@ -99,10 +97,6 @@ public class AgentBootstrap { return arthasClassLoader; } - private static void initSpy() throws NoSuchMethodException { - Spy.AGENT_RESET_METHOD = AgentBootstrap.class.getMethod(RESET); - } - private static synchronized void main(String args, final Instrumentation inst) { try { ps.println("Arthas server agent start..."); @@ -155,7 +149,6 @@ public class AgentBootstrap { * Use a dedicated thread to run the binding logic to prevent possible memory leak. #195 */ final ClassLoader agentLoader = getClassLoader(inst, spyJarFile, arthasCoreJarFile); - initSpy(); Thread bindingThread = new Thread() { @Override diff --git a/bytekit/pom.xml b/bytekit/pom.xml new file mode 100644 index 000000000..0b7bd94d1 --- /dev/null +++ b/bytekit/pom.xml @@ -0,0 +1,96 @@ + + + 4.0.0 + + com.taobao.arthas + arthas-all + 3.2.1-SNAPSHOT + ../pom.xml + + arthas-bytekit + arthas-bytekit + + + + com.taobao.arthas + arthas-common + ${project.version} + + + + com.alibaba.arthas + arthas-repackage-asm + + + + org.benf + cfr + provided + true + + + + net.bytebuddy + byte-buddy + 1.7.10 + provided + true + + + + net.bytebuddy + byte-buddy-agent + 1.7.10 + provided + true + + + + + junit + junit + test + + + + org.assertj + assertj-core + test + true + + + + org.springframework.boot + spring-boot-starter-test + 1.5.9.RELEASE + test + true + + + com.taobao.arthas + arthas-demo + ${project.version} + test + + + + + + arthas-bytekit + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + UTF-8 + true + + + + + + diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/ByteKit.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/ByteKit.java new file mode 100644 index 000000000..281ce7431 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/ByteKit.java @@ -0,0 +1,22 @@ +package com.taobao.arthas.bytekit; + +import java.util.List; + +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorClassParser; +import com.taobao.arthas.bytekit.asm.matcher.ClassMatcher; +import com.taobao.arthas.bytekit.asm.matcher.MethodMatcher; + +public class ByteKit { + + + private ClassMatcher classMatcher; + private MethodMatcher methodMatcher; + + private Class interceptorClass; + + private InterceptorClassParser interceptorClassParser; + + private List interceptorProcessors; +} + diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/InliningAdapter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/InliningAdapter.java new file mode 100644 index 000000000..bf34a3520 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/InliningAdapter.java @@ -0,0 +1,86 @@ +package com.taobao.arthas.bytekit.asm; + +import com.alibaba.arthas.deps.org.objectweb.asm.Label; +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.LocalVariablesSorter; + +/** + * Adapter for to be inlined code. + * + * This adapter does all parameter renaming and replacing of the RETURN opcodes + * + * + */ +public class InliningAdapter extends LocalVariablesSorter { + private final Label end; + private LocalVariablesSorter lvs; + + public InliningAdapter(LocalVariablesSorter mv, int access, String desc, Label end) { + super(Opcodes.ASM8, access, desc, mv); + this.end = end; + this.lvs = mv; + +// int off = (access & Opcodes.ACC_STATIC) != 0 ? +// 0 : 1; +// Type[] args = Type.getArgumentTypes(desc); +// for (int i = args.length - 1; i >= 0; i--) { +// super.visitVarInsn(args[i].getOpcode( +// Opcodes.ISTORE), i + off); +// } +// if (off > 0) { +// super.visitVarInsn(Opcodes.ASTORE, 0); +// } + + // save args to local vars + int off = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1; + Type[] args = Type.getArgumentTypes(desc); + int argsOff = off; + + for(int i = 0; i < args.length; ++i) { + argsOff += args[i].getSize(); + } + + for(int i = args.length - 1; i >= 0; --i) { + argsOff -= args[i].getSize(); + this.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), argsOff); + } + + // this + if (off > 0) { + this.visitVarInsn(Opcodes.ASTORE, 0); + } + } + + @Override + public void visitInsn(int opcode) { + if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) { + super.visitJumpInsn(Opcodes.GOTO, end); + } else { + super.visitInsn(opcode); + } + } + + @Override + public void visitMaxs(int stack, int locals) { +// super.visitMaxs(stack, locals); + } + + @Override + protected int newLocalMapping(Type type) { + return lvs.newLocal(type); + } + + @Override + public void visitVarInsn(int opcode, int var) { + super.visitVarInsn(opcode, var + this.firstLocal); + } + @Override + public void visitIincInsn(int var, int increment) { + super.visitIincInsn(var + this.firstLocal, increment); + } + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + super.visitLocalVariable(name, desc, signature, start, end, index + this.firstLocal); + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodCallInliner.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodCallInliner.java new file mode 100644 index 000000000..f001a2784 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodCallInliner.java @@ -0,0 +1,97 @@ +package com.taobao.arthas.bytekit.asm; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Label; +import com.alibaba.arthas.deps.org.objectweb.asm.MethodVisitor; +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.GeneratorAdapter; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; + +/** + * @author hengyunabc 2018-01-31 + * + */ +public abstract class MethodCallInliner extends GeneratorAdapter { + public class CatchBlock { + + private Label start; + private Label handler; + private String type; + private Label end; + + public CatchBlock(Label start, Label end, Label handler, String type) { + this.start = start; + this.end = end; + this.handler = handler; + this.type = type; + } + + } + + private final MethodNode toBeInlined; + private List blocks = new ArrayList(); + private boolean inlining; + private boolean afterInlining; + + public MethodCallInliner(int access, String name, String desc, MethodVisitor mv, + MethodNode toBeInlined) { + super(Opcodes.ASM8, mv, access, name, desc); + this.toBeInlined = toBeInlined; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if (!shouldBeInlined(owner, name, desc)) { + mv.visitMethodInsn(opcode, owner, name, desc, itf); + return; + } + + // if (this.analyzerAdapter != null) { + // mv = new MergeFrameAdapter(this.api, this.analyzerAdapter, + // (MethodVisitor)mv); + // } + + Label end = new Label(); + inlining = true; + toBeInlined.instructions.resetLabels(); + + // pass the to be inlined method through the inlining adapter to this + toBeInlined.accept(new InliningAdapter(this, toBeInlined.access, toBeInlined.desc, end)); + inlining = false; + afterInlining = true; + + // visit the end label + super.visitLabel(end); + + // box the return value if necessary + // Type returnType = + // Type.getMethodType(toBeInlined.desc).getReturnType(); + // valueOf(returnType); + + } + + abstract boolean shouldBeInlined(String owner, String name, String desc); + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (!inlining) { + blocks.add(new CatchBlock(start, end, handler, type)); + } else { + super.visitTryCatchBlock(start, end, handler, type); + } + } + + @Override + public void visitMaxs(int stack, int locals) { + for (CatchBlock b : blocks) + super.visitTryCatchBlock(b.start, b.end, b.handler, b.type); + super.visitMaxs(stack, locals); + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + // swallow + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..8188d4d7d --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodInfo.java @@ -0,0 +1,48 @@ +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; + + public int getAccess() { + return access; + } + + public void setAccess(int access) { + this.access = access; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + 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 new file mode 100644 index 000000000..a0c7c8aa8 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java @@ -0,0 +1,778 @@ +package com.taobao.arthas.bytekit.asm; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.Method; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FrameNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.IntInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LdcInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.TryCatchBlockNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.VarInsnNode; +import com.taobao.arthas.bytekit.asm.location.filter.DefaultLocationFilter; +import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +public class MethodProcessor { + + private String owner; + /** + * maybe null + */ + private ClassNode classNode; + private MethodNode methodNode; + + private final Type[] argumentTypes; + private final Type returnType; + + private int nextLocals; + + private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte"); + + private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean"); + + private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short"); + + private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character"); + + private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer"); + + private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float"); + + private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long"); + + private static final Type DOUBLE_TYPE = Type.getObjectType("java/lang/Double"); + + private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object"); + + private static final Type STRING_TYPE = Type.getObjectType("java/lang/String"); + + private static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable"); + + private static final Type NUMBER_TYPE = Type.getObjectType("java/lang/Number"); + + private static final Type OBJECT_ARRAY_TYPE = Type.getType(Object[].class); + + private static final Method BOOLEAN_VALUE = Method.getMethod("boolean booleanValue()"); + + private static final Method CHAR_VALUE = Method.getMethod("char charValue()"); + + private static final Method INT_VALUE = Method.getMethod("int intValue()"); + + private static final Method FLOAT_VALUE = Method.getMethod("float floatValue()"); + + 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(); + private final LabelNode interceptorVariableEndLabelNode = new LabelNode(); + + private AbstractInsnNode enterInsnNode; + // TODO 这里应该直接从 InsnList 里来取?因为插入代码之后,这个会改变的。 + // TODO 这个没有被使用到,是不是没用的?? + private AbstractInsnNode lastInsnNode; + + /** + * 保留中间生成的 variable的名字 + */ + private boolean keepLocalVariableNames; + + private String innerVariablePrefix; + + private String returnVariableName; + private String throwVariableName; + private String invokeArgsVariableName; + private String monitorVariableName; + private LocalVariableNode returnVariableNode = null; + 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; + + private LocationFilter locationFilter = new DefaultLocationFilter(); + + public MethodProcessor(final ClassNode classNode, final MethodNode methodNode) { + this(classNode, methodNode, false); + } + + public MethodProcessor(final ClassNode classNode, final MethodNode methodNode, LocationFilter locationFilter) { + this(classNode, methodNode, false); + this.locationFilter = locationFilter; + } + + 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; + this.nextLocals = methodNode.maxLocals; + this.argumentTypes = Type.getArgumentTypes(methodNode.desc); + this.returnType = Type.getReturnType(methodNode.desc); + this.keepLocalVariableNames = keepLocalVariableNames; + + // find enter & exit instruction. + if (isConstructor()) { + this.enterInsnNode = findInitConstructorInstruction(); + } 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; + while(existLocalVariableWithPrefix(prefix)) { + prefix = DEFAULT_INNER_VARIABLE_PREFIX + count + "_"; + 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)) { + return true; + } + } + return false; + } + + public LocalVariableNode initMonitorVariableNode() { + if (monitorVariableNode == null) { + monitorVariableNode = this.addInterceptorLocalVariable(monitorVariableName, OBJECT_TYPE.getDescriptor()); + } + return monitorVariableNode; + } + + public LocalVariableNode initThrowVariableNode() { + if (throwVariableNode == null) { + throwVariableNode = this.addInterceptorLocalVariable(throwVariableName, THROWABLE_TYPE.getDescriptor()); + } + return throwVariableNode; + } + + public LocalVariableNode initInvokeArgsVariableNode() { + if (invokeArgsVariableNode == null) { + invokeArgsVariableNode = this.addInterceptorLocalVariable(invokeArgsVariableName, + OBJECT_ARRAY_TYPE.getDescriptor()); + } + return invokeArgsVariableNode; + } + + public LocalVariableNode initReturnVariableNode() { + if (returnVariableNode == null) { + returnVariableNode = this.addInterceptorLocalVariable(returnVariableName, returnType.getDescriptor()); + } + return returnVariableNode; + } + + /** + * + * @param name + * @param type + * @return + */ + public LocalVariableNode initInvokeReturnVariableNode(String name, Type type) { + String key = this.invokeReturnVariablePrefix + name; + LocalVariableNode variableNode = invokeReturnVariableNodeMap.get(key); + if (variableNode == null) { + variableNode = this.addInterceptorLocalVariable(key, type.getDescriptor()); + invokeReturnVariableNodeMap.put(key, variableNode); + } + return variableNode; + } + + public TryCatchBlock initTryCatchBlock() { + return initTryCatchBlock(THROWABLE_TYPE.getInternalName()); + } + + public TryCatchBlock initTryCatchBlock(String exception) { + if( this.tryCatchBlock == null) { + this.tryCatchBlock = new TryCatchBlock(methodNode, exception); + this.methodNode.instructions.insertBefore(this.getEnterInsnNode(), tryCatchBlock.getStartLabelNode()); + this.methodNode.instructions.insert(this.getLastInsnNode(), tryCatchBlock.getEndLabelNode()); + InsnList instructions = new InsnList(); + AsmOpUtils.throwException(instructions); + this.methodNode.instructions.insert(tryCatchBlock.getEndLabelNode(), instructions); + + tryCatchBlock.sort(); + } + return tryCatchBlock; + } + + AbstractInsnNode findInitConstructorInstruction() { + int nested = 0; + for (AbstractInsnNode insnNode = this.methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof TypeInsnNode) { + if (insnNode.getOpcode() == Opcodes.NEW) { + // new object(). + nested++; + } + } else if (insnNode instanceof MethodInsnNode) { + final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.getOpcode() == Opcodes.INVOKESPECIAL && methodInsnNode.name.equals("")) { + if (--nested < 0) { + // find this() or super(). + return insnNode.getNext(); + } + } + } + } + + return null; + } + + public AbstractInsnNode getEnterInsnNode() { + return enterInsnNode; + } + + public AbstractInsnNode getLastInsnNode() { + return lastInsnNode; + } + + public String[] getParameterTypes() { + final String[] parameterTypes = new String[this.argumentTypes.length]; + for (int i = 0; i < this.argumentTypes.length; i++) { + parameterTypes[i] = this.argumentTypes[i].getClassName(); + } + + return parameterTypes; + } + + public String[] getParameterNames() { + if (this.argumentTypes.length == 0) { + return new String[0]; + } + + final List localVariableNodes = this.methodNode.localVariables; + int localVariableStartIndex = 1; + if (isStatic()) { + // static method is none this. + localVariableStartIndex = 0; + } + + if (localVariableNodes == null || localVariableNodes.size() <= localVariableStartIndex + || (this.argumentTypes.length + localVariableStartIndex) > localVariableNodes.size()) { + // make simple argument names. + final String[] names = new String[this.argumentTypes.length]; + for (int i = 0; i < this.argumentTypes.length; i++) { + final String className = this.argumentTypes[i].getClassName(); + if (className != null) { + final int findIndex = className.lastIndexOf('.'); + if (findIndex == -1) { + names[i] = className; + } else { + names[i] = className.substring(findIndex + 1); + } + } else { + names[i] = this.argumentTypes[i].getDescriptor(); + } + } + return names; + } + + // sort by index. + Collections.sort(localVariableNodes, new Comparator() { + + @Override + public int compare(LocalVariableNode o1, LocalVariableNode o2) { + return o1.index - o2.index; + } + }); + String[] names = new String[this.argumentTypes.length]; + + for (int i = 0; i < this.argumentTypes.length; i++) { + final String name = localVariableNodes.get(localVariableStartIndex++).name; + if (name != null) { + names[i] = name; + } else { + names[i] = ""; + } + } + + return names; + } + + public Type getReturnType() { + return this.returnType; + } + + private boolean hasLocalVariable(String name) { + List localVariableNodes = this.methodNode.localVariables; + if (localVariableNodes == null) { + return false; + } + + for (LocalVariableNode node : localVariableNodes) { + if (node.name.equals(name)) { + return true; + } + } + + return false; + } + + public void loadThis(final InsnList instructions) { + if (isConstructor()) { + // load this. + loadVar(instructions, 0); + } else { + if (isStatic()) { + // load null. + loadNull(instructions); + } else { + // load this. + loadVar(instructions, 0); + } + } + } + + void storeVar(final InsnList instructions, final int index) { + instructions.add(new VarInsnNode(Opcodes.ASTORE, index)); + } + + void storeInt(final InsnList instructions, final int index) { + instructions.add(new VarInsnNode(Opcodes.ISTORE, index)); + } + + void loadNull(final InsnList instructions) { + instructions.add(new InsnNode(Opcodes.ACONST_NULL)); + } + + void loadVar(final InsnList instructions, final int index) { + instructions.add(new VarInsnNode(Opcodes.ALOAD, index)); + } + + void loadInt(final InsnList instructions, final int index) { + instructions.add(new VarInsnNode(Opcodes.ILOAD, index)); + } + + boolean isReturnCode(final int opcode) { + return opcode == Opcodes.IRETURN || opcode == Opcodes.LRETURN || opcode == Opcodes.FRETURN + || opcode == Opcodes.DRETURN || opcode == Opcodes.ARETURN || opcode == Opcodes.RETURN; + } + + Type getBoxedType(final Type type) { + switch (type.getSort()) { + case Type.BYTE: + return BYTE_TYPE; + case Type.BOOLEAN: + return BOOLEAN_TYPE; + case Type.SHORT: + return SHORT_TYPE; + case Type.CHAR: + return CHARACTER_TYPE; + case Type.INT: + return INTEGER_TYPE; + case Type.FLOAT: + return FLOAT_TYPE; + case Type.LONG: + return LONG_TYPE; + case Type.DOUBLE: + return DOUBLE_TYPE; + } + return type; + } + + void push(InsnList insnList, final int value) { + if (value >= -1 && value <= 5) { + insnList.add(new InsnNode(Opcodes.ICONST_0 + value)); + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + insnList.add(new IntInsnNode(Opcodes.BIPUSH, value)); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + insnList.add(new IntInsnNode(Opcodes.SIPUSH, value)); + } else { + insnList.add(new LdcInsnNode(value)); + } + } + + void push(InsnList insnList, final String value) { + if (value == null) { + insnList.add(new InsnNode(Opcodes.ACONST_NULL)); + } else { + insnList.add(new LdcInsnNode(value)); + } + } + + void newArray(final InsnList insnList, final Type type) { + insnList.add(new TypeInsnNode(Opcodes.ANEWARRAY, type.getInternalName())); + } + + void dup(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP)); + } + + void dup2(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP2)); + } + + void dupX1(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP_X1)); + } + + void dupX2(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP_X2)); + } + + void pop(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.POP)); + } + + void swap(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.SWAP)); + } + + void loadArgsVar(final InsnList instructions) { + if (this.argumentTypes.length == 0) { + // null. + loadNull(instructions); + return; + } + + push(instructions, this.argumentTypes.length); + // new array + newArray(instructions, OBJECT_TYPE); + for (int i = 0; i < this.argumentTypes.length; i++) { + Type type = this.argumentTypes[i]; + dup(instructions); + push(instructions, i); + // loadArg + loadArg(instructions, this.argumentTypes, i); + // box + box(instructions, type); + // arrayStore + arrayStore(instructions, OBJECT_TYPE); + } + } + + void loadArgs(final InsnList instructions) { + for (int i = 0; i < this.argumentTypes.length; i++) { + loadArg(instructions, this.argumentTypes, i); + } + } + + void loadArg(final InsnList instructions, Type[] argumentTypes, int i) { + final int index = getArgIndex(argumentTypes, i); + final Type type = argumentTypes[i]; + instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index)); + } + + int getArgIndex(final Type[] argumentTypes, final int arg) { + int index = isStatic() ? 0 : 1; + for (int i = 0; i < arg; i++) { + index += argumentTypes[i].getSize(); + } + return index; + } + + void box(final InsnList instructions, Type type) { + if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + return; + } + + if (type == Type.VOID_TYPE) { + // push null + instructions.add(new InsnNode(Opcodes.ACONST_NULL)); + } else { + Type boxed = getBoxedType(type); + // new instance. + newInstance(instructions, boxed); + if (type.getSize() == 2) { + // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o + // dupX2 + dupX2(instructions); + // dupX2 + dupX2(instructions); + // pop + pop(instructions); + } else { + // p -> po -> opo -> oop -> o + // dupX1 + dupX1(instructions); + // swap + swap(instructions); + } + invokeConstructor(instructions, boxed, new Method("", Type.VOID_TYPE, new Type[] { type })); + } + } + + void unbox(final InsnList instructions, Type type) { + Type t = NUMBER_TYPE; + Method sig = null; + switch (type.getSort()) { + case Type.VOID: + return; + case Type.CHAR: + t = CHARACTER_TYPE; + sig = CHAR_VALUE; + break; + case Type.BOOLEAN: + t = BOOLEAN_TYPE; + sig = BOOLEAN_VALUE; + break; + case Type.DOUBLE: + sig = DOUBLE_VALUE; + break; + case Type.FLOAT: + sig = FLOAT_VALUE; + break; + case Type.LONG: + sig = LONG_VALUE; + break; + case Type.INT: + case Type.SHORT: + case Type.BYTE: + sig = INT_VALUE; + } + if (sig == null) { + instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName())); + } else { + instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, t.getInternalName())); + instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, t.getInternalName(), sig.getName(), + sig.getDescriptor(), false)); + } + } + + void arrayStore(final InsnList instructions, final Type type) { + instructions.add(new InsnNode(type.getOpcode(Opcodes.IASTORE))); + } + + void arrayLoad(final InsnList instructions, final Type type) { + instructions.add(new InsnNode(type.getOpcode(Opcodes.IALOAD))); + } + + void newInstance(final InsnList instructions, final Type type) { + instructions.add(new TypeInsnNode(Opcodes.NEW, type.getInternalName())); + } + + void invokeConstructor(final InsnList instructions, final Type type, final Method method) { + String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); + instructions + .add(new MethodInsnNode(Opcodes.INVOKESPECIAL, owner, method.getName(), method.getDescriptor(), false)); + } + + LocalVariableNode addInterceptorLocalVariable(final String name, final String desc) { + return addLocalVariable(name, desc, this.interceptorVariableStartLabelNode, + this.interceptorVariableEndLabelNode); + } + + LocalVariableNode addLocalVariable(final String name, final String desc, final LabelNode start, + final LabelNode end) { + Type type = Type.getType(desc); + int index = this.nextLocals; + this.nextLocals += type.getSize(); + methodNode.maxLocals = this.nextLocals; + final LocalVariableNode node = new LocalVariableNode(name, desc, null, start, end, index); + if (keepLocalVariableNames) { + this.methodNode.localVariables.add(node); + } + + return node; + } + + public void returnValue(final InsnList instructions) { + instructions.add(new InsnNode(this.returnType.getOpcode(Opcodes.IRETURN))); + } + + public boolean isStatic() { + return (this.methodNode.access & Opcodes.ACC_STATIC) != 0; + } + + public boolean isConstructor() { + return this.methodNode.name != null && this.methodNode.name.equals(""); + } + + public MethodNode getMethodNode() { + return methodNode; + } + + public void setMethodNode(MethodNode methodNode) { + this.methodNode = methodNode; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public ClassNode getClassNode() { + return classNode; + } + public void setClassNode(ClassNode classNode) { + this.classNode = classNode; + } + public LocationFilter getLocationFilter() { + return locationFilter; + } + + /** + * TODO 可以考虑实现修改值的功能,原理是传入的 args实际转化为一个stack上的slot,只要在inline之后,把 stack上面的对应的slot保存到想要保存的位置就可以了。 + * @param owner + * @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) + && methodInsnNode.desc.equals(toInlineMethodNode.desc)) { + // 要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; + + for(int i = 0; i < args.length; ++i) { + argsOff += args[i].getSize(); + } + // 记录新的 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); + } + + // this + if (off > 0) { +// this.visitVarInsn(Opcodes.ASTORE, 0); + AsmOpUtils.storeVar(instructions, OBJECT_TYPE, currentMaxLocals); + } + + + 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; + } + int opcode = abstractInsnNode.getOpcode(); + if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) { +// super.visitJumpInsn(Opcodes.GOTO, end); +// instructions.add(new JumpInsnNode(Opcodes.GOTO, end)); + 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(); + // try catch 块加上,然后排序 + if(this.methodNode.tryCatchBlocks != null && tmpToInlineMethodNode.tryCatchBlocks != null) { + this.methodNode.tryCatchBlocks.addAll(tmpToInlineMethodNode.tryCatchBlocks); + } + this.sortTryCatchBlock(); + } + } + } + + } + + public void sortTryCatchBlock() { + if (this.methodNode.tryCatchBlocks == null) { + return; + } + + // Compares TryCatchBlockNodes by the length of their "try" block. + Collections.sort(this.methodNode.tryCatchBlocks, new Comparator() { + @Override + public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) { + int len1 = blockLength(t1); + int len2 = blockLength(t2); + return len1 - len2; + } + + private int blockLength(TryCatchBlockNode block) { + final int startidx = methodNode.instructions.indexOf(block.start); + final int endidx = methodNode.instructions.indexOf(block.end); + return endidx - startidx; + } + }); + + // Updates the 'target' of each try catch block annotation. + for (int i = 0; i < this.methodNode.tryCatchBlocks.size(); i++) { + this.methodNode.tryCatchBlocks.get(i).updateIndex(i); + } + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MyTryCatchBlock.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MyTryCatchBlock.java new file mode 100644 index 000000000..3c90ec99d --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MyTryCatchBlock.java @@ -0,0 +1,64 @@ +package com.taobao.arthas.bytekit.asm; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.TryCatchBlockNode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class MyTryCatchBlock { + private final MethodNode methodNode; + private final LabelNode startLabelNode = new LabelNode(); + private final LabelNode endLabelNode = new LabelNode(); + private final LabelNode handlerLabelNode = new LabelNode(); + + public MyTryCatchBlock(final MethodNode methodNode) { + this.methodNode = methodNode; + + final TryCatchBlockNode tryCatchBlockNode = new TryCatchBlockNode(this.startLabelNode, this.endLabelNode, this.handlerLabelNode, "java/lang/Throwable"); + if (this.methodNode.tryCatchBlocks == null) { + this.methodNode.tryCatchBlocks = new ArrayList(); + } + this.methodNode.tryCatchBlocks.add(tryCatchBlockNode); + } + + public LabelNode getStartLabelNode() { + return this.startLabelNode; + } + + public LabelNode getEndLabelNode() { + return this.endLabelNode; + } + + public LabelNode getHandlerLabelNode() { + return this.handlerLabelNode; + } + + public void sort() { + if (this.methodNode.tryCatchBlocks == null) { + return; + } + + // Compares TryCatchBlockNodes by the length of their "try" block. + Collections.sort(this.methodNode.tryCatchBlocks, new Comparator() { + @Override + public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) { + int len1 = blockLength(t1); + int len2 = blockLength(t2); + return len1 - len2; + } + + private int blockLength(TryCatchBlockNode block) { + final int startidx = methodNode.instructions.indexOf(block.start); + final int endidx = methodNode.instructions.indexOf(block.end); + return endidx - startidx; + } + }); + + // Updates the 'target' of each try catch block annotation. + for (int i = 0; i < this.methodNode.tryCatchBlocks.size(); i++) { + this.methodNode.tryCatchBlocks.get(i).updateIndex(i); + } + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TryCatchBlock.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TryCatchBlock.java new file mode 100644 index 000000000..9068fa2aa --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TryCatchBlock.java @@ -0,0 +1,66 @@ +package com.taobao.arthas.bytekit.asm; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.TryCatchBlockNode; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class TryCatchBlock { + private final MethodNode methodNode; + private final LabelNode startLabelNode = new LabelNode(); + private final LabelNode endLabelNode = new LabelNode(); + + public TryCatchBlock(final MethodNode methodNode) { + this(methodNode, Type.getType(Throwable.class).getInternalName()); + } + + public TryCatchBlock(final MethodNode methodNode, String exception) { + this.methodNode = methodNode; + + final TryCatchBlockNode tryCatchBlockNode = new TryCatchBlockNode(this.startLabelNode, this.endLabelNode, + this.endLabelNode, exception); + if (this.methodNode.tryCatchBlocks == null) { + this.methodNode.tryCatchBlocks = new ArrayList(); + } + this.methodNode.tryCatchBlocks.add(tryCatchBlockNode); + } + + public LabelNode getStartLabelNode() { + return this.startLabelNode; + } + + public LabelNode getEndLabelNode() { + return this.endLabelNode; + } + + public void sort() { + if (this.methodNode.tryCatchBlocks == null) { + return; + } + + // Compares TryCatchBlockNodes by the length of their "try" block. + Collections.sort(this.methodNode.tryCatchBlocks, new Comparator() { + @Override + public int compare(TryCatchBlockNode t1, TryCatchBlockNode t2) { + int len1 = blockLength(t1); + int len2 = blockLength(t2); + return len1 - len2; + } + + private int blockLength(TryCatchBlockNode block) { + final int startidx = methodNode.instructions.indexOf(block.start); + final int endidx = methodNode.instructions.indexOf(block.end); + return endidx - startidx; + } + }); + + // Updates the 'target' of each try catch block annotation. + for (int i = 0; i < this.methodNode.tryCatchBlocks.size(); i++) { + this.methodNode.tryCatchBlocks.get(i).updateIndex(i); + } + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TypeHelper.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TypeHelper.java new file mode 100644 index 000000000..0e8d35f80 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TypeHelper.java @@ -0,0 +1,431 @@ +/* +* JBoss, Home of Professional Open Source +* Copyright 2008-10 Red Hat and individual contributors +* by the @authors tag. See the copyright.txt in the distribution for a +* full listing of individual contributors. +* +* This is free software; you can redistribute it and/or modify it +* under the terms of the GNU Lesser General Public License as +* published by the Free Software Foundation; either version 2.1 of +* the License, or (at your option) any later version. +* +* This software is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this software; if not, write to the Free +* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +* 02110-1301 USA, or see the FSF site: http://www.fsf.org. +* +* @authors Andrew Dinn +*/ +package com.taobao.arthas.bytekit.asm; + +/** + * Helpoer class providing static methods for manipulating type and class names, + * field and method descriptor names etc + */ +public class TypeHelper { + + public static boolean equalDescriptors(String desc1, String desc2) + { + int idx1 = 0, idx2 = 0; + int len1 = desc1.length(), len2 = desc2.length(); + while (idx1 < len1) { + // check the other has not dropped off the end + if (idx2 == len2) { + if ((idx1 == (len1 - 1)) && (desc1.charAt(idx1) == '$')) { + return true; + } + return false; + } + // check type is the same + char char1 = desc1.charAt(idx1); + char char2 = desc2.charAt(idx2); + // if we have a $ at the end of the descriptor then this means any return + // type so special case this + if ((char1 == '$' && idx1 == len1 - 1) || (char2 == '$' && idx2 == len2 - 1)) { + return true; + } + // otherwise the chars must match + if (char1 != char2) { + return false; + } + // however an L indicates a class name and we allow a classname without a package + // to match a class name with a package + if (char1 == 'L') { + // ok, ensure the names must match modulo a missing package + int end1 = idx1 + 1; + int end2 = idx2 + 1; + while (end1 < len1 && desc1.charAt(end1) != ';') { + end1++; + } + while (end2 < len2 && desc2.charAt(end2) != ';') { + end2++; + } + if (end1 == len1 || end2 == len2) { + // bad format for desc!! + return false; + } + String typeName1 = desc1.substring(idx1 + 1, end1); + String typeName2 = desc2.substring(idx2 + 1, end2); + if (!typeName1.equals(typeName2)) { + int tailIdx1 = typeName1.lastIndexOf('/'); + int tailIdx2 = typeName2.lastIndexOf('/'); + if (tailIdx1 > 0) { + if (tailIdx2 > 0) { + // both specify packages so they must be different types + return false; + } else { + // only type 1 specifies a package so type 2 should match the tail + if (!typeName2.equals(typeName1.substring(tailIdx1 + 1))) { + return false; + } + } + } else { + if (tailIdx2 > 0) { + // only type 2 specifies a package so type 1 should match the tail + if (!typeName1.equals(typeName2.substring(tailIdx2 + 1))) { + return false; + } + } else { + // neither specify packages so they must be different types + return false; + } + } + } + // skp past ';'s + idx1 = end1; + idx2 = end2; + } + idx1++; + idx2++; + } + + // check the other has not reached the end + if (idx2 != len2) { + return false; + } + + return true; + } + /** + * convert a classname from canonical form to the form used to represent it externally i.e. replace + * all dots with slashes + * + * @param className the canonical name + * @return the external name + */ + public static String externalizeClass(String className) + { + return className.replace('.', '/'); + } + + /** + * convert a classname from external form to canonical form i.e. replace + * all slashes with dots + * + * @param className the external name + * @return the canonical name + */ + public static String internalizeClass(String className) + { + String result = className; + int length = result.length(); + if (result.charAt(length - 1) == ';') { + result = result.substring(1, length - 2); + } + result = result.replace('/', '.'); + return result; + } + + /** + * convert a type name from canonical form to the form used to represent it externally i.e. + * replace primitive type names by the appropriate single letter types, class names + * by the externalized class name bracketed by 'L' and ';' and array names by the + * base type name preceded by '['. + * + * @param typeName the type name + * @return the external name + */ + public static String externalizeType(String typeName) + { + String externalName = ""; + String[] typeAndArrayIndices = typeName.split("\\["); + String baseType = typeAndArrayIndices[0].trim(); + for (int i = 1; i< typeAndArrayIndices.length; i++) { + String arrayIdx = typeAndArrayIndices[i]; + if (arrayIdx.indexOf("\\]") != 0) { + externalName += '['; + } + } + for (int i = 0; i < internalNames.length; i++) { + if (internalNames[i].equals(baseType)) { + externalName += externalNames[i]; + return externalName; + } + } + + externalName += "L" + externalizeClass(baseType) + ";"; + + return externalName; + } + + /** + * list of well known typenames as written in Java code + */ + final static private String[] internalNames = { + "", /* equivalent to void */ + "void", + "byte", + "char", + "short", + "int", + "long", + "float", + "double", + "boolean", + "Byte", + "Character", + "Short", + "Integer", + "Long", + "Float", + "Double", + "String", + "java.lang.Byte", + "java.lang.Character", + "java.lang.Short", + "java.lang.Integer", + "java.lang.Long", + "java.lang.Float", + "java.lang.Double", + "java.lang.String" + }; + + /** + * list of typenames in external form corresponding to entries ni previous list + */ + final static private String[] externalNames = { + "$", + "V", + "B", + "C", + "S", + "I", + "J", + "F", + "D", + "Z", + "Ljava/lang/Byte;", + "Ljava/lang/Character;", + "Ljava/lang/Short;", + "Ljava/lang/Integer;", + "Ljava/lang/Long;", + "Ljava/lang/Float;", + "Ljava/lang/Double;", + "Ljava/lang/String;", + "Ljava/lang/Byte;", + "Ljava/lang/Character;", + "Ljava/lang/Short;", + "Ljava/lang/Integer;", + "Ljava/lang/Long;", + "Ljava/lang/Float;", + "Ljava/lang/Double;", + "Ljava/lang/String;" + }; + + /** + * convert a method descriptor from canonical form to the form used to represent it externally + * + * @param desc the method descriptor which must be trimmed of any surrounding white space + * @return an externalised form for the descriptor + */ + public static String externalizeDescriptor(String desc) + { + // the descriptor will start with '(' and the arguments list should end with ')' and, + // if it is not void be followed by a return type + int openIdx = desc.indexOf('('); + int closeIdx = desc.indexOf(')'); + int length = desc.length(); + if (openIdx != 0) { + return ""; + } + if (closeIdx < 0) { + return ""; + } + String retType = (closeIdx < length ? desc.substring(closeIdx + 1).trim() : ""); + String externalRetType = externalizeType(retType); + String argString = desc.substring(1, closeIdx).trim(); + String externalArgs = ""; + if (argString.equals("")) { + externalArgs = argString; + } else { + String[] args = desc.substring(1, closeIdx).trim().split(","); + for (int i = 0; i < args.length ; i++) { + externalArgs += externalizeType(args[i]); + } + } + + return "(" + externalArgs + ")" + externalRetType; + } + + /** + * convert a method descriptor from the form used to represent it externally to canonical form + * + * @param desc the method descriptor which must be trimmed of any surrounding white space and start with "(". + * it must end either with ")" or with ") " followed by an exernalized return type + * @return an internalised form for the descriptor, possibly followed by a space and externalized return type + */ + public static String internalizeDescriptor(String desc) + { + StringBuffer buffer = new StringBuffer(); + String sepr = ""; + int argStart = desc.indexOf('('); + int argEnd = desc.indexOf(')'); + int max = desc.length(); + if (argStart < 0 || argEnd < 0) { + return "(...)"; + } + int arrayCount = 0; + boolean addSepr = false; + + buffer.append("("); + + for (int idx = argStart + 1; idx < max;) { + char next = desc.charAt(idx); + if (addSepr) { + while (arrayCount > 0) { + buffer.append("[]"); + arrayCount--; + } + buffer.append(sepr); + } + addSepr = true; + switch(next) { + case 'B': + { + buffer.append("byte"); + } + break; + case 'C': + { + buffer.append("char"); + } + break; + case 'S': + { + buffer.append("short"); + } + break; + case 'I': + { + buffer.append("int"); + } + break; + case 'J': + { + buffer.append("long"); + } + break; + case 'Z': + { + buffer.append("boolean"); + } + break; + case 'F': + { + buffer.append("float"); + } + break; + case 'D': + { + buffer.append("double"); + } + case 'V': + { + buffer.append("void"); + } + break; + case 'L': + { + int tailIdx = idx+1; + while (tailIdx < max) { + char tailChar = desc.charAt(tailIdx); + if (tailChar == ';') { + break; + } + if (tailChar == '/') + { + tailChar = '.'; + } + buffer.append(tailChar); + tailIdx++; + } + idx = tailIdx; + } + break; + case '[': + { + arrayCount++; + addSepr = false; + } + break; + case ')': + { + if (idx == argEnd - 1) { + buffer.append(")"); + } else { + // leave room for return type + buffer.append(") "); + } + addSepr = false; + } + break; + default: + { + addSepr = false; + } + } + idx++; + if (idx < argEnd) { + sepr = ","; + } else { + sepr = ""; + } + } + + return buffer.toString(); + } + + /** + * split off the method name preceding the signature and return it + * @param targetMethod - the unqualified method name, possibly including signature + * @return the method name + */ + public static String parseMethodName(String targetMethod) { + int sigIdx = targetMethod.indexOf("("); + if (sigIdx > 0) { + return targetMethod.substring(0, sigIdx).trim(); + } else { + return targetMethod; + } + } + + /** + * split off the signature following the method name and return it + * @param targetMethod - the unqualified method name, possibly including signature + * @return the signature + */ + public static String parseMethodDescriptor(String targetMethod) { + int descIdx = targetMethod.indexOf("("); + if (descIdx >= 0) { + String desc = targetMethod.substring(descIdx, targetMethod.length()).trim(); + return externalizeDescriptor(desc); + } else { + return ""; + } + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgNamesBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgNamesBinding.java new file mode 100644 index 000000000..b752daf1c --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgNamesBinding.java @@ -0,0 +1,33 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class ArgNamesBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + + String[] parameterNames = bindingContext.getMethodProcessor().getParameterNames(); + + AsmOpUtils.push(instructions, parameterNames.length); + AsmOpUtils.newArray(instructions, AsmOpUtils.STRING_TYPE); + + for(int i = 0; i < parameterNames.length; ++i) { + AsmOpUtils.dup(instructions); + + AsmOpUtils.push(instructions, i); + AsmOpUtils.push(instructions, parameterNames[i]); + + AsmOpUtils.arrayStore(instructions, AsmOpUtils.STRING_TYPE); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + return AsmOpUtils.STRING_ARRAY_TYPE; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgsBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgsBinding.java new file mode 100644 index 000000000..8b31c84ac --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgsBinding.java @@ -0,0 +1,20 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class ArgsBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + AsmOpUtils.loadArgArray(instructions, bindingContext.getMethodProcessor().getMethodNode()); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Object[].class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArrayBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArrayBinding.java new file mode 100644 index 000000000..363ef8928 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArrayBinding.java @@ -0,0 +1,50 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * TODO 这个判断是否要从stack上取数据,要看 其它的binding是否需要。 是否 optional,这个应该是由 ArrayBinding 整体设定?? + * @author hengyunabc + * + */ +public class ArrayBinding extends Binding{ + + // TODO 数组的 type是什么? +// private Type type; + + List bindingList = new ArrayList(); + + public ArrayBinding(List bindingList) { + this.bindingList = bindingList; + } + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + AsmOpUtils.push(instructions, bindingList.size()); + AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE); + + for(int i = 0; i < bindingList.size(); ++i) { + AsmOpUtils.dup(instructions); + + AsmOpUtils.push(instructions, i); + Binding binding = bindingList.get(i); + binding.pushOntoStack(instructions, bindingContext); + AsmOpUtils.box(instructions, binding.getType(bindingContext)); + + AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/Binding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/Binding.java new file mode 100644 index 000000000..24b99cdef --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/Binding.java @@ -0,0 +1,424 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParser; +import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParserHandler; + + +public abstract class Binding { + + /** + * 是否可选的,当不符合条件,或者获取不到值时,会转为 null,这个不支持原始类型,就像java.util.Optional 一样? + * @return + */ + public boolean optional() { + return false; + } + + /** + * 检查当前条件下这个binding是否可以工作,比如检查field是否有这个field。 + * @return + */ + public boolean check(BindingContext bindingContext) { + return true; + } + + /** + * 把这个binding本身放到栈上 + * @param instructions + * @param bindingContext + */ + public abstract void pushOntoStack(InsnList instructions, BindingContext bindingContext); + + public abstract Type getType( BindingContext bindingContext); + + public boolean fromStack() { + return false; + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = ArgsBindingParser.class) + public static @interface Args { + + boolean optional() default false; + + } + public static class ArgsBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new ArgsBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = ArgNamesBindingParser.class) + public static @interface ArgNames { + + boolean optional() default false; + + } + public static class ArgNamesBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new ArgNamesBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = LocalVarsBindingParser.class) + public static @interface LocalVars { + + boolean optional() default false; + + } + public static class LocalVarsBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new LocalVarsBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = LocalVarNamesBindingParser.class) + public static @interface LocalVarNames { + + boolean optional() default false; + + } + public static class LocalVarNamesBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new LocalVarNamesBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = ClassBindingParser.class) + public static @interface Class { + + boolean optional() default false; + + } + + public static class ClassBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new ClassBinding(); + } + + } + + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = FieldBindingParser.class) + public static @interface Field { + boolean optional() default false; + java.lang.Class owner() default Void.class; + java.lang.Class type() default Void.class; + String name(); + boolean isStatic() default false; + boolean box() default false; + } + + public static class FieldBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + Field field = (Field) annotation; + Type ownerType = Type.getType(field.owner()); + if(field.owner().equals(Void.class)) { + ownerType = null; + } + Type fieldType = Type.getType(field.type()); + if(field.type().equals(Void.class)) { + fieldType = null; + } + return new FieldBinding(ownerType, field.name(), fieldType, + field.isStatic(), field.box()); + } + } + + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = InvokeArgsBindingParser.class) + public static @interface InvokeArgs { + + boolean optional() default false; + + } + + public static class InvokeArgsBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new InvokeArgsBinding(); + } + } + + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = InvokeReturnBindingParser.class) + public static @interface InvokeReturn { + + boolean optional() default false; + + } + + public static class InvokeReturnBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new InvokeReturnBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = InvokeMethodNameBindingParser.class) + public static @interface InvokeMethodName { + + boolean optional() default false; + + } + + public static class InvokeMethodNameBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new InvokeMethodNameBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = InvokeMethodOwnerBindingParser.class) + public static @interface InvokeMethodOwner { + + boolean optional() default false; + + } + + public static class InvokeMethodOwnerBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new InvokeMethodOwnerBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = InvokeMethodDeclarationBindingParser.class) + public static @interface InvokeMethodDeclaration { + + boolean optional() default false; + + } + + public static class InvokeMethodDeclarationBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new InvokeMethodDeclarationBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = InvokeInfoBindingParser.class) + public static @interface InvokeInfo { + + boolean optional() default false; + + } + + public static class InvokeInfoBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new InvokeInfoBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MethodBindingParser.class) + public static @interface Method { + boolean optional() default false; + } + + public static class MethodBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MethodBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MethodNameBindingParser.class) + public static @interface MethodName { + boolean optional() default false; + } + + public static class MethodNameBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MethodNameBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MethodDescBindingParser.class) + public static @interface MethodDesc { + boolean optional() default false; + } + + public static class MethodDescBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MethodDeclarationBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MethodInfoBindingParser.class) + public static @interface MethodInfo { + boolean optional() default false; + } + + public static class MethodInfoBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MethodInfoBinding(); + } + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = ReturnBindingParser.class) + public static @interface Return { + + boolean optional() default false; + + } + + public static class ReturnBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new ReturnBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = ThisBindingParser.class) + public static @interface This { + + } + + public static class ThisBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new ThisBinding(); + } + + } + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = ThrowableBindingParser.class) + public static @interface Throwable { + + boolean optional() default false; + + } + + public static class ThrowableBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new ThrowableBinding(); + } + + } + + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = LineBindingParser.class) + public static @interface Line { + boolean optional() default false; + + /** + * 是否精确是在某个 LineNumberNode 上。如果为true的话,会向上找到最接近的 LineNumberNode + * + * @return + */ + boolean exact() default false; + + } + + public static class LineBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + Line line = (Line) annotation; + return new LineBinding(line.exact()); + } + } + + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MonitorBindingParser.class) + public static @interface Monitor { + + boolean optional() default false; + + } + + public static class MonitorBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new MonitorBinding(); + } + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/BindingContext.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/BindingContext.java new file mode 100644 index 000000000..2fcfb3bed --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/BindingContext.java @@ -0,0 +1,41 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location; + +public class BindingContext { + private MethodProcessor methodProcessor; + private Location location; + private StackSaver stackSaver; + + public BindingContext(Location location, MethodProcessor methodProcessor, StackSaver stackSaver) { + this.location = location; + this.methodProcessor = methodProcessor; + this.stackSaver = stackSaver; + } + + public MethodProcessor getMethodProcessor() { + return methodProcessor; + } + + public void setMethodProcessor(MethodProcessor methodProcessor) { + this.methodProcessor = methodProcessor; + } + + public Location getLocation() { + return location; + } + + public void setLocation(Location location) { + this.location = location; + } + + public StackSaver getStackSaver() { + return stackSaver; + } + + public void setStackSaver(StackSaver stackSaver) { + this.stackSaver = stackSaver; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ClassBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ClassBinding.java new file mode 100644 index 000000000..360afcf75 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ClassBinding.java @@ -0,0 +1,21 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class ClassBinding extends Binding{ + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + String owner = bindingContext.getMethodProcessor().getOwner(); + AsmOpUtils.ldc(instructions, Type.getObjectType(owner)); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Class.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/FieldBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/FieldBinding.java new file mode 100644 index 000000000..9c20b2664 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/FieldBinding.java @@ -0,0 +1,95 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +public class FieldBinding extends Binding { + /** + * maybe null + */ + private Type owner; + + private boolean box = false; + + private String name; + + private boolean isStatic = false; + + /** + * maybe null + */ + private Type type; + + public FieldBinding(Type owner, String name, Type type, boolean isStatic, boolean box) { + this.owner = owner; + this.name = name; + this.isStatic = isStatic; + this.box = box; + this.type = type; + } + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Type onwerType = owner; + Type fieldType = type; + boolean fieldIsStatic = isStatic; + if (owner == null) { + onwerType = Type.getObjectType(bindingContext.getMethodProcessor().getOwner()); + } + // 当type是null里,需要从ClassNode里查找到files,确定type + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + if (fieldType == null) { + ClassNode classNode = methodProcessor.getClassNode(); + if (classNode == null) { + throw new IllegalArgumentException( + "classNode is null, cann not get owner type. FieldBinding name:" + name); + } + FieldNode field = AsmUtils.findField(classNode.fields, name); + if (field == null) { + throw new IllegalArgumentException("can not find field in ClassNode. FieldBinding name:" + name); + } + fieldType = Type.getType(field.desc); + if ((field.access & Opcodes.ACC_STATIC) != 0) { + fieldIsStatic = true; + }else { + fieldIsStatic = false; + } + } + + if (fieldIsStatic) { + AsmOpUtils.getStatic(instructions, onwerType, name, fieldType); + } else { + methodProcessor.loadThis(instructions); + AsmOpUtils.getField(instructions, onwerType, name, fieldType); + } + if (box) { + AsmOpUtils.box(instructions, fieldType); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + Type fieldType = type; + if (fieldType == null) { + ClassNode classNode = bindingContext.getMethodProcessor().getClassNode(); + if (classNode == null) { + throw new IllegalArgumentException( + "classNode is null, cann not get owner type. FieldBinding name:" + name); + } + FieldNode field = AsmUtils.findField(classNode.fields, name); + if (field == null) { + throw new IllegalArgumentException("can not find field in ClassNode. FieldBinding name:" + name); + } + fieldType = Type.getType(field.desc); + } + return fieldType; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/IntBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/IntBinding.java new file mode 100644 index 000000000..7400da633 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/IntBinding.java @@ -0,0 +1,36 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class IntBinding extends Binding { + + private int value; + + private boolean box = true; + + public IntBinding(int value) { + this(value, true); + } + + public IntBinding(int value, boolean box) { + this.value = value; + this.box = box; + } + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + AsmOpUtils.push(instructions, value); + if (box) { + AsmOpUtils.box(instructions, Type.INT_TYPE); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.INT_TYPE; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeArgsBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeArgsBinding.java new file mode 100644 index 000000000..0b7532ab0 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeArgsBinding.java @@ -0,0 +1,47 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; + +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * invoke 传入的参数列表,有严格的限制,只能在 invoke 之前。 + * + * TODO ,当 static 函数时,在数组前,传一个null进去? 不然,不好区分是否 static 函数调用?? + * + * @author hengyunabc + * + */ +public class InvokeArgsBinding extends Binding { + + @Override + public boolean fromStack() { + return true; + } + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + + if(location instanceof InvokeLocation) { + InvokeLocation invokeLocation = (InvokeLocation) location; + if(invokeLocation.isWhenComplete()) { + throw new IllegalArgumentException("InvokeArgsBinding can not work on InvokeLocation whenComplete is true."); + } + }else { + throw new IllegalArgumentException("current location is not invoke location. location: " + location); + } + + LocalVariableNode invokeArgsVariableNode = bindingContext.getMethodProcessor().initInvokeArgsVariableNode(); + AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Object[].class); + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeInfoBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeInfoBinding.java new file mode 100644 index 000000000..36e550160 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeInfoBinding.java @@ -0,0 +1,64 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LineNumberNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.taobao.arthas.bytekit.asm.location.MethodInsnNodeWare; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * 包含 owner/method name/ method desc/ line number + * + * @author hengyunabc 2020-05-14 + * + */ +public class InvokeInfoBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + if (location instanceof MethodInsnNodeWare) { + MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location; + MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode(); + + int line = -1; + + if (location.isWhenComplete() == false) { + AbstractInsnNode insnNode = methodInsnNode.getPrevious(); + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + line = ((LineNumberNode) insnNode).line; + break; + } + insnNode = insnNode.getPrevious(); + } + } else { + AbstractInsnNode insnNode = methodInsnNode.getNext(); + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + line = ((LineNumberNode) insnNode).line; + break; + } + insnNode = insnNode.getNext(); + } + } + + String result = methodInsnNode.owner + "|" + methodInsnNode.name + "|" + methodInsnNode.desc + "|" + line; + AsmOpUtils.push(instructions, result); + + } else { + throw new IllegalArgumentException( + "InvokeMethodNameBinding location is not Invocation location, location: " + location); + } + + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodDeclarationBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodDeclarationBinding.java new file mode 100644 index 000000000..2c802068a --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodDeclarationBinding.java @@ -0,0 +1,37 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.taobao.arthas.bytekit.asm.location.MethodInsnNodeWare; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * + * @author hengyunabc + * + */ +public class InvokeMethodDeclarationBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + if (location instanceof MethodInsnNodeWare) { + MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location; + MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode(); + AsmOpUtils.push(instructions, methodInsnNode.desc); + + } else { + throw new IllegalArgumentException( + "InvokeMethodDeclarationBinding location is not Invocation location, location: " + location); + } + + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodNameBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodNameBinding.java new file mode 100644 index 000000000..581bb0646 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodNameBinding.java @@ -0,0 +1,37 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.taobao.arthas.bytekit.asm.location.MethodInsnNodeWare; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * + * @author hengyunabc + * + */ +public class InvokeMethodNameBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + if (location instanceof MethodInsnNodeWare) { + MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location; + MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode(); + AsmOpUtils.push(instructions, methodInsnNode.name); + + } else { + throw new IllegalArgumentException( + "InvokeMethodNameBinding location is not Invocation location, location: " + location); + } + + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodOwnerBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodOwnerBinding.java new file mode 100644 index 000000000..9a962d4bc --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodOwnerBinding.java @@ -0,0 +1,37 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.taobao.arthas.bytekit.asm.location.MethodInsnNodeWare; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * + * @author hengyunabc 2020-05-02 + * + */ +public class InvokeMethodOwnerBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + if (location instanceof MethodInsnNodeWare) { + MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location; + MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode(); + AsmOpUtils.push(instructions, methodInsnNode.owner); + + } else { + throw new IllegalArgumentException( + "InvokeMethodOwnerBinding location is not Invocation location, location: " + location); + } + + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeReturnBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeReturnBinding.java new file mode 100644 index 000000000..d36d786c5 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeReturnBinding.java @@ -0,0 +1,62 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +/** + * invoke 的返回值 + * @author hengyunabc + * + */ +public class InvokeReturnBinding extends Binding { + + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode(); + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + String uniqueNameForMethod = AsmUtils.uniqueNameForMethod(methodInsnNode.owner, methodInsnNode.name, + methodInsnNode.desc); + Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType(); + if(invokeReturnType.equals(Type.VOID_TYPE)) { + AsmOpUtils.push(instructions, null); + }else { + LocalVariableNode invokeReturnVariableNode = methodProcessor.initInvokeReturnVariableNode( + uniqueNameForMethod, Type.getMethodType(methodInsnNode.desc).getReturnType()); + AsmOpUtils.loadVar(instructions, invokeReturnType, invokeReturnVariableNode.index); + } + } else { + throw new IllegalArgumentException( + "InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode); + } + + } + + @Override + public boolean fromStack() { + return true; + } + + @Override + public Type getType(BindingContext bindingContext) { + AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType(); + return invokeReturnType; + } else { + throw new IllegalArgumentException( + "InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode); + } + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LineBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LineBinding.java new file mode 100644 index 000000000..7c1e80073 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LineBinding.java @@ -0,0 +1,63 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LineNumberNode; + +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * + * @author hengyunabc + * + */ +public class LineBinding extends Binding { + + private boolean exact; + + public LineBinding(boolean exact) { + this.exact = exact; + } + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + AbstractInsnNode insnNode = location.getInsnNode(); + + int line = -1; + if (exact) { + if (insnNode instanceof LineNumberNode) { + line = ((LineNumberNode) insnNode).line; + } else { + throw new IllegalArgumentException("LineBinding location is not LineNumberNode, insnNode: " + insnNode); + } + } else { + if (location.isWhenComplete() == false) { + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + line = ((LineNumberNode) insnNode).line; + break; + } + insnNode = insnNode.getPrevious(); + } + } else { + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + line = ((LineNumberNode) insnNode).line; + break; + } + insnNode = insnNode.getNext(); + } + } + } + AsmOpUtils.push(instructions, line); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(int.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarNamesBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarNamesBinding.java new file mode 100644 index 000000000..56bffb23e --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarNamesBinding.java @@ -0,0 +1,38 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class LocalVarNamesBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + AbstractInsnNode currentInsnNode = bindingContext.getLocation().getInsnNode(); + List results = AsmOpUtils + .validVariables(bindingContext.getMethodProcessor().getMethodNode().localVariables, currentInsnNode); + + AsmOpUtils.push(instructions, results.size()); + AsmOpUtils.newArray(instructions, AsmOpUtils.STRING_TYPE); + + for (int i = 0; i < results.size(); ++i) { + AsmOpUtils.dup(instructions); + + AsmOpUtils.push(instructions, i); + AsmOpUtils.push(instructions, results.get(i).name); + + AsmOpUtils.arrayStore(instructions, AsmOpUtils.STRING_TYPE); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + return AsmOpUtils.STRING_ARRAY_TYPE; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarsBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarsBinding.java new file mode 100644 index 000000000..3953fdc41 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarsBinding.java @@ -0,0 +1,49 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * TODO 增加一个配置,是否包含 method args + * @author hengyunabc + * + */ +public class LocalVarsBinding extends Binding{ + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + + AbstractInsnNode currentInsnNode = bindingContext.getLocation().getInsnNode(); + + List results = AsmOpUtils + .validVariables(bindingContext.getMethodProcessor().getMethodNode().localVariables, currentInsnNode); + + AsmOpUtils.push(instructions, results.size()); + AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE); + + for (int i = 0; i < results.size(); ++i) { + AsmOpUtils.dup(instructions); + + AsmOpUtils.push(instructions, i); + + LocalVariableNode variableNode = results.get(i); + AsmOpUtils.loadVar(instructions, Type.getType(variableNode.desc), variableNode.index); + AsmOpUtils.box(instructions, Type.getType(variableNode.desc)); + + AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE); + } + + } + + @Override + public Type getType(BindingContext bindingContext) { + return AsmOpUtils.OBJECT_TYPE; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodBinding.java new file mode 100644 index 000000000..d3ba3c785 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodBinding.java @@ -0,0 +1,52 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * @author hengyunabc + * + */ +public class MethodBinding extends Binding{ + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + // 先获取类本身的 class ,再调用 getDeclaredMethod ,它需要一个变长参数,实际上要传一个数组 + /** + * @see java.lang.Class.getDeclaredMethod(String, Class...) + */ + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + AsmOpUtils.ldc(instructions, Type.getObjectType(methodProcessor.getOwner())); + + AsmOpUtils.push(instructions, methodProcessor.getMethodNode().name); + + Type[] argumentTypes = Type.getMethodType(methodProcessor.getMethodNode().desc).getArgumentTypes(); + + AsmOpUtils.push(instructions, argumentTypes.length); + AsmOpUtils.newArray(instructions, Type.getType(Class.class)); + + for(int i = 0; i < argumentTypes.length; ++i) { + AsmOpUtils.dup(instructions); + + AsmOpUtils.push(instructions, i); + + AsmOpUtils.ldc(instructions, argumentTypes[i]); + AsmOpUtils.arrayStore(instructions, Type.getType(Class.class)); + } + + MethodInsnNode declaredMethodInsnNode = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(Class.class).getInternalName(), + "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false); + instructions.add(declaredMethodInsnNode); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(java.lang.reflect.Method.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodDeclarationBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodDeclarationBinding.java new file mode 100644 index 000000000..bd9d442e3 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodDeclarationBinding.java @@ -0,0 +1,30 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * TODO 提供一个完整的 method 的string,包含类名,并不是desc?用户可以自己提取descs method的定义,前面是 public + * /static 这些关键字,是有限的几个。后面是 throws ,的异常信息。 或者做一下取巧比如把 classname | methoname | desc 之类连起一个String + * + * @author hengyunabc + * + */ +public class MethodDeclarationBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); +// AsmOpUtils.ldc(instructions, AsmUtils.methodDeclaration(Type.getObjectType(methodProcessor.getOwner()), +// methodProcessor.getMethodNode())); + AsmOpUtils.ldc(instructions, methodProcessor.getMethodNode().desc); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodInfoBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodInfoBinding.java new file mode 100644 index 000000000..849bf661f --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodInfoBinding.java @@ -0,0 +1,30 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * method name | method desc 的方式组织 + * + * TODO 是否要有 line number ? + * + * @author hengyunabc 2020-05-16 + * + */ +public class MethodInfoBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + MethodNode methodNode = methodProcessor.getMethodNode(); + AsmOpUtils.ldc(instructions, methodNode.name + '|' + methodNode.desc); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodNameBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodNameBinding.java new file mode 100644 index 000000000..148d5178f --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodNameBinding.java @@ -0,0 +1,25 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +/** + * @author hengyunabc + * + */ +public class MethodNameBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + AsmOpUtils.ldc(instructions, methodProcessor.getMethodNode().name); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(String.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MonitorBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MonitorBinding.java new file mode 100644 index 000000000..8dcffb6e8 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MonitorBinding.java @@ -0,0 +1,41 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; + +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.asm.location.Location.SyncEnterLocation; +import com.taobao.arthas.bytekit.asm.location.Location.SyncExitLocation; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class MonitorBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + + if (location.isWhenComplete()) { + throw new IllegalArgumentException("MonitorBinding only support location whenComplete is false."); + } + + if (location instanceof SyncEnterLocation || location instanceof SyncExitLocation) { + LocalVariableNode monitorVariableNode = bindingContext.getMethodProcessor().initMonitorVariableNode(); + AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_TYPE, monitorVariableNode.index); + } else { + throw new IllegalArgumentException( + "MonitorBinding only support SyncEnterLocation or SyncExitLocation. location: " + location); + } + } + + @Override + public boolean fromStack() { + return true; + } + + @Override + public Type getType(BindingContext bindingContext) { + return AsmOpUtils.OBJECT_TYPE; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ReturnBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ReturnBinding.java new file mode 100644 index 000000000..66c959ba5 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ReturnBinding.java @@ -0,0 +1,43 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; + +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; + +public class ReturnBinding extends Binding { + + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + //check location + + Location location = bindingContext.getLocation(); + + if (!AsmOpUtils.isReturnCode(location.getInsnNode().getOpcode())) { + throw new IllegalArgumentException("current location is not return location. location: " + location); + } + + Type returnType = bindingContext.getMethodProcessor().getReturnType(); + if(returnType.equals(Type.VOID_TYPE)) { + AsmOpUtils.push(instructions, null); + }else { + LocalVariableNode returnVariableNode = bindingContext.getMethodProcessor().initReturnVariableNode(); + AsmOpUtils.loadVar(instructions, returnType, returnVariableNode.index); + } + + } + + @Override + public boolean fromStack() { + return true; + } + + @Override + public Type getType(BindingContext bindingContext) { + return bindingContext.getMethodProcessor().getReturnType(); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/StackSaver.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/StackSaver.java new file mode 100644 index 000000000..2f5b22aa1 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/StackSaver.java @@ -0,0 +1,23 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +/** + * 在 return/throw/invoke 等location时,需要把栈上的值保存到locals里 + * @author hengyunabc + * + */ +public interface StackSaver { + /** + * 有可能在两个地方被调用。1: 在最开始保存栈上的值时, 2: callback函数有返回值,想更新这个值时。stackSaver自己内部要保证保存的locals index是一致的 + * @param instructions + * @param bindingContext + */ + public void store(InsnList instructions, BindingContext bindingContext); + + public void load(InsnList instructions, BindingContext bindingContext); + + public Type getType(BindingContext bindingContext); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThisBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThisBinding.java new file mode 100644 index 000000000..1e90140a4 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThisBinding.java @@ -0,0 +1,18 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +public class ThisBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + bindingContext.getMethodProcessor().loadThis(instructions); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Object.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThrowableBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThrowableBinding.java new file mode 100644 index 000000000..f05f5399d --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThrowableBinding.java @@ -0,0 +1,30 @@ +package com.taobao.arthas.bytekit.asm.binding; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; + +/** + * TODO 要检查 location 是否是合法的 + * @author hengyunabc + * + */ +public class ThrowableBinding extends Binding { + + @Override + public boolean fromStack() { + return true; + } + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + // TODO 这里从 StackSaver 里取是否合理? + bindingContext.getStackSaver().load(instructions, bindingContext); + // 是否要 check cast ? + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Throwable.class); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParser.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParser.java new file mode 100644 index 000000000..c507e3846 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParser.java @@ -0,0 +1,11 @@ +package com.taobao.arthas.bytekit.asm.binding.annotation; + +import java.lang.annotation.Annotation; + +import com.taobao.arthas.bytekit.asm.binding.Binding; + +public interface BindingParser { + + public Binding parse(Annotation annotation); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParserHandler.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParserHandler.java new file mode 100644 index 000000000..b898f6002 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParserHandler.java @@ -0,0 +1,15 @@ +package com.taobao.arthas.bytekit.asm.binding.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.ANNOTATION_TYPE) +public @interface BindingParserHandler { + + Class parser(); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/Instrument.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/Instrument.java new file mode 100644 index 000000000..1dc51dba6 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/Instrument.java @@ -0,0 +1,35 @@ +package com.taobao.arthas.bytekit.asm.inst; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + + +/** + * 在这里支持配置一个 error hander ?做为插入的异常处理的 + * + * 按名字匹配,按模糊匹配??有没有这样子的需求?,按interface匹配,按基础类继承的匹配 + * + * 函数的匹配,直接是名字一样,desc 一样的。 匹配有 annotation 的 + * + * 只有 NewField 才是加新的field,原来类里有的field,就直接写上就可以了。 + * @author hengyunabc + * + */ +@Target({ java.lang.annotation.ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Instrument { +// Instrumentation +// MatchType type() default MatchType.ExactClass; + + String[] Class() default {}; + String[] BaseClass() default {}; + String[] Interface() default {}; + + String originalName() default ""; + + Class suppress() default Throwable.class; + + Class suppressHandler() default Void.class; +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/InstrumentApi.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/InstrumentApi.java new file mode 100644 index 000000000..d9e48a5db --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/InstrumentApi.java @@ -0,0 +1,32 @@ +package com.taobao.arthas.bytekit.asm.inst; + + +/** + * + *
+ * 实现这个 invokeOrigin(),需要多步处理:
+ *
+ * 传入要被替换的类,读取到标记了 @Instrument 的类。 类名不一样的话,先替换类名?
+ *
+ * 然后查找所有的 field,如果有标记了 @NewField ,则增加到要被替换的类里。
+ *
+ * 然后查找所有的函数, 再查找是否在 旧类里有同样签名的,如果有,则执行清除行号, 替换 invokeOrigin() ,再 inline 原来的旧函数
+ *
+ * 再替换函数到 旧类里。
+ *
+ * 类名有可能要替换
+ *
+ * 
+ * + * + * + * + * + * @author hengyunabc 2019-02-25 + * + */ +public class InstrumentApi { + public static final T invokeOrigin() { + return null; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/NewField.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/NewField.java new file mode 100644 index 000000000..3e0707a04 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/NewField.java @@ -0,0 +1,10 @@ +package com.taobao.arthas.bytekit.asm.inst; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ java.lang.annotation.ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface NewField { +} \ No newline at end of file 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..977a42050 --- /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 com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.Method; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.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..b45d22eba --- /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 com.alibaba.arthas.deps.org.objectweb.asm.Label; +import com.alibaba.arthas.deps.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/asm/interceptor/EnterInteceptor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/EnterInteceptor.java new file mode 100644 index 000000000..5ff6fc0f6 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/EnterInteceptor.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +public class EnterInteceptor { + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExceptionInterceptor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExceptionInterceptor.java new file mode 100644 index 000000000..9c44d2bf1 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExceptionInterceptor.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +public class ExceptionInterceptor { + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExitInterceptor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExitInterceptor.java new file mode 100644 index 000000000..894cf1996 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExitInterceptor.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +public class ExitInterceptor { + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/Inteceptor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/Inteceptor.java new file mode 100644 index 000000000..ac690943b --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/Inteceptor.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +public interface Inteceptor { + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorMethodConfig.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorMethodConfig.java new file mode 100644 index 000000000..33fba23e0 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorMethodConfig.java @@ -0,0 +1,71 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import java.util.List; + +import com.taobao.arthas.bytekit.asm.binding.Binding; + +public class InterceptorMethodConfig { + + private boolean inline; + + private String owner; + + private String methodName; + + private String methodDesc; + + private List bindings; + + /** + * 插入的代码用 try/catch 包围的异常类型 + */ + private String suppress; + + public boolean isInline() { + return inline; + } + + public void setInline(boolean inline) { + this.inline = inline; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String getMethodDesc() { + return methodDesc; + } + + public void setMethodDesc(String methodDesc) { + this.methodDesc = methodDesc; + } + + public List getBindings() { + return bindings; + } + + public void setBindings(List bindings) { + this.bindings = bindings; + } + + public String getSuppress() { + return suppress; + } + + public void setSuppress(String suppress) { + this.suppress = suppress; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorProcessor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorProcessor.java new file mode 100644 index 000000000..739d9eb79 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorProcessor.java @@ -0,0 +1,255 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.TryCatchBlock; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.binding.BindingContext; +import com.taobao.arthas.bytekit.asm.binding.StackSaver; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class InterceptorProcessor { + + private LocationMatcher locationMatcher; + + /** + * 插入的回调函数的配置 + */ + private InterceptorMethodConfig interceptorMethodConfig; + + /** + * 插入的代码被 try/catch 包围的配置,注意有一些location插入try/catch会可能失败,因为不能确切知道栈上的情况 + */ + private InterceptorMethodConfig exceptionHandlerConfig; + + /** + * 加载inlne类所需要的ClassLoader + */ + private ClassLoader classLoader; + + public InterceptorProcessor(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public List process(MethodProcessor methodProcessor) throws Exception { + List locations = locationMatcher.match(methodProcessor); + + List interceptorBindings = interceptorMethodConfig.getBindings(); + + for (Location location : locations) { + + // 有三小段代码,1: 保存当前栈上的值的 , 2: 插入的回调的 , 3:恢复当前栈的 + + InsnList toInsert = new InsnList(); + + InsnList stackSaveInsnList = new InsnList(); + InsnList stackLoadInsnList = new InsnList(); + + StackSaver stackSaver = null; + if(location.isStackNeedSave()) { + stackSaver = location.getStackSaver(); + } + BindingContext bindingContext = new BindingContext(location, methodProcessor, stackSaver); + + if(stackSaver != null) { + stackSaver.store(stackSaveInsnList, bindingContext); + stackSaver.load(stackLoadInsnList, bindingContext); + } + + + Type methodType = Type.getMethodType(interceptorMethodConfig.getMethodDesc()); + Type[] argumentTypes = methodType.getArgumentTypes(); + // 检查回调函数的参数和 binding数一致 + if(interceptorBindings.size() != argumentTypes.length) { + throw new IllegalArgumentException("interceptorBindings size no equals with interceptorMethod args size."); + } + + // 把当前栈上的数据保存起来 + int fromStackBindingCount = 0; + for (Binding binding : interceptorBindings) { + if(binding.fromStack()) { + fromStackBindingCount++; + } + } + // 只允许一个binding从栈上保存数据 + if(fromStackBindingCount > 1) { + throw new IllegalArgumentException("interceptorBindings have more than one from stack Binding."); + } + + + // 组装好要调用的 static 函数的参数 + for(int i = 0 ; i < argumentTypes.length; ++i) { + Binding binding = interceptorBindings.get(i); + binding.pushOntoStack(toInsert, bindingContext); + // 检查 回调函数的参数类型,看是否要box一下 ,检查是否原始类型就可以了。 + // 只有类型不一样时,才需要判断。比如两个都是 long,则不用判断 + Type bindingType = binding.getType(bindingContext); + if(!bindingType.equals(argumentTypes[i])) { + if(AsmOpUtils.needBox(bindingType)) { + AsmOpUtils.box(toInsert, binding.getType(bindingContext)); + } + } + } + + // TODO 要检查 binding 和 回调的函数的参数类型是否一致。回调函数的类型可以是 Object,或者super。但是不允许一些明显的类型问题,比如array转到int + + toInsert.add(new MethodInsnNode(Opcodes.INVOKESTATIC, interceptorMethodConfig.getOwner(), interceptorMethodConfig.getMethodName(), + interceptorMethodConfig.getMethodDesc(), false)); + + if (!methodType.getReturnType().equals(Type.VOID_TYPE)) { + if (location.canChangeByReturn()) { + // 当回调函数有返回值时,需要更新到之前保存的栈上 + // TODO 这里应该有 type 的问题?需要检查是否要 box + Type returnType = methodType.getReturnType(); + Type stackSaverType = stackSaver.getType(bindingContext); + if (!returnType.equals(stackSaverType)) { + AsmOpUtils.unbox(toInsert, stackSaverType); + } + stackSaver.store(toInsert, bindingContext); + } else { + // 没有使用到回调函数的返回值的话,则需要从栈上清理掉 + int size = methodType.getReturnType().getSize(); + if (size == 1) { + AsmOpUtils.pop(toInsert); + } else if (size == 2) { + AsmOpUtils.pop2(toInsert); + } + } + } + + + TryCatchBlock errorHandlerTryCatchBlock = null; + // 生成的代码用try/catch包围起来 + if( exceptionHandlerConfig != null) { + LabelNode gotoDest = new LabelNode(); + + errorHandlerTryCatchBlock = new TryCatchBlock(methodProcessor.getMethodNode(), exceptionHandlerConfig.getSuppress()); + toInsert.insertBefore(toInsert.getFirst(), errorHandlerTryCatchBlock.getStartLabelNode()); + toInsert.add(new JumpInsnNode(Opcodes.GOTO, gotoDest)); + toInsert.add(errorHandlerTryCatchBlock.getEndLabelNode()); +// 这里怎么把栈上的数据保存起来?还是强制回调函数的第一个参数是 exception,后面的binding可以随便搞。 + +// MethodInsnNode printStackTrace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false); +// toInsert.add(printStackTrace); + + errorHandler(methodProcessor, toInsert); + + toInsert.add(gotoDest); + } + +// System.err.println(Decompiler.toString(toInsert)); + + + stackSaveInsnList.add(toInsert); + stackSaveInsnList.add(stackLoadInsnList); + if (location.isWhenComplete()) { + methodProcessor.getMethodNode().instructions.insert(location.getInsnNode(), stackSaveInsnList); + }else { + methodProcessor.getMethodNode().instructions.insertBefore(location.getInsnNode(), stackSaveInsnList); + } + + if( exceptionHandlerConfig != null) { + errorHandlerTryCatchBlock.sort(); + } + + // inline callback + if(interceptorMethodConfig.isInline()) { +// Class forName = Class.forName(Type.getObjectType(interceptorMethodConfig.getOwner()).getClassName()); + + Class forName = classLoader.loadClass(Type.getObjectType(interceptorMethodConfig.getOwner()).getClassName()); + MethodNode toInlineMethodNode = AsmUtils.findMethod(AsmUtils.loadClass(forName).methods, interceptorMethodConfig.getMethodName(), interceptorMethodConfig.getMethodDesc()); + + methodProcessor.inline(interceptorMethodConfig.getOwner(), toInlineMethodNode); + } + if(exceptionHandlerConfig != null && exceptionHandlerConfig.isInline()) { +// Class forName = Class.forName(Type.getObjectType(exceptionHandlerConfig.getOwner()).getClassName()); + + Class forName = classLoader.loadClass(Type.getObjectType(exceptionHandlerConfig.getOwner()).getClassName()); + MethodNode toInlineMethodNode = AsmUtils.findMethod(AsmUtils.loadClass(forName).methods, exceptionHandlerConfig.getMethodName(), exceptionHandlerConfig.getMethodDesc()); + + methodProcessor.inline(exceptionHandlerConfig.getOwner(), toInlineMethodNode); + } + +// System.err.println(Decompiler.toString(methodProcessor.getMethodNode())); +// System.err.println(AsmUtils.toASMCode(methodProcessor.getMethodNode())); + } + + return locations; + } + + private void errorHandler(MethodProcessor methodProcessor, InsnList insnList) { +// MethodInsnNode printStackTrace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false); +// insnList.add(printStackTrace); + // 第一个参数要求是 throwable ,或者一个exception ? + // 有很多 binding 并不能使用的,因为location不生效 + BindingContext bindingContext = new BindingContext(null, methodProcessor, null); + Type methodType = Type.getMethodType(this.exceptionHandlerConfig.getMethodDesc()); + Type[] argumentTypes = methodType.getArgumentTypes(); + List bindings = this.exceptionHandlerConfig.getBindings(); + if(bindings.size() + 1 != argumentTypes.length) { + throw new IllegalArgumentException("errorHandler bindings size do not match error method args size."); + } + if(!argumentTypes[0].equals(Type.getType(Throwable.class))) { + throw new IllegalArgumentException("errorHandler method first arg type must be Throwable."); + } + // 组装好要调用的 static 函数的参数 + for(Binding binding: bindings) { + if(binding.fromStack()) { + throw new IllegalArgumentException("errorHandler binding can not load value from stack!"); + } + binding.pushOntoStack(insnList, bindingContext); + // 检查 回调函数的参数类型,看是否要box一下 ,检查是否原始类型就可以了。 + if(AsmOpUtils.needBox(binding.getType(bindingContext))) { + AsmOpUtils.box(insnList, binding.getType(bindingContext)); + } + } + + insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, exceptionHandlerConfig.getOwner(), exceptionHandlerConfig.getMethodName(), + exceptionHandlerConfig.getMethodDesc(), false)); + + int size = methodType.getReturnType().getSize(); + if (size == 1) { + AsmOpUtils.pop(insnList); + } else if (size == 2) { + AsmOpUtils.pop2(insnList); + } + } + + public LocationMatcher getLocationMatcher() { + return locationMatcher; + } + + public void setLocationMatcher(LocationMatcher locationMatcher) { + this.locationMatcher = locationMatcher; + } + + public InterceptorMethodConfig getInterceptorMethodConfig() { + return interceptorMethodConfig; + } + + public void setInterceptorMethodConfig(InterceptorMethodConfig interceptorMethodConfig) { + this.interceptorMethodConfig = interceptorMethodConfig; + } + + public InterceptorMethodConfig getExceptionHandlerConfig() { + return exceptionHandlerConfig; + } + + public void setExceptionHandlerConfig(InterceptorMethodConfig exceptionHandlerConfig) { + this.exceptionHandlerConfig = exceptionHandlerConfig; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtEnter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtEnter.java new file mode 100644 index 000000000..163f7d1e8 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtEnter.java @@ -0,0 +1,64 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter.EnterInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.EnterLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = EnterInterceptorProcessorParser.class) +public @interface AtEnter { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + class EnterInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + LocationMatcher locationMatcher = new EnterLocationMatcher(); + interceptorProcessor.setLocationMatcher(locationMatcher); + + AtEnter atEnter = (AtEnter) annotationOnMethod; + interceptorMethodConfig.setInline(atEnter.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atEnter.suppress(), atEnter.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExceptionExit.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExceptionExit.java new file mode 100644 index 000000000..644e8d17c --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExceptionExit.java @@ -0,0 +1,69 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit.ExceptionExitInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.ExceptionExitLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = ExceptionExitInterceptorProcessorParser.class) +public @interface AtExceptionExit { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + Class onException() default Throwable.class; + + class ExceptionExitInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + + AtExceptionExit atExceptionExit = (AtExceptionExit) annotationOnMethod; + interceptorMethodConfig.setInline(atExceptionExit.inline()); + + LocationMatcher locationMatcher = new ExceptionExitLocationMatcher(Type.getInternalName(atExceptionExit.onException()));; + + interceptorProcessor.setLocationMatcher(locationMatcher); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atExceptionExit.suppress(), atExceptionExit.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExit.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExit.java new file mode 100644 index 000000000..6bc354c4f --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExit.java @@ -0,0 +1,63 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit.ExitInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.ExitLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = ExitInterceptorProcessorParser.class) +public @interface AtExit { + boolean inline() default true; + Class suppress() default None.class; + Class suppressHandler() default Void.class; + + class ExitInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + LocationMatcher locationMatcher = new ExitLocationMatcher(); + interceptorProcessor.setLocationMatcher(locationMatcher); + + AtExit atExit = (AtExit) annotationOnMethod; + interceptorMethodConfig.setInline(atExit.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atExit.suppress(), atExit.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtFieldAccess.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtFieldAccess.java new file mode 100644 index 000000000..a2888fe41 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtFieldAccess.java @@ -0,0 +1,91 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtFieldAccess.FieldAccessInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.FieldAccessLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = FieldAccessInterceptorProcessorParser.class) +public @interface AtFieldAccess { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + java.lang.Class owner() default Void.class; + + java.lang.Class type() default Void.class; + + String name(); + + int count() default -1; + + int flags() default Location.ACCESS_READ | Location.ACCESS_WRITE; + + boolean whenComplete() default false; + + class FieldAccessInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtFieldAccess atFieldAccess = (AtFieldAccess) annotationOnMethod; + + String ownerClass = null; + String fieldDesc = null; + if(! atFieldAccess.owner().equals(Void.class)) { + ownerClass = Type.getType(atFieldAccess.owner()).getInternalName(); + } + if(!atFieldAccess.type().equals(Void.class)) { + fieldDesc = Type.getType(atFieldAccess.type()).getDescriptor(); + } + + LocationMatcher locationMatcher = new FieldAccessLocationMatcher( + ownerClass, + fieldDesc, atFieldAccess.name(), atFieldAccess.count(), + atFieldAccess.flags(), atFieldAccess.whenComplete()); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atFieldAccess.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atFieldAccess.suppress(), atFieldAccess.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvoke.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvoke.java new file mode 100644 index 000000000..455adb016 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvoke.java @@ -0,0 +1,98 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke.InvokeInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.InvokeLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = InvokeInterceptorProcessorParser.class) +public @interface AtInvoke { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + Class owner() default Void.class; + + String name(); + + String desc() default ""; + + int count() default -1; + + boolean whenComplete() default false; + + /** + * method name excludes + * @return + */ + String[] excludes() default {}; + + class InvokeInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtInvoke atInvoke = (AtInvoke) annotationOnMethod; + + String owner = null; + String desc = null; + if (!atInvoke.owner().equals(Void.class)) { + owner = Type.getType(atInvoke.owner()).getInternalName(); + } + if (atInvoke.desc().isEmpty()) { + desc = null; + } + + List excludes = new ArrayList(); + for (String exclude : atInvoke.excludes()) { + excludes.add(exclude); + } + + LocationMatcher locationMatcher = new InvokeLocationMatcher(owner, atInvoke.name(), desc, atInvoke.count(), + atInvoke.whenComplete(), excludes); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atInvoke.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atInvoke.suppress(), atInvoke.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvokeException.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvokeException.java new file mode 100644 index 000000000..f3aa6639f --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvokeException.java @@ -0,0 +1,102 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvokeException.InvokeExceptionInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.InvokeLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +/** + * + * @author hengyunabc 2020-05-03 + * + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = InvokeExceptionInterceptorProcessorParser.class) +public @interface AtInvokeException { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + Class owner() default Void.class; + + /** + * method name + * + * @return + */ + String name(); + + String desc() default ""; + + int count() default -1; + + String[] excludes() default {}; + + class InvokeExceptionInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor( + method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtInvokeException atInvokeException = (AtInvokeException) annotationOnMethod; + + String owner = null; + String desc = null; + if (!atInvokeException.owner().equals(Void.class)) { + owner = Type.getType(atInvokeException.owner()).getInternalName(); + } + if (atInvokeException.desc().isEmpty()) { + desc = null; + } + + List excludes = new ArrayList(); + for (String exclude : atInvokeException.excludes()) { + excludes.add(exclude); + } + + LocationMatcher locationMatcher = new InvokeLocationMatcher(owner, atInvokeException.name(), desc, + atInvokeException.count(), true, excludes, true); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atInvokeException.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atInvokeException.suppress(), atInvokeException.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtLine.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtLine.java new file mode 100644 index 000000000..e5eb98678 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtLine.java @@ -0,0 +1,67 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtLine.LineInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.LineLocationMatcher; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = LineInterceptorProcessorParser.class) +public @interface AtLine { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + int[] lines(); + + class LineInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtLine atLine = (AtLine) annotationOnMethod; + LocationMatcher locationMatcher = new LineLocationMatcher(atLine.lines()); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atLine.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atLine.suppress(), atLine.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncEnter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncEnter.java new file mode 100644 index 000000000..b2e81da0d --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncEnter.java @@ -0,0 +1,70 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncEnter.SyncEnterInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; +import com.taobao.arthas.bytekit.asm.location.SyncLocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = SyncEnterInterceptorProcessorParser.class) +public @interface AtSyncEnter { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + int count() default -1; + boolean whenComplete() default false; + + class SyncEnterInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtSyncEnter atSyncEnter = (AtSyncEnter) annotationOnMethod; + + LocationMatcher locationMatcher = new SyncLocationMatcher(Opcodes.MONITORENTER, atSyncEnter.count(), atSyncEnter.whenComplete()); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atSyncEnter.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atSyncEnter.suppress(), atSyncEnter.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncExit.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncExit.java new file mode 100644 index 000000000..42007b926 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncExit.java @@ -0,0 +1,70 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncExit.SyncExitInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; +import com.taobao.arthas.bytekit.asm.location.SyncLocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = SyncExitInterceptorProcessorParser.class) +public @interface AtSyncExit { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + int count() default -1; + boolean whenComplete() default false; + + class SyncExitInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtSyncExit atSyncExit = (AtSyncExit) annotationOnMethod; + + LocationMatcher locationMatcher = new SyncLocationMatcher(Opcodes.MONITOREXIT, atSyncExit.count(), atSyncExit.whenComplete()); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atSyncExit.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atSyncExit.suppress(), atSyncExit.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtThrow.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtThrow.java new file mode 100644 index 000000000..d2a7c681f --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtThrow.java @@ -0,0 +1,67 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Method; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtThrow.ThrowInterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; +import com.taobao.arthas.bytekit.asm.location.LocationMatcher; +import com.taobao.arthas.bytekit.asm.location.ThrowLocationMatcher; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +@InterceptorParserHander(parserHander = ThrowInterceptorProcessorParser.class) +public @interface AtThrow { + boolean inline() default true; + + Class suppress() default None.class; + + Class suppressHandler() default Void.class; + + int count() default -1; + + class ThrowInterceptorProcessorParser implements InterceptorProcessorParser { + + @Override + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + AtThrow atThrow = (AtThrow) annotationOnMethod; + LocationMatcher locationMatcher = new ThrowLocationMatcher(atThrow.count()); + interceptorProcessor.setLocationMatcher(locationMatcher); + + interceptorMethodConfig.setInline(atThrow.inline()); + + List bindings = BindingParserUtils.parseBindings(method); + + interceptorMethodConfig.setBindings(bindings); + + InterceptorMethodConfig errorHandlerMethodConfig = ExceptionHandlerUtils + .errorHandlerMethodConfig(atThrow.suppress(), atThrow.suppressHandler()); + if (errorHandlerMethodConfig != null) { + interceptorProcessor.setExceptionHandlerConfig(errorHandlerMethodConfig); + } + + return interceptorProcessor; + } + + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/BindingParserUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/BindingParserUtils.java new file mode 100644 index 000000000..5228c3014 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/BindingParserUtils.java @@ -0,0 +1,36 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParser; +import com.taobao.arthas.bytekit.asm.binding.annotation.BindingParserHandler; +import com.taobao.arthas.bytekit.utils.InstanceUtils; + +public class BindingParserUtils { + + public static List parseBindings(Method method) { + // 从 parameter 里解析出来 binding + List bindings = new ArrayList(); + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + for (int parameterIndex = 0; parameterIndex < parameterAnnotations.length; ++parameterIndex) { + Annotation[] annotationsOnParameter = parameterAnnotations[parameterIndex]; + for (int j = 0; j < annotationsOnParameter.length; ++j) { + + Annotation[] annotationsOnBinding = annotationsOnParameter[j].annotationType().getAnnotations(); + for (Annotation annotationOnBinding : annotationsOnBinding) { + if (BindingParserHandler.class.isAssignableFrom(annotationOnBinding.annotationType())) { + BindingParserHandler bindingParserHandler = (BindingParserHandler) annotationOnBinding; + BindingParser bindingParser = InstanceUtils.newInstance(bindingParserHandler.parser()); + Binding binding = bindingParser.parse(annotationsOnParameter[j]); + bindings.add(binding); + } + } + } + } + return bindings; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/EmptySuppressHandler.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/EmptySuppressHandler.java new file mode 100644 index 000000000..4568199d9 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/EmptySuppressHandler.java @@ -0,0 +1,10 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +public class EmptySuppressHandler { + + @ExceptionHandler + public static void onSuppress() { + + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandler.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandler.java new file mode 100644 index 000000000..384906832 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandler.java @@ -0,0 +1,13 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.METHOD) +public @interface ExceptionHandler { + boolean inline() default true; +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandlerUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandlerUtils.java new file mode 100644 index 000000000..65bfce33b --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandlerUtils.java @@ -0,0 +1,85 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.binding.ThrowableBinding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.taobao.arthas.bytekit.utils.AnnotationUtils; +import com.taobao.arthas.bytekit.utils.ReflectionUtils; +import com.taobao.arthas.bytekit.utils.ReflectionUtils.MethodCallback; +import com.taobao.arthas.bytekit.utils.ReflectionUtils.MethodFilter; + +public class ExceptionHandlerUtils { + + public static InterceptorMethodConfig errorHandlerMethodConfig(Class suppress, Class handlerClass) { + + // TODO 要解析 errorHander Class里的内容 + final InterceptorMethodConfig errorHandlerMethodConfig = new InterceptorMethodConfig(); + + if(suppress.equals(None.class)) { + suppress = Throwable.class; + } + errorHandlerMethodConfig.setSuppress(Type.getType(suppress).getInternalName()); + + if (!handlerClass.equals(Void.class)) { + // find method with @ExceptionHandler + ReflectionUtils.doWithMethods(handlerClass, new MethodCallback() { + + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + for (Annotation onMethodAnnotation : method.getAnnotations()) { + if (ExceptionHandler.class.isAssignableFrom(onMethodAnnotation.annotationType())) { + + if (!Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException("method must be static. method: " + method); + } + + ExceptionHandler handler = (ExceptionHandler) onMethodAnnotation; + + errorHandlerMethodConfig.setInline(handler.inline()); + + List errorHandlerBindings = BindingParserUtils.parseBindings(method); + // 检查第一个 bidning要是 Throwable Binding + if (errorHandlerBindings.size() == 0) { + throw new IllegalArgumentException( + "error handler bingins must have at least a binding"); + } + if (!(errorHandlerBindings.get(0) instanceof ThrowableBinding)) { + throw new IllegalArgumentException( + "error handler bingins first binding must be ThrowableBinding."); + } + // 去掉第一个 ThrowableBinding + // TODO 可能要copy一下,保证可以修改成功 + errorHandlerBindings.remove(0); + errorHandlerMethodConfig.setBindings(errorHandlerBindings); + errorHandlerMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + errorHandlerMethodConfig.setMethodName(method.getName()); + errorHandlerMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + } + } + + } + + }, new MethodFilter() { + + @Override + public boolean matches(Method method) { + return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null; + } + + }); + } + + if (errorHandlerMethodConfig.getMethodDesc() == null) { + return null; + } + + return errorHandlerMethodConfig; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/InterceptorParserHander.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/InterceptorParserHander.java new file mode 100644 index 000000000..82603f0d5 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/InterceptorParserHander.java @@ -0,0 +1,17 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import com.taobao.arthas.bytekit.asm.interceptor.parser.InterceptorProcessorParser; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@java.lang.annotation.Target(ElementType.ANNOTATION_TYPE) +public @interface InterceptorParserHander { + + Class parserHander(); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/None.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/None.java new file mode 100644 index 000000000..f4c87c178 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/None.java @@ -0,0 +1,13 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +/** + * 用于声明没有异常 + * @author hengyunabc + * + */ +public class None extends Throwable { + private static final long serialVersionUID = 1L; + + private None() { + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/PrintSuppressHandler.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/PrintSuppressHandler.java new file mode 100644 index 000000000..191cdb0d3 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/PrintSuppressHandler.java @@ -0,0 +1,11 @@ +package com.taobao.arthas.bytekit.asm.interceptor.annotation; + +import com.taobao.arthas.bytekit.asm.binding.Binding; + +public class PrintSuppressHandler { + + @ExceptionHandler(inline = true) + public static void onSuppress(@Binding.Throwable Throwable e) { + e.printStackTrace(); + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/DefaultInterceptorClassParser.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/DefaultInterceptorClassParser.java new file mode 100644 index 000000000..f1c703449 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/DefaultInterceptorClassParser.java @@ -0,0 +1,50 @@ +package com.taobao.arthas.bytekit.asm.interceptor.parser; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.InterceptorParserHander; +import com.taobao.arthas.bytekit.utils.InstanceUtils; +import com.taobao.arthas.bytekit.utils.ReflectionUtils; +import com.taobao.arthas.bytekit.utils.ReflectionUtils.MethodCallback; + +public class DefaultInterceptorClassParser implements InterceptorClassParser { + + @Override + public List parse(Class clazz) { + final List result = new ArrayList(); + + MethodCallback methodCallback = new MethodCallback() { + + @Override + public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { + for (Annotation onMethodAnnotation : method.getAnnotations()) { + for (Annotation onAnnotation : onMethodAnnotation.annotationType().getAnnotations()) { + if (InterceptorParserHander.class.isAssignableFrom(onAnnotation.annotationType())) { + + if (!Modifier.isStatic(method.getModifiers())) { + throw new IllegalArgumentException("method must be static. method: " + method); + } + + InterceptorParserHander handler = (InterceptorParserHander) onAnnotation; + InterceptorProcessorParser interceptorProcessorParser = InstanceUtils + .newInstance(handler.parserHander()); + InterceptorProcessor interceptorProcessor = interceptorProcessorParser.parse(method, + onMethodAnnotation); + result.add(interceptorProcessor); + } + } + } + } + + }; + ReflectionUtils.doWithMethods(clazz, methodCallback); + + return result; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorClassParser.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorClassParser.java new file mode 100644 index 000000000..e28c66eb0 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorClassParser.java @@ -0,0 +1,10 @@ +package com.taobao.arthas.bytekit.asm.interceptor.parser; + +import java.util.List; + +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; + +public interface InterceptorClassParser { + + public List parse(Class clazz); +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorProcessorParser.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorProcessorParser.java new file mode 100644 index 000000000..233ff4d24 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorProcessorParser.java @@ -0,0 +1,11 @@ +package com.taobao.arthas.bytekit.asm.interceptor.parser; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; + +public interface InterceptorProcessorParser { + + public InterceptorProcessor parse(Method method, Annotation annotationOnMethod); +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/AccessLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/AccessLocationMatcher.java new file mode 100644 index 000000000..e773b781a --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/AccessLocationMatcher.java @@ -0,0 +1,24 @@ +package com.taobao.arthas.bytekit.asm.location; + +public abstract class AccessLocationMatcher implements LocationMatcher { + protected int count; + + /** + * flags identifying which type of access should be used to identify the + * trigger. this is either ACCESS_READ, ACCESS_WRITE or an OR of these two + * values + */ + protected int flags; + + /** + * flag which is false if the trigger should be inserted before the field + * access is performed and true if it should be inserted after + */ + protected boolean whenComplete; + + AccessLocationMatcher(int count, int flags, boolean whenComplete) { + this.count = count; + this.flags = flags; + this.whenComplete = whenComplete; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/EnterLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/EnterLocationMatcher.java new file mode 100644 index 000000000..ccb93b672 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/EnterLocationMatcher.java @@ -0,0 +1,26 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.EnterLocation; +import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter; + +public class EnterLocationMatcher implements LocationMatcher { + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode enterInsnNode = methodProcessor.getEnterInsnNode(); + + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + if (locationFilter.allow(enterInsnNode, LocationType.ENTER, true)) { + EnterLocation enterLocation = new EnterLocation(enterInsnNode); + locations.add(enterLocation); + } + return locations; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExceptionExitLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExceptionExitLocationMatcher.java new file mode 100644 index 000000000..88fa9343c --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExceptionExitLocationMatcher.java @@ -0,0 +1,39 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.TryCatchBlock; +import com.taobao.arthas.bytekit.asm.location.Location.ExceptionExitLocation; +import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter; + +public class ExceptionExitLocationMatcher implements LocationMatcher { + + private String exception; + + public ExceptionExitLocationMatcher() { + this(Type.getType(Throwable.class).getInternalName()); + } + + public ExceptionExitLocationMatcher(String exception) { + this.exception = exception; + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + TryCatchBlock tryCatchBlock = methodProcessor.initTryCatchBlock(exception); + + LabelNode endLabelNode = tryCatchBlock.getEndLabelNode(); + + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + if (locationFilter.allow(endLabelNode, LocationType.EXCEPTION_EXIT, false)) { + locations.add(new ExceptionExitLocation(tryCatchBlock.getEndLabelNode())); + } + return locations; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExitLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExitLocationMatcher.java new file mode 100644 index 000000000..c532ad123 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExitLocationMatcher.java @@ -0,0 +1,50 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.ExitLocation; +import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter; + +public class ExitLocationMatcher implements LocationMatcher { + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + while (insnNode != null) { + if (insnNode instanceof InsnNode) { + InsnNode node = (InsnNode) insnNode; + if (matchExit(node)) { + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + if (locationFilter.allow(node, LocationType.EXIT, false)) { + ExitLocation ExitLocation = new ExitLocation(node); + locations.add(ExitLocation); + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } + + public boolean matchExit(InsnNode node) { + switch (node.getOpcode()) { + case Opcodes.RETURN: // empty stack + case Opcodes.IRETURN: // 1 before n/a after + case Opcodes.FRETURN: // 1 before n/a after + case Opcodes.ARETURN: // 1 before n/a after + case Opcodes.LRETURN: // 2 before n/a after + case Opcodes.DRETURN: // 2 before n/a after + return true; + } + return false; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/FieldAccessLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/FieldAccessLocationMatcher.java new file mode 100644 index 000000000..fa537e8f3 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/FieldAccessLocationMatcher.java @@ -0,0 +1,96 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldInsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.FieldAccessLocation; + +public class FieldAccessLocationMatcher extends AccessLocationMatcher { + + /** + * maybe null + */ + private String ownerClass; + + /** + * the name of the field being accessed at the point where the trigger point + * should be inserted + */ + private String fieldName; + + /** + * The field's descriptor (see {@link org.objectweb.asm.Type}). maybe null. + */ + private String fieldDesc; + + + public FieldAccessLocationMatcher(String ownerClass, String fieldDesc, String fieldName, int count, int flags, + boolean whenComplete) { + super(count, flags, whenComplete); + this.ownerClass = ownerClass; + this.fieldDesc = fieldDesc; + this.fieldName = fieldName; + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof FieldInsnNode) { + FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode; + + if (matchField(fieldInsnNode)) { + matchedCount++; + if (count <= 0 || count == matchedCount) { + FieldAccessLocation fieldAccessLocation = new FieldAccessLocation(fieldInsnNode, count, flags, whenComplete); + locations.add(fieldAccessLocation); + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } + + private boolean matchField(FieldInsnNode fieldInsnNode) { + if (!fieldName.equals(fieldInsnNode.name)) { + return false; + } + + if (this.fieldDesc != null && !this.fieldDesc.equals(fieldInsnNode.desc)) { + return false; + } + + switch (fieldInsnNode.getOpcode()) { + case Opcodes.GETSTATIC: + case Opcodes.GETFIELD: { + if ((flags & Location.ACCESS_READ) == 0) { + return false; + } + } + break; + case Opcodes.PUTSTATIC: + case Opcodes.PUTFIELD: { + if ((flags & Location.ACCESS_WRITE) == 0) { + return false; + } + } + break; + } + if (ownerClass != null) { + if (!ownerClass.equals(fieldInsnNode.owner)) { + return false; + } + } + return true; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/InvokeLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/InvokeLocationMatcher.java new file mode 100644 index 000000000..844658b29 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/InvokeLocationMatcher.java @@ -0,0 +1,243 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.TryCatchBlock; +import com.taobao.arthas.bytekit.asm.location.Location.InvokeExceptionExitLocation; +import com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation; +import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.MatchUtils; + +/** + * + * @author hengyunabc + * + */ +public class InvokeLocationMatcher implements LocationMatcher { + + /** + * the name of the method being invoked at the point where the trigger point + * should be inserted. maybe null, when null, match all method invoke. + */ + private String methodName; + + /** + * the name of the type to which the method belongs or null if any type will do + */ + private String owner; + + /** + * the method signature in externalised form, maybe null. + */ + private String desc; + + /** + * count identifying which invocation should be taken as the trigger point. if + * not specified as a parameter this defaults to the first invocation. + */ + private int count; + + /** + * flag which is false if the trigger should be inserted before the method + * invocation is performed and true if it should be inserted after + */ + private boolean whenComplete; + + /** + * wildcard matcher to exclude the invoke class, such as java.* to exclude jdk + * invoke. + */ + private List excludes = new ArrayList(); + + private boolean atInvokeExcpetionExit = false; + + public InvokeLocationMatcher(String owner, String methodName, String desc, int count, boolean whenComplete, + List excludes, boolean atInvokeExcpetionExit) { + super(); + this.owner = owner; + this.methodName = methodName; + this.desc = desc; + this.count = count; + this.whenComplete = whenComplete; + this.excludes = excludes; + this.atInvokeExcpetionExit = atInvokeExcpetionExit; + } + + public InvokeLocationMatcher(String owner, String methodName, String desc, int count, boolean whenComplete, + List excludes) { + this(owner, methodName, desc, count, whenComplete, excludes, false); + } + + public InvokeLocationMatcher(String owner, String methodName, String desc, int count, boolean whenComplete) { + this(owner, methodName, desc, count, whenComplete, new ArrayList()); + } + + @Override + public List match(MethodProcessor methodProcessor) { + if (this.atInvokeExcpetionExit) { + return matchForException(methodProcessor); + } + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + + LocationType locationType = whenComplete ? LocationType.INVOKE_COMPLETED : LocationType.INVOKE; + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + + if (matchCall(methodInsnNode)) { + if(locationFilter.allow(methodInsnNode, locationType, this.whenComplete)) { + matchedCount++; + if (count <= 0 || count == matchedCount) { + InvokeLocation invokeLocation = new InvokeLocation(methodInsnNode, count, whenComplete); + locations.add(invokeLocation); + } + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } + + public List matchForException(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + MethodNode methodNode = methodProcessor.getMethodNode(); + + List methodInsnNodes = new ArrayList(); + + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + + LocationType locationType = LocationType.INVOKE_EXCEPTION_EXIT; + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + + if (matchCall(methodInsnNode)) { + if(locationFilter.allow(methodInsnNode, locationType, this.whenComplete)) { + matchedCount++; + if (count <= 0 || count == matchedCount) { + methodInsnNodes.add(methodInsnNode); + } + } + } + } + insnNode = insnNode.getNext(); + } + + // insert try/catch + for (MethodInsnNode methodInsnNode : methodInsnNodes) { + TryCatchBlock tryCatchBlock = new TryCatchBlock(methodNode); + + InsnList toInsert = new InsnList(); + + LabelNode gotoDest = new LabelNode(); + + LabelNode startLabelNode = tryCatchBlock.getStartLabelNode(); + LabelNode endLabelNode = tryCatchBlock.getEndLabelNode(); + + toInsert.add(new JumpInsnNode(Opcodes.GOTO, gotoDest)); + toInsert.add(endLabelNode); + AsmOpUtils.throwException(toInsert); + locations.add(new InvokeExceptionExitLocation(methodInsnNode, endLabelNode)); + + toInsert.add(gotoDest); + + methodNode.instructions.insertBefore(methodInsnNode, startLabelNode); + methodNode.instructions.insert(methodInsnNode, toInsert); + + tryCatchBlock.sort(); + } + + return locations; + } + + private boolean matchCall(MethodInsnNode methodInsnNode) { + + if (methodName != null && !methodName.isEmpty()) { + if (!this.methodName.equals(methodInsnNode.name)) { + return false; + } + } + + if (!excludes.isEmpty()) { + String ownerClassName = Type.getObjectType(methodInsnNode.owner).getClassName(); + for (String exclude : excludes) { + if (MatchUtils.wildcardMatch(ownerClassName, exclude)) { + return false; + } + } + } + + if (this.owner != null && !this.owner.equals(methodInsnNode.owner)) { + return false; + } + + if (this.desc != null && !desc.equals(methodInsnNode.desc)) { + return false; + } + + return true; + + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public boolean isWhenComplete() { + return whenComplete; + } + + public void setWhenComplete(boolean whenComplete) { + this.whenComplete = whenComplete; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LineLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LineLocationMatcher.java new file mode 100644 index 000000000..a4ee285e4 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LineLocationMatcher.java @@ -0,0 +1,60 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LineNumberNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.LineLocation; + +public class LineLocationMatcher implements LocationMatcher { + + private List targetLines = Collections.emptyList(); + + public LineLocationMatcher(int... targetLines) { + if (targetLines != null) { + ArrayList result = new ArrayList(targetLines.length); + for (int targetLine : targetLines) { + result.add(targetLine); + } + this.targetLines = result; + } + } + + public LineLocationMatcher(List targetLines) { + this.targetLines = targetLines; + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + LineNumberNode lineNumberNode = (LineNumberNode) insnNode; + if (match(lineNumberNode.line)) { + locations.add(new LineLocation(lineNumberNode, lineNumberNode.line)); + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } + + private boolean match(int line) { + for (int targetLine : targetLines) { + if (targetLine == -1) { + return true; + } else if (line == targetLine) { + return true; + } + + } + return false; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/Location.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/Location.java new file mode 100644 index 000000000..e024b577d --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/Location.java @@ -0,0 +1,696 @@ +package com.taobao.arthas.bytekit.asm.location; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.binding.BindingContext; +import com.taobao.arthas.bytekit.asm.binding.StackSaver; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +/** + * Specifies a location in a method at which a rule trigger should be inserted + */ +public abstract class Location { + + AbstractInsnNode insnNode; + + boolean whenComplete = false; + + boolean stackNeedSave = false; + + public Location(AbstractInsnNode insnNode) { + this(insnNode, false); + } + + public Location(AbstractInsnNode insnNode, boolean whenComplete) { + this.insnNode = insnNode; + this.whenComplete = whenComplete; + } + + public boolean isWhenComplete() { + return whenComplete; + } + + public AbstractInsnNode getInsnNode() { + return insnNode; + } + + /** + * 标记在这个location,栈上原来的值可以被 callback 函数的return值替换掉 + * + * @return + */ + public boolean canChangeByReturn() { + return false; + } + + public boolean isStackNeedSave() { + return stackNeedSave; + } + + public StackSaver getStackSaver() { + throw new UnsupportedOperationException("this location do not StackSaver, type:" + getLocationType()); + } + + /** + * identify the type of this location + * + * @return the type of this location + */ + public abstract LocationType getLocationType(); + + /** + * flag indicating that a field access location refers to field READ operations + */ + public static final int ACCESS_READ = 1; + + /** + * flag indicating that a field access location refers to field WRITE operations + */ + public static final int ACCESS_WRITE = 2; + + /** + * location identifying a method enter trigger point + */ + static class EnterLocation extends Location { + public EnterLocation(AbstractInsnNode enterInsnNode) { + super(enterInsnNode); + this.insnNode = enterInsnNode; + } + + public LocationType getLocationType() { + return LocationType.ENTER; + } + + } + + /** + * location identifying a method line trigger point + */ + public static class LineLocation extends Location { + /** + * the line at which the trigger point should be inserted + */ + private int targetLine; + + public LineLocation(AbstractInsnNode insnNode, int targetLine) { + super(insnNode); + this.targetLine = targetLine; + } + + public LocationType getLocationType() { + return LocationType.LINE; + } + + } + + /** + * location identifying a generic access trigger point + */ + private static abstract class AccessLocation extends Location { + /** + * count identifying which access should be taken as the trigger point. if not + * specified as a parameter this defaults to the first access. + */ + protected int count; + + /** + * flags identifying which type of access should be used to identify the + * trigger. this is either ACCESS_READ, ACCESS_WRITE or an OR of these two + * values + */ + protected int flags; + + protected AccessLocation(AbstractInsnNode insnNode, int count, int flags, boolean whenComplete) { + super(insnNode, whenComplete); + this.count = count; + this.flags = flags; + } + + public LocationType getLocationType() { + if ((flags & ACCESS_WRITE) != 0) { + if (whenComplete) { + return LocationType.WRITE_COMPLETED; + } else { + return LocationType.WRITE; + } + } else { + if (whenComplete) { + return LocationType.READ_COMPLETED; + } else { + return LocationType.READ; + } + } + } + } + + /** + * location identifying a field access trigger point + */ + public static class FieldAccessLocation extends AccessLocation { + + public FieldAccessLocation(FieldInsnNode fieldInsnNode, int count, int flags, boolean whenComplete) { + super(fieldInsnNode, count, flags, whenComplete); + } + + } + + /** + * location identifying a variable access trigger point + */ + private static class VariableAccessLocation extends AccessLocation { + /** + * the name of the variable being accessed at the point where the trigger point + * should be inserted + */ + private String variableName; + + /** + * flag which is true if the name is a method parameter index such as $0, $1 etc + * otherwise false + */ + private boolean isIndex; + + protected VariableAccessLocation(AbstractInsnNode insnNode, String variablename, int count, int flags, + boolean whenComplete) { + super(insnNode, count, flags, whenComplete); + this.variableName = variablename; + isIndex = variablename.matches("[0-9]+"); + } + + public LocationType getLocationType() { + if ((flags & ACCESS_WRITE) != 0) { + if (whenComplete) { + return LocationType.WRITE_COMPLETED; + } else { + return LocationType.WRITE; + } + } else { + if (whenComplete) { + return LocationType.READ_COMPLETED; + } else { + return LocationType.READ; + } + } + } + + } + + /** + * location identifying a method invocation trigger point + */ + public static class InvokeLocation extends Location implements MethodInsnNodeWare { + + /** + * count identifying which invocation should be taken as the trigger point. if + * not specified as a parameter this defaults to the first invocation. + */ + private int count; + + public InvokeLocation(MethodInsnNode insnNode, int count, boolean whenComplete) { + super(insnNode, whenComplete); + this.count = count; + this.stackNeedSave = false; + } + + @Override + public boolean canChangeByReturn() { + // 对于 invoke ,只有在 complete 时,才能有返回值 + return whenComplete; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public LocationType getLocationType() { + if (whenComplete) { + return LocationType.INVOKE_COMPLETED; + } else { + return LocationType.INVOKE; + } + } + + @Override + public StackSaver getStackSaver() { + StackSaver stackSaver = null; + if(whenComplete) { + stackSaver = new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode(); + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + String uniqueNameForMethod = AsmUtils.uniqueNameForMethod(methodInsnNode.owner, methodInsnNode.name, + methodInsnNode.desc); + Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType(); + + if(!invokeReturnType.equals(Type.VOID_TYPE)) { + LocalVariableNode invokeReturnVariableNode = methodProcessor.initInvokeReturnVariableNode( + uniqueNameForMethod, invokeReturnType); + AsmOpUtils.storeVar(instructions, invokeReturnType, invokeReturnVariableNode.index); + } + } else { + throw new IllegalArgumentException( + "InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode); + } + + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode(); + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + String uniqueNameForMethod = AsmUtils.uniqueNameForMethod(methodInsnNode.owner, methodInsnNode.name, + methodInsnNode.desc); + Type invokeReturnType = Type.getMethodType(methodInsnNode.desc).getReturnType(); + + if(!invokeReturnType.equals(Type.VOID_TYPE)) { + LocalVariableNode invokeReturnVariableNode = methodProcessor.initInvokeReturnVariableNode( + uniqueNameForMethod, invokeReturnType); + AsmOpUtils.loadVar(instructions, invokeReturnType, invokeReturnVariableNode.index); + } + } else { + throw new IllegalArgumentException( + "InvokeReturnBinding location is not MethodInsnNode, insnNode: " + insnNode); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + return Type.getMethodType(methodInsnNode.desc).getReturnType(); + } + + }; + }else { + stackSaver = new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + // 需要从要调用的 函数的 des ,找到参数的类型,再从栈上一个个吐出来,再保存到数组里 + Location location = bindingContext.getLocation(); + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + if(location instanceof InvokeLocation) { + InvokeLocation invokeLocation = (InvokeLocation) location; + // 如果是非 static的,会有this指针 + MethodInsnNode methodInsnNode = (MethodInsnNode)invokeLocation.getInsnNode(); + Type methodType = Type.getMethodType(methodInsnNode.desc); + boolean isStatic = AsmUtils.isStatic(methodInsnNode); + Type[] argumentTypes = methodType.getArgumentTypes(); + +// // 如果是非static,则存放到数组的index要多 1 +// AsmOpUtils.push(instructions, argumentTypes.length + (isStatic ? 0 : 1)); +// AsmOpUtils.newArray(instructions, AsmOpUtils.OBJECT_TYPE); +// LocalVariableNode invokeArgsVariableNode = methodProcessor.initInvokeArgsVariableNode(); +// AsmOpUtils.storeVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index); + + // 从invoke的参数的后面,一个个存到数组里 +// for(int i = argumentTypes.length - 1; i >= 0 ; --i) { +// AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index); +// +// AsmOpUtils.swap(instructions, argumentTypes[i], AsmOpUtils.OBJECT_ARRAY_TYPE); +// // 如果是非static,则存放到数组的index要多 1 +// AsmOpUtils.push(instructions, i + (isStatic ? 0 : 1)); +// AsmOpUtils.swap(instructions, argumentTypes[i], Type.INT_TYPE); +// +// AsmOpUtils.box(instructions, argumentTypes[i]); +// AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE); +// +// } + // 处理this +// if(!isStatic) { +// AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index); +// +// AsmOpUtils.swap(instructions, AsmOpUtils.OBJECT_TYPE, AsmOpUtils.OBJECT_ARRAY_TYPE); +// AsmOpUtils.push(instructions, 0); +// AsmOpUtils.swap(instructions, AsmOpUtils.OBJECT_TYPE, Type.INT_TYPE); +// AsmOpUtils.arrayStore(instructions, AsmOpUtils.OBJECT_TYPE); +// } + + }else { + throw new IllegalArgumentException("location is not a InvokeLocation, location: " + location); + } + + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + // 从数组里取出来,一个个再放到栈上,要检查是否要unbox + Location location = bindingContext.getLocation(); + MethodProcessor methodProcessor = bindingContext.getMethodProcessor(); + LocalVariableNode invokeArgsVariableNode = methodProcessor.initInvokeArgsVariableNode(); + + if(location instanceof InvokeLocation) { + InvokeLocation invokeLocation = (InvokeLocation) location; + // 如果是非 static的,会有this指针 + MethodInsnNode methodInsnNode = (MethodInsnNode)invokeLocation.getInsnNode(); + Type methodType = Type.getMethodType(methodInsnNode.desc); + boolean isStatic = AsmUtils.isStatic(methodInsnNode); + Type[] argumentTypes = methodType.getArgumentTypes(); + +// if(!isStatic) { +// // 取出this +// AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index); +// AsmOpUtils.push(instructions, 0); +// AsmOpUtils.arrayLoad(instructions, AsmOpUtils.OBJECT_TYPE); +// AsmOpUtils.checkCast(instructions, Type.getObjectType(methodInsnNode.owner)); +// } +// +// for(int i = 0; i < argumentTypes.length; ++i) { +// AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_ARRAY_TYPE, invokeArgsVariableNode.index); +// AsmOpUtils.push(instructions, i + (isStatic ? 0 : 1)); +// AsmOpUtils.arrayLoad(instructions, AsmOpUtils.OBJECT_TYPE); +// // TODO 这里直接 unbox 就可以了??unbox里带有 check cast +// if(AsmOpUtils.needBox(argumentTypes[i])) { +// AsmOpUtils.unbox(instructions, argumentTypes[i]); +// }else { +// AsmOpUtils.checkCast(instructions, argumentTypes[i]); +// } +// } + + }else { + throw new IllegalArgumentException("location is not a InvokeLocation, location: " + location); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + throw new UnsupportedOperationException("InvokeLocation saver do not support getType()"); + } + + }; + } + + return stackSaver; + } + + @Override + public MethodInsnNode methodInsnNode() { + return (MethodInsnNode) insnNode; + } + + } + + /** + * location identifying a synchronization trigger point + */ + public static class SyncEnterLocation extends Location { + /** + * count identifying which synchronization should be taken as the trigger point. + * if not specified as a parameter this defaults to the first synchronization. + */ + private int count; + + public SyncEnterLocation(AbstractInsnNode insnNode, int count, boolean whenComplete) { + super(insnNode, whenComplete); + this.count = count; + this.whenComplete = whenComplete; + this.stackNeedSave = !whenComplete; + } + + public LocationType getLocationType() { + if (whenComplete) { + return LocationType.SYNC_ENTER_COMPLETED; + } else { + return LocationType.SYNC_ENTER; + } + } + + @Override + public StackSaver getStackSaver() { + return new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode(); + AsmOpUtils.storeVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index); + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode(); + AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index); + } + + @Override + public Type getType(BindingContext bindingContext) { + return AsmOpUtils.OBJECT_TYPE; + } + + }; + } + } + + /** + * location identifying a synchronization trigger point + */ + public static class SyncExitLocation extends Location { + /** + * count identifying which synchronization should be taken as the trigger point. + * if not specified as a parameter this defaults to the first synchronization. + */ + private int count; + + public SyncExitLocation(AbstractInsnNode insnNode, int count, boolean whenComplete) { + super(insnNode, whenComplete); + this.count = count; + this.whenComplete = whenComplete; + this.stackNeedSave = !whenComplete; + } + + public LocationType getLocationType() { + if (whenComplete) { + return LocationType.SYNC_ENTER_COMPLETED; + } else { + return LocationType.SYNC_ENTER; + } + } + + @Override + public StackSaver getStackSaver() { + return new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode(); + AsmOpUtils.storeVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index); + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode variableNode = bindingContext.getMethodProcessor().initMonitorVariableNode(); + AsmOpUtils.loadVar(instructions, AsmOpUtils.OBJECT_TYPE, variableNode.index); + } + + @Override + public Type getType(BindingContext bindingContext) { + return AsmOpUtils.OBJECT_TYPE; + } + + }; + } + } + + /** + * location identifying a throw trigger point + */ + public static class ThrowLocation extends Location { + /** + * count identifying which throw operation should be taken as the trigger point. + * if not specified as a parameter this defaults to the first throw. + */ + private int count; + + public ThrowLocation(AbstractInsnNode insnNode, int count) { + super(insnNode); + this.count = count; + stackNeedSave = true; + } + + @Override + public boolean canChangeByReturn() { + return true; + } + + public LocationType getLocationType() { + return LocationType.THROW; + } + + public StackSaver getStackSaver() { + StackSaver stackSaver = new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode(); + AsmOpUtils.storeVar(instructions, Type.getType(Throwable.class), throwVariableNode.index); + + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode(); + AsmOpUtils.loadVar(instructions, Type.getType(Throwable.class), throwVariableNode.index); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Throwable.class); + } + + }; + return stackSaver; + } + } + + /** + * location identifying a method exit trigger point + */ + public static class ExitLocation extends Location { + + public ExitLocation(AbstractInsnNode insnNode) { + super(insnNode); + stackNeedSave = true; + } + + @Override + public boolean canChangeByReturn() { + return true; + } + + public LocationType getLocationType() { + return LocationType.EXIT; + } + + public StackSaver getStackSaver() { + StackSaver stackSaver = new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + Type returnType = bindingContext.getMethodProcessor().getReturnType(); + if(!returnType.equals(Type.VOID_TYPE)) { + LocalVariableNode returnVariableNode = bindingContext.getMethodProcessor().initReturnVariableNode(); + AsmOpUtils.storeVar(instructions, returnType, returnVariableNode.index); + } + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + Type returnType = bindingContext.getMethodProcessor().getReturnType(); + if(!returnType.equals(Type.VOID_TYPE)) { + LocalVariableNode returnVariableNode = bindingContext.getMethodProcessor().initReturnVariableNode(); + AsmOpUtils.loadVar(instructions, returnType, returnVariableNode.index); + } + } + + @Override + public Type getType(BindingContext bindingContext) { + return bindingContext.getMethodProcessor().getReturnType(); + } + + }; + return stackSaver; + } + + } + + /** + * location identifying a method exceptional exit trigger point + */ + public static class ExceptionExitLocation extends Location{ + public ExceptionExitLocation(AbstractInsnNode insnNode) { + super(insnNode, true); + stackNeedSave = true; + } + + public LocationType getLocationType() { + return LocationType.EXCEPTION_EXIT; + } + + public StackSaver getStackSaver() { + StackSaver stackSaver = new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode(); + AsmOpUtils.storeVar(instructions, Type.getType(Throwable.class), throwVariableNode.index); + + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode(); + AsmOpUtils.loadVar(instructions, Type.getType(Throwable.class), throwVariableNode.index); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Throwable.class); + } + + }; + return stackSaver; + } + } + + /** + * location identifying a method exceptional exit trigger point + */ + public static class InvokeExceptionExitLocation extends Location implements MethodInsnNodeWare { + private MethodInsnNode methodInsnNode; + + public InvokeExceptionExitLocation(MethodInsnNode methodInsnNode, AbstractInsnNode insnNode) { + super(insnNode, true); + stackNeedSave = true; + this.methodInsnNode = methodInsnNode; + } + + public LocationType getLocationType() { + return LocationType.INVOKE_EXCEPTION_EXIT; + } + + public StackSaver getStackSaver() { + StackSaver stackSaver = new StackSaver() { + + @Override + public void store(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode(); + AsmOpUtils.storeVar(instructions, Type.getType(Throwable.class), throwVariableNode.index); + + } + + @Override + public void load(InsnList instructions, BindingContext bindingContext) { + LocalVariableNode throwVariableNode = bindingContext.getMethodProcessor().initThrowVariableNode(); + AsmOpUtils.loadVar(instructions, Type.getType(Throwable.class), throwVariableNode.index); + } + + @Override + public Type getType(BindingContext bindingContext) { + return Type.getType(Throwable.class); + } + + }; + return stackSaver; + } + + @Override + public MethodInsnNode methodInsnNode() { + return methodInsnNode; + } + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationMatcher.java new file mode 100644 index 000000000..c37746244 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationMatcher.java @@ -0,0 +1,11 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.List; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; + +public interface LocationMatcher { + + public List match(MethodProcessor methodProcessor); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationType.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationType.java new file mode 100644 index 000000000..b2d0b1c61 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationType.java @@ -0,0 +1,91 @@ + +package com.taobao.arthas.bytekit.asm.location; + +public enum LocationType { + /** + * user define. + */ + USER_DEFINE, + + /** + * method enter. + * + */ + ENTER, + /** + * line number. + * + */ + LINE, + /** + * field read operation. + * + */ + READ, + /** + * field read operation. + */ + READ_COMPLETED, + /** + * field write operation. + * + */ + WRITE, + /** + * field write operation. + * + */ + WRITE_COMPLETED, + /** + * method invoke operation + * + */ + INVOKE, + /** + * method invoke operation + * + */ + INVOKE_COMPLETED, + + /** + * method invoke exception + */ + INVOKE_EXCEPTION_EXIT, + /** + * synchronize operation + * + */ + SYNC_ENTER, + /** + * synchronize operation + * + */ + SYNC_ENTER_COMPLETED, + + /** + * synchronize operation + * + */ + SYNC_EXIT, + /** + * synchronize operation + * + */ + SYNC_EXIT_COMPLETED, + + /** + * throw + */ + THROW, + + /** + * return + */ + EXIT, + + /** + * add try/catch + */ + EXCEPTION_EXIT; + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MatchResult.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MatchResult.java new file mode 100644 index 000000000..77c31c016 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MatchResult.java @@ -0,0 +1,9 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.List; + +public class MatchResult { + + List locations; + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MethodInsnNodeWare.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MethodInsnNodeWare.java new file mode 100644 index 000000000..d3d0d6fe7 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MethodInsnNodeWare.java @@ -0,0 +1,8 @@ +package com.taobao.arthas.bytekit.asm.location; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; + +public interface MethodInsnNodeWare { + + public MethodInsnNode methodInsnNode(); +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncExitLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncExitLocationMatcher.java new file mode 100644 index 000000000..bd38e7de4 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncExitLocationMatcher.java @@ -0,0 +1,46 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.SyncEnterLocation; + +public class SyncExitLocationMatcher implements LocationMatcher { + + private int count; + + boolean whenComplete; + + public SyncExitLocationMatcher(int count, boolean whenComplete) { + this.count = count; + this.whenComplete = whenComplete; + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof InsnNode) { + InsnNode node = (InsnNode) insnNode; + if (node.getOpcode() == Opcodes.MONITOREXIT) { + ++matchedCount; + if (count <= 0 || count == matchedCount) { + SyncEnterLocation location = new SyncEnterLocation(node, matchedCount, whenComplete); + locations.add(location); + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncLocationMatcher.java new file mode 100644 index 000000000..119573126 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncLocationMatcher.java @@ -0,0 +1,53 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.SyncEnterLocation; + +public class SyncLocationMatcher implements LocationMatcher { + + private int count; + + boolean whenComplete; + + int opcode; + + public SyncLocationMatcher(int opcode, int count, boolean whenComplete) { + if (!(Opcodes.MONITORENTER == opcode || Opcodes.MONITOREXIT == opcode)) { + throw new IllegalArgumentException( + "SyncLocationMatcher only support Opcodes.MONITORENTER or Opcodes.MONITOREXIT."); + } + this.opcode = opcode; + this.count = count; + this.whenComplete = whenComplete; + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof InsnNode) { + InsnNode node = (InsnNode) insnNode; + if (node.getOpcode() == opcode) { + ++matchedCount; + if (count <= 0 || count == matchedCount) { + SyncEnterLocation location = new SyncEnterLocation(node, matchedCount, whenComplete); + locations.add(location); + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ThrowLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ThrowLocationMatcher.java new file mode 100644 index 000000000..3b74e9207 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ThrowLocationMatcher.java @@ -0,0 +1,47 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.ThrowLocation; + +public class ThrowLocationMatcher implements LocationMatcher { + + public ThrowLocationMatcher(int count) { + this.count = count; + } + + /** + * count identifying which invocation should be taken as the trigger point. + * if not specified as a parameter this defaults to the first invocation. + */ + private int count; + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof InsnNode) { + InsnNode node = (InsnNode) insnNode; + if (node.getOpcode() == Opcodes.ATHROW) { + ++matchedCount; + if (count <= 0 || count == matchedCount) { + ThrowLocation location = new ThrowLocation(node, matchedCount); + locations.add(location); + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/VariableAccessLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/VariableAccessLocationMatcher.java new file mode 100644 index 000000000..590f40a77 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/VariableAccessLocationMatcher.java @@ -0,0 +1,34 @@ +package com.taobao.arthas.bytekit.asm.location; + +import java.util.List; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; + +public class VariableAccessLocationMatcher extends AccessLocationMatcher { + + /** + * the name of the variable being accessed at the point where the trigger + * point should be inserted + */ + private String variableName; + + /** + * flag which is true if the name is a method parameter index such as $0, $1 + * etc otherwise false + */ + private boolean isIndex; + + + protected VariableAccessLocationMatcher(String variablename, int count, int flags, boolean whenComplete) { + super(count, flags, whenComplete); + this.variableName = variablename; + isIndex = variablename.matches("[0-9]+"); + } + + @Override + public List match(MethodProcessor methodProcessor) { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/DefaultLocationFilter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/DefaultLocationFilter.java new file mode 100644 index 000000000..32e73f6ab --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/DefaultLocationFilter.java @@ -0,0 +1,38 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.taobao.arthas.bytekit.asm.location.LocationType; +import com.taobao.arthas.bytekit.utils.MatchUtils; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class DefaultLocationFilter implements LocationFilter { + +// private List signatures; +// +// public DefaultLocationFilter(List signatures) { +// this.signatures = signatures; +// } + + @Override + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { + return true; + } + +// @Override +// public boolean allow(String signature) { +// for (String s : signatures) { +// if (MatchUtils.wildcardMatch(signature, s)) { +// return false; +// } +// } +// +// return true; +// } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/GroupLocationFilter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/GroupLocationFilter.java new file mode 100644 index 000000000..9ee6bce64 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/GroupLocationFilter.java @@ -0,0 +1,38 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.taobao.arthas.bytekit.asm.location.LocationType; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class GroupLocationFilter implements LocationFilter { + + List filters = new ArrayList(); + + public GroupLocationFilter(LocationFilter... filters) { + for (LocationFilter filter : filters) { + this.filters.add(filter); + } + } + + public void addFilter(LocationFilter filter) { + this.filters.add(filter); + } + + @Override + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { + for (LocationFilter filter : filters) { + if (filter.allow(insnNode, locationType, complete)) { + return true; + } + } + return false; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilter.java new file mode 100644 index 000000000..806f62bfd --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilter.java @@ -0,0 +1,64 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.taobao.arthas.bytekit.asm.location.LocationType; + +/** + * + * 检查某个 AbstractInsnNode 的前面是否有某个函数调用,如果有,则认为这个location是已被处理过的 + * + * @author hengyunabc 2020-05-04 + * + */ +public class InvokeCheckLocationFilter implements LocationFilter { + + private String owner; + private String methodName; + private LocationType locationType; + + public InvokeCheckLocationFilter(String owner, String methodName, LocationType locationType) { + this.owner = owner; + this.methodName = methodName; + this.locationType = locationType; + } + + @Override + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { + // 只检查自己对应的 LocationType + if (!this.locationType.equals(locationType)) { + return false; + } + + MethodInsnNode methodInsnNode = findMethodInsnNode(insnNode, complete); + if (methodInsnNode != null) { + if (methodInsnNode.owner.equals(this.owner) && methodInsnNode.name.equals(this.methodName)) { + return false; + } + } + + return true; + } + + private MethodInsnNode findMethodInsnNode(AbstractInsnNode insnNode, boolean complete) { + if (complete) { + while (insnNode != null) { + insnNode = insnNode.getNext(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + return methodInsnNode; + } + } + } else { + while (insnNode != null) { + insnNode = insnNode.getPrevious(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + return methodInsnNode; + } + } + } + return null; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilter.java new file mode 100644 index 000000000..af59fbefe --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilter.java @@ -0,0 +1,68 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.taobao.arthas.bytekit.asm.location.LocationType; + +/** + * + * 检查整个method里,是否有某个函数调用。用于检查 enter/exit/exception exit + * + * @author hengyunabc 2020-05-04 + * + */ +public class InvokeContainLocationFilter implements LocationFilter { + + private String owner; + private String methodName; + private LocationType locationType; + + public InvokeContainLocationFilter(String owner, String methodName, LocationType locationType) { + this.owner = owner; + this.methodName = methodName; + this.locationType = locationType; + } + + @Override + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete) { + // 只检查自己对应的 LocationType + if (!this.locationType.equals(locationType)) { + return false; + } + + MethodInsnNode methodInsnNode = findMethodInsnNode(insnNode); + if (methodInsnNode != null) { + if (methodInsnNode.owner.equals(this.owner) && methodInsnNode.name.equals(this.methodName)) { + return false; + } + } + + return true; + } + + private MethodInsnNode findMethodInsnNode(AbstractInsnNode insnNode) { + + AbstractInsnNode current = insnNode; + while (current != null) { + current = current.getNext(); + if (current instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) current; + if (methodInsnNode.owner.equals(this.owner) && methodInsnNode.name.equals(this.methodName)) { + return methodInsnNode; + } + } + } + current = insnNode; + while (current != null) { + current = current.getPrevious(); + if (current instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) current; + if (methodInsnNode.owner.equals(this.owner) && methodInsnNode.name.equals(this.methodName)) { + return methodInsnNode; + } + } + } + return null; + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/LocationFilter.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/LocationFilter.java new file mode 100644 index 000000000..ac754b5a3 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/filter/LocationFilter.java @@ -0,0 +1,15 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.taobao.arthas.bytekit.asm.location.LocationType; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public interface LocationFilter { + + public boolean allow(AbstractInsnNode insnNode, LocationType locationType, boolean complete); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/ClassMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/ClassMatcher.java new file mode 100644 index 000000000..e9a4b4fbc --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/ClassMatcher.java @@ -0,0 +1,7 @@ +package com.taobao.arthas.bytekit.asm.matcher; + +public interface ClassMatcher { + + boolean match(String name, ClassLoader classLoader); + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/MethodMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/MethodMatcher.java new file mode 100644 index 000000000..2edc72486 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/MethodMatcher.java @@ -0,0 +1,8 @@ +package com.taobao.arthas.bytekit.asm.matcher; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; + +public interface MethodMatcher { + + boolean match(String className, MethodNode methodNode); +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java new file mode 100644 index 000000000..b8558e1b9 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java @@ -0,0 +1,61 @@ +package com.taobao.arthas.bytekit.utils; + +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.security.ProtectionDomain; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; + +import net.bytebuddy.agent.ByteBuddyAgent; + +public class AgentUtils { + + private static class InstrumentationHolder { + static final Instrumentation instance = ByteBuddyAgent.install(); + } + + public static void redefine(Class clazz, byte[] classFile) + throws ClassNotFoundException, UnmodifiableClassException { + ClassDefinition classDefinition = new ClassDefinition(clazz, classFile); + InstrumentationHolder.instance.redefineClasses(classDefinition); + } + + public static void reTransform(Class clazz, byte[] classFile) throws UnmodifiableClassException { + + SimpleClassFileTransformer transformer = new SimpleClassFileTransformer(clazz.getClassLoader(), clazz.getName(), + classFile); + InstrumentationHolder.instance.addTransformer(transformer, true); + + InstrumentationHolder.instance.retransformClasses(clazz); + InstrumentationHolder.instance.removeTransformer(transformer); + } + + public static class SimpleClassFileTransformer implements ClassFileTransformer { + private byte[] classBuffer; + private ClassLoader classLoader; + private String className; + + public SimpleClassFileTransformer(ClassLoader classLoader, String className, byte[] classBuffer) { + this.classLoader = classLoader; + this.className = className.replace('.', '/'); + this.classBuffer = classBuffer; + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + + if (this.classLoader == loader && className.equals(this.className)) { + return classBuffer; + } + + return null; + + } + + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AnnotationUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AnnotationUtils.java new file mode 100644 index 000000000..327b610a1 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AnnotationUtils.java @@ -0,0 +1,12 @@ +package com.taobao.arthas.bytekit.utils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +public class AnnotationUtils { + + public static A findAnnotation(Method method, Class annotationType) { + return method.getAnnotation(annotationType); + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtils.java new file mode 100644 index 000000000..70d381d39 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtils.java @@ -0,0 +1,77 @@ +package com.taobao.arthas.bytekit.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AnnotationNode; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class AsmAnnotationUtils { + + public static List queryAnnotationInfo(List annotations, String annotationType, + String key) { + List result = new ArrayList(); + if (annotations != null) { + for (AnnotationNode annotationNode : annotations) { + if (annotationNode.desc.equals(annotationType)) { + if (annotationNode.values != null) { + Iterator iterator = annotationNode.values.iterator(); + while (iterator.hasNext()) { + String name = (String) iterator.next(); + Object values = iterator.next(); + if (key.equals(name)) { + result.addAll((List) values); + } + } + } + } + } + } + return result; + } + + public static void addAnnotationInfo(List annotations, String annotationType, String key, + String value) { + + AnnotationNode annotationNode = null; + for (AnnotationNode tmp : annotations) { + if (tmp.desc.equals(annotationType)) { + annotationNode = tmp; + } + } + + if (annotationNode == null) { + annotationNode = new AnnotationNode(annotationType); + annotations.add(annotationNode); + } + + if (annotationNode.values == null) { + annotationNode.values = new ArrayList(); + } + + // 查找有没有对应的key + String name = null; + List values = null; + Iterator iterator = annotationNode.values.iterator(); + while (iterator.hasNext()) { + if (key.equals(iterator.next())) { + values = (List) iterator.next(); + } else { + iterator.next(); + } + } + if (values == null) { + values = new ArrayList(); + annotationNode.values.add(key); + annotationNode.values.add(values); + } + if (!values.contains(values)) { + values.add(value); + } + } +} 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 new file mode 100644 index 000000000..81f1a7f85 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java @@ -0,0 +1,463 @@ +package com.taobao.arthas.bytekit.utils; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.Method; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.IntInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LdcInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.VarInsnNode; + +public class AsmOpUtils { + + private static final Type BYTE_TYPE = Type.getObjectType("java/lang/Byte"); + + private static final Type BOOLEAN_TYPE = Type.getObjectType("java/lang/Boolean"); + + private static final Type SHORT_TYPE = Type.getObjectType("java/lang/Short"); + + private static final Type CHARACTER_TYPE = Type.getObjectType("java/lang/Character"); + + private static final Type INTEGER_TYPE = Type.getObjectType("java/lang/Integer"); + + private static final Type FLOAT_TYPE = Type.getObjectType("java/lang/Float"); + + private static final Type LONG_TYPE = Type.getObjectType("java/lang/Long"); + + 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"); + + private static final Method BOOLEAN_VALUE = Method.getMethod("boolean booleanValue()"); + + 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()"); + + private static final Method LONG_VALUE = Method.getMethod("long longValue()"); + + private static final Method DOUBLE_VALUE = Method.getMethod("double doubleValue()"); + + public static boolean isBoxType(final Type type) { + if (BYTE_TYPE.equals(type) || BOOLEAN_TYPE.equals(type) || SHORT_TYPE.equals(type) + || CHARACTER_TYPE.equals(type) || INTEGER_TYPE.equals(type) || FLOAT_TYPE.equals(type) + || LONG_TYPE.equals(type) || DOUBLE_TYPE.equals(type)) { + return true; + } + return false; + } + + public static Type getBoxedType(final Type type) { + switch (type.getSort()) { + case Type.BYTE: + return BYTE_TYPE; + case Type.BOOLEAN: + return BOOLEAN_TYPE; + case Type.SHORT: + return SHORT_TYPE; + case Type.CHAR: + return CHARACTER_TYPE; + case Type.INT: + return INTEGER_TYPE; + case Type.FLOAT: + return FLOAT_TYPE; + case Type.LONG: + return LONG_TYPE; + case Type.DOUBLE: + return DOUBLE_TYPE; + } + 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())); + } + + public static void push(InsnList insnList, final int value) { + if (value >= -1 && value <= 5) { + insnList.add(new InsnNode(Opcodes.ICONST_0 + value)); + } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { + insnList.add(new IntInsnNode(Opcodes.BIPUSH, value)); + } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { + insnList.add(new IntInsnNode(Opcodes.SIPUSH, value)); + } else { + insnList.add(new LdcInsnNode(value)); + } + } + + public static void push(InsnList insnList, final String value) { + if (value == null) { + insnList.add(new InsnNode(Opcodes.ACONST_NULL)); + } else { + 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 + */ + public static void ldc(InsnList insnList, Object value) { + insnList.add(new LdcInsnNode(value)); + } + + public static void newArray(final InsnList insnList, final Type type) { + insnList.add(new TypeInsnNode(Opcodes.ANEWARRAY, type.getInternalName())); + } + + public static void dup(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP)); + } + + public static void dup2(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP2)); + } + + public static void dupX1(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP_X1)); + } + + public static void dupX2(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP_X2)); + } + + /** + * Generates a DUP2_X1 instruction. + */ + public static void dup2X1(final InsnList insnList) { + insnList.add(new InsnNode(Opcodes.DUP2_X1)); + } + + /** + * Generates a DUP2_X2 instruction. + */ + 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)); + } + + /** + * Generates a POP2 instruction. + */ + 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 + * type of the top stack value. + */ + public static void swap(final InsnList insnList, final Type prev, final Type type) { + if (type.getSize() == 1) { + if (prev.getSize() == 1) { + swap(insnList); // same as dupX1(), pop(); + } else { + dupX2(insnList); + pop(insnList); + } + } else { + if (prev.getSize() == 1) { + dup2X1(insnList); + pop2(insnList); + } else { + dup2X2(insnList); + pop2(insnList); + } + } + } + + public static void box(final InsnList instructions, Type type) { + if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + return; + } + + if (type == Type.VOID_TYPE) { + // push null + instructions.add(new InsnNode(Opcodes.ACONST_NULL)); + } else { + Type boxed = getBoxedType(type); + // new instance. + newInstance(instructions, boxed); + if (type.getSize() == 2) { + // Pp -> Ppo -> oPpo -> ooPpo -> ooPp -> o + // dupX2 + dupX2(instructions); + // dupX2 + dupX2(instructions); + // pop + pop(instructions); + } else { + // p -> po -> opo -> oop -> o + // dupX1 + dupX1(instructions); + // swap + swap(instructions); + } + invokeConstructor(instructions, boxed, new Method("", Type.VOID_TYPE, new Type[] { type })); + } + } + + public static void invokeConstructor(final InsnList instructions, final Type type, final Method method) { + String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName(); + instructions + .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; + switch (type.getSort()) { + case Type.VOID: + return; + case Type.CHAR: + t = CHARACTER_TYPE; + sig = CHAR_VALUE; + break; + case Type.BOOLEAN: + t = BOOLEAN_TYPE; + sig = BOOLEAN_VALUE; + break; + case Type.DOUBLE: + sig = DOUBLE_VALUE; + break; + case Type.FLOAT: + sig = FLOAT_VALUE; + break; + case Type.LONG: + sig = LONG_VALUE; + break; + case Type.INT: + case Type.SHORT: + case Type.BYTE: + sig = INT_VALUE; + } + if (sig == null) { + instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, type.getInternalName())); + } else { + instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, t.getInternalName())); + instructions.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, t.getInternalName(), sig.getName(), + sig.getDescriptor(), false)); + } + } + + public static boolean needBox(Type type) { + switch (type.getSort()) { + case Type.BYTE: + case Type.BOOLEAN: + case Type.SHORT: + case Type.CHAR: + case Type.INT: + case Type.FLOAT: + case Type.LONG: + case Type.DOUBLE: + return true; + } + return false; + } + + public static void getStatic(final InsnList insnList, final Type owner, final String name, final Type type) { + insnList.add(new FieldInsnNode(Opcodes.GETSTATIC, owner.getInternalName(), name, type.getDescriptor())); + } + + /** + * Generates the instruction to push the value of a non static field on the + * stack. + * + * @param owner + * the class in which the field is defined. + * @param name + * the name of the field. + * @param type + * the type of the field. + */ + public static void getField(final InsnList insnList, final Type owner, final String name, final Type type) { + insnList.add(new FieldInsnNode(Opcodes.GETFIELD, owner.getInternalName(), name, type.getDescriptor())); + } + + public static void arrayStore(final InsnList instructions, final Type type) { + instructions.add(new InsnNode(type.getOpcode(Opcodes.IASTORE))); + } + + public static void arrayLoad(final InsnList instructions, final Type type) { + 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. + * + * @see org.objectweb.asm.commons.GeneratorAdapter#loadArgArray() + */ + public static void loadArgArray(final InsnList instructions, MethodNode methodNode) { + boolean isStatic = AsmUtils.isStatic(methodNode); + Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc); + push(instructions, argumentTypes.length); + newArray(instructions, OBJECT_TYPE); + for (int i = 0; i < argumentTypes.length; i++) { + dup(instructions); + push(instructions, i); + loadArg(isStatic, instructions, argumentTypes, i); + box(instructions, argumentTypes[i]); + arrayStore(instructions, OBJECT_TYPE); + } + } + + public static void loadArgs(final InsnList instructions, MethodNode methodNode) { + Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc); + boolean isStatic = AsmUtils.isStatic(methodNode); + for (int i = 0; i < argumentTypes.length; i++) { + loadArg(isStatic, instructions, argumentTypes, i); + } + } + + public static void loadArg(boolean staticAccess, final InsnList instructions, Type[] argumentTypes, int i) { + final int index = getArgIndex(staticAccess, argumentTypes, i); + final Type type = argumentTypes[i]; + instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index)); + } + + static int getArgIndex(boolean staticAccess, final Type[] argumentTypes, final int arg) { + int index = staticAccess ? 0 : 1; + for (int i = 0; i < arg; i++) { + index += argumentTypes[i].getSize(); + } + return index; + } + + public static void loadVar(final InsnList instructions, Type type, final int index) { + instructions.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index)); + } + + 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 + * the instruction's operand. + */ + 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. + */ + public static void checkCast(final InsnList instructions, final Type type) { + if (!type.equals(OBJECT_TYPE)) { + 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(); + + // find out current valid local variables + for (LocalVariableNode localVariableNode : localVariables) { + for (AbstractInsnNode iter = localVariableNode.start; iter != null + && (!iter.equals(localVariableNode.end)); iter = iter.getNext()) { + if (iter.equals(currentInsnNode)) { + results.add(localVariableNode); + break; + } + } + } + + return results; + } +} 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 new file mode 100644 index 000000000..7c1a37a84 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java @@ -0,0 +1,530 @@ +package com.taobao.arthas.bytekit.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.ClassReader; +import com.alibaba.arthas.deps.org.objectweb.asm.ClassVisitor; +import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter; +import com.alibaba.arthas.deps.org.objectweb.asm.Label; +import com.alibaba.arthas.deps.org.objectweb.asm.MethodVisitor; +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.ClassRemapper; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.JSRInlinerAdapter; +import com.alibaba.arthas.deps.org.objectweb.asm.commons.Remapper; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LocalVariableNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.TypeInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.util.ASMifier; +import com.alibaba.arthas.deps.org.objectweb.asm.util.TraceClassVisitor; + +/** + * + * @author hengyunabc + * + */ +public class AsmUtils { + + public static ClassNode loadClass(Class clazz) throws IOException { + String resource = clazz.getName().replace('.', '/') + ".class"; + InputStream is = clazz.getClassLoader().getResourceAsStream(resource); + ClassReader cr = new ClassReader(is); + ClassNode classNode = new ClassNode(); + cr.accept(classNode, ClassReader.SKIP_FRAMES); + return classNode; + } + + public static ClassNode toClassNode(byte[] classBytes) { + ClassReader reader = new ClassReader(classBytes); + ClassNode result = new ClassNode(Opcodes.ASM8); + reader.accept(result, ClassReader.SKIP_FRAMES); + return result; + } + + public static byte[] toBytes(ClassNode classNode) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + return writer.toByteArray(); + } + + public static byte[] renameClass(byte[] classBytes, final String newClassName) { + final String internalName = newClassName.replace('.', '/'); + + ClassReader reader = new ClassReader(classBytes); + ClassWriter writer = new ClassWriter(0); + + class RenameRemapper extends Remapper { + private String className; + + @Override + public String map(String typeName) { + if (typeName.equals(className)) { + return internalName; + } + return super.map(typeName); + } + + public void setClassName(String className) { + this.className = className; + } + } + + final RenameRemapper renameRemapper = new RenameRemapper(); + ClassRemapper adapter = new ClassRemapper(writer, renameRemapper) { + @Override + public void visit(final int version, final int access, final String name, final String signature, + final String superName, final String[] interfaces) { + renameRemapper.setClassName(name); + super.visit(version, access, name, signature, superName, interfaces); + } + }; + reader.accept(adapter, ClassReader.EXPAND_FRAMES); + writer.visitEnd(); + return writer.toByteArray(); + } + + public static void replaceMethod(ClassNode classNode, MethodNode methodNode) { + for (int index = 0; index < classNode.methods.size(); ++index) { + MethodNode tmp = classNode.methods.get(index); + if (tmp.name.equals(methodNode.name) && tmp.desc.equals(methodNode.desc)) { + classNode.methods.set(index, methodNode); + } + } + } + + public static String toASMCode(byte[] bytecode) throws IOException { + return toASMCode(bytecode, true); + } + + public static String toASMCode(byte[] bytecode, boolean debug) throws IOException { + int flags = ClassReader.SKIP_DEBUG; + + if (debug) { + flags = 0; + } + + ClassReader cr = new ClassReader(new ByteArrayInputStream(bytecode)); + StringWriter sw = new StringWriter(); + cr.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(sw)), flags); + return sw.toString(); + } + + public static String toASMCode(ClassNode classNode) { + StringWriter sw = new StringWriter(); + classNode.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(sw))); + return sw.toString(); + } + + public static String toASMCode(MethodNode methodNode) { + ClassNode classNode = new ClassNode(); + classNode.methods.add(methodNode); + return toASMCode(classNode); + } + + public static MethodNode newMethodNode(MethodNode source) { + return new MethodNode(Opcodes.ASM8, source.access, source.name, source.desc, source.signature, + source.exceptions.toArray(new String[source.exceptions.size()])); + } + + public static MethodNode removeJSRInstructions(MethodNode subjectMethod) { + MethodNode result = newMethodNode(subjectMethod); + subjectMethod.accept(new JSRInlinerAdapter(result, subjectMethod.access, subjectMethod.name, subjectMethod.desc, + subjectMethod.signature, + subjectMethod.exceptions.toArray(new String[subjectMethod.exceptions.size()]))); + return result; + } + + public static MethodNode removeLineNumbers(MethodNode methodNode) { + MethodNode result = newMethodNode(methodNode); + methodNode.accept(new MethodVisitor(Opcodes.ASM8, result) { + public void visitLineNumber(int line, Label start) { + } + }); + return result; + } + + public static MethodNode findFirstMethod(Collection methodNodes, String name) { + for (MethodNode methodNode : methodNodes) { + if (methodNode.name.equals(name)) { + return methodNode; + } + } + return null; + } + + public static List findMethods(Collection methodNodes, String name) { + List result = new ArrayList(); + for (MethodNode methodNode : methodNodes) { + if (methodNode.name.equals(name)) { + result.add(methodNode); + } + } + return result; + } + + public static MethodNode findMethod(Collection methodNodes, MethodNode target) { + return findMethod(methodNodes, target.name, target.desc); + } + + public static MethodNode findMethod(Collection methodNodes, String name, String desc) { + for (MethodNode methodNode : methodNodes) { + if (methodNode.name.equals(name) && methodNode.desc.equals(desc)) { + return methodNode; + } + } + return null; + } + + public static AbstractInsnNode findInitConstructorInstruction(MethodNode methodNode) { + int nested = 0; + for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof TypeInsnNode) { + if (insnNode.getOpcode() == Opcodes.NEW) { + // new object(). + nested++; + } + } else if (insnNode instanceof MethodInsnNode) { + final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.getOpcode() == Opcodes.INVOKESPECIAL && methodInsnNode.name.equals("")) { + if (--nested < 0) { + // find this() or super(). + return insnNode.getNext(); + } + } + } + } + + return null; + } + + public static List findMethodInsnNodeWithPrefix(MethodNode methodNode, String prefix) { + 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.name.startsWith(prefix)) { + result.add(methodInsnNode); + } + } + } + return result; + } + + public static List findMethodInsnNode(MethodNode methodNode, String owner, String name) { + 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)) { + result.add(methodInsnNode); + } + } + } + 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 containsMethodInsnNode(MethodNode methodNode, String owner, String name) { + 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)) { + return true; + } + } + } + return false; + } + + public static boolean isStatic(MethodNode methodNode) { + return (methodNode.access & Opcodes.ACC_STATIC) != 0; + } + + public static boolean isStatic(MethodInsnNode methodInsnNode) { + return methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC; + } + + + public static boolean isConstructor(MethodNode methodNode) { + return methodNode.name != null && methodNode.name.equals(""); + } + + + public String[] getParameterNames(MethodNode methodNode) { + Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc); + if (argumentTypes.length == 0) { + return new String[0]; + } + + final List localVariableNodes = methodNode.localVariables; + int localVariableStartIndex = 1; + if (isStatic(methodNode)) { + // static method is none this. + localVariableStartIndex = 0; + } + + if (localVariableNodes == null || localVariableNodes.size() <= localVariableStartIndex || + (argumentTypes.length + localVariableStartIndex) > localVariableNodes.size()) { + // make simple argument names. + final String[] names = new String[argumentTypes.length]; + for (int i = 0; i < argumentTypes.length; i++) { + final String className = argumentTypes[i].getClassName(); + if (className != null) { + final int findIndex = className.lastIndexOf('.'); + if (findIndex == -1) { + names[i] = className; + } else { + names[i] = className.substring(findIndex + 1); + } + } else { + names[i] = argumentTypes[i].getDescriptor(); + } + } + return names; + } + + // sort by index. + Collections.sort(localVariableNodes, new Comparator() { + + @Override + public int compare(LocalVariableNode o1, LocalVariableNode o2) { + return o1.index - o2.index; + } + }); + String[] names = new String[argumentTypes.length]; + + for (int i = 0; i < argumentTypes.length; i++) { + final String name = localVariableNodes.get(localVariableStartIndex++).name; + if (name != null) { + names[i] = name; + } else { + names[i] = ""; + } + } + + return names; + } + + public static MethodNode copy(MethodNode source) { + MethodNode result = newMethodNode(source); + source.accept(result); + return result; + } + + public static ClassNode copy(ClassNode source) { + ClassNode result = new ClassNode(Opcodes.ASM8); + source.accept(new ClassVisitor(Opcodes.ASM8, result) { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + return new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions); + } + }); + return result; + } + + + public static String methodDeclaration(MethodInsnNode methodInsnNode) { + StringBuilder sb = new StringBuilder(128); + + int opcode = methodInsnNode.getOpcode(); + if(opcode == Opcodes.INVOKESTATIC) { + sb.append("static "); + } + Type methodType = Type.getMethodType(methodInsnNode.desc); + Type ownerType = Type.getObjectType(methodInsnNode.owner); + + //skip constructor return type + if(methodInsnNode.name.equals("")) { + sb.append(ownerType.getClassName()); + }else { + sb.append(methodType.getReturnType().getClassName()).append(' '); + sb.append(methodInsnNode.name); + } + + sb.append('('); + Type[] argumentTypes = methodType.getArgumentTypes(); + for(int i = 0 ; i < argumentTypes.length; ++i) { + sb.append(argumentTypes[i].getClassName()); + if(i != argumentTypes.length - 1) { + sb.append(", "); + } + } + sb.append(')'); + return sb.toString(); + + } + + public static String methodDeclaration(Type owner, MethodNode methodNode) { + int access = methodNode.access; + StringBuilder sb = new StringBuilder(128); + +// int ACC_PUBLIC = 0x0001; // class, field, method +// int ACC_PRIVATE = 0x0002; // class, field, method +// int ACC_PROTECTED = 0x0004; // class, field, method +// int ACC_STATIC = 0x0008; // field, method +// int ACC_FINAL = 0x0010; // class, field, method, parameter +// int ACC_SUPER = 0x0020; // class +// int ACC_SYNCHRONIZED = 0x0020; // method +// int ACC_OPEN = 0x0020; // module +// int ACC_TRANSITIVE = 0x0020; // module requires +// int ACC_VOLATILE = 0x0040; // field +// int ACC_BRIDGE = 0x0040; // method +// int ACC_STATIC_PHASE = 0x0040; // module requires +// int ACC_VARARGS = 0x0080; // method +// int ACC_TRANSIENT = 0x0080; // field +// int ACC_NATIVE = 0x0100; // method +// int ACC_INTERFACE = 0x0200; // class +// int ACC_ABSTRACT = 0x0400; // class, method +// int ACC_STRICT = 0x0800; // method +// int ACC_SYNTHETIC = 0x1000; // class, field, method, parameter, module * +// int ACC_ANNOTATION = 0x2000; // class +// int ACC_ENUM = 0x4000; // class(?) field inner +// int ACC_MANDATED = 0x8000; // parameter, module, module * +// int ACC_MODULE = 0x8000; // class + + if((access & Opcodes.ACC_PUBLIC) != 0) { + sb.append("public "); + } + if((access & Opcodes.ACC_PRIVATE) != 0) { + sb.append("private "); + } + if((access & Opcodes.ACC_PROTECTED) != 0) { + sb.append("protected "); + } + if((access & Opcodes.ACC_STATIC) != 0) { + sb.append("static "); + } + + if((access & Opcodes.ACC_FINAL) != 0) { + sb.append("final "); + } + if((access & Opcodes.ACC_SYNCHRONIZED) != 0) { + sb.append("synchronized "); + } + if((access & Opcodes.ACC_NATIVE) != 0) { + sb.append("native "); + } + if((access & Opcodes.ACC_ABSTRACT) != 0) { + sb.append("abstract "); + } + + Type methodType = Type.getMethodType(methodNode.desc); + + //skip constructor return type + if(methodNode.name.equals("")) { + sb.append(owner.getClassName()); + }else { + sb.append(methodType.getReturnType().getClassName()).append(' '); + sb.append(methodNode.name); + } + + sb.append('('); + Type[] argumentTypes = methodType.getArgumentTypes(); + for(int i = 0 ; i < argumentTypes.length; ++i) { + sb.append(argumentTypes[i].getClassName()); + if(i != argumentTypes.length - 1) { + sb.append(", "); + } + } + sb.append(')'); + if(methodNode.exceptions != null) { + int exceptionSize = methodNode.exceptions.size(); + if( exceptionSize > 0) { + sb.append(" throws"); + for(int i = 0; i < exceptionSize; ++i) { + sb.append(' '); + sb.append(Type.getObjectType(methodNode.exceptions.get(i)).getClassName()); + if(i != exceptionSize -1) { + sb.append(','); + } + } + } + + } + + return sb.toString(); + } + + public static FieldNode findField(List fields, String name) { + for(FieldNode field : fields) { + if(field.name.equals(name)) { + return field; + } + } + return null; + } + + public static void addField(ClassNode classNode, FieldNode fieldNode) { + // TODO 检查是否有重复? + classNode.fields.add(fieldNode); + } + + public static void addMethod(ClassNode classNode, MethodNode methodNode) { + classNode.methods.add(methodNode); + } + + // TODO 是否真的 unique 了? + public static String uniqueNameForMethod(String className, String methodName, String desc) { + StringBuilder result = new StringBuilder(128); + result.append(cleanClassName(className)).append('_').append(methodName); + for(Type arg : Type.getMethodType(desc).getArgumentTypes()) { + result.append('_').append(cleanClassName(arg.getClassName())); + } + return result.toString(); + } + + private static String cleanClassName(String className) { + char[] charArray = className.toCharArray(); + int length = charArray.length; + for(int i = 0 ; i < length; ++i) { + switch( charArray[i]) { + case '[' : + case ']' : + case '<' : + case '>' : + case ';' : + case '/' : + case '.' : + charArray[i] = '_'; + break; + } + } + return new String(charArray); + } + + + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ClassLoaderUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ClassLoaderUtils.java new file mode 100644 index 000000000..8f5a78a9a --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ClassLoaderUtils.java @@ -0,0 +1,48 @@ +package com.taobao.arthas.bytekit.utils; + +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; + +import sun.misc.Unsafe; + + +/** + * + * @author hengyunabc 2017-10-12 + * + */ +public class ClassLoaderUtils { + @SuppressWarnings({ "restriction", "unchecked" }) + public static URL[] getUrls(ClassLoader classLoader) { + if (classLoader instanceof URLClassLoader) { + return ((URLClassLoader) classLoader).getURLs(); + } + + // jdk9 + if (classLoader.getClass().getName().startsWith("jdk.internal.loader.ClassLoaders$")) { + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + Unsafe unsafe = (Unsafe) field.get(null); + + // jdk.internal.loader.ClassLoaders.AppClassLoader.ucp + Field ucpField = classLoader.getClass().getDeclaredField("ucp"); + long ucpFieldOffset = unsafe.objectFieldOffset(ucpField); + Object ucpObject = unsafe.getObject(classLoader, ucpFieldOffset); + + // jdk.internal.loader.URLClassPath.path + Field pathField = ucpField.getType().getDeclaredField("path"); + long pathFieldOffset = unsafe.objectFieldOffset(pathField); + ArrayList path = (ArrayList) unsafe.getObject(ucpObject, pathFieldOffset); + + return path.toArray(new URL[path.size()]); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + return null; + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/Decompiler.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/Decompiler.java new file mode 100644 index 000000000..1a815208a --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/Decompiler.java @@ -0,0 +1,152 @@ +package com.taobao.arthas.bytekit.utils; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; + +import org.benf.cfr.reader.api.CfrDriver; +import org.benf.cfr.reader.api.OutputSinkFactory; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.objectweb.asm.util.Printer; +import com.alibaba.arthas.deps.org.objectweb.asm.util.Textifier; +import com.alibaba.arthas.deps.org.objectweb.asm.util.TraceClassVisitor; +import com.alibaba.arthas.deps.org.objectweb.asm.util.TraceMethodVisitor; +import com.taobao.arthas.common.FileUtils; + +/** + * TODO com.taobao.arthas.core.util.Decompiler + * @author hengyunabc + * + */ +public class Decompiler { + + public static String decompile(byte[] bytecode) throws IOException { + String result = ""; + + File tempDirectory = new File(System.getProperty("java.io.tmpdir")); + File file = new File(tempDirectory, UUID.randomUUID().toString()); + FileUtils.writeByteArrayToFile(file, bytecode); + + result = decompile(file.getAbsolutePath(), null); + return result; + } + + public static String decompile(String path) throws IOException { + byte[] byteArray = FileUtils.readFileToByteArray(new File(path)); + return decompile(byteArray); + } + + public static String toString(MethodNode methodNode) { + Printer printer = new Textifier(); + TraceMethodVisitor methodPrinter = new TraceMethodVisitor(printer); + + methodNode.accept(methodPrinter); + + StringWriter sw = new StringWriter(); + printer.print(new PrintWriter(sw)); + printer.getText().clear(); + + return sw.toString(); + } + + public static String toString(ClassNode classNode) { + Printer printer = new Textifier(); + StringWriter sw = new StringWriter(); + PrintWriter printWriter = new PrintWriter(sw); + + TraceClassVisitor traceClassVisitor = new TraceClassVisitor(printWriter); + + classNode.accept(traceClassVisitor); + + printer.print(printWriter); + printer.getText().clear(); + + return sw.toString(); + } + + + + public static String toString(InsnList insnList) { + Printer printer = new Textifier(); + TraceMethodVisitor mp = new TraceMethodVisitor(printer); + insnList.accept(mp); + + StringWriter sw = new StringWriter(); + printer.print(new PrintWriter(sw)); + printer.getText().clear(); + return sw.toString(); + } + + public static String toString(AbstractInsnNode insn) { + Printer printer = new Textifier(); + TraceMethodVisitor mp = new TraceMethodVisitor(printer); + insn.accept(mp); + + StringWriter sw = new StringWriter(); + printer.print(new PrintWriter(sw)); + printer.getText().clear(); + return sw.toString(); + } + + + /** + * @param classFilePath + * @param methodName + * @return + */ + public static String decompile(String classFilePath, String methodName) { + final StringBuilder result = new StringBuilder(8192); + + OutputSinkFactory mySink = new OutputSinkFactory() { + @Override + public List getSupportedSinks(SinkType sinkType, Collection collection) { + return Arrays.asList(SinkClass.STRING, SinkClass.DECOMPILED, SinkClass.DECOMPILED_MULTIVER, + SinkClass.EXCEPTION_MESSAGE); + } + + @Override + public Sink getSink(final SinkType sinkType, SinkClass sinkClass) { + return new Sink() { + @Override + public void write(T sinkable) { + // skip message like: Analysing type demo.MathGame + if (sinkType == SinkType.PROGRESS) { + return; + } + result.append(sinkable); + } + }; + } + }; + + HashMap options = new HashMap(); + /** + * @see org.benf.cfr.reader.util.MiscConstants.Version.getVersion() Currently, + * the cfr version is wrong. so disable show cfr version. + */ + options.put("showversion", "false"); + if (methodName != null) { + options.put("methodname", methodName); + } + + CfrDriver driver = new CfrDriver.Builder().withOptions(options).withOutputSink(mySink).build(); + List toAnalyse = new ArrayList(); + toAnalyse.add(classFilePath); + driver.analyse(toAnalyse); + + return result.toString(); + } + + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/InstanceUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/InstanceUtils.java new file mode 100644 index 000000000..a1526b8f9 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/InstanceUtils.java @@ -0,0 +1,13 @@ +package com.taobao.arthas.bytekit.utils; + +public class InstanceUtils { + + public static T newInstance(Class clazz) { + try { + return clazz.newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java new file mode 100644 index 000000000..077040521 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java @@ -0,0 +1,169 @@ +package com.taobao.arthas.bytekit.utils; +import java.util.ArrayList; +import java.util.Stack; + +/** + * from org.apache.commons.io.FilenameUtils + * + * @author hengyunabc + * + */ +public class MatchUtils { + + /** + * The wildcard matcher uses the characters '?' and '*' to represent a + * single or multiple wildcard characters. + * + * @param str + * @param wildcardMatcher + * @return + */ + public static boolean wildcardMatch(String str, String wildcardMatcher) { + return wildcardMatch(str, wildcardMatcher, false); + } + + /** + * The wildcard matcher uses the characters '?' and '*' to represent a + * single or multiple wildcard characters. + * + * @param str + * @param wildcardMatcher + * @param sensitive + * if sensitive is true, str and wildcardMatcher will + * toLowerCase. + * @return + */ + public static boolean wildcardMatch(String str, String wildcardMatcher, boolean sensitive) { + if (str == null && wildcardMatcher == null) { + return true; + } + if (str == null || wildcardMatcher == null) { + return false; + } + str = convertCase(str, sensitive); + wildcardMatcher = convertCase(wildcardMatcher, sensitive); + String[] wcs = splitOnTokens(wildcardMatcher); + boolean anyChars = false; + int textIdx = 0; + int wcsIdx = 0; + Stack backtrack = new Stack(); + + // loop around a backtrack stack, to handle complex * matching + do { + if (backtrack.size() > 0) { + int[] array = (int[]) backtrack.pop(); + wcsIdx = array[0]; + textIdx = array[1]; + anyChars = true; + } + + // loop whilst tokens and text left to process + while (wcsIdx < wcs.length) { + + if (wcs[wcsIdx].equals("?")) { + // ? so move to next text char + textIdx++; + anyChars = false; + + } else if (wcs[wcsIdx].equals("*")) { + // set any chars status + anyChars = true; + if (wcsIdx == wcs.length - 1) { + textIdx = str.length(); + } + + } else { + // matching text token + if (anyChars) { + // any chars then try to locate text token + textIdx = str.indexOf(wcs[wcsIdx], textIdx); + if (textIdx == -1) { + // token not found + break; + } + int repeat = str.indexOf(wcs[wcsIdx], textIdx + 1); + if (repeat >= 0) { + backtrack.push(new int[] { wcsIdx, repeat }); + } + } else { + // matching from current position + if (!str.startsWith(wcs[wcsIdx], textIdx)) { + // couldnt match token + break; + } + } + + // matched text token, move text index to end of matched + // token + textIdx += wcs[wcsIdx].length(); + anyChars = false; + } + + wcsIdx++; + } + + // full match + if (wcsIdx == wcs.length && textIdx == str.length()) { + return true; + } + + } while (backtrack.size() > 0); + + return false; + } + + /** + * Splits a string into a number of tokens. + * + * @param text + * the text to split + * @return the tokens, never null + */ + static String[] splitOnTokens(String text) { + // used by wildcardMatch + // package level so a unit test may run on this + + if (text.indexOf("?") == -1 && text.indexOf("*") == -1) { + return new String[] { text }; + } + + char[] array = text.toCharArray(); + ArrayList list = new ArrayList(); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + if (array[i] == '?' || array[i] == '*') { + if (buffer.length() != 0) { + list.add(buffer.toString()); + buffer.setLength(0); + } + if (array[i] == '?') { + list.add("?"); + } else if (list.size() == 0 || (i > 0 && list.get(list.size() - 1).equals("*") == false)) { + list.add("*"); + } + } else { + buffer.append(array[i]); + } + } + if (buffer.length() != 0) { + list.add(buffer.toString()); + } + + return (String[]) list.toArray(new String[list.size()]); + } + + /** + * Converts the case of the input String to a standard format. Subsequent + * operations can then use standard String methods. + * + * @param str + * the string to convert, null returns null + * @return the lower-case version if case-insensitive + */ + static String convertCase(String str, boolean sensitive) { + if (str == null) { + return null; + } + return sensitive ? str : str.toLowerCase(); + } +} \ No newline at end of file diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java new file mode 100644 index 000000000..8023d3b24 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.utils; + +public class MethodUtils { + +} diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java new file mode 100644 index 000000000..b96f30b3c --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java @@ -0,0 +1,807 @@ +/* + * Copyright 2002-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.taobao.arthas.bytekit.utils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.UndeclaredThrowableException; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * Simple utility class for working with the reflection API and handling + * reflection exceptions. + * + *

Only intended for internal use. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Rod Johnson + * @author Costin Leau + * @author Sam Brannen + * @author Chris Beams + * @since 1.2.2 + */ +public abstract class ReflectionUtils { + + /** + * Naming prefix for CGLIB-renamed methods. + * @see #isCglibRenamedMethod + */ + private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$"; + + /** + * Attempt to find a {@link Field field} on the supplied {@link Class} with the + * supplied {@code name}. Searches all superclasses up to {@link Object}. + * @param clazz the class to introspect + * @param name the name of the field + * @return the corresponding Field object, or {@code null} if not found + */ + public static Field findField(Class clazz, String name) { + return findField(clazz, name, null); + } + + /** + * Attempt to find a {@link Field field} on the supplied {@link Class} with the + * supplied {@code name} and/or {@link Class type}. Searches all superclasses + * up to {@link Object}. + * @param clazz the class to introspect + * @param name the name of the field (may be {@code null} if type is specified) + * @param type the type of the field (may be {@code null} if name is specified) + * @return the corresponding Field object, or {@code null} if not found + */ + public static Field findField(Class clazz, String name, Class type) { + Class searchType = clazz; + while (Object.class != searchType && searchType != null) { + Field[] fields = getDeclaredFields(searchType); + for (Field field : fields) { + if ((name == null || name.equals(field.getName())) && + (type == null || type.equals(field.getType()))) { + return field; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } + + /** + * Set the field represented by the supplied {@link Field field object} on the + * specified {@link Object target object} to the specified {@code value}. + * In accordance with {@link Field#set(Object, Object)} semantics, the new value + * is automatically unwrapped if the underlying field has a primitive type. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. + * @param field the field to set + * @param target the target object on which to set the field + * @param value the value to set (may be {@code null}) + */ + public static void setField(Field field, Object target, Object value) { + try { + field.set(target, value); + } + catch (IllegalAccessException ex) { + handleReflectionException(ex); + throw new IllegalStateException( + "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); + } + } + + /** + * Get the field represented by the supplied {@link Field field object} on the + * specified {@link Object target object}. In accordance with {@link Field#get(Object)} + * semantics, the returned value is automatically wrapped if the underlying field + * has a primitive type. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. + * @param field the field to get + * @param target the target object from which to get the field + * @return the field's current value + */ + public static Object getField(Field field, Object target) { + try { + return field.get(target); + } + catch (IllegalAccessException ex) { + handleReflectionException(ex); + throw new IllegalStateException( + "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); + } + } + + /** + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and no parameters. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * @param clazz the class to introspect + * @param name the name of the method + * @return the Method object, or {@code null} if none found + */ + public static Method findMethod(Class clazz, String name) { + return findMethod(clazz, name, new Class[0]); + } + + /** + * Attempt to find a {@link Method} on the supplied class with the supplied name + * and parameter types. Searches all superclasses up to {@code Object}. + *

Returns {@code null} if no {@link Method} can be found. + * @param clazz the class to introspect + * @param name the name of the method + * @param paramTypes the parameter types of the method + * (may be {@code null} to indicate any signature) + * @return the Method object, or {@code null} if none found + */ + public static Method findMethod(Class clazz, String name, Class... paramTypes) { + Class searchType = clazz; + while (searchType != null) { + Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); + for (Method method : methods) { + if (name.equals(method.getName()) && + (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { + return method; + } + } + searchType = searchType.getSuperclass(); + } + return null; + } + + /** + * Invoke the specified {@link Method} against the supplied target object with no arguments. + * The target object can be {@code null} when invoking a static {@link Method}. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @return the invocation result, if any + * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) + */ + public static Object invokeMethod(Method method, Object target) { + return invokeMethod(method, target, new Object[0]); + } + + /** + * Invoke the specified {@link Method} against the supplied target object with the + * supplied arguments. The target object can be {@code null} when invoking a + * static {@link Method}. + *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @param args the invocation arguments (may be {@code null}) + * @return the invocation result, if any + */ + public static Object invokeMethod(Method method, Object target, Object... args) { + try { + return method.invoke(target, args); + } + catch (Exception ex) { + handleReflectionException(ex); + } + throw new IllegalStateException("Should never get here"); + } + + /** + * Invoke the specified JDBC API {@link Method} against the supplied target + * object with no arguments. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @return the invocation result, if any + * @throws SQLException the JDBC API SQLException to rethrow (if any) + * @see #invokeJdbcMethod(java.lang.reflect.Method, Object, Object[]) + */ + public static Object invokeJdbcMethod(Method method, Object target) throws SQLException { + return invokeJdbcMethod(method, target, new Object[0]); + } + + /** + * Invoke the specified JDBC API {@link Method} against the supplied target + * object with the supplied arguments. + * @param method the method to invoke + * @param target the target object to invoke the method on + * @param args the invocation arguments (may be {@code null}) + * @return the invocation result, if any + * @throws SQLException the JDBC API SQLException to rethrow (if any) + * @see #invokeMethod(java.lang.reflect.Method, Object, Object[]) + */ + public static Object invokeJdbcMethod(Method method, Object target, Object... args) throws SQLException { + try { + return method.invoke(target, args); + } + catch (IllegalAccessException ex) { + handleReflectionException(ex); + } + catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof SQLException) { + throw (SQLException) ex.getTargetException(); + } + handleInvocationTargetException(ex); + } + throw new IllegalStateException("Should never get here"); + } + + /** + * Handle the given reflection exception. Should only be called if no + * checked exception is expected to be thrown by the target method. + *

Throws the underlying RuntimeException or Error in case of an + * InvocationTargetException with such a root cause. Throws an + * IllegalStateException with an appropriate message or + * UndeclaredThrowableException otherwise. + * @param ex the reflection exception to handle + */ + public static void handleReflectionException(Exception ex) { + if (ex instanceof NoSuchMethodException) { + throw new IllegalStateException("Method not found: " + ex.getMessage()); + } + if (ex instanceof IllegalAccessException) { + throw new IllegalStateException("Could not access method: " + ex.getMessage()); + } + if (ex instanceof InvocationTargetException) { + handleInvocationTargetException((InvocationTargetException) ex); + } + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Handle the given invocation target exception. Should only be called if no + * checked exception is expected to be thrown by the target method. + *

Throws the underlying RuntimeException or Error in case of such a root + * cause. Throws an UndeclaredThrowableException otherwise. + * @param ex the invocation target exception to handle + */ + public static void handleInvocationTargetException(InvocationTargetException ex) { + rethrowRuntimeException(ex.getTargetException()); + } + + /** + * Rethrow the given {@link Throwable exception}, which is presumably the + * target exception of an {@link InvocationTargetException}. + * Should only be called if no checked exception is expected to be thrown + * by the target method. + *

Rethrows the underlying exception cast to a {@link RuntimeException} or + * {@link Error} if appropriate; otherwise, throws an + * {@link UndeclaredThrowableException}. + * @param ex the exception to rethrow + * @throws RuntimeException the rethrown exception + */ + public static void rethrowRuntimeException(Throwable ex) { + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + if (ex instanceof Error) { + throw (Error) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Rethrow the given {@link Throwable exception}, which is presumably the + * target exception of an {@link InvocationTargetException}. + * Should only be called if no checked exception is expected to be thrown + * by the target method. + *

Rethrows the underlying exception cast to an {@link Exception} or + * {@link Error} if appropriate; otherwise, throws an + * {@link UndeclaredThrowableException}. + * @param ex the exception to rethrow + * @throws Exception the rethrown exception (in case of a checked exception) + */ + public static void rethrowException(Throwable ex) throws Exception { + if (ex instanceof Exception) { + throw (Exception) ex; + } + if (ex instanceof Error) { + throw (Error) ex; + } + throw new UndeclaredThrowableException(ex); + } + + /** + * Determine whether the given method explicitly declares the given + * exception or one of its superclasses, which means that an exception + * of that type can be propagated as-is within a reflective invocation. + * @param method the declaring method + * @param exceptionType the exception to throw + * @return {@code true} if the exception can be thrown as-is; + * {@code false} if it needs to be wrapped + */ + public static boolean declaresException(Method method, Class exceptionType) { + Class[] declaredExceptions = method.getExceptionTypes(); + for (Class declaredException : declaredExceptions) { + if (declaredException.isAssignableFrom(exceptionType)) { + return true; + } + } + return false; + } + + /** + * Determine whether the given field is a "public static final" constant. + * @param field the field to check + */ + public static boolean isPublicStaticFinal(Field field) { + int modifiers = field.getModifiers(); + return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); + } + + /** + * Determine whether the given method is an "equals" method. + * @see java.lang.Object#equals(Object) + */ + public static boolean isEqualsMethod(Method method) { + if (method == null || !method.getName().equals("equals")) { + return false; + } + Class[] paramTypes = method.getParameterTypes(); + return (paramTypes.length == 1 && paramTypes[0] == Object.class); + } + + /** + * Determine whether the given method is a "hashCode" method. + * @see java.lang.Object#hashCode() + */ + public static boolean isHashCodeMethod(Method method) { + return (method != null && method.getName().equals("hashCode") && method.getParameterTypes().length == 0); + } + + /** + * Determine whether the given method is a "toString" method. + * @see java.lang.Object#toString() + */ + public static boolean isToStringMethod(Method method) { + return (method != null && method.getName().equals("toString") && method.getParameterTypes().length == 0); + } + + /** + * Determine whether the given method is originally declared by {@link java.lang.Object}. + */ + public static boolean isObjectMethod(Method method) { + if (method == null) { + return false; + } + try { + Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes()); + return true; + } + catch (Exception ex) { + return false; + } + } + + /** + * Determine whether the given method is a CGLIB 'renamed' method, + * following the pattern "CGLIB$methodName$0". + * @param renamedMethod the method to check + * @see org.springframework.cglib.proxy.Enhancer#rename + */ + public static boolean isCglibRenamedMethod(Method renamedMethod) { + String name = renamedMethod.getName(); + if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) { + int i = name.length() - 1; + while (i >= 0 && Character.isDigit(name.charAt(i))) { + i--; + } + return ((i > CGLIB_RENAMED_METHOD_PREFIX.length()) && + (i < name.length() - 1) && name.charAt(i) == '$'); + } + return false; + } + + /** + * Make the given field accessible, explicitly setting it accessible if + * necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * @param field the field to make accessible + * @see java.lang.reflect.Field#setAccessible + */ + public static void makeAccessible(Field field) { + if ((!Modifier.isPublic(field.getModifiers()) || + !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || + Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { + field.setAccessible(true); + } + } + + /** + * Make the given method accessible, explicitly setting it accessible if + * necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * @param method the method to make accessible + * @see java.lang.reflect.Method#setAccessible + */ + public static void makeAccessible(Method method) { + if ((!Modifier.isPublic(method.getModifiers()) || + !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { + method.setAccessible(true); + } + } + + /** + * Make the given constructor accessible, explicitly setting it accessible + * if necessary. The {@code setAccessible(true)} method is only called + * when actually necessary, to avoid unnecessary conflicts with a JVM + * SecurityManager (if active). + * @param ctor the constructor to make accessible + * @see java.lang.reflect.Constructor#setAccessible + */ + public static void makeAccessible(Constructor ctor) { + if ((!Modifier.isPublic(ctor.getModifiers()) || + !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { + ctor.setAccessible(true); + } + } + + /** + * Perform the given callback operation on all matching methods of the given + * class, as locally declared or equivalent thereof (such as default methods + * on Java 8 based interfaces that the given class implements). + * @param clazz the class to introspect + * @param mc the callback to invoke for each method + * @since 4.2 + * @see #doWithMethods + */ + public static void doWithLocalMethods(Class clazz, MethodCallback mc) { + Method[] methods = getDeclaredMethods(clazz); + for (Method method : methods) { + try { + mc.doWith(method); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); + } + } + } + + /** + * Perform the given callback operation on all matching methods of the given + * class and superclasses. + *

The same named method occurring on subclass and superclass will appear + * twice, unless excluded by a {@link MethodFilter}. + * @param clazz the class to introspect + * @param mc the callback to invoke for each method + * @see #doWithMethods(Class, MethodCallback, MethodFilter) + */ + public static void doWithMethods(Class clazz, MethodCallback mc) { + doWithMethods(clazz, mc, null); + } + + /** + * Perform the given callback operation on all matching methods of the given + * class and superclasses (or given interface and super-interfaces). + *

The same named method occurring on subclass and superclass will appear + * twice, unless excluded by the specified {@link MethodFilter}. + * @param clazz the class to introspect + * @param mc the callback to invoke for each method + * @param mf the filter that determines the methods to apply the callback to + */ + public static void doWithMethods(Class clazz, MethodCallback mc, MethodFilter mf) { + // Keep backing up the inheritance hierarchy. + Method[] methods = getDeclaredMethods(clazz); + for (Method method : methods) { + if (mf != null && !mf.matches(method)) { + continue; + } + try { + mc.doWith(method); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); + } + } + if (clazz.getSuperclass() != null) { + doWithMethods(clazz.getSuperclass(), mc, mf); + } + else if (clazz.isInterface()) { + for (Class superIfc : clazz.getInterfaces()) { + doWithMethods(superIfc, mc, mf); + } + } + } + + /** + * Get all declared methods on the leaf class and all superclasses. + * Leaf class methods are included first. + * @param leafClass the class to introspect + */ + public static Method[] getAllDeclaredMethods(Class leafClass) { + final List methods = new ArrayList(32); + doWithMethods(leafClass, new MethodCallback() { + @Override + public void doWith(Method method) { + methods.add(method); + } + }); + return methods.toArray(new Method[methods.size()]); + } + + /** + * Get the unique set of declared methods on the leaf class and all superclasses. + * Leaf class methods are included first and while traversing the superclass hierarchy + * any methods found with signatures matching a method already included are filtered out. + * @param leafClass the class to introspect + */ + public static Method[] getUniqueDeclaredMethods(Class leafClass) { + final List methods = new ArrayList(32); + doWithMethods(leafClass, new MethodCallback() { + @Override + public void doWith(Method method) { + boolean knownSignature = false; + Method methodBeingOverriddenWithCovariantReturnType = null; + for (Method existingMethod : methods) { + if (method.getName().equals(existingMethod.getName()) && + Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) { + // Is this a covariant return type situation? + if (existingMethod.getReturnType() != method.getReturnType() && + existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) { + methodBeingOverriddenWithCovariantReturnType = existingMethod; + } + else { + knownSignature = true; + } + break; + } + } + if (methodBeingOverriddenWithCovariantReturnType != null) { + methods.remove(methodBeingOverriddenWithCovariantReturnType); + } + if (!knownSignature && !isCglibRenamedMethod(method)) { + methods.add(method); + } + } + }); + return methods.toArray(new Method[methods.size()]); + } + + /** + * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache + * in order to avoid the JVM's SecurityManager check and defensive array copying. + * In addition, it also includes Java 8 default methods from locally implemented + * interfaces, since those are effectively to be treated just like declared methods. + * @param clazz the class to introspect + * @return the cached array of methods + * @see Class#getDeclaredMethods() + */ + private static Method[] getDeclaredMethods(Class clazz) { + Method[] result = null; + if (result == null) { + Method[] declaredMethods = clazz.getDeclaredMethods(); + List defaultMethods = findConcreteMethodsOnInterfaces(clazz); + if (defaultMethods != null) { + result = new Method[declaredMethods.length + defaultMethods.size()]; + System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); + int index = declaredMethods.length; + for (Method defaultMethod : defaultMethods) { + result[index] = defaultMethod; + index++; + } + } + else { + result = declaredMethods; + } + } + return result; + } + + private static List findConcreteMethodsOnInterfaces(Class clazz) { + List result = null; + for (Class ifc : clazz.getInterfaces()) { + for (Method ifcMethod : ifc.getMethods()) { + if (!Modifier.isAbstract(ifcMethod.getModifiers())) { + if (result == null) { + result = new LinkedList(); + } + result.add(ifcMethod); + } + } + } + return result; + } + + /** + * Invoke the given callback on all fields in the target class, going up the + * class hierarchy to get all declared fields. + * @param clazz the target class to analyze + * @param fc the callback to invoke for each field + * @since 4.2 + * @see #doWithFields + */ + public static void doWithLocalFields(Class clazz, FieldCallback fc) { + for (Field field : getDeclaredFields(clazz)) { + try { + fc.doWith(field); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); + } + } + } + + /** + * Invoke the given callback on all fields in the target class, going up the + * class hierarchy to get all declared fields. + * @param clazz the target class to analyze + * @param fc the callback to invoke for each field + */ + public static void doWithFields(Class clazz, FieldCallback fc) { + doWithFields(clazz, fc, null); + } + + /** + * Invoke the given callback on all fields in the target class, going up the + * class hierarchy to get all declared fields. + * @param clazz the target class to analyze + * @param fc the callback to invoke for each field + * @param ff the filter that determines the fields to apply the callback to + */ + public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) { + // Keep backing up the inheritance hierarchy. + Class targetClass = clazz; + do { + Field[] fields = getDeclaredFields(targetClass); + for (Field field : fields) { + if (ff != null && !ff.matches(field)) { + continue; + } + try { + fc.doWith(field); + } + catch (IllegalAccessException ex) { + throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); + } + } + targetClass = targetClass.getSuperclass(); + } + while (targetClass != null && targetClass != Object.class); + } + + /** + * This variant retrieves {@link Class#getDeclaredFields()} from a local cache + * in order to avoid the JVM's SecurityManager check and defensive array copying. + * @param clazz the class to introspect + * @return the cached array of fields + * @see Class#getDeclaredFields() + */ + private static Field[] getDeclaredFields(Class clazz) { + Field[] result = null; + if (result == null) { + result = clazz.getDeclaredFields(); + } + return result; + } + + /** + * Given the source object and the destination, which must be the same class + * or a subclass, copy all fields, including inherited fields. Designed to + * work on objects with public no-arg constructors. + */ + public static void shallowCopyFieldState(final Object src, final Object dest) { + if (src == null) { + throw new IllegalArgumentException("Source for field copy cannot be null"); + } + if (dest == null) { + throw new IllegalArgumentException("Destination for field copy cannot be null"); + } + if (!src.getClass().isAssignableFrom(dest.getClass())) { + throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() + + "] must be same or subclass as source class [" + src.getClass().getName() + "]"); + } + doWithFields(src.getClass(), new FieldCallback() { + @Override + public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException { + makeAccessible(field); + Object srcValue = field.get(src); + field.set(dest, srcValue); + } + }, COPYABLE_FIELDS); + } + + + /** + * Action to take on each method. + */ + public interface MethodCallback { + + /** + * Perform an operation using the given method. + * @param method the method to operate on + */ + void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; + } + + + /** + * Callback optionally used to filter methods to be operated on by a method callback. + */ + public interface MethodFilter { + + /** + * Determine whether the given method matches. + * @param method the method to check + */ + boolean matches(Method method); + } + + + /** + * Callback interface invoked on each field in the hierarchy. + */ + public interface FieldCallback { + + /** + * Perform an operation using the given field. + * @param field the field to operate on + */ + void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; + } + + + /** + * Callback optionally used to filter fields to be operated on by a field callback. + */ + public interface FieldFilter { + + /** + * Determine whether the given field matches. + * @param field the field to check + */ + boolean matches(Field field); + } + + + /** + * Pre-built FieldFilter that matches all non-static, non-final fields. + */ + public static final FieldFilter COPYABLE_FIELDS = new FieldFilter() { + + @Override + public boolean matches(Field field) { + return !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())); + } + }; + + + /** + * Pre-built MethodFilter that matches all non-bridge methods. + */ + public static final MethodFilter NON_BRIDGED_METHODS = new MethodFilter() { + + @Override + public boolean matches(Method method) { + return !method.isBridge(); + } + }; + + + /** + * Pre-built MethodFilter that matches all non-bridge methods + * which are not declared on {@code java.lang.Object}. + */ + public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() { + + @Override + public boolean matches(Method method) { + return (!method.isBridge() && method.getDeclaringClass() != Object.class); + } + }; + +} 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 new file mode 100644 index 000000000..98eed71aa --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java @@ -0,0 +1,74 @@ +package com.taobao.arthas.bytekit.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +import com.alibaba.arthas.deps.org.objectweb.asm.ClassReader; +import com.alibaba.arthas.deps.org.objectweb.asm.ClassVisitor; +import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.util.CheckClassAdapter; + +/** + * + * @author hengyunabc + * + */ +public class VerifyUtils { + + public static void asmVerify(byte[] bytes, boolean printResults) throws IOException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + ClassReader cr = new ClassReader(inputStream); + CheckClassAdapter.verify(cr, true, new PrintWriter(System.out)); + } + + 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); + } + + public static Object instanceVerity(byte[] bytes) throws Exception { + String name = Type.getObjectType(AsmUtils.toClassNode(bytes).name).getClassName(); + + @SuppressWarnings("resource") + ClassbyteClassLoader cl = new ClassbyteClassLoader(ClassLoaderUtils.getUrls(ClassLoader.getSystemClassLoader()), + ClassLoader.getSystemClassLoader().getParent()); + + cl.addClass(name, bytes); + + Class loadClass = cl.loadClass(name); + return loadClass.newInstance(); + } + + 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 static class ClassbyteClassLoader extends URLClassLoader { + public ClassbyteClassLoader(URL[] urls, ClassLoader cl) { + super(urls, 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/TryCatchBlockTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/TryCatchBlockTest.java new file mode 100644 index 000000000..ec34f6353 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/TryCatchBlockTest.java @@ -0,0 +1,127 @@ +package com.taobao.arthas.bytekit.asm; + +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.InsnList; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.JumpInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.LabelNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +import net.bytebuddy.agent.ByteBuddyAgent; + +public class TryCatchBlockTest { + + static public class Hello { + + public long sss(String msg, int i, long l) { + return 124L; + } + + public long toBeCall(int i , long l, String s) { + return l + i; + } + +// public String say(String msg, int i, long l) { +// i = 0; +// System.out.println("hello"); +// i = 0; +// sss(msg, i, l); +// return ""; +// } + + public int say(int ii) { + toBeCall(ii, 123L, ""); + return 123; + } + } + + public static void ttt() throws Throwable { + try { + System.out.println(); + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } + try { + System.out.println(); + } catch (Throwable e) { + e.printStackTrace(); + throw e; + } + } + + @Test + public void test() throws Exception { + + Instrumentation instrumentation = ByteBuddyAgent.install(); + + ClassNode classNode = AsmUtils.loadClass(Hello.class); + + List methods = AsmUtils.findMethods(classNode.methods, "say"); + + MethodNode methodNode = methods.get(0); + + AbstractInsnNode insnNode = methodNode.instructions.getFirst(); + + List methodInsnNodes = new ArrayList(); + + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + methodInsnNodes.add(methodInsnNode); + } + insnNode = insnNode.getNext(); + } + + for (MethodInsnNode methodInsnNode : methodInsnNodes) { + TryCatchBlock tryCatchBlock = new TryCatchBlock(methodNode); + + InsnList toInsert = new InsnList(); + + LabelNode gotoDest = new LabelNode(); + + LabelNode startLabelNode = tryCatchBlock.getStartLabelNode(); + LabelNode endLabelNode = tryCatchBlock.getEndLabelNode(); + + toInsert.add(new JumpInsnNode(Opcodes.GOTO, gotoDest)); + toInsert.add(endLabelNode); + + AsmOpUtils.dup(toInsert); + toInsert.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getType(Throwable.class).getInternalName(), + "printStackTrace", "()V", false)); + + AsmOpUtils.throwException(toInsert); + + toInsert.add(gotoDest); + + methodNode.instructions.insertBefore(methodInsnNode, startLabelNode); + methodNode.instructions.insert(methodInsnNode, toInsert); + + tryCatchBlock.sort(); + } + + byte[] bytes = AsmUtils.toBytes(classNode); + + String decompile = Decompiler.decompile(bytes); + System.err.println(decompile); + + VerifyUtils.asmVerify(bytes, true); + + VerifyUtils.instanceVerity(bytes); + + } + +} 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 new file mode 100644 index 000000000..2daf425f5 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java @@ -0,0 +1,18 @@ +package com.taobao.arthas.bytekit.asm.inst; + +public class InstDemo { + + public int returnInt(int i) { + System.out.println(new Object[] { i }); + return 9998; + } + + public static void onEnter(Object[] args) { + System.out.println(args); + } + + public static int returnIntStatic(int i) { + return 9998; + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java new file mode 100644 index 000000000..8dc26a9f1 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java @@ -0,0 +1,96 @@ +package com.taobao.arthas.bytekit.asm.inst; + +import java.util.List; + +import org.junit.Test; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AnnotationNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.FieldNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +public class InstDemoTest { + + @Test + public void test() throws Exception { + + ClassNode apmClassNode = AsmUtils.loadClass(InstDemo_APM.class); + + ClassNode originClassNode = AsmUtils.loadClass(InstDemo.class); + + ClassNode targetClassNode = AsmUtils.copy(originClassNode); + + + byte[] renameClass = AsmUtils.renameClass(AsmUtils.toBytes(apmClassNode), Type.getObjectType(originClassNode.name).getClassName()); + + apmClassNode = AsmUtils.toClassNode(renameClass); + + for(FieldNode fieldNode : apmClassNode.fields) { + if( fieldNode.visibleAnnotations != null) { + for( AnnotationNode annotationNode : fieldNode.visibleAnnotations) { + System.err.println(annotationNode.desc); + System.err.println(annotationNode.values); + + if(Type.getType(NewField.class).equals(Type.getType(annotationNode.desc))) { + AsmUtils.addField(targetClassNode, fieldNode); + } + + } + } + } + + for (MethodNode methodNode : apmClassNode.methods) { + methodNode = AsmUtils.removeLineNumbers(methodNode); + if (methodNode.name.startsWith("__origin_")) { + continue; + } else { + MethodNode findMethod = AsmUtils.findMethod(originClassNode.methods, methodNode); + if (findMethod != null) { + // 先要替换 invokeOrigin ,要判断 + // 从 apm 里查找 __origin_ 开头的函数,忽略 + // 查找 非 __origin_ 开头的函数,在原来的类里查找,如果有同样签名的函数 + // 则从函数里查找 是否有 __origin_ 的函数调用。如果有的话,则从原有的类里查找到 method,再inline掉。 + + List originMethodInsnNodes = AsmUtils.findMethodInsnNodeWithPrefix(methodNode, + "__origin_"); + + for (MethodInsnNode methodInsnNode : originMethodInsnNodes) { + String toInlineMethodName = methodInsnNode.name.substring("__origin_".length()); + MethodNode originMethodNode = AsmUtils.findMethod(originClassNode.methods, toInlineMethodName, + findMethod.desc); + + MethodNode tmpMethodNode = AsmUtils.copy(originMethodNode); + tmpMethodNode.name = methodInsnNode.name; + + MethodProcessor methodProcessor = new MethodProcessor(apmClassNode.name, methodNode); + methodProcessor.inline(originClassNode.name, tmpMethodNode); + + AsmUtils.replaceMethod(targetClassNode, methodProcessor.getMethodNode()); + + } + + } else { + // 没找到的函数,则加进去 + AsmUtils.addMethod(targetClassNode, methodNode); + } + } + + + } + + byte[] resutlBytes = AsmUtils.toBytes(targetClassNode); + + System.err.println(Decompiler.decompile(resutlBytes)); + + System.err.println(AsmUtils.toASMCode(resutlBytes)); + + VerifyUtils.asmVerify(resutlBytes); + VerifyUtils.instanceVerity(resutlBytes); + } +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java new file mode 100644 index 000000000..75cfd3aed --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java @@ -0,0 +1,29 @@ +package com.taobao.arthas.bytekit.asm.inst; + +@Instrument +public class InstDemo_APM { + + @NewField + private String newField; + + public int newMethod(String s) { + return s.length() + 998; + } + + // 这种方式来写怎么样?有点丑,但是不需要写那些转换的代码。 在插件的编绎出结果后,可以检查下 名字,static,参数等是否匹配的。 + // 这种处理有点丑,但inline应该没问题 + public int __origin_returnInt(int i) { + return 0; + } + + public int returnInt(int i) { + + int re = __origin_returnInt(i); + + return 9998 + re; + } + + 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 new file mode 100644 index 000000000..041d26ea0 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java @@ -0,0 +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 new file mode 100644 index 000000000..a5d41a27d --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java @@ -0,0 +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..4410b1a31 --- /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 com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.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); + } +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java new file mode 100644 index 000000000..a90dc7177 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java @@ -0,0 +1,93 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtEnterTest { + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + @Rule + public OutputCapture capture = new OutputCapture(); + + public static class Sample { + + long longField; + String strField; + static int intField; + + public int hello(String str, boolean exception) { + if (exception) { + throw new RuntimeException("test exception"); + } + return str.length(); + } + + public long toBeInvoke(int i , long l, String s, long ll) { + return l + ll; + } + + public void testInvokeArgs() { + toBeInvoke(1, 123L, "abc", 100L); + } + + } + + public static class TestPrintSuppressHandler { + + @ExceptionHandler(inline = true) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.err.println("exception handler: " + clazz); + e.printStackTrace(); + } + } + + public static class EnterInterceptor { + + @AtEnter(inline = true + , suppress = RuntimeException.class, suppressHandler = TestPrintSuppressHandler.class + ) + public static long onEnter( + @Binding.This Object object, @Binding.Class Object clazz, + @Binding.Field(name = "longField") long longField, + @Binding.Field(name = "longField") Object longFieldObject, + @Binding.Field(name = "intField") int intField, + @Binding.Field(name = "strField") String strField, + @Binding.Field(name = "intField") Object intFielObject, + @Binding.MethodName String methodName, + @Binding.MethodDesc String methodDesc + ) { + System.err.println("onEnter, object:" + object); + System.err.println("onEnter, methodName:" + methodName); + System.err.println("onEnter, methodDesc:" + methodDesc); + return 123L; + } + + } + + + + @Test + public void testEnter() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(EnterInterceptor.class).methodMatcher("hello") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + new Sample().hello("abc", false); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("onEnter, object:"); + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java new file mode 100644 index 000000000..323e85863 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java @@ -0,0 +1,84 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtExceptionExitTest { + + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + + @Rule + public OutputCapture capture = new OutputCapture(); + + public static class Sample { + + long longField; + String strField; + static int intField; + + public int hello(String str, boolean exception) { + if (exception) { + throw new RuntimeException("test exception"); + } + return str.length(); + } + + public long toBeInvoke(int i , long l, String s, long ll) { + return l + ll; + } + + public void testInvokeArgs() { + toBeInvoke(1, 123L, "abc", 100L); + } + + } + + public static class TestPrintSuppressHandler { + + @ExceptionHandler(inline = true) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.err.println("exception handler: " + clazz); + System.err.println(e.getMessage()); + assertThat(e).hasMessage("exception for ExceptionHandler"); + } + } + + public static class ExceptionExitInterceptor { + @AtExceptionExit(inline = false, onException = RuntimeException.class ,suppress = Throwable.class, suppressHandler = TestPrintSuppressHandler.class) + public static void onExceptionExit(@Binding.Throwable RuntimeException ex, @Binding.This Object object, + @Binding.Class Object clazz) { + System.err.println("AtExceptionExit, ex:" + ex); + throw new RuntimeException("exception for ExceptionHandler"); + } + } + + + @Test + public void testExecptionExitException() throws Exception { + + TestHelper helper = TestHelper.builder().interceptorClass(ExceptionExitInterceptor.class).methodMatcher("hello") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + System.err.println(Decompiler.decompile(bytes)); + try { + new Sample().hello("abc", true); + } catch (Exception e) { + assertThat(e).isInstanceOf(RuntimeException.class).hasMessageContaining("test exception"); + } + + assertThat(capture.toString()).contains("AtExceptionExit, ex:"); + + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java new file mode 100644 index 000000000..1462500ad --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java @@ -0,0 +1,93 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtExitTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + static class Sample { + long longField; + int intField; + String strField; + + public void voidExit() { + + } + + public long longExit() { + return 100L; + } + + public static long staticExit() { + return 999L; + } + } + + public static class TestPrintSuppressHandler { + + @ExceptionHandler(inline = false) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.err.println("exception handler: " + clazz); + e.printStackTrace(); + } + } + + public static class TestAccessInterceptor { + @AtExit(inline = false) + public static void atExit(@Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.Return Object re + ) { + System.err.println("AtFieldAccess: this" + object); + } + } + + public static class ChangeReturnInterceptor { + + @AtExit(inline = false, suppress = RuntimeException.class, suppressHandler = TestPrintSuppressHandler.class) + public static Object onExit(@Binding.This Object object, @Binding.Class Object clazz) { + System.err.println("onExit, object:" + object); + return 123L; + } + } + + @Test + public void testExit() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("voidExit") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + new Sample().voidExit(); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("AtFieldAccess: this"); + } + + + @Test + public void testExitAndChangeReturn() throws Exception { + + TestHelper helper = TestHelper.builder().interceptorClass(ChangeReturnInterceptor.class).methodMatcher("longExit") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + System.err.println(Decompiler.decompile(bytes)); + + long re = new Sample().longExit(); + + assertThat(re).isEqualTo(123); + assertThat(capture.toString()).contains("onExit, object:"); + } +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java new file mode 100644 index 000000000..45024ce2d --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java @@ -0,0 +1,60 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtFieldAccess; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtFieldAccessTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + class Sample { + long longField; + int intField; + String strField; + + public int testReadField(int ii) { + longField = 999; + return 123; + } + } + + public static class TestPrintSuppressHandler { + + @ExceptionHandler(inline = false) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.err.println("exception handler: " + clazz); + e.printStackTrace(); + } + } + + public static class FieldAccessInterceptor { + @AtFieldAccess(name = "longField" , inline =false) + public static void onFieldAccess(@Binding.This Object object, + @Binding.Class Object clazz) { + System.err.println("AtFieldAccess: this" + object); + } + } + + @Test + public void testEnter() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(FieldAccessInterceptor.class).methodMatcher("testReadField") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + new Sample().testReadField(100); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("AtFieldAccess: this"); + } + + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java new file mode 100644 index 000000000..f6c565829 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java @@ -0,0 +1,105 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtInvokeTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + static class Sample { + long longField; + int intField; + String strField; + + public Sample(int i, long l, String s) { + staticToBeCall(i, l, s); + aaa("aaa"); + } + + public int testCall(int ii) { + toBeCall(ii, 123L, ""); + System.err.println("abc"); + + aaa("abc"); + return 123; + } + + + public void aaa(String aaa) { + return ; + } + + public long toBeCall(int i , long l, String s) { + return l + i; + } + + public static long staticToBeCall(int i , long l, String s) { + return l + i; + } + } + + public static class TestPrintSuppressHandler { + + @ExceptionHandler(inline = false) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.err.println("exception handler: " + clazz); + e.printStackTrace(); + } + } + + public static class TestAccessInterceptor { + @AtInvoke(name = "", inline = false, whenComplete=false, excludes = {"System."}) + public static void onInvoke( + @Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.Line int line, + @Binding.InvokeArgs Object[] args + ) { + System.err.println("onInvoke: line: " + line); + System.err.println("onInvoke: this: " + object); + } + + @AtInvoke(name = "toBeCall", inline = false, whenComplete = true) + public static void onInvokeAfter( + @Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.InvokeReturn Object invokeReturn + , + @Binding.InvokeMethodDeclaration String declaration + ) { + System.err.println("onInvokeAfter: this" + object); + System.err.println("declaration: " + declaration); + assertThat(declaration).isEqualTo("long toBeCall(int, long, java.lang.String)"); + + System.err.println("invokeReturn: " + invokeReturn); + assertThat(invokeReturn).isEqualTo(100 + 123L); + } + } + + //@Test + // TODO fix com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation satck save + public void testInvokeBefore() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("testCall") + .redefine(true); + byte[] bytes = helper.process(Sample.class); + + new Sample(100, 100L, "").testCall(100); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("onInvoke: this"); + } + + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java new file mode 100644 index 000000000..2bcba1672 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java @@ -0,0 +1,85 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtLine; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtLineTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + static class Sample { + + public int testLine(int i) { + String s = "" + i; + if(i > 0) { + String abc = s + i; + i++; + i = i * 100 + + i + - 100 + Math.max(100, i); + i += s.length() + abc.length(); + }else { + if(i == -1) { + try { + System.err.println("i is -1"); + throw new RuntimeException(); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + + } + } + return i * 2; + } + + } + + public static class TestAccessInterceptor { + + @AtLine(lines = { -1}, inline = false) + public static void atLine( + @Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.Line int line, + @Binding.Args Object[] args + , + @Binding.ArgNames String[] argNames + , + @Binding.LocalVars Object[] vars, + @Binding.LocalVarNames String[] varNames + ) { + System.err.println("atLine: this" + object); + System.err.println("line: " + line); + System.err.println("args: " + Arrays.toString(args)); + System.err.println("argNames: " + Arrays.toString(argNames)); + + System.err.println("vars: " + Arrays.toString(vars)); + System.err.println("varNames: " + Arrays.toString(varNames)); + } + } + + @Test + public void testLine() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("*") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + new Sample().testLine(100); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("atLine: this"); + } + + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java new file mode 100644 index 000000000..91ccbb512 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java @@ -0,0 +1,89 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncEnter; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtSyncEnterTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + static class Sample { + + public int testLine(int i) { + String s = "" + i; + synchronized (s) { + if(i > 0) { + String abc = s + i; + i++; + i = i * 100 + + i + - 100 + Math.max(100, i); + i += s.length() + abc.length(); + }else { + if(i == -1) { + try { + System.err.println("i is -1"); + throw new RuntimeException(); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + + } + } + } + + return i * 2; + } + + } + + public static class TestInterceptor { + + @AtSyncEnter(whenComplete=false, inline = false) + public static void atSyncEnter( + @Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.Args Object[] args + , + @Binding.ArgNames String[] argNames + , + @Binding.LocalVars Object[] vars, + @Binding.LocalVarNames String[] varNames + , + @Binding.Monitor Object monitor + ) { + System.err.println("atSyncEnter: this" + object); + System.err.println("args: " + Arrays.toString(args)); + System.err.println("argNames: " + Arrays.toString(argNames)); + + System.err.println("vars: " + Arrays.toString(vars)); + System.err.println("varNames: " + Arrays.toString(varNames)); + + } + } + + @Test + public void test() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(TestInterceptor.class).methodMatcher("*") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + new Sample().testLine(100); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("atSyncEnter: this"); + } + + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java new file mode 100644 index 000000000..9aef147d4 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java @@ -0,0 +1,89 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtSyncExit; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtSyncExitTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + static class Sample { + + public int testLine(int i) { + String s = "" + i; + synchronized (s) { + if(i > 0) { + String abc = s + i; + i++; + i = i * 100 + + i + - 100 + Math.max(100, i); + i += s.length() + abc.length(); + }else { + if(i == -1) { + try { + System.err.println("i is -1"); + throw new RuntimeException(); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + + } + } + } + + return i * 2; + } + + } + + public static class TestInterceptor { + + @AtSyncExit(whenComplete=false, inline = false) + public static void atSyncExit( + @Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.Args Object[] args + , + @Binding.ArgNames String[] argNames + , + @Binding.LocalVars Object[] vars, + @Binding.LocalVarNames String[] varNames + , + @Binding.Monitor Object monitor + ) { + System.err.println("atSyncExit: this" + object); + System.err.println("args: " + Arrays.toString(args)); + System.err.println("argNames: " + Arrays.toString(argNames)); + + System.err.println("vars: " + Arrays.toString(vars)); + System.err.println("varNames: " + Arrays.toString(varNames)); + + } + } + + @Test + public void test() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(TestInterceptor.class).methodMatcher("*") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + new Sample().testLine(100); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("atSyncExit: this"); + } + + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java new file mode 100644 index 000000000..d696ca978 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java @@ -0,0 +1,76 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.boot.test.rule.OutputCapture; + +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtThrow; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.ExceptionHandler; +import com.taobao.arthas.bytekit.utils.Decompiler; + +public class AtThrowTest { + @Rule + public OutputCapture capture = new OutputCapture(); + + static class Sample { + + public static long testThrow(int i , long l, String s) { + try { + if(i < 0) { + throw new RuntimeException("eeeee"); + } + } catch (Exception e) { + + System.err.println(e.getMessage()); + } + return l + i; + } + } + + public static class TestPrintSuppressHandler { + + @ExceptionHandler(inline = false) + public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { + System.err.println("exception handler: " + clazz); + e.printStackTrace(); + } + } + + public static class TestAccessInterceptor { + + @AtThrow(inline = false) + public static void atThrow( + @Binding.This Object object, + @Binding.Class Object clazz + , + @Binding.LocalVars Object[] vars, + @Binding.Throwable Throwable t + ) { + System.err.println("atThrow: this" + object); + System.err.println("vars: " + Arrays.toString(vars)); + System.err.println("t: " + t); + + assertThat(t).hasMessage("eeeee"); + } + } + + @Test + public void testThrow() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(TestAccessInterceptor.class).methodMatcher("testThrow") + .reTransform(true); + byte[] bytes = helper.process(Sample.class); + + Sample.testThrow(-1, 0, null); + + System.err.println(Decompiler.decompile(bytes)); + + assertThat(capture.toString()).contains("atThrow: this"); + } + + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java new file mode 100644 index 000000000..aff87158b --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java @@ -0,0 +1,94 @@ +package com.taobao.arthas.bytekit.asm.interceptor; + +import java.util.ArrayList; +import java.util.List; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.utils.AgentUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.MatchUtils; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +/** + * + * @author hengyunabc + * + */ +public class TestHelper { + + private Class interceptorClass; + + private boolean redefine; + + private boolean reTransform; + + private String methodMatcher = "*"; + + private boolean asmVerity = true; + + public static TestHelper builder() { + return new TestHelper(); + } + + public TestHelper interceptorClass(Class interceptorClass) { + this.interceptorClass = interceptorClass; + return this; + } + + public TestHelper redefine(boolean redefine) { + this.redefine = redefine; + return this; + } + + public TestHelper reTransform(boolean reTransform) { + this.reTransform = reTransform; + return this; + } + + public TestHelper methodMatcher(String methodMatcher) { + this.methodMatcher = methodMatcher; + return this; + } + + public byte[] process(Class transform) throws Exception { + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser.parse(interceptorClass); + + ClassNode classNode = AsmUtils.loadClass(transform); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, methodMatcher)) { + matchedMethods.add(methodNode); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + byte[] bytes = AsmUtils.toBytes(classNode); + if (asmVerity) { + VerifyUtils.asmVerify(bytes); + } + + if (redefine) { + AgentUtils.redefine(transform, bytes); + } + + if (reTransform) { + AgentUtils.reTransform(transform, bytes); + } + + return bytes; + } +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/GroupFilterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/GroupFilterTest.java new file mode 100644 index 000000000..9aa9cae02 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/GroupFilterTest.java @@ -0,0 +1,318 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvokeException; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.asm.location.LocationType; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.bytekit.utils.MatchUtils; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class GroupFilterTest { + + public static class SpyInterceptor { + + @AtEnter(inline = true) + public static void atEnter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc, + @Binding.Args Object[] args) { + SpyAPI.atEnter(clazz, methodName, methodDesc, target, args); + } + + @AtExit(inline = true) + public static void atExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc, + @Binding.Args Object[] args, @Binding.Return Object returnObj) { + SpyAPI.atExit(clazz, methodName, methodDesc, target, args, returnObj); + } + + @AtExceptionExit(inline = true) + public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc, + @Binding.Args Object[] args, @Binding.Throwable Throwable throwable) { + SpyAPI.atExceptionExit(clazz, methodName, methodDesc, target, args, throwable); + } + } + + public static class SpyTraceInterceptor { + @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "java.**", "**SpyAPI**" }) + public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atBeforeInvoke(clazz, invokeInfo, target); + } + + @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "java.**", "**SpyAPI**" }) + public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atAfterInvoke(clazz, invokeInfo, target); + } + + @AtInvokeException(name = "", inline = true, excludes = { "java.**", "**SpyAPI**" }) + public static void onInvokeException(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) { + SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable); + } + } + + public static class SpyTraceInterceptor2 { + @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "**SpyAPI**" }) + public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + + SpyAPI.atBeforeInvoke(clazz, invokeInfo, target); + } + + @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "**SpyAPI**" }) + public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atAfterInvoke(clazz, invokeInfo, target); + } + + @AtInvokeException(name = "", inline = true, excludes = { "**SpyAPI**" }) + public static void onInvokeException(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) { + SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable); + } + } + + public static class SpyAPI { + public static void atEnter(Class clazz, String methodName, String methodDesc, Object target, Object[] args) { + + } + + public static void atExceptionExit(Class clazz, String methodName, String methodDesc, Object target, + Object[] args, Throwable throwable) { + + } + + public static void atExit(Class clazz, String methodName, String methodDesc, Object target, Object[] args, + Object returnObj) { + + } + + public static void atBeforeInvoke(Class clazz, String invokeInfo, + Object target) { + + } + + public static void atInvokeException(Class clazz, String invokeInfo, + Object target, Throwable throwable) { + + } + + public static void atAfterInvoke(Class clazz, String invokeInfo, + Object target) { + + } + + } + + public static class TestAAA { + + int i = 0; + long l = 0; + + public TestAAA() { + xxx(1, 134L); + } + + public String hello(String str) { + + String result = ""; + + xxx(i, l); + + return result; + + } + + public String xxx(int i, long l) { + return "" + i + l; + } + + } + + @Test + public void test() throws Exception { + boolean skipJDKTrace = false; + + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser + .parse(SpyInterceptor.class); + + Class spyTraceInterceptorClass = SpyTraceInterceptor2.class; + if (skipJDKTrace == false) { + spyTraceInterceptorClass = SpyTraceInterceptor.class; + } + List traceInvokeProcessors = defaultInterceptorClassParser + .parse(spyTraceInterceptorClass); + interceptorProcessors.addAll(traceInvokeProcessors); + + + ClassNode classNode = AsmUtils.loadClass(TestAAA.class); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, "*")) { + matchedMethods.add(methodNode); + } + } + + GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); + + LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter", + LocationType.ENTER); + LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit", + LocationType.EXIT); + LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), + "atExceptionExit", LocationType.EXCEPTION_EXIT); + + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke", + LocationType.INVOKE); + LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_COMPLETED); + LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); + groupLocationFilter.addFilter(invokeBeforeFilter); + groupLocationFilter.addFilter(invokeAfterFilter); + groupLocationFilter.addFilter(invokeExceptionFilter); + + + for(int i = 0 ; i < 20 ; ++i) { + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + } + + + byte[] bytes = AsmUtils.toBytes(classNode); + VerifyUtils.asmVerify(bytes); + + VerifyUtils.instanceVerity(bytes); + + System.out.println(Decompiler.decompile(bytes)); + + ClassNode classNode2 = AsmUtils.toClassNode(bytes); + for (MethodNode methodNode : classNode2.methods) { + if (!methodNode.name.equals("xxx")) { + System.err.println("method name: " + methodNode.name); + Assertions.assertThat( + AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) + .size().isEqualTo(1); + } + } + + } + + @Test + public void test2() throws Exception { + + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser + .parse(SpyTraceInterceptor2.class); + + ClassNode classNode = AsmUtils.loadClass(TestAAA.class); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, "*")) { + matchedMethods.add(methodNode); + } + } + + LocationFilter enterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke", + LocationType.INVOKE); + LocationFilter existFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_COMPLETED); + LocationFilter exceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); + GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + byte[] bytes = AsmUtils.toBytes(classNode); + VerifyUtils.asmVerify(bytes); + + VerifyUtils.instanceVerity(bytes); + + System.out.println(Decompiler.decompile(bytes)); + + ClassNode classNode2 = AsmUtils.toClassNode(bytes); + for (MethodNode methodNode : classNode2.methods) { + if (!methodNode.name.equals("xxx")) { + System.err.println("method name: " + methodNode.name); + Assertions.assertThat( + AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) + .size().isEqualTo(1); + } + } + + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilterTest.java new file mode 100644 index 000000000..7fd58688d --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeCheckLocationFilterTest.java @@ -0,0 +1,258 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvokeException; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.asm.location.LocationType; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.bytekit.utils.MatchUtils; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class InvokeCheckLocationFilterTest { + + public static class SpyTraceInterceptor { + @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "java.**", "**SpyAPI**" }) + public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner, + @Binding.InvokeMethodName String methodName) { + SpyAPI.atBeforeInvoke(clazz, owner, methodName, methodDesc, target); + } + + @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "java.**", "**SpyAPI**" }) + public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner, + @Binding.InvokeMethodName String methodName) { + SpyAPI.atAfterInvoke(clazz, owner, methodName, methodDesc, target); + } + + @AtInvokeException(name = "", inline = true, excludes = { "java.**", "**SpyAPI**" }) + public static void onInvokeException(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner, + @Binding.InvokeMethodName String methodName, @Binding.Throwable Throwable throwable) { + SpyAPI.atInvokeException(clazz, owner, methodName, methodDesc, target, throwable); + } + } + + public static class SpyTraceInterceptor2 { + @AtInvoke(name = "", inline = true, whenComplete = false, excludes = { "**SpyAPI**" }) + public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner, + @Binding.InvokeMethodName String methodName) { + SpyAPI.atBeforeInvoke(clazz, owner, methodName, methodDesc, target); + } + + @AtInvoke(name = "", inline = true, whenComplete = true, excludes = { "**SpyAPI**" }) + public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner, + @Binding.InvokeMethodName String methodName) { + SpyAPI.atAfterInvoke(clazz, owner, methodName, methodDesc, target); + } + + @AtInvokeException(name = "", inline = true, excludes = { "**SpyAPI**" }) + public static void onInvokeException(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeMethodDeclaration String methodDesc, @Binding.InvokeMethodOwner String owner, + @Binding.InvokeMethodName String methodName, @Binding.Throwable Throwable throwable) { + SpyAPI.atInvokeException(clazz, owner, methodName, methodDesc, target, throwable); + } + } + + public static class SpyAPI { + + public static void atBeforeInvoke(Class clazz, String owner, String methodName, String methodDesc, + Object target) { + + } + + public static void atInvokeException(Class clazz, String owner, String methodName, String methodDesc, + Object target, Throwable throwable) { + + } + + public static void atAfterInvoke(Class clazz, String owner, String methodName, String methodDesc, + Object target) { + + } + + } + + public static class TestAAA { + + int i = 0; + long l = 0; + + public TestAAA() { + xxx(1, 134L); + } + + public String hello(String str) { + + String result = ""; + + xxx(i, l); + + return result; + + } + + public String xxx(int i, long l) { + return "" + i + l; + } + + } + + @Test + public void test() throws Exception { + + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser + .parse(SpyTraceInterceptor.class); + + ClassNode classNode = AsmUtils.loadClass(TestAAA.class); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, "*")) { + matchedMethods.add(methodNode); + } + } + + LocationFilter enterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke", + LocationType.INVOKE); + LocationFilter existFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_COMPLETED); + LocationFilter exceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); + GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + byte[] bytes = AsmUtils.toBytes(classNode); + VerifyUtils.asmVerify(bytes); + + VerifyUtils.instanceVerity(bytes); + + System.out.println(Decompiler.decompile(bytes)); + + ClassNode classNode2 = AsmUtils.toClassNode(bytes); + for (MethodNode methodNode : classNode2.methods) { + if (!methodNode.name.equals("xxx")) { + System.err.println("method name: " + methodNode.name); + Assertions.assertThat( + AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) + .size().isEqualTo(1); + } + } + + } + + + @Test + public void test2() throws Exception { + + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser + .parse(SpyTraceInterceptor2.class); + + ClassNode classNode = AsmUtils.loadClass(TestAAA.class); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, "*")) { + matchedMethods.add(methodNode); + } + } + + LocationFilter enterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), "atBeforeInvoke", + LocationType.INVOKE); + LocationFilter existFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_COMPLETED); + LocationFilter exceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); + GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + byte[] bytes = AsmUtils.toBytes(classNode); + VerifyUtils.asmVerify(bytes); + + VerifyUtils.instanceVerity(bytes); + + System.out.println(Decompiler.decompile(bytes)); + + ClassNode classNode2 = AsmUtils.toClassNode(bytes); + for (MethodNode methodNode : classNode2.methods) { + if (!methodNode.name.equals("xxx")) { + System.err.println("method name: " + methodNode.name); + Assertions.assertThat( + AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) + .size().isEqualTo(1); + } + } + + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilterTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilterTest.java new file mode 100644 index 000000000..6b12eb9b0 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/location/filter/InvokeContainLocationFilterTest.java @@ -0,0 +1,164 @@ +package com.taobao.arthas.bytekit.asm.location.filter; + +import java.util.ArrayList; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.asm.location.LocationType; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.bytekit.utils.MatchUtils; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class InvokeContainLocationFilterTest { + + public static class SpyInterceptor { + + @AtEnter(inline = true) + public static void atEnter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc, + @Binding.Args Object[] args) { + SpyAPI.atEnter(clazz, methodName, methodDesc, target, args); + } + + @AtExit(inline = true) + public static void atExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc, + @Binding.Args Object[] args, @Binding.Return Object returnObj) { + SpyAPI.atExit(clazz, methodName, methodDesc, target, args, returnObj); + } + + @AtExceptionExit(inline = true) + public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodName String methodName, @Binding.MethodDesc String methodDesc, + @Binding.Args Object[] args, @Binding.Throwable Throwable throwable) { + SpyAPI.atExceptionExit(clazz, methodName, methodDesc, target, args, throwable); + } + } + + public static class SpyAPI { + + public static void atEnter(Class clazz, String methodName, String methodDesc, Object target, Object[] args) { + + } + + public static void atExceptionExit(Class clazz, String methodName, String methodDesc, Object target, + Object[] args, Throwable throwable) { + + } + + public static void atExit(Class clazz, String methodName, String methodDesc, Object target, Object[] args, + Object returnObj) { + + } + + } + + public static class TestAAA { + + int i = 0; + long l = 0; + + public String hello(String str) { + + String result = ""; + + xxx(i, l); + + return result; + + } + + public String xxx(int i, long l) { + return "" + i + l; + } + + } + + @Test + public void test() throws Exception { + + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser.parse(SpyInterceptor.class); + + ClassNode classNode = AsmUtils.loadClass(TestAAA.class); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, "*")) { + matchedMethods.add(methodNode); + } + } + + LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter", + LocationType.ENTER); + LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit", + LocationType.EXIT); + LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), + "atExceptionExit", LocationType.EXCEPTION_EXIT); + GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + byte[] bytes = AsmUtils.toBytes(classNode); + VerifyUtils.asmVerify(bytes); + + System.out.println(Decompiler.decompile(bytes)); + + ClassNode classNode2 = AsmUtils.toClassNode(bytes); + for (MethodNode methodNode : classNode2.methods) { + System.err.println("method name: " + methodNode.name); + Assertions + .assertThat(AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atEnter")) + .size().isEqualTo(1); + Assertions + .assertThat(AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atExit")) + .size().isEqualTo(1); + Assertions + .assertThat(AsmUtils.findMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atExceptionExit")) + .size().isEqualTo(1); + } + + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtilsTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtilsTest.java new file mode 100644 index 000000000..366fac9a7 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmAnnotationUtilsTest.java @@ -0,0 +1,78 @@ +package com.taobao.arthas.bytekit.utils; + +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Arrays; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.springframework.stereotype.Service; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.taobao.arthas.bytekit.utils.AsmAnnotationUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +/** + * + * @author hengyunabc 2020-05-04 + * + */ +public class AsmAnnotationUtilsTest { + + @Target(value = { ElementType.TYPE, ElementType.METHOD }) + @Retention(value = RetentionPolicy.RUNTIME) + public @interface AdviceInfo { + + public String[] adviceInfos(); + } + + @Service + @AdviceInfo(adviceInfos = { "xxxx", "yyy" }) + static class AAA { + + @AdviceInfo(adviceInfos = { "mmm", "yyy" }) + public void test() { + + } + + } + + @Service + static class BBB { + public void test() { + } + } + + @Test + public void test() throws IOException { + ClassNode classNodeA = AsmUtils.loadClass(AAA.class); + + ClassNode classNodeB = AsmUtils.loadClass(BBB.class); + + Assertions.assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeA.visibleAnnotations, + Type.getDescriptor(AdviceInfo.class), "adviceInfos")).isEqualTo(Arrays.asList("xxxx", "yyy")); + + AsmAnnotationUtils.addAnnotationInfo(classNodeA.visibleAnnotations, Type.getDescriptor(AdviceInfo.class), + "adviceInfos", "fff"); + + Assertions + .assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeA.visibleAnnotations, + Type.getDescriptor(AdviceInfo.class), "adviceInfos")) + .isEqualTo(Arrays.asList("xxxx", "yyy", "fff")); + + Assertions.assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeB.visibleAnnotations, + Type.getDescriptor(AdviceInfo.class), "adviceInfos")).isEmpty(); + + AsmAnnotationUtils.addAnnotationInfo(classNodeB.visibleAnnotations, Type.getDescriptor(AdviceInfo.class), + "adviceInfos", "fff"); + + Assertions.assertThat(AsmAnnotationUtils.queryAnnotationInfo(classNodeB.visibleAnnotations, + Type.getDescriptor(AdviceInfo.class), "adviceInfos")).isEqualTo(Arrays.asList("fff")); + + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java new file mode 100644 index 000000000..eef1d9ab4 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java @@ -0,0 +1,144 @@ +package com.taobao.arthas.bytekit.utils; + +import java.io.IOException; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import com.alibaba.arthas.deps.org.objectweb.asm.ClassWriter; +import com.alibaba.arthas.deps.org.objectweb.asm.MethodVisitor; +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; + +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +public class AsmUtilsTest { + + abstract static class TestClass { + public static synchronized List sss(int i, long l, List list) throws IOException, ArrayIndexOutOfBoundsException { + return null; + } + protected abstract String hello(String ss); + } + + static class TestConstructorClass { + public TestConstructorClass(int i, String s) { + + } + } + + @Test + public void testMethodDeclaration() throws IOException { + ClassNode classNode = AsmUtils.loadClass(TestClass.class); + MethodNode sss = AsmUtils.findFirstMethod(classNode.methods, "sss"); + + MethodNode hello = AsmUtils.findFirstMethod(classNode.methods, "hello"); + + MethodNode constructor = AsmUtils.findFirstMethod(AsmUtils.loadClass(TestConstructorClass.class).methods, ""); + + String helloDeclaration = AsmUtils.methodDeclaration(Type.getType(TestClass.class), hello); + String sssDeclaration = AsmUtils.methodDeclaration(Type.getType(TestClass.class), sss); + + String constructorDeclaration = AsmUtils.methodDeclaration(Type.getType(TestConstructorClass.class), constructor); + + System.err.println(helloDeclaration); + System.err.println(sssDeclaration); + System.err.println(constructorDeclaration); + + Assertions.assertThat(helloDeclaration).isEqualTo("protected abstract java.lang.String hello(java.lang.String)"); + Assertions.assertThat(sssDeclaration).isEqualTo( + "public static synchronized java.util.List sss(int, long, java.util.List) throws java.io.IOException, java.lang.ArrayIndexOutOfBoundsException"); + Assertions.assertThat(constructorDeclaration).isEqualTo("public com.taobao.arthas.bytekit.utils.AsmUtilsTest$TestConstructorClass(int, java.lang.String)"); + } + + public static byte[] emptyMethodBytes() throws Exception { + ClassWriter cw = new ClassWriter(0); + MethodVisitor mv; + + cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, "LEmptyClass", null, "java/lang/Object", null); + + { + mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(1, 1); + mv.visitEnd(); + } + { + mv = cw.visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "emptyMethod", "()V", null, null); + // mv.visitCode(); + mv.visitInsn(Opcodes.RETURN); + // mv.visitMaxs(0, 0); + // mv.visitEnd(); + } + cw.visitEnd(); + + return cw.toByteArray(); + } + + @Test + public void emptyMethodTest() throws Exception { + + byte[] emptyMethodBytes = emptyMethodBytes(); + + VerifyUtils.asmVerify(emptyMethodBytes); + VerifyUtils.instanceVerity(emptyMethodBytes); + + ClassNode classNode = AsmUtils.toClassNode(emptyMethodBytes); + MethodNode methodNode = AsmUtils.findFirstMethod(classNode.methods, "emptyMethod"); + + AbstractInsnNode first = methodNode.instructions.getFirst(); + AbstractInsnNode last = methodNode.instructions.getLast(); + System.err.println(first); + System.err.println(last); + + int size = methodNode.instructions.size(); + for (int i = 0; i < size; ++i) { + System.err.println(methodNode.instructions.get(i)); + } + + // String asmCode = AsmUtils.toASMCode(classNode); + // System.err.println(asmCode); + } + + private String aaa = ""; + public void xxx () { + aaa = "bbb"; + } + + @Test + public void testFieldAccess() throws IOException { + ClassNode classNode = AsmUtils.loadClass(AsmUtilsTest.class); + + MethodNode methodNode = AsmUtils.findFirstMethod(classNode.methods, "xxx"); + + int size = methodNode.instructions.size(); + for (int i = 0; i < size; ++i) { + System.err.println(methodNode.instructions.get(i)); + } + + + } + + + @Test + public void testRenameClass() throws Exception { + ClassNode classNode = AsmUtils.loadClass(AsmUtilsTest.class); + + byte[] classBytes = AsmUtils.toBytes(classNode); + + byte[] renameClass = AsmUtils.renameClass(classBytes, "com.test.Test.XXX"); + + VerifyUtils.asmVerify(renameClass); + Object object = VerifyUtils.instanceVerity(renameClass); + + Assertions.assertThat(object.getClass().getName()).isEqualTo("com.test.Test.XXX"); + } + +} diff --git a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java new file mode 100644 index 000000000..4e5399ae4 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java @@ -0,0 +1,9 @@ +package com.taobao.arthas.bytekit.utils; + +public class EmptyClass { + + public static void emptyMethod() { + + } + +} diff --git a/common/src/main/java/com/taobao/arthas/common/FileUtils.java b/common/src/main/java/com/taobao/arthas/common/FileUtils.java new file mode 100644 index 000000000..2b58caad5 --- /dev/null +++ b/common/src/main/java/com/taobao/arthas/common/FileUtils.java @@ -0,0 +1,150 @@ +package com.taobao.arthas.common; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @see org.apache.commons.io.FileUtils + * @author hengyunabc 2020-05-03 + * + */ +public class FileUtils { + + public static File getTempDirectory() { + return new File(System.getProperty("java.io.tmpdir")); + } + + /** + * Writes a byte array to a file creating the file if it does not exist. + *

+ * NOTE: As from v1.3, the parent directories of the file will be created if + * they do not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @throws IOException in case of an I/O error + * @since 1.1 + */ + public static void writeByteArrayToFile(final File file, final byte[] data) throws IOException { + writeByteArrayToFile(file, data, false); + } + + /** + * Writes a byte array to a file creating the file if it does not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @param append if {@code true}, then bytes will be added to the end of the + * file rather than overwriting + * @throws IOException in case of an I/O error + * @since 2.1 + */ + public static void writeByteArrayToFile(final File file, final byte[] data, final boolean append) + throws IOException { + writeByteArrayToFile(file, data, 0, data.length, append); + } + + /** + * Writes {@code len} bytes from the specified byte array starting at offset + * {@code off} to a file, creating the file if it does not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @param off the start offset in the data + * @param len the number of bytes to write + * @throws IOException in case of an I/O error + * @since 2.5 + */ + public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len) + throws IOException { + writeByteArrayToFile(file, data, off, len, false); + } + + /** + * Writes {@code len} bytes from the specified byte array starting at offset + * {@code off} to a file, creating the file if it does not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @param off the start offset in the data + * @param len the number of bytes to write + * @param append if {@code true}, then bytes will be added to the end of the + * file rather than overwriting + * @throws IOException in case of an I/O error + * @since 2.5 + */ + public static void writeByteArrayToFile(final File file, final byte[] data, final int off, final int len, + final boolean append) throws IOException { + FileOutputStream out = null; + try { + out = openOutputStream(file, append); + out.write(data, off, len); + } finally { + IOUtils.close(out); + } + } + + /** + * Opens a {@link FileOutputStream} for the specified file, checking and + * creating the parent directory if it does not exist. + *

+ * At the end of the method either the stream will be successfully opened, or an + * exception will have been thrown. + *

+ * The parent directory will be created if it does not exist. The file will be + * created if it does not exist. An exception is thrown if the file object + * exists but is a directory. An exception is thrown if the file exists but + * cannot be written to. An exception is thrown if the parent directory cannot + * be created. + * + * @param file the file to open for output, must not be {@code null} + * @param append if {@code true}, then bytes will be added to the end of the + * file rather than overwriting + * @return a new {@link FileOutputStream} for the specified file + * @throws IOException if the file object is a directory + * @throws IOException if the file cannot be written to + * @throws IOException if a parent directory needs creating but that fails + * @since 2.1 + */ + public static FileOutputStream openOutputStream(final File file, final boolean append) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + if (file.canWrite() == false) { + throw new IOException("File '" + file + "' cannot be written to"); + } + } else { + final File parent = file.getParentFile(); + if (parent != null) { + if (!parent.mkdirs() && !parent.isDirectory()) { + throw new IOException("Directory '" + parent + "' could not be created"); + } + } + } + return new FileOutputStream(file, append); + } + + /** + * Reads the contents of a file into a byte array. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the file contents, never {@code null} + * @throws IOException in case of an I/O error + * @since 1.1 + */ + public static byte[] readFileToByteArray(final File file) throws IOException { + InputStream in = null; + try { + in = new FileInputStream(file); + return IOUtils.getBytes(in); + } finally { + IOUtils.close(in); + } + } +} diff --git a/common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java b/common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java new file mode 100644 index 000000000..a77770b29 --- /dev/null +++ b/common/src/main/java/com/taobao/arthas/common/concurrent/ConcurrentWeakKeyHashMap.java @@ -0,0 +1,1473 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + */ +package com.taobao.arthas.common.concurrent; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.AbstractCollection; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; + + +/** + * An alternative weak-key {@link ConcurrentMap} which is similar to + * {@link ConcurrentHashMap}. + * @param the type of keys maintained by this map + * @param the type of mapped values + */ +public final class ConcurrentWeakKeyHashMap extends AbstractMap implements ConcurrentMap { + + /* + * The basic strategy is to subdivide the table among Segments, + * each of which itself is a concurrently readable hash table. + */ + + /** + * The default initial capacity for this table, used when not otherwise + * specified in a constructor. + */ + static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The default load factor for this table, used when not otherwise specified + * in a constructor. + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The default concurrency level for this table, used when not otherwise + * specified in a constructor. + */ + static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The maximum capacity, used if a higher value is implicitly specified by + * either of the constructors with arguments. MUST be a power of two + * <= 1<<30 to ensure that entries are indexable using integers. + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The maximum number of segments to allow; used to bound constructor + * arguments. + */ + static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + + /** + * Number of unsynchronized retries in size and containsValue methods before + * resorting to locking. This is used to avoid unbounded retries if tables + * undergo continuous modification which would make it impossible to obtain + * an accurate result. + */ + static final int RETRIES_BEFORE_LOCK = 2; + + /* ---------------- Fields -------------- */ + + /** + * Mask value for indexing into segments. The upper bits of a key's hash + * code are used to choose the segment. + */ + final int segmentMask; + + /** + * Shift value for indexing within segments. + */ + final int segmentShift; + + /** + * The segments, each of which is a specialized hash table + */ + final Segment[] segments; + + Set keySet; + Set> entrySet; + Collection values; + + /* ---------------- Small Utilities -------------- */ + + /** + * Applies a supplemental hash function to a given hashCode, which defends + * against poor quality hash functions. This is critical because + * ConcurrentReferenceHashMap uses power-of-two length hash tables, that + * otherwise encounter collisions for hashCodes that do not differ in lower + * or upper bits. + */ + private static int hash(int h) { + // Spread bits to regularize both segment and index locations, + // using variant of single-word Wang/Jenkins hash. + h += h << 15 ^ 0xffffcd7d; + h ^= h >>> 10; + h += h << 3; + h ^= h >>> 6; + h += (h << 2) + (h << 14); + return h ^ h >>> 16; + } + + /** + * Returns the segment that should be used for key with given hash. + * + * @param hash the hash code for the key + * @return the segment + */ + Segment segmentFor(int hash) { + return segments[hash >>> segmentShift & segmentMask]; + } + + private static int hashOf(Object key) { + return hash(key.hashCode()); + } + + /* ---------------- Inner Classes -------------- */ + + /** + * A weak-key reference which stores the key hash needed for reclamation. + */ + static final class WeakKeyReference extends WeakReference { + + final int hash; + + WeakKeyReference(K key, int hash, ReferenceQueue refQueue) { + super(key, refQueue); + this.hash = hash; + } + + public int keyHash() { + return hash; + } + + public Object keyRef() { + return this; + } + } + + /** + * ConcurrentReferenceHashMap list entry. Note that this is never exported + * out as a user-visible Map.Entry. + * + * Because the value field is volatile, not final, it is legal wrt + * the Java Memory Model for an unsynchronized reader to see null + * instead of initial value when read via a data race. Although a + * reordering leading to this is not likely to ever actually + * occur, the Segment.readValueUnderLock method is used as a + * backup in case a null (pre-initialized) value is ever seen in + * an unsynchronized access method. + */ + static final class HashEntry { + final Object keyRef; + final int hash; + volatile Object valueRef; + final HashEntry next; + + HashEntry( + K key, int hash, HashEntry next, V value, + ReferenceQueue refQueue) { + this.hash = hash; + this.next = next; + keyRef = new WeakKeyReference(key, hash, refQueue); + valueRef = value; + } + + @SuppressWarnings("unchecked") + K key() { + return ((Reference) keyRef).get(); + } + + V value() { + return dereferenceValue(valueRef); + } + + @SuppressWarnings("unchecked") + V dereferenceValue(Object value) { + if (value instanceof WeakKeyReference) { + return ((Reference) value).get(); + } + + return (V) value; + } + + void setValue(V value) { + valueRef = value; + } + + @SuppressWarnings("unchecked") + static HashEntry[] newArray(int i) { + return new HashEntry[i]; + } + } + + /** + * Segments are specialized versions of hash tables. This subclasses from + * ReentrantLock opportunistically, just to simplify some locking and avoid + * separate construction. + */ + static final class Segment extends ReentrantLock { + /* + * Segments maintain a table of entry lists that are ALWAYS kept in a + * consistent state, so can be read without locking. Next fields of + * nodes are immutable (final). All list additions are performed at the + * front of each bin. This makes it easy to check changes, and also fast + * to traverse. When nodes would otherwise be changed, new nodes are + * created to replace them. This works well for hash tables since the + * bin lists tend to be short. (The average length is less than two for + * the default load factor threshold.) + * + * Read operations can thus proceed without locking, but rely on + * selected uses of volatiles to ensure that completed write operations + * performed by other threads are noticed. For most purposes, the + * "count" field, tracking the number of elements, serves as that + * volatile variable ensuring visibility. This is convenient because + * this field needs to be read in many read operations anyway: + * + * - All (unsynchronized) read operations must first read the + * "count" field, and should not look at table entries if + * it is 0. + * + * - All (synchronized) write operations should write to + * the "count" field after structurally changing any bin. + * The operations must not take any action that could even + * momentarily cause a concurrent read operation to see + * inconsistent data. This is made easier by the nature of + * the read operations in Map. For example, no operation + * can reveal that the table has grown but the threshold + * has not yet been updated, so there are no atomicity + * requirements for this with respect to reads. + * + * As a guide, all critical volatile reads and writes to the count field + * are marked in code comments. + */ + + private static final long serialVersionUID = -8328104880676891126L; + + /** + * The number of elements in this segment's region. + */ + transient volatile int count; + + /** + * Number of updates that alter the size of the table. This is used + * during bulk-read methods to make sure they see a consistent snapshot: + * If modCounts change during a traversal of segments computing size or + * checking containsValue, then we might have an inconsistent view of + * state so (usually) must retry. + */ + int modCount; + + /** + * The table is rehashed when its size exceeds this threshold. + * (The value of this field is always (capacity * loadFactor).) + */ + int threshold; + + /** + * The per-segment table. + */ + transient volatile HashEntry[] table; + + /** + * The load factor for the hash table. Even though this value is same + * for all segments, it is replicated to avoid needing links to outer + * object. + */ + final float loadFactor; + + /** + * The collected weak-key reference queue for this segment. This should + * be (re)initialized whenever table is assigned, + */ + transient volatile ReferenceQueue refQueue; + + Segment(int initialCapacity, float lf) { + loadFactor = lf; + setTable(HashEntry.newArray(initialCapacity)); + } + + @SuppressWarnings("unchecked") + static Segment[] newArray(int i) { + return new Segment[i]; + } + + private static boolean keyEq(Object src, Object dest) { + return src.equals(dest); + } + + /** + * Sets table to new HashEntry array. Call only while holding lock or in + * constructor. + */ + void setTable(HashEntry[] newTable) { + threshold = (int) (newTable.length * loadFactor); + table = newTable; + refQueue = new ReferenceQueue(); + } + + /** + * Returns properly casted first entry of bin for given hash. + */ + HashEntry getFirst(int hash) { + HashEntry[] tab = table; + return tab[hash & tab.length - 1]; + } + + HashEntry newHashEntry( + K key, int hash, HashEntry next, V value) { + return new HashEntry( + key, hash, next, value, refQueue); + } + + /** + * Reads value field of an entry under lock. Called if value field ever + * appears to be null. This is possible only if a compiler happens to + * reorder a HashEntry initialization with its table assignment, which + * is legal under memory model but is not known to ever occur. + */ + V readValueUnderLock(HashEntry e) { + lock(); + try { + removeStale(); + return e.value(); + } finally { + unlock(); + } + } + + /* Specialized implementations of map methods */ + + V get(Object key, int hash) { + if (count != 0) { // read-volatile + HashEntry e = getFirst(hash); + while (e != null) { + if (e.hash == hash && keyEq(key, e.key())) { + Object opaque = e.valueRef; + if (opaque != null) { + return e.dereferenceValue(opaque); + } + + return readValueUnderLock(e); // recheck + } + e = e.next; + } + } + return null; + } + + boolean containsKey(Object key, int hash) { + if (count != 0) { // read-volatile + HashEntry e = getFirst(hash); + while (e != null) { + if (e.hash == hash && keyEq(key, e.key())) { + return true; + } + e = e.next; + } + } + return false; + } + + boolean containsValue(Object value) { + if (count != 0) { // read-volatile + for (HashEntry e: table) { + for (; e != null; e = e.next) { + Object opaque = e.valueRef; + V v; + + if (opaque == null) { + v = readValueUnderLock(e); // recheck + } else { + v = e.dereferenceValue(opaque); + } + + if (value.equals(v)) { + return true; + } + } + } + } + return false; + } + + boolean replace(K key, int hash, V oldValue, V newValue) { + lock(); + try { + removeStale(); + HashEntry e = getFirst(hash); + while (e != null && (e.hash != hash || !keyEq(key, e.key()))) { + e = e.next; + } + + boolean replaced = false; + if (e != null && oldValue.equals(e.value())) { + replaced = true; + e.setValue(newValue); + } + return replaced; + } finally { + unlock(); + } + } + + V replace(K key, int hash, V newValue) { + lock(); + try { + removeStale(); + HashEntry e = getFirst(hash); + while (e != null && (e.hash != hash || !keyEq(key, e.key()))) { + e = e.next; + } + + V oldValue = null; + if (e != null) { + oldValue = e.value(); + e.setValue(newValue); + } + return oldValue; + } finally { + unlock(); + } + } + + V put(K key, int hash, V value, boolean onlyIfAbsent) { + lock(); + try { + removeStale(); + int c = count; + if (c ++ > threshold) { // ensure capacity + int reduced = rehash(); + if (reduced > 0) { + count = (c -= reduced) - 1; // write-volatile + } + } + + HashEntry[] tab = table; + int index = hash & tab.length - 1; + HashEntry first = tab[index]; + HashEntry e = first; + while (e != null && (e.hash != hash || !keyEq(key, e.key()))) { + e = e.next; + } + + V oldValue; + if (e != null) { + oldValue = e.value(); + if (!onlyIfAbsent) { + e.setValue(value); + } + } else { + oldValue = null; + ++ modCount; + tab[index] = newHashEntry(key, hash, first, value); + count = c; // write-volatile + } + return oldValue; + } finally { + unlock(); + } + } + + int rehash() { + HashEntry[] oldTable = table; + int oldCapacity = oldTable.length; + if (oldCapacity >= MAXIMUM_CAPACITY) { + return 0; + } + + /* + * Reclassify nodes in each list to new Map. Because we are using + * power-of-two expansion, the elements from each bin must either + * stay at same index, or move with a power of two offset. We + * eliminate unnecessary node creation by catching cases where old + * nodes can be reused because their next fields won't change. + * Statistically, at the default threshold, only about one-sixth of + * them need cloning when a table doubles. The nodes they replace + * will be garbage collectable as soon as they are no longer + * referenced by any reader thread that may be in the midst of + * traversing table right now. + */ + + HashEntry[] newTable = HashEntry.newArray(oldCapacity << 1); + threshold = (int) (newTable.length * loadFactor); + int sizeMask = newTable.length - 1; + int reduce = 0; + for (HashEntry e: oldTable) { + // We need to guarantee that any existing reads of old Map can + // proceed. So we cannot yet null out each bin. + if (e != null) { + HashEntry next = e.next; + int idx = e.hash & sizeMask; + + // Single node on list + if (next == null) { + newTable[idx] = e; + } else { + // Reuse trailing consecutive sequence at same slot + HashEntry lastRun = e; + int lastIdx = idx; + for (HashEntry last = next; last != null; last = last.next) { + int k = last.hash & sizeMask; + if (k != lastIdx) { + lastIdx = k; + lastRun = last; + } + } + newTable[lastIdx] = lastRun; + // Clone all remaining nodes + for (HashEntry p = e; p != lastRun; p = p.next) { + // Skip GC'd weak references + K key = p.key(); + if (key == null) { + reduce++; + continue; + } + int k = p.hash & sizeMask; + HashEntry n = newTable[k]; + newTable[k] = newHashEntry(key, p.hash, n, p.value()); + } + } + } + } + table = newTable; + return reduce; + } + + /** + * Remove; match on key only if value null, else match both. + */ + V remove(Object key, int hash, Object value, boolean refRemove) { + lock(); + try { + if (!refRemove) { + removeStale(); + } + int c = count - 1; + HashEntry[] tab = table; + int index = hash & tab.length - 1; + HashEntry first = tab[index]; + HashEntry e = first; + // a reference remove operation compares the Reference instance + while (e != null && key != e.keyRef && + (refRemove || hash != e.hash || !keyEq(key, e.key()))) { + e = e.next; + } + + V oldValue = null; + if (e != null) { + V v = e.value(); + if (value == null || value.equals(v)) { + oldValue = v; + // All entries following removed node can stay in list, + // but all preceding ones need to be cloned. + ++ modCount; + HashEntry newFirst = e.next; + for (HashEntry p = first; p != e; p = p.next) { + K pKey = p.key(); + if (pKey == null) { // Skip GC'd keys + c --; + continue; + } + + newFirst = newHashEntry( + pKey, p.hash, newFirst, p.value()); + } + tab[index] = newFirst; + count = c; // write-volatile + } + } + return oldValue; + } finally { + unlock(); + } + } + + @SuppressWarnings("rawtypes") + void removeStale() { + WeakKeyReference ref; + while ((ref = (WeakKeyReference) refQueue.poll()) != null) { + remove(ref.keyRef(), ref.keyHash(), null, true); + } + } + + void clear() { + if (count != 0) { + lock(); + try { + HashEntry[] tab = table; + for (int i = 0; i < tab.length; i ++) { + tab[i] = null; + } + ++ modCount; + // replace the reference queue to avoid unnecessary stale + // cleanups + refQueue = new ReferenceQueue(); + count = 0; // write-volatile + } finally { + unlock(); + } + } + } + } + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the specified initial capacity, load factor + * and concurrency level. + * + * @param initialCapacity the initial capacity. The implementation performs + * internal sizing to accommodate this many elements. + * @param loadFactor the load factor threshold, used to control resizing. + * Resizing may be performed when the average number of + * elements per bin exceeds this threshold. + * @param concurrencyLevel the estimated number of concurrently updating + * threads. The implementation performs internal + * sizing to try to accommodate this many threads. + * @throws IllegalArgumentException if the initial capacity is negative or + * the load factor or concurrencyLevel are + * nonpositive. + */ + public ConcurrentWeakKeyHashMap( + int initialCapacity, float loadFactor, int concurrencyLevel) { + if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) { + throw new IllegalArgumentException(); + } + + if (concurrencyLevel > MAX_SEGMENTS) { + concurrencyLevel = MAX_SEGMENTS; + } + + // Find power-of-two sizes best matching arguments + int sshift = 0; + int ssize = 1; + while (ssize < concurrencyLevel) { + ++ sshift; + ssize <<= 1; + } + segmentShift = 32 - sshift; + segmentMask = ssize - 1; + segments = Segment.newArray(ssize); + + if (initialCapacity > MAXIMUM_CAPACITY) { + initialCapacity = MAXIMUM_CAPACITY; + } + int c = initialCapacity / ssize; + if (c * ssize < initialCapacity) { + ++ c; + } + int cap = 1; + while (cap < c) { + cap <<= 1; + } + + for (int i = 0; i < segments.length; ++ i) { + segments[i] = new Segment(cap, loadFactor); + } + } + + /** + * Creates a new, empty map with the specified initial capacity and load + * factor and with the default reference types (weak keys, strong values), + * and concurrencyLevel (16). + * + * @param initialCapacity The implementation performs internal sizing to + * accommodate this many elements. + * @param loadFactor the load factor threshold, used to control resizing. + * Resizing may be performed when the average number of + * elements per bin exceeds this threshold. + * @throws IllegalArgumentException if the initial capacity of elements is + * negative or the load factor is + * nonpositive + */ + public ConcurrentWeakKeyHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new, empty map with the specified initial capacity, and with + * default reference types (weak keys, strong values), load factor (0.75) + * and concurrencyLevel (16). + * + * @param initialCapacity the initial capacity. The implementation performs + * internal sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of elements is + * negative. + */ + public ConcurrentWeakKeyHashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new, empty map with a default initial capacity (16), reference + * types (weak keys, strong values), default load factor (0.75) and + * concurrencyLevel (16). + */ + public ConcurrentWeakKeyHashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); + } + + /** + * Creates a new map with the same mappings as the given map. The map is + * created with a capacity of 1.5 times the number of mappings in the given + * map or 16 (whichever is greater), and a default load factor (0.75) and + * concurrencyLevel (16). + * + * @param m the map + */ + public ConcurrentWeakKeyHashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR, + DEFAULT_CONCURRENCY_LEVEL); + putAll(m); + } + + /** + * Returns true if this map contains no key-value mappings. + * + * @return true if this map contains no key-value mappings + */ + @Override + public boolean isEmpty() { + final Segment[] segments = this.segments; + /* + * We keep track of per-segment modCounts to avoid ABA problems in which + * an element in one segment was added and in another removed during + * traversal, in which case the table was never actually empty at any + * point. Note the similar use of modCounts in the size() and + * containsValue() methods, which are the only other methods also + * susceptible to ABA problems. + */ + int[] mc = new int[segments.length]; + int mcsum = 0; + for (int i = 0; i < segments.length; ++ i) { + if (segments[i].count != 0) { + return false; + } else { + mcsum += mc[i] = segments[i].modCount; + } + } + // If mcsum happens to be zero, then we know we got a snapshot before + // any modifications at all were made. This is probably common enough + // to bother tracking. + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++ i) { + if (segments[i].count != 0 || mc[i] != segments[i].modCount) { + return false; + } + } + } + return true; + } + + /** + * Returns the number of key-value mappings in this map. If the map contains + * more than Integer.MAX_VALUE elements, returns + * Integer.MAX_VALUE. + * + * @return the number of key-value mappings in this map + */ + @Override + public int size() { + final Segment[] segments = this.segments; + long sum = 0; + long check = 0; + int[] mc = new int[segments.length]; + // Try a few times to get accurate count. On failure due to continuous + // async changes in table, resort to locking. + for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) { + check = 0; + sum = 0; + int mcsum = 0; + for (int i = 0; i < segments.length; ++ i) { + sum += segments[i].count; + mcsum += mc[i] = segments[i].modCount; + } + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++ i) { + check += segments[i].count; + if (mc[i] != segments[i].modCount) { + check = -1; // force retry + break; + } + } + } + if (check == sum) { + break; + } + } + if (check != sum) { // Resort to locking all segments + sum = 0; + for (Segment segment: segments) { + segment.lock(); + } + for (Segment segment: segments) { + sum += segment.count; + } + for (Segment segment: segments) { + segment.unlock(); + } + } + if (sum > Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) sum; + } + } + + /** + * Returns the value to which the specified key is mapped, or {@code null} + * if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key {@code k} to + * a value {@code v} such that {@code key.equals(k)}, then this method + * returns {@code v}; otherwise it returns {@code null}. (There can be at + * most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @Override + public V get(Object key) { + int hash = hashOf(key); + return segmentFor(hash).get(key, hash); + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return true if and only if the specified object is a key in + * this table, as determined by the equals method; + * false otherwise. + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean containsKey(Object key) { + int hash = hashOf(key); + return segmentFor(hash).containsKey(key, hash); + } + + /** + * Returns true if this map maps one or more keys to the specified + * value. Note: This method requires a full internal traversal of the hash + * table, and so is much slower than method containsKey. + * + * @param value value whose presence in this map is to be tested + * @return true if this map maps one or more keys to the specified + * value + * @throws NullPointerException if the specified value is null + */ + + @Override + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } + + // See explanation of modCount use above + + final Segment[] segments = this.segments; + int[] mc = new int[segments.length]; + + // Try a few times without locking + for (int k = 0; k < RETRIES_BEFORE_LOCK; ++ k) { + int mcsum = 0; + for (int i = 0; i < segments.length; ++ i) { + mcsum += mc[i] = segments[i].modCount; + if (segments[i].containsValue(value)) { + return true; + } + } + boolean cleanSweep = true; + if (mcsum != 0) { + for (int i = 0; i < segments.length; ++ i) { + if (mc[i] != segments[i].modCount) { + cleanSweep = false; + break; + } + } + } + if (cleanSweep) { + return false; + } + } + // Resort to locking all segments + for (Segment segment: segments) { + segment.lock(); + } + boolean found = false; + try { + for (Segment segment: segments) { + if (segment.containsValue(value)) { + found = true; + break; + } + } + } finally { + for (Segment segment: segments) { + segment.unlock(); + } + } + return found; + } + + /** + * Legacy method testing if some key maps into the specified value in this + * table. This method is identical in functionality to + * {@link #containsValue}, and exists solely to ensure full compatibility + * with class {@link Hashtable}, which supported this method prior to + * introduction of the Java Collections framework. + * + * @param value a value to search for + * @return true if and only if some key maps to the value + * argument in this table as determined by the equals + * method; false otherwise + * @throws NullPointerException if the specified value is null + */ + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Maps the specified key to the specified value in this table. Neither the + * key nor the value can be null. + * + *

The value can be retrieved by calling the get method with a + * key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with key, or null + * if there was no mapping for key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V put(K key, V value) { + if (value == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).put(key, hash, value, false); + } + + /** + * @return the previous value associated with the specified key, or + * null if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + public V putIfAbsent(K key, V value) { + if (value == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).put(key, hash, value, true); + } + + /** + * Copies all of the mappings from the specified map to this one. These + * mappings replace any mappings that this map had for any of the keys + * currently in the specified map. + * + * @param m mappings to be stored in this map + */ + @Override + public void putAll(Map m) { + for (Map.Entry e: m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + + /** + * Removes the key (and its corresponding value) from this map. This method + * does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with key, or null + * if there was no mapping for key + * @throws NullPointerException if the specified key is null + */ + @Override + public V remove(Object key) { + int hash = hashOf(key); + return segmentFor(hash).remove(key, hash, null, false); + } + + /** + * @throws NullPointerException if the specified key is null + */ + public boolean remove(Object key, Object value) { + int hash = hashOf(key); + if (value == null) { + return false; + } + return segmentFor(hash).remove(key, hash, value, false) != null; + } + + /** + * @throws NullPointerException if any of the arguments are null + */ + public boolean replace(K key, V oldValue, V newValue) { + if (oldValue == null || newValue == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).replace(key, hash, oldValue, newValue); + } + + /** + * @return the previous value associated with the specified key, or + * null if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + public V replace(K key, V value) { + if (value == null) { + throw new NullPointerException(); + } + int hash = hashOf(key); + return segmentFor(hash).replace(key, hash, value); + } + + /** + * Removes all of the mappings from this map. + */ + @Override + public void clear() { + for (Segment segment: segments) { + segment.clear(); + } + } + + /** + * Removes any stale entries whose keys have been finalized. Use of this + * method is normally not necessary since stale entries are automatically + * removed lazily, when blocking operations are required. However, there are + * some cases where this operation should be performed eagerly, such as + * cleaning up old references to a ClassLoader in a multi-classloader + * environment. + * + * Note: this method will acquire locks, one at a time, across all segments + * of this table, so if it is to be used, it should be used sparingly. + */ + public void purgeStaleEntries() { + for (Segment segment: segments) { + segment.removeStale(); + } + } + + /** + * Returns a {@link Set} view of the keys contained in this map. The set is + * backed by the map, so changes to the map are reflected in the set, and + * vice-versa. The set supports element removal, which removes the + * corresponding mapping from this map, via the Iterator.remove, + * Set.remove, removeAll, retainAll, and + * clear operations. It does not support the add or + * addAll operations. + * + *

The view's iterator is a "weakly consistent" iterator that + * will never throw {@link ConcurrentModificationException}, and guarantees + * to traverse elements as they existed upon construction of the iterator, + * and may (but is not guaranteed to) reflect any modifications subsequent + * to construction. + */ + @Override + public Set keySet() { + Set ks = keySet; + return ks != null? ks : (keySet = new KeySet()); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are reflected + * in the collection, and vice-versa. The collection supports element + * removal, which removes the corresponding mapping from this map, via the + * Iterator.remove, Collection.remove, removeAll, + * retainAll, and clear operations. It does not support + * the add or addAll operations. + * + *

The view's iterator is a "weakly consistent" iterator that + * will never throw {@link ConcurrentModificationException}, and guarantees + * to traverse elements as they existed upon construction of the iterator, + * and may (but is not guaranteed to) reflect any modifications subsequent + * to construction. + */ + @Override + public Collection values() { + Collection vs = values; + return vs != null? vs : (values = new Values()); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are reflected in the + * set, and vice-versa. The set supports element removal, which removes the + * corresponding mapping from the map, via the Iterator.remove, + * Set.remove, removeAll, retainAll, and + * clear operations. It does not support the add or + * addAll operations. + * + *

The view's iterator is a "weakly consistent" iterator that + * will never throw {@link ConcurrentModificationException}, and guarantees + * to traverse elements as they existed upon construction of the iterator, + * and may (but is not guaranteed to) reflect any modifications subsequent + * to construction. + */ + @Override + public Set> entrySet() { + Set> es = entrySet; + return es != null? es : (entrySet = new EntrySet()); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration keys() { + return new KeyIterator(); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration elements() { + return new ValueIterator(); + } + + /* ---------------- Iterator Support -------------- */ + + abstract class HashIterator { + int nextSegmentIndex; + int nextTableIndex; + HashEntry[] currentTable; + HashEntry nextEntry; + HashEntry lastReturned; + K currentKey; // Strong reference to weak key (prevents gc) + + HashIterator() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + advance(); + } + + public void rewind() { + nextSegmentIndex = segments.length - 1; + nextTableIndex = -1; + currentTable = null; + nextEntry = null; + lastReturned = null; + currentKey = null; + advance(); + } + + public boolean hasMoreElements() { + return hasNext(); + } + + final void advance() { + if (nextEntry != null && (nextEntry = nextEntry.next) != null) { + return; + } + + while (nextTableIndex >= 0) { + if ((nextEntry = currentTable[nextTableIndex --]) != null) { + return; + } + } + + while (nextSegmentIndex >= 0) { + Segment seg = segments[nextSegmentIndex --]; + if (seg.count != 0) { + currentTable = seg.table; + for (int j = currentTable.length - 1; j >= 0; -- j) { + if ((nextEntry = currentTable[j]) != null) { + nextTableIndex = j - 1; + return; + } + } + } + } + } + + public boolean hasNext() { + while (nextEntry != null) { + if (nextEntry.key() != null) { + return true; + } + advance(); + } + + return false; + } + + HashEntry nextEntry() { + do { + if (nextEntry == null) { + throw new NoSuchElementException(); + } + + lastReturned = nextEntry; + currentKey = lastReturned.key(); + advance(); + } while (currentKey == null); // Skip GC'd keys + + return lastReturned; + } + + public void remove() { + if (lastReturned == null) { + throw new IllegalStateException(); + } + ConcurrentWeakKeyHashMap.this.remove(currentKey); + lastReturned = null; + } + } + + final class KeyIterator + extends HashIterator implements ReusableIterator, Enumeration { + + public K next() { + return nextEntry().key(); + } + + public K nextElement() { + return nextEntry().key(); + } + } + + final class ValueIterator + extends HashIterator implements ReusableIterator, Enumeration { + + public V next() { + return nextEntry().value(); + } + + public V nextElement() { + return nextEntry().value(); + } + } + + /* + * This class is needed for JDK5 compatibility. + */ + static class SimpleEntry implements Entry { + + private final K key; + + private V value; + + public SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + } + + public SimpleEntry(Entry entry) { + key = entry.getKey(); + value = entry.getValue(); + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + @SuppressWarnings("rawtypes") + Map.Entry e = (Map.Entry) o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + @Override + public int hashCode() { + return (key == null? 0 : key.hashCode()) ^ (value == null? 0 : value.hashCode()); + } + + @Override + public String toString() { + return key + "=" + value; + } + + private static boolean eq(Object o1, Object o2) { + return o1 == null? o2 == null : o1.equals(o2); + } + } + + /** + * Custom Entry class used by EntryIterator.next(), that relays setValue + * changes to the underlying map. + */ + final class WriteThroughEntry extends SimpleEntry { + + WriteThroughEntry(K k, V v) { + super(k, v); + } + + /** + * Set our entry's value and write through to the map. The value to + * return is somewhat arbitrary here. Since a WriteThroughEntry does not + * necessarily track asynchronous changes, the most recent "previous" + * value could be different from what we return (or could even have been + * removed in which case the put will re-establish). We do not and can + * not guarantee more. + */ + @Override + public V setValue(V value) { + + if (value == null) { + throw new NullPointerException(); + } + V v = super.setValue(value); + put(getKey(), value); + return v; + } + } + + final class EntryIterator extends HashIterator implements + ReusableIterator> { + public Map.Entry next() { + HashEntry e = nextEntry(); + return new WriteThroughEntry(e.key(), e.value()); + } + } + + final class KeySet extends AbstractSet { + @Override + public Iterator iterator() { + return new KeyIterator(); + } + + @Override + public int size() { + return ConcurrentWeakKeyHashMap.this.size(); + } + + @Override + public boolean isEmpty() { + return ConcurrentWeakKeyHashMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return containsKey(o); + } + + @Override + public boolean remove(Object o) { + return ConcurrentWeakKeyHashMap.this.remove(o) != null; + } + + @Override + public void clear() { + ConcurrentWeakKeyHashMap.this.clear(); + } + } + + final class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return new ValueIterator(); + } + + @Override + public int size() { + return ConcurrentWeakKeyHashMap.this.size(); + } + + @Override + public boolean isEmpty() { + return ConcurrentWeakKeyHashMap.this.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + + @Override + public void clear() { + ConcurrentWeakKeyHashMap.this.clear(); + } + } + + final class EntrySet extends AbstractSet> { + @Override + public Iterator> iterator() { + return new EntryIterator(); + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + V v = get(e.getKey()); + return v != null && v.equals(e.getValue()); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) { + return false; + } + Map.Entry e = (Map.Entry) o; + return ConcurrentWeakKeyHashMap.this.remove(e.getKey(), e.getValue()); + } + + @Override + public int size() { + return ConcurrentWeakKeyHashMap.this.size(); + } + + @Override + public boolean isEmpty() { + return ConcurrentWeakKeyHashMap.this.isEmpty(); + } + + @Override + public void clear() { + ConcurrentWeakKeyHashMap.this.clear(); + } + } +} \ No newline at end of file diff --git a/common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java b/common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java new file mode 100644 index 000000000..f146a029c --- /dev/null +++ b/common/src/main/java/com/taobao/arthas/common/concurrent/ReusableIterator.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.taobao.arthas.common.concurrent; + +import java.util.Iterator; + +public interface ReusableIterator extends Iterator { + void rewind(); +} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index c1112f2e1..e346354a8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -116,6 +116,19 @@ arthas-common ${project.version} + + com.taobao.arthas + arthas-bytekit + ${project.version} + + + net.bytebuddy + byte-buddy-agent + 1.7.10 + provided + true + + com.taobao.arthas arthas-memorycompiler @@ -203,6 +216,23 @@ assertj-core test + + org.mockito + mockito-core + test + + + com.taobao.arthas + arthas-demo + ${project.version} + test + + + org.zeroturnaround + zt-zip + 1.14 + test + org.benf @@ -216,6 +246,7 @@ provided true + diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java index 28d322edf..bcfefee0e 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java @@ -31,7 +31,7 @@ public interface AdviceListener { * @throws Throwable 通知过程出错 */ void before( - ClassLoader loader, String className, String methodName, String methodDesc, + Class clazz, String methodName, String methodDesc, Object target, Object[] args) throws Throwable; /** @@ -49,7 +49,7 @@ public interface AdviceListener { * @throws Throwable 通知过程出错 */ void afterReturning( - ClassLoader loader, String className, String methodName, String methodDesc, + Class clazz, String methodName, String methodDesc, Object target, Object[] args, Object returnObject) throws Throwable; @@ -67,7 +67,7 @@ public interface AdviceListener { * @throws Throwable 通知过程出错 */ void afterThrowing( - ClassLoader loader, String className, String methodName, String methodDesc, + Class clazz, String methodName, String methodDesc, Object target, Object[] args, Throwable throwable) throws Throwable; diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java index d291c4b31..cf1012e55 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java @@ -1,43 +1,138 @@ package com.taobao.arthas.core.advisor; -/** - * 通知监听适配器 - */ -public class AdviceListenerAdapter implements AdviceListener { +import com.taobao.arthas.core.command.express.ExpressException; +import com.taobao.arthas.core.command.express.ExpressFactory; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.shell.system.Process; +import com.taobao.arthas.core.shell.system.ProcessAware; +import com.taobao.arthas.core.util.Constants; +import com.taobao.arthas.core.util.StringUtils; +/** + * + * @author hengyunabc 2020-05-20 + * + */ +public abstract class AdviceListenerAdapter implements AdviceListener, ProcessAware { + private Process process; @Override public void create() { - + // default no-op } @Override public void destroy() { + // default no-op + } + public Process getProcess() { + return process; + } + + public void setProcess(Process process) { + this.process = process; } @Override - public void before( - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args) throws Throwable { - + final public void before(Class clazz, String methodName, String methodDesc, Object target, Object[] args) + throws Throwable { + before(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args); } @Override - public void afterReturning( - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args, + final public void afterReturning(Class clazz, String methodName, String methodDesc, Object target, Object[] args, Object returnObject) throws Throwable { - + afterReturning(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args, + returnObject); } @Override - public void afterThrowing( - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args, + final public void afterThrowing(Class clazz, String methodName, String methodDesc, Object target, Object[] args, Throwable throwable) throws Throwable { + afterThrowing(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args, + throwable); + } + /** + * 前置通知 + * + * @param loader 类加载器 + * @param clazz 类 + * @param method 方法 + * @param target 目标类实例 若目标为静态方法,则为null + * @param args 参数列表 + * @throws Throwable 通知过程出错 + */ + public abstract void before(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args) + throws Throwable; + + /** + * 返回通知 + * + * @param loader 类加载器 + * @param clazz 类 + * @param method 方法 + * @param target 目标类实例 若目标为静态方法,则为null + * @param args 参数列表 + * @param returnObject 返回结果 若为无返回值方法(void),则为null + * @throws Throwable 通知过程出错 + */ + public abstract void afterReturning(ClassLoader loader, Class clazz, ArthasMethod method, Object target, + Object[] args, Object returnObject) throws Throwable; + + /** + * 异常通知 + * + * @param loader 类加载器 + * @param clazz 类 + * @param method 方法 + * @param target 目标类实例 若目标为静态方法,则为null + * @param args 参数列表 + * @param throwable 目标异常 + * @throws Throwable 通知过程出错 + */ + public abstract void afterThrowing(ClassLoader loader, Class clazz, ArthasMethod method, Object target, + Object[] args, Throwable throwable) throws Throwable; + + /** + * 判断条件是否满足,满足的情况下需要输出结果 + * + * @param conditionExpress 条件表达式 + * @param advice 当前的advice对象 + * @param cost 本次执行的耗时 + * @return true 如果条件表达式满足 + */ + protected boolean isConditionMet(String conditionExpress, Advice advice, double cost) throws ExpressException { + return StringUtils.isEmpty(conditionExpress) + || ExpressFactory.threadLocalExpress(advice).bind(Constants.COST_VARIABLE, cost).is(conditionExpress); + } + + protected Object getExpressionResult(String express, Advice advice, double cost) throws ExpressException { + return ExpressFactory.threadLocalExpress(advice).bind(Constants.COST_VARIABLE, cost).get(express); + } + + /** + * 是否超过了上限,超过之后,停止输出 + * + * @param limit 命令执行上限 + * @param currentTimes 当前执行次数 + * @return true 如果超过或者达到了上限 + */ + protected boolean isLimitExceeded(int limit, int currentTimes) { + return currentTimes >= limit; + } + + /** + * 超过次数上限,则不再输出,命令终止 + * + * @param process the process to be aborted + * @param limit the limit to be printed + */ + protected void abortProcess(CommandProcess process, int limit) { + process.write("Command execution times exceed limit: " + limit + + ", so command will exit. You can set it with -n option.\n"); + process.end(); } } - diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java new file mode 100644 index 000000000..bb3aca3d7 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java @@ -0,0 +1,225 @@ +package com.taobao.arthas.core.advisor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import com.taobao.arthas.common.concurrent.ConcurrentWeakKeyHashMap; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.shell.system.ExecStatus; +import com.taobao.arthas.core.shell.system.ProcessAware; + +/** + * + * TODO line 的记录 listener方式? 还是有string为key,不过 classname|method|desc|num 这样子? + * 判断是否已插入了,可以在两行中间查询,有没有 SpyAPI 的invoke? + * + * TODO trace的怎么搞? trace 只记录一次就可以了 classname|method|desc|trace ? 怎么避免 trace 到 + * SPY的invoke ?直接忽略? + * + * TODO trace命令可以动态的增加 新的函数进去不?只要关联上同一个 Listener应该是可以的。 + * + * TODO 在SPY里放很多的 Object数组,然后动态的设置进去? 比如有新的 Listener来的时候。 这样子连查表都不用了。 甚至可以动态生成 + * 存放这些 Listener数组的类? 这样子的话,只要有 Binding那里,查询到一个具体分配好的类, 这样子就可以了? + * 甚至每个ClassLoader里都动态生成这样子的 存放类,那么这样子不可以避免查 ClassLoader了么? + * + * 动态为每一个增强类,生成一个新的类,新的类里,有各种的 ID 数组,保存每一个类的每一种 trace 点的信息?? + * + * 多个 watch命令 对同一个类,现在的逻辑是,每个watch都有一个自己的 TransForm,但不会重复增强,因为做了判断。 + * watch命令停止时,也没有去掉增强的代码。 只有reset时 才会去掉。 + * + * 其实用户想查看局部变量,并不是想查看哪一行! 而是想看某个函数里子调用时的 局部变量的值! 所以实际上是想要一个新的命令,比如 watchinmethod + * , 可以 在某个子调用里, + * + * TODO 现在的trace 可以输出行号,可能不是很精确,但是可以对应上的。 这个在新的方式里怎么支持? 增加一个 linenumber binding? + * 从mehtodNode,向上查找到最近的行号? + * + * TODO 防止重复增强,最重要的应该还是动态增加 annotation,这个才是真正可以做到某一行,某一个子 invoke 都能识别出来的! 无论是 + * transform多少次! 字节码怎么动态加 annotation ? annotation里签名用 url ?的key/value方式表达! + * 这样子可以有效还原信息 + * + * TODO 是否考虑一个 trace /watch命令之后,得到一个具体的 Listener ID, 允许在另外的窗口里,再次 + * trace/watch时指定这个ID,就会查找到,并处理。 这样子的话,真正达到了动态灵活的,一层一层增加的trace ! + * + * + * @author hengyunabc 2020-04-24 + * + */ +public class AdviceListenerManager { + + private static Timer timer = ArthasBootstrap.getInstance().getTimer(); + private static final FakeBootstrapClassLoader FAKEBOOTSTRAPCLASSLOADER = new FakeBootstrapClassLoader(); + + static { + timer.scheduleAtFixedRate(new TimerTask() { + + @Override + public void run() { + if (adviceListenerMap != null) { + for (Entry entry : adviceListenerMap.entrySet()) { + ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue(); + synchronized (adviceListenerManager) { + for (Entry> eee : adviceListenerManager.map.entrySet()) { + List listeners = eee.getValue(); + List newResult = new ArrayList(); + for (AdviceListener listener : listeners) { + if (listener instanceof ProcessAware) { + ProcessAware processAware = (ProcessAware) listener; + ExecStatus status = processAware.getProcess().status(); + if (!status.equals(ExecStatus.TERMINATED)) { + newResult.add(listener); + } + } + } + + if (newResult.size() != listeners.size()) { + adviceListenerManager.map.put(eee.getKey(), newResult); + } + + } + } + } + } + } + + }, 3000, 3000); + } + + static private ConcurrentWeakKeyHashMap adviceListenerMap = new ConcurrentWeakKeyHashMap(); + + static class ClassLoaderAdviceListenerManager { + private ConcurrentHashMap> map = new ConcurrentHashMap>(); + + private String key(String className, String methodName, String methodDesc) { + return className + methodName + methodDesc; + } + + private String keyForTrace(String className, String owner, String methodName, String methodDesc) { + return className + owner + methodName + methodDesc; + } + + public void registerAdviceListener(String className, String methodName, String methodDesc, + AdviceListener listener) { + synchronized (this) { + className = className.replace('/', '.'); + String key = key(className, methodName, methodDesc); + + List listeners = map.get(key); + if (listeners == null) { + listeners = new ArrayList(); + map.put(key, listeners); + } + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + } + + public List queryAdviceListeners(String className, String methodName, String methodDesc) { + className = className.replace('/', '.'); + String key = key(className, methodName, methodDesc); + + List listeners = map.get(key); + + return listeners; + } + + public void registerTraceAdviceListener(String className, String owner, String methodName, String methodDesc, + AdviceListener listener) { + + className = className.replace('/', '.'); + String key = keyForTrace(className, owner, methodName, methodDesc); + + List listeners = map.get(key); + if (listeners == null) { + listeners = new ArrayList(); + map.put(key, listeners); + } + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + public List queryTraceAdviceListeners(String className, String owner, String methodName, + String methodDesc) { + className = className.replace('/', '.'); + String key = keyForTrace(className, owner, methodName, methodDesc); + + List listeners = map.get(key); + + return listeners; + } + } + + public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName, + String methodDesc, AdviceListener listener) { + classLoader = wrap(classLoader); + className = className.replace('/', '.'); + + ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader); + + if (manager == null) { + manager = new ClassLoaderAdviceListenerManager(); + adviceListenerMap.put(classLoader, manager); + } + manager.registerAdviceListener(className, methodName, methodDesc, listener); + } + + public static void updateAdviceListeners() { + + } + + public static List queryAdviceListeners(ClassLoader classLoader, String className, + String methodName, String methodDesc) { + classLoader = wrap(classLoader); + className = className.replace('/', '.'); + ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader); + + if (manager != null) { + return manager.queryAdviceListeners(className, methodName, methodDesc); + } + + return null; + } + + public static void registerTraceAdviceListener(ClassLoader classLoader, String className, String owner, + String methodName, String methodDesc, AdviceListener listener) { + classLoader = wrap(classLoader); + className = className.replace('/', '.'); + + ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader); + + if (manager == null) { + manager = new ClassLoaderAdviceListenerManager(); + adviceListenerMap.put(classLoader, manager); + } + manager.registerTraceAdviceListener(className, owner, methodName, methodDesc, listener); + } + + public static List queryTraceAdviceListeners(ClassLoader classLoader, String className, + String owner, String methodName, String methodDesc) { + classLoader = wrap(classLoader); + className = className.replace('/', '.'); + ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader); + + if (manager != null) { + return manager.queryTraceAdviceListeners(className, owner, methodName, methodDesc); + } + + return null; + } + + private static ClassLoader wrap(ClassLoader classLoader) { + if (classLoader != null) { + return classLoader; + } + return FAKEBOOTSTRAPCLASSLOADER; + } + + private static class FakeBootstrapClassLoader extends ClassLoader { + + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java index e7d26b0fc..2b607da6f 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceWeaver.java @@ -1,23 +1,10 @@ package com.taobao.arthas.core.advisor; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; -import com.taobao.arthas.core.GlobalOptions; -import com.taobao.arthas.core.util.matcher.Matcher; -import com.taobao.arthas.core.util.*; -import com.taobao.arthas.core.util.affect.EnhancerAffect; -import com.taobao.arthas.core.util.collection.GaStack; -import com.taobao.arthas.core.util.collection.ThreadUnsafeFixGaStack; -import com.taobao.arthas.core.util.collection.ThreadUnsafeGaStack; -import org.objectweb.asm.*; -import org.objectweb.asm.commons.AdviceAdapter; -import org.objectweb.asm.commons.JSRInlinerAdapter; -import org.objectweb.asm.commons.Method; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * 通知编织者
@@ -27,226 +14,13 @@ import java.util.concurrent.ConcurrentHashMap; *

* Created by vlinux on 15/5/17. */ -public class AdviceWeaver extends ClassVisitor implements Opcodes { +public class AdviceWeaver { private static final Logger logger = LoggerFactory.getLogger(AdviceWeaver.class); - public static final String ON_BEFORE = "methodOnBegin"; - public static final String ON_RETURN = "methodOnReturnEnd"; - public static final String ON_THROWS = "methodOnThrowingEnd"; - public static final String BEFORE_INVOKE = "methodOnInvokeBeforeTracing"; - public static final String AFTER_INVOKE = "methodOnInvokeAfterTracing"; - public static final String THROW_INVOKE = "methodOnInvokeThrowTracing"; - public static final String RESET = "resetArthasClassLoader"; - - // 线程帧栈堆栈大小 - private final static int FRAME_STACK_SIZE = 7; // 通知监听器集合 private final static Map advices = new ConcurrentHashMap(); - // 线程帧封装 - private static final ThreadLocal>> threadBoundContext - = new ThreadLocal>>(); - // 防止自己递归调用 - private static final ThreadLocal isSelfCallRef = new ThreadLocal() { - - @Override - protected Boolean initialValue() { - return false; - } - - }; - - - /** - * 方法开始
- * 用于编织通知器,外部不会直接调用 - * - * @param loader 类加载器 - * @param adviceId 通知ID - * @param className 类名 - * @param methodName 方法名 - * @param methodDesc 方法描述 - * @param target 返回结果 - * 若为无返回值方法(void),则为null - * @param args 参数列表 - */ - public static void methodOnBegin( - int adviceId, - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args) { - - if (isSelfCallRef.get()) { - return; - } else { - isSelfCallRef.set(true); - } - - try { - // 构建执行帧栈,保护当前的执行现场 - final GaStack frameStack = new ThreadUnsafeFixGaStack(FRAME_STACK_SIZE); - frameStack.push(loader); - frameStack.push(className); - frameStack.push(methodName); - frameStack.push(methodDesc); - frameStack.push(target); - frameStack.push(args); - - final AdviceListener listener = getListener(adviceId); - frameStack.push(listener); - - // 获取通知器并做前置通知 - before(listener, loader, className, methodName, methodDesc, target, args); - - // 保护当前执行帧栈,压入线程帧栈 - threadFrameStackPush(frameStack); - } finally { - isSelfCallRef.set(false); - } - - } - - - /** - * 方法以返回结束
- * 用于编织通知器,外部不会直接调用 - * - * @param returnObject 返回对象 - * 若目标为静态方法,则为null - */ - public static void methodOnReturnEnd(Object returnObject) { - methodOnEnd(false, returnObject); - } - - /** - * 方法以抛异常结束
- * 用于编织通知器,外部不会直接调用 - * - * @param throwable 抛出异常 - */ - public static void methodOnThrowingEnd(Throwable throwable) { - methodOnEnd(true, throwable); - } - - /** - * 所有的返回都统一处理 - * - * @param isThrowing 标记正常返回结束还是抛出异常结束 - * @param returnOrThrowable 正常返回或者抛出异常对象 - */ - private static void methodOnEnd(boolean isThrowing, Object returnOrThrowable) { - - if (isSelfCallRef.get()) { - return; - } else { - isSelfCallRef.set(true); - } - - try { - // 弹射线程帧栈,恢复Begin所保护的执行帧栈 - final GaStack frameStack = threadFrameStackPop(); - - // 弹射执行帧栈,恢复Begin所保护的现场 - final AdviceListener listener = (AdviceListener) frameStack.pop(); - final Object[] args = (Object[]) frameStack.pop(); - final Object target = frameStack.pop(); - final String methodDesc = (String) frameStack.pop(); - final String methodName = (String) frameStack.pop(); - final String className = (String) frameStack.pop(); - final ClassLoader loader = (ClassLoader) frameStack.pop(); - - // 异常通知 - if (isThrowing) { - afterThrowing(listener, loader, className, methodName, methodDesc, target, args, (Throwable) returnOrThrowable); - } - - // 返回通知 - else { - afterReturning(listener, loader, className, methodName, methodDesc, target, args, returnOrThrowable); - } - } finally { - isSelfCallRef.set(false); - } - - } - - /** - * 方法内部调用开始 - * - * @param adviceId 通知ID - * @param owner 调用类名 - * @param name 调用方法名 - * @param desc 调用方法描述 - */ - public static void methodOnInvokeBeforeTracing(int adviceId, String owner, String name, String desc, int lineNumber) { - final InvokeTraceable listener = (InvokeTraceable) getListener(adviceId); - if (null != listener) { - try { - listener.invokeBeforeTracing(owner, name, desc, lineNumber); - } catch (Throwable t) { - logger.warn("advice before tracing failed.", t); - } - } - } - - /** - * 方法内部调用结束(正常返回) - * - * @param adviceId 通知ID - * @param owner 调用类名 - * @param name 调用方法名 - * @param desc 调用方法描述 - */ - public static void methodOnInvokeAfterTracing(int adviceId, String owner, String name, String desc, int lineNumber) { - final InvokeTraceable listener = (InvokeTraceable) getListener(adviceId); - if (null != listener) { - try { - listener.invokeAfterTracing(owner, name, desc, lineNumber); - } catch (Throwable t) { - logger.warn("advice after tracing failed.", t); - } - } - } - - /** - * 方法内部调用结束(异常返回) - * - * @param adviceId 通知ID - * @param owner 调用类名 - * @param name 调用方法名 - * @param desc 调用方法描述 - */ - public static void methodOnInvokeThrowTracing(int adviceId, String owner, String name, String desc, int lineNumber) { - final InvokeTraceable listener = (InvokeTraceable) getListener(adviceId); - if (null != listener) { - try { - listener.invokeThrowTracing(owner, name, desc, lineNumber); - } catch (Throwable t) { - logger.warn("advice throw tracing failed.", t); - } - } - } - - /* - * 线程帧栈压栈
- * 将当前执行帧栈压入线程栈 - */ - private static void threadFrameStackPush(GaStack frameStack) { - GaStack> threadFrameStack = threadBoundContext.get(); - if (null == threadFrameStack) { - threadBoundContext.set(threadFrameStack = new ThreadUnsafeGaStack>()); - } - - threadFrameStack.push(frameStack); - } - - private static GaStack threadFrameStackPop() { - return threadBoundContext.get().pop(); - } - - private static AdviceListener getListener(int adviceId) { - return advices.get(adviceId); - } /** * 注册监听器 @@ -302,704 +76,4 @@ public class AdviceWeaver extends ClassVisitor implements Opcodes { return advices.remove(adviceId); } - private static void before(AdviceListener listener, - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args) { - - if (null != listener) { - try { - listener.before(loader, className, methodName, methodDesc, target, args); - } catch (Throwable t) { - logger.warn("advice before failed.", t); - } - } - - } - - private static void afterReturning(AdviceListener listener, - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args, Object returnObject) { - if (null != listener) { - try { - listener.afterReturning(loader, className, methodName, methodDesc, target, args, returnObject); - } catch (Throwable t) { - logger.warn("advice returning failed.", t); - } - } - } - - private static void afterThrowing(AdviceListener listener, - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args, Throwable throwable) { - if (null != listener) { - try { - listener.afterThrowing(loader, className, methodName, methodDesc, target, args, throwable); - } catch (Throwable t) { - logger.warn("advice throwing failed.", t); - } - } - } - - - private final int adviceId; - private final boolean isTracing; - private final boolean skipJDKTrace; - private final String className; - private String superName; - private final Matcher matcher; - private final EnhancerAffect affect; - - - /** - * 构建通知编织器 - * - * @param adviceId 通知ID - * @param isTracing 可跟踪方法调用 - * @param skipJDKTrace 是否忽略对JDK内部方法的跟踪 - * @param className 类名称 - * @param matcher 方法匹配 - * 只有匹配上的方法才会被织入通知器 - * @param affect 影响计数 - * @param cv ClassVisitor for ASM - */ - public AdviceWeaver(int adviceId, boolean isTracing, boolean skipJDKTrace, String className, Matcher matcher, EnhancerAffect affect, ClassVisitor cv) { - super(Opcodes.ASM7, cv); - this.adviceId = adviceId; - this.isTracing = isTracing; - this.skipJDKTrace = skipJDKTrace; - this.className = className; - this.matcher = matcher; - this.affect = affect; - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - this.superName = superName; - } - - protected boolean isSuperOrSiblingConstructorCall(int opcode, String owner, String name) { - return (opcode == Opcodes.INVOKESPECIAL && name.equals("") - && (superName.equals(owner) || className.equals(owner))); - } - - /** - * 是否抽象属性 - */ - private boolean isAbstract(int access) { - return (ACC_ABSTRACT & access) == ACC_ABSTRACT; - } - - - /** - * 是否需要忽略 - */ - private boolean isIgnore(MethodVisitor mv, int access, String methodName) { - return null == mv - || isAbstract(access) - || !matcher.matching(methodName) - || ArthasCheckUtils.isEquals(methodName, ""); - } - - @Override - public MethodVisitor visitMethod( - final int access, - final String name, - final String desc, - final String signature, - final String[] exceptions) { - - final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); - - if (isIgnore(mv, access, name)) { - return mv; - } - - // 编织方法计数 - affect.mCnt(1); - - return new AdviceAdapter(Opcodes.ASM7, new JSRInlinerAdapter(mv, access, name, desc, signature, exceptions), access, name, desc) { - - // -- Label for try...catch block - private final Label beginLabel = new Label(); - private final Label endLabel = new Label(); - - // -- KEY of advice -- - private final int KEY_ARTHAS_ADVICE_BEFORE_METHOD = 0; - private final int KEY_ARTHAS_ADVICE_RETURN_METHOD = 1; - private final int KEY_ARTHAS_ADVICE_THROWS_METHOD = 2; - private final int KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD = 3; - private final int KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD = 4; - private final int KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD = 5; - - - // -- KEY of ASM_TYPE or ASM_METHOD -- - private final Type ASM_TYPE_SPY = Type.getType("Ljava/arthas/Spy;"); - private final Type ASM_TYPE_OBJECT = Type.getType(Object.class); - private final Type ASM_TYPE_OBJECT_ARRAY = Type.getType(Object[].class); - private final Type ASM_TYPE_CLASS = Type.getType(Class.class); - private final Type ASM_TYPE_INTEGER = Type.getType(Integer.class); - private final Type ASM_TYPE_CLASS_LOADER = Type.getType(ClassLoader.class); - private final Type ASM_TYPE_STRING = Type.getType(String.class); - private final Type ASM_TYPE_THROWABLE = Type.getType(Throwable.class); - private final Type ASM_TYPE_INT = Type.getType(int.class); - private final Type ASM_TYPE_METHOD = Type.getType(java.lang.reflect.Method.class); - private final Method ASM_METHOD_METHOD_INVOKE = Method.getMethod("Object invoke(Object,Object[])"); - - // 代码锁 - private final CodeLock codeLockForTracing = new TracingAsmCodeLock(this); - - private int lineNumber; - - private void _debug(final StringBuilder append, final String msg) { - - if (!GlobalOptions.isDebugForAsm) { - return; - } - - // println msg - visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); - if (StringUtils.isBlank(append.toString())) { - visitLdcInsn(append.append(msg).toString()); - } else { - visitLdcInsn(append.append(" >> ").append(msg).toString()); - } - - visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); - } - -// private void _debug_dup(final String msg) { -// -// if (!isDebugForAsm) { -// return; -// } -// -// // print prefix -// visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); -// visitLdcInsn(msg); -// visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false); -// -// // println msg -// dup(); -// visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); -// swap(); -// visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "toString", "()Ljava/lang/String;", false); -// visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); -// } - - /** - * 加载通知方法 - * @param keyOfMethod 通知方法KEY - */ - private void loadAdviceMethod(int keyOfMethod) { - - switch (keyOfMethod) { - - case KEY_ARTHAS_ADVICE_BEFORE_METHOD: { - getStatic(ASM_TYPE_SPY, "ON_BEFORE_METHOD", ASM_TYPE_METHOD); - break; - } - - case KEY_ARTHAS_ADVICE_RETURN_METHOD: { - getStatic(ASM_TYPE_SPY, "ON_RETURN_METHOD", ASM_TYPE_METHOD); - break; - } - - case KEY_ARTHAS_ADVICE_THROWS_METHOD: { - getStatic(ASM_TYPE_SPY, "ON_THROWS_METHOD", ASM_TYPE_METHOD); - break; - } - - case KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD: { - getStatic(ASM_TYPE_SPY, "BEFORE_INVOKING_METHOD", ASM_TYPE_METHOD); - break; - } - - case KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD: { - getStatic(ASM_TYPE_SPY, "AFTER_INVOKING_METHOD", ASM_TYPE_METHOD); - break; - } - - case KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD: { - getStatic(ASM_TYPE_SPY, "THROW_INVOKING_METHOD", ASM_TYPE_METHOD); - break; - } - - default: { - throw new IllegalArgumentException("illegal keyOfMethod=" + keyOfMethod); - } - - } - - } - - /** - * 加载ClassLoader
- * 这里分开静态方法中ClassLoader的获取以及普通方法中ClassLoader的获取 - * 主要是性能上的考虑 - */ - private void loadClassLoader() { - - if (this.isStaticMethod()) { - visitLdcInsn(StringUtils.normalizeClassName(className)); - invokeStatic(ASM_TYPE_CLASS, Method.getMethod("Class forName(String)")); - invokeVirtual(ASM_TYPE_CLASS, Method.getMethod("ClassLoader getClassLoader()")); - - } else { - loadThis(); - invokeVirtual(ASM_TYPE_OBJECT, Method.getMethod("Class getClass()")); - invokeVirtual(ASM_TYPE_CLASS, Method.getMethod("ClassLoader getClassLoader()")); - } - - } - - /** - * 加载before通知参数数组 - */ - private void loadArrayForBefore() { - push(7); - newArray(ASM_TYPE_OBJECT); - - dup(); - push(0); - push(adviceId); - box(ASM_TYPE_INT); - arrayStore(ASM_TYPE_INTEGER); - - dup(); - push(1); - loadClassLoader(); - arrayStore(ASM_TYPE_CLASS_LOADER); - - dup(); - push(2); - push(className); - arrayStore(ASM_TYPE_STRING); - - dup(); - push(3); - push(name); - arrayStore(ASM_TYPE_STRING); - - dup(); - push(4); - push(desc); - arrayStore(ASM_TYPE_STRING); - - dup(); - push(5); - loadThisOrPushNullIfIsStatic(); - arrayStore(ASM_TYPE_OBJECT); - - dup(); - push(6); - loadArgArray(); - arrayStore(ASM_TYPE_OBJECT_ARRAY); - } - - - @Override - protected void onMethodEnter() { - - codeLockForTracing.lock(new CodeLock.Block() { - @Override - public void code() { - - final StringBuilder append = new StringBuilder(); - _debug(append, "debug:onMethodEnter()"); - - // 加载before方法 - loadAdviceMethod(KEY_ARTHAS_ADVICE_BEFORE_METHOD); - - _debug(append, "debug:onMethodEnter() > loadAdviceMethod()"); - - // 推入Method.invoke()的第一个参数 - pushNull(); - - // 方法参数 - loadArrayForBefore(); - - _debug(append, "debug:onMethodEnter() > loadAdviceMethod() > loadArrayForBefore()"); - - // 调用方法 - invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE); - pop(); - - _debug(append, "debug:onMethodEnter() > loadAdviceMethod() > loadArrayForBefore() > invokeVirtual()"); - } - }); - - mark(beginLabel); - - } - - - /* - * 加载return通知参数数组 - */ - private void loadReturnArgs() { - dup2X1(); - pop2(); - push(1); - newArray(ASM_TYPE_OBJECT); - dup(); - dup2X1(); - pop2(); - push(0); - swap(); - arrayStore(ASM_TYPE_OBJECT); - } - - @Override - protected void onMethodExit(final int opcode) { - - if (!isThrow(opcode)) { - codeLockForTracing.lock(new CodeLock.Block() { - @Override - public void code() { - - final StringBuilder append = new StringBuilder(); - _debug(append, "debug:onMethodExit()"); - - // 加载返回对象 - loadReturn(opcode); - _debug(append, "debug:onMethodExit() > loadReturn()"); - - - // 加载returning方法 - loadAdviceMethod(KEY_ARTHAS_ADVICE_RETURN_METHOD); - _debug(append, "debug:onMethodExit() > loadReturn() > loadAdviceMethod()"); - - // 推入Method.invoke()的第一个参数 - pushNull(); - - // 加载return通知参数数组 - loadReturnArgs(); - _debug(append, "debug:onMethodExit() > loadReturn() > loadAdviceMethod() > loadReturnArgs()"); - - invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE); - pop(); - - _debug(append, "debug:onMethodExit() > loadReturn() > loadAdviceMethod() > loadReturnArgs() > invokeVirtual()"); - } - }); - } - - } - - - /* - * 创建throwing通知参数本地变量 - */ - private void loadThrowArgs() { - dup2X1(); - pop2(); - push(1); - newArray(ASM_TYPE_OBJECT); - dup(); - dup2X1(); - pop2(); - push(0); - swap(); - arrayStore(ASM_TYPE_THROWABLE); - } - - @Override - public void visitMaxs(int maxStack, int maxLocals) { - - mark(endLabel); -// catchException(beginLabel, endLabel, ASM_TYPE_THROWABLE); - visitTryCatchBlock(beginLabel, endLabel, mark(), - ASM_TYPE_THROWABLE.getInternalName()); - - codeLockForTracing.lock(new CodeLock.Block() { - @Override - public void code() { - - final StringBuilder append = new StringBuilder(); - _debug(append, "debug:catchException()"); - - // 加载异常 - loadThrow(); - _debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod()"); - - // 加载throwing方法 - loadAdviceMethod(KEY_ARTHAS_ADVICE_THROWS_METHOD); - _debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod()"); - - - // 推入Method.invoke()的第一个参数 - pushNull(); - - // 加载throw通知参数数组 - loadThrowArgs(); - _debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod() > loadThrowArgs()"); - - // 调用方法 - invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE); - pop(); - _debug(append, "debug:catchException() > loadThrow() > loadAdviceMethod() > loadThrowArgs() > invokeVirtual()"); - - } - }); - - throwException(); - - super.visitMaxs(maxStack, maxLocals); - } - - @Override - public void visitLineNumber(int line, Label start) { - super.visitLineNumber(line, start); - lineNumber = line; - } - - /** - * 是否静态方法 - * @return true:静态方法 / false:非静态方法 - */ - private boolean isStaticMethod() { - return (methodAccess & ACC_STATIC) != 0; - } - - /** - * 是否抛出异常返回(通过字节码判断) - * @param opcode 操作码 - * @return true:以抛异常形式返回 / false:非抛异常形式返回(return) - */ - private boolean isThrow(int opcode) { - return opcode == ATHROW; - } - - /** - * 将NULL推入堆栈 - */ - private void pushNull() { - push((Type) null); - } - - /** - * 加载this/null - */ - private void loadThisOrPushNullIfIsStatic() { - if (isStaticMethod()) { - pushNull(); - } else { - loadThis(); - } - } - - /** - * 加载返回值 - * @param opcode 操作吗 - */ - private void loadReturn(int opcode) { - switch (opcode) { - - case RETURN: { - pushNull(); - break; - } - - case ARETURN: { - dup(); - break; - } - - case LRETURN: - case DRETURN: { - dup2(); - box(Type.getReturnType(methodDesc)); - break; - } - - default: { - dup(); - box(Type.getReturnType(methodDesc)); - break; - } - - } - } - - /** - * 加载异常 - */ - private void loadThrow() { - dup(); - } - - - /** - * 加载方法调用跟踪通知所需参数数组 - */ - private void loadArrayForInvokeTracing(String owner, String name, String desc, int lineNumber) { - push(5); - newArray(ASM_TYPE_OBJECT); - - dup(); - push(0); - push(adviceId); - box(ASM_TYPE_INT); - arrayStore(ASM_TYPE_INTEGER); - - dup(); - push(1); - push(owner); - arrayStore(ASM_TYPE_STRING); - - dup(); - push(2); - push(name); - arrayStore(ASM_TYPE_STRING); - - dup(); - push(3); - push(desc); - arrayStore(ASM_TYPE_STRING); - - dup(); - push(4); - push(lineNumber); - box(ASM_TYPE_INT); - arrayStore(ASM_TYPE_INTEGER); - } - - - @Override - public void visitInsn(int opcode) { - super.visitInsn(opcode); - codeLockForTracing.code(opcode); - } - - @Override - public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { - tcbs.add(new AsmTryCatchBlock(start, end, handler, type)); - } - - List tcbs = new ArrayList(); - - @Override - public void visitEnd() { - for (AsmTryCatchBlock tcb : tcbs) { - super.visitTryCatchBlock(tcb.start, tcb.end, tcb.handler, tcb.type); - } - - super.visitEnd(); - } - - /* - * 跟踪代码 - */ - private void tracing(final int tracingType, final String owner, final String name, final String desc, final int lineNumber) { - - final String label; - switch (tracingType) { - case KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD: { - label = "beforeInvoking"; - break; - } - case KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD: { - label = "afterInvoking"; - break; - } - case KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD: { - label = "throwInvoking"; - break; - } - default: { - throw new IllegalStateException("illegal tracing type: " + tracingType); - } - } - - codeLockForTracing.lock(new CodeLock.Block() { - @Override - public void code() { - - final StringBuilder append = new StringBuilder(); - _debug(append, "debug:" + label + "()"); - - loadAdviceMethod(tracingType); - _debug(append, "loadAdviceMethod()"); - - pushNull(); - loadArrayForInvokeTracing(owner, name, desc, lineNumber); - _debug(append, "loadArrayForInvokeTracing()"); - - invokeVirtual(ASM_TYPE_METHOD, ASM_METHOD_METHOD_INVOKE); - pop(); - _debug(append, "invokeVirtual()"); - - } - }); - - } - - @Override - public void visitMethodInsn(int opcode, final String owner, final String name, final String desc, boolean itf) { - if (isSuperOrSiblingConstructorCall(opcode, owner, name)) { - super.visitMethodInsn(opcode, owner, name, desc, itf); - return; - } - - if (!isTracing || codeLockForTracing.isLock()) { - super.visitMethodInsn(opcode, owner, name, desc, itf); - return; - } - - //是否要对JDK内部的方法调用进行trace - if (skipJDKTrace && owner.startsWith("java/")) { - super.visitMethodInsn(opcode, owner, name, desc, itf); - return; - } - - // 方法调用前通知 - tracing(KEY_ARTHAS_ADVICE_BEFORE_INVOKING_METHOD, owner, name, desc, lineNumber); - - final Label beginLabel = new Label(); - final Label endLabel = new Label(); - final Label finallyLabel = new Label(); - - // try - // { - - mark(beginLabel); - super.visitMethodInsn(opcode, owner, name, desc, itf); - mark(endLabel); - - // 方法调用后通知 - tracing(KEY_ARTHAS_ADVICE_AFTER_INVOKING_METHOD, owner, name, desc, lineNumber); - goTo(finallyLabel); - - // } - // catch - // { - - catchException(beginLabel, endLabel, ASM_TYPE_THROWABLE); - tracing(KEY_ARTHAS_ADVICE_THROW_INVOKING_METHOD, owner, name, desc, lineNumber); - - throwException(); - - // } - // finally - // { - mark(finallyLabel); - // } - } - }; - } - - static class AsmTryCatchBlock { - Label start; - Label end; - Label handler; - String type; - - AsmTryCatchBlock(Label start, Label end, Label handler, String type) { - this.start = start; - this.end = end; - this.handler = handler; - this.type = type; - } - } } diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/ArthasMethod.java b/core/src/main/java/com/taobao/arthas/core/advisor/ArthasMethod.java index cd8f60fdf..9339e6c6e 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/ArthasMethod.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/ArthasMethod.java @@ -4,43 +4,111 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.objectweb.asm.Type; + +import com.taobao.arthas.core.util.StringUtils; + /** - * Arthas封装的方法
- * 主要用来封装构造函数cinit/init/method - * Created by vlinux on 15/5/24. + * + * 主要用于 tt 命令重放使用 + * + * @author vlinux on 15/5/24 + * @author hengyunabc 2020-05-20 + * */ public class ArthasMethod { + private final Class clazz; + private final String methodName; + private final String methodDesc; - private final int type; - private final Constructor constructor; - private final Method method; + private Constructor constructor; + private Method method; - /* - * 构造方法 - */ - private static final int TYPE_INIT = 1 << 1; + private void initMethod() { + if (constructor != null || method != null) { + return; + } - /* - * 普通方法 - */ - private static final int TYPE_METHOD = 1 << 2; + try { + ClassLoader loader = this.clazz.getClassLoader(); + final org.objectweb.asm.Type asmType = org.objectweb.asm.Type.getMethodType(methodDesc); + + // to arg types + final Class[] argsClasses = new Class[asmType.getArgumentTypes().length]; + for (int index = 0; index < argsClasses.length; index++) { + // asm class descriptor to jvm class + final Class argumentClass; + final Type argumentAsmType = asmType.getArgumentTypes()[index]; + switch (argumentAsmType.getSort()) { + case Type.BOOLEAN: { + argumentClass = boolean.class; + break; + } + case Type.CHAR: { + argumentClass = char.class; + break; + } + case Type.BYTE: { + argumentClass = byte.class; + break; + } + case Type.SHORT: { + argumentClass = short.class; + break; + } + case Type.INT: { + argumentClass = int.class; + break; + } + case Type.FLOAT: { + argumentClass = float.class; + break; + } + case Type.LONG: { + argumentClass = long.class; + break; + } + case Type.DOUBLE: { + argumentClass = double.class; + break; + } + case Type.ARRAY: { + argumentClass = toClass(loader, argumentAsmType.getInternalName()); + break; + } + case Type.VOID: { + argumentClass = void.class; + break; + } + case Type.OBJECT: + case Type.METHOD: + default: { + argumentClass = toClass(loader, argumentAsmType.getClassName()); + break; + } + } + + argsClasses[index] = argumentClass; + } + + if ("".equals(this.methodName)) { + this.constructor = clazz.getDeclaredConstructor(argsClasses); + ; + } else { + this.method = clazz.getDeclaredMethod(methodName, argsClasses); + } + } catch (Throwable e) { + throw new RuntimeException(e); + } - /** - * 是否构造方法 - * - * @return true/false - */ - public boolean isInit() { - return (TYPE_INIT & type) == TYPE_INIT; } - /** - * 是否普通方法 - * - * @return true/false - */ - public boolean isMethod() { - return (TYPE_METHOD & type) == TYPE_METHOD; + private Class toClass(ClassLoader loader, String className) throws ClassNotFoundException { + return Class.forName(StringUtils.normalizeClassName(className), true, toClassLoader(loader)); + } + + private ClassLoader toClassLoader(ClassLoader loader) { + return null != loader ? loader : ArthasMethod.class.getClassLoader(); } /** @@ -49,50 +117,53 @@ public class ArthasMethod { * @return 返回方法名称 */ public String getName() { - return isInit() - ? "" - : method.getName(); + return this.methodName; } @Override public String toString() { - return isInit() - ? constructor.toString() - : method.toString(); + initMethod(); + if (constructor != null) { + return constructor.toString(); + } else if (method != null) { + return method.toString(); + } + return "ERROR_METHOD"; } public boolean isAccessible() { - return isInit() - ? constructor.isAccessible() - : method.isAccessible(); + initMethod(); + if (this.method != null) { + return method.isAccessible(); + } else if (this.constructor != null) { + return constructor.isAccessible(); + } + return false; } public void setAccessible(boolean accessFlag) { - if (isInit()) { + initMethod(); + if (constructor != null) { constructor.setAccessible(accessFlag); - } else { + } else if (method != null) { method.setAccessible(accessFlag); } } - public Object invoke(Object target, Object... args) throws IllegalAccessException, InvocationTargetException, InstantiationException { - return isInit() - ? constructor.newInstance(args) - : method.invoke(target, args); + public Object invoke(Object target, Object... args) + throws IllegalAccessException, InvocationTargetException, InstantiationException { + initMethod(); + if (method != null) { + return method.invoke(target, args); + } else if (this.constructor != null) { + return constructor.newInstance(args); + } + return null; } - private ArthasMethod(int type, Constructor constructor, Method method) { - this.type = type; - this.constructor = constructor; - this.method = method; + public ArthasMethod(Class clazz, String methodName, String methodDesc) { + this.clazz = clazz; + this.methodName = methodName; + this.methodDesc = methodDesc; } - - public static ArthasMethod newInit(Constructor constructor) { - return new ArthasMethod(TYPE_INIT, constructor, null); - } - - public static ArthasMethod newMethod(Method method) { - return new ArthasMethod(TYPE_METHOD, null, method); - } - } diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java index 85fb26ebc..bb3cfaca3 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java @@ -1,18 +1,9 @@ package com.taobao.arthas.core.advisor; -import com.alibaba.arthas.deps.org.slf4j.Logger; -import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; -import com.taobao.arthas.core.GlobalOptions; -import com.taobao.arthas.core.util.Constants; -import com.taobao.arthas.core.util.FileUtils; -import com.taobao.arthas.core.util.matcher.Matcher; -import com.taobao.arthas.core.util.SearchUtils; -import com.taobao.arthas.core.util.affect.EnhancerAffect; - -import com.taobao.arthas.core.util.reflect.FieldUtils; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; +import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals; +import static java.lang.System.arraycopy; +import java.arthas.SpyAPI; import java.io.File; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; @@ -21,32 +12,71 @@ import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.lang.reflect.Method; import java.security.ProtectionDomain; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; -import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals; -import static java.lang.System.arraycopy; -import static org.objectweb.asm.ClassReader.EXPAND_FRAMES; -import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; -import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; +import com.alibaba.arthas.deps.org.objectweb.asm.Opcodes; +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodInsnNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvoke; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtInvokeException; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.asm.location.Location; +import com.taobao.arthas.bytekit.asm.location.LocationType; +import com.taobao.arthas.bytekit.asm.location.MethodInsnNodeWare; +import com.taobao.arthas.bytekit.asm.location.filter.GroupLocationFilter; +import com.taobao.arthas.bytekit.asm.location.filter.InvokeCheckLocationFilter; +import com.taobao.arthas.bytekit.asm.location.filter.InvokeContainLocationFilter; +import com.taobao.arthas.bytekit.asm.location.filter.LocationFilter; +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.core.GlobalOptions; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.util.ArthasCheckUtils; +import com.taobao.arthas.core.util.FileUtils; +import com.taobao.arthas.core.util.SearchUtils; +import com.taobao.arthas.core.util.affect.EnhancerAffect; +import com.taobao.arthas.core.util.matcher.Matcher; /** - * 对类进行通知增强 - * Created by vlinux on 15/5/17. + * 对类进行通知增强 Created by vlinux on 15/5/17. + * @author hengyunabc */ public class Enhancer implements ClassFileTransformer { private static final Logger logger = LoggerFactory.getLogger(Enhancer.class); - private final int adviceId; + private final AdviceListener listener; private final boolean isTracing; private final boolean skipJDKTrace; private final Set> matchingClasses; private final Matcher methodNameMatcher; private final EnhancerAffect affect; - // 类-字节码缓存 - private final static Map/*Class*/, byte[]/*bytes of Class*/> classBytesCache - = new WeakHashMap, byte[]>(); + // 被增强的类的缓存 + private final static Map/* Class */, Object> classBytesCache = new WeakHashMap, Object>(); + private static SpyImpl spyImpl = new SpyImpl(); + + static { + SpyAPI.setSpy(spyImpl); + } /** * @param adviceId 通知编号 @@ -56,13 +86,9 @@ public class Enhancer implements ClassFileTransformer { * @param methodNameMatcher 方法名匹配 * @param affect 影响统计 */ - private Enhancer(int adviceId, - boolean isTracing, - boolean skipJDKTrace, - Set> matchingClasses, - Matcher methodNameMatcher, - EnhancerAffect affect) { - this.adviceId = adviceId; + Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Set> matchingClasses, + Matcher methodNameMatcher, EnhancerAffect affect) { + this.listener = listener; this.isTracing = isTracing; this.skipJDKTrace = skipJDKTrace; this.matchingClasses = matchingClasses; @@ -70,100 +96,212 @@ public class Enhancer implements ClassFileTransformer { this.affect = affect; } - private void spy(final ClassLoader targetClassLoader) throws Exception { - if (targetClassLoader == null) { - // 增强JDK自带的类,targetClassLoader为null - return; + public static class SpyInterceptor { + + @AtEnter(inline = true) + public static void atEnter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args) { + SpyAPI.atEnter(clazz, methodInfo, target, args); } - // 因为 Spy 是被bootstrap classloader加载的,所以一定可以被找到,如果找不到的话,说明应用方的classloader实现有问题 - Class spyClass = targetClassLoader.loadClass(Constants.SPY_CLASSNAME); - final ClassLoader arthasClassLoader = Enhancer.class.getClassLoader(); + @AtExit(inline = true) + public static void atExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, @Binding.Return Object returnObj) { + SpyAPI.atExit(clazz, methodInfo, target, args, returnObj); + } - // 初始化间谍, AgentLauncher会把各种hook设置到ArthasClassLoader当中 - // 这里我们需要把这些hook取出来设置到目标classloader当中 - Method initMethod = spyClass.getMethod("init", ClassLoader.class, Method.class, - Method.class, Method.class, Method.class, Method.class, Method.class); - initMethod.invoke(null, arthasClassLoader, - FieldUtils.getField(spyClass, "ON_BEFORE_METHOD").get(null), - FieldUtils.getField(spyClass, "ON_RETURN_METHOD").get(null), - FieldUtils.getField(spyClass, "ON_THROWS_METHOD").get(null), - FieldUtils.getField(spyClass, "BEFORE_INVOKING_METHOD").get(null), - FieldUtils.getField(spyClass, "AFTER_INVOKING_METHOD").get(null), - FieldUtils.getField(spyClass, "THROW_INVOKING_METHOD").get(null)); - } + @AtExceptionExit(inline = true) + public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, + @Binding.Throwable Throwable throwable) { + SpyAPI.atExceptionExit(clazz, methodInfo, target, args, throwable); + } + } + + public static class SpyTraceExcludeJDKInterceptor { + @AtInvoke(name = "", inline = true, whenComplete = false, excludes = "java.**") + public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atBeforeInvoke(clazz, invokeInfo, target); + } + + @AtInvoke(name = "", inline = true, whenComplete = true, excludes = "java.**") + public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atAfterInvoke(clazz, invokeInfo, target); + } + + @AtInvokeException(name = "", inline = true, excludes = "java.**") + public static void onInvokeException(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) { + SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable); + } + } + + public static class SpyTraceInterceptor { + @AtInvoke(name = "", inline = true, whenComplete = false, excludes = {"java.arthas.SpyAPI", "java.lang.Byte" + , "java.lang.Boolean" + , "java.lang.Short" + , "java.lang.Character" + , "java.lang.Integer" + , "java.lang.Float" + , "java.lang.Long" + , "java.lang.Double"}) + public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atBeforeInvoke(clazz, invokeInfo, target); + } + + @AtInvoke(name = "", inline = true, whenComplete = true, excludes = {"java.arthas.SpyAPI", "java.lang.Byte" + , "java.lang.Boolean" + , "java.lang.Short" + , "java.lang.Character" + , "java.lang.Integer" + , "java.lang.Float" + , "java.lang.Long" + , "java.lang.Double"}) + public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo) { + SpyAPI.atAfterInvoke(clazz, invokeInfo, target); + } + + @AtInvokeException(name = "", inline = true, excludes = {"java.arthas.SpyAPI", "java.lang.Byte" + , "java.lang.Boolean" + , "java.lang.Short" + , "java.lang.Character" + , "java.lang.Integer" + , "java.lang.Float" + , "java.lang.Long" + , "java.lang.Double"}) + public static void onInvokeException(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) { + SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable); + } + } @Override public byte[] transform(final ClassLoader inClassLoader, String className, Class classBeingRedefined, - ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { + // 检查classloader能否加载到 SpyAPI,如果不能,则放弃增强 + try { + if (inClassLoader != null) { + inClassLoader.loadClass(SpyAPI.class.getName()); + } + } catch (Throwable e) { + logger.error("the classloader can not load SpyAPI, ignore it. classloader: {}, className: {}", + inClassLoader.getClass().getName(), className); + return null; + } + // 这里要再次过滤一次,为啥?因为在transform的过程中,有可能还会再诞生新的类 // 所以需要将之前需要转换的类集合传递下来,再次进行判断 if (!matchingClasses.contains(classBeingRedefined)) { return null; } - final ClassReader cr; + ClassNode classNode = AsmUtils.toClassNode(classfileBuffer); - // 首先先检查是否在缓存中存在Class字节码 - // 因为要支持多人协作,存在多人同时增强的情况 - final byte[] byteOfClassInCache = classBytesCache.get(classBeingRedefined); - if (null != byteOfClassInCache) { - cr = new ClassReader(byteOfClassInCache); + // 生成增强字节码 + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + final List interceptorProcessors = new ArrayList(); + + List traceProcessors = defaultInterceptorClassParser.parse(SpyInterceptor.class); + interceptorProcessors.addAll(traceProcessors); + + if (this.isTracing) { + Class spyTraceInterceptorClass = SpyTraceExcludeJDKInterceptor.class; + if (this.skipJDKTrace == false) { + spyTraceInterceptorClass = SpyTraceInterceptor.class; + } + List traceInvokeProcessors = defaultInterceptorClassParser + .parse(spyTraceInterceptorClass); + interceptorProcessors.addAll(traceInvokeProcessors); } - // 如果没有命中缓存,则从原始字节码开始增强 - else { - cr = new ClassReader(classfileBuffer); + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (!isIgnore(methodNode, methodNameMatcher)) { + matchedMethods.add(methodNode); + } } - // 字节码增强 - final ClassWriter cw = new ClassWriter(cr, COMPUTE_FRAMES | COMPUTE_MAXS) { + // 用于检查是否已插入了 spy函数,如果已有则不重复处理 + GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); - /* - * 注意,为了自动计算帧的大小,有时必须计算两个类共同的父类。 - * 缺省情况下,ClassWriter将会在getCommonSuperClass方法中计算这些,通过在加载这两个类进入虚拟机时,使用反射API来计算。 - * 但是,如果你将要生成的几个类相互之间引用,这将会带来问题,因为引用的类可能还不存在。 - * 在这种情况下,你可以重写getCommonSuperClass方法来解决这个问题。 - * - * 通过重写 getCommonSuperClass() 方法,更正获取ClassLoader的方式,改成使用指定ClassLoader的方式进行。 - * 规避了原有代码采用Object.class.getClassLoader()的方式 - */ - @Override - protected String getCommonSuperClass(String type1, String type2) { - Class c, d; - final ClassLoader classLoader = inClassLoader; - try { - c = Class.forName(type1.replace('/', '.'), false, classLoader); - d = Class.forName(type2.replace('/', '.'), false, classLoader); - } catch (Exception e) { - throw new RuntimeException(e); + LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter", + LocationType.ENTER); + LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit", + LocationType.EXIT); + LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), + "atExceptionExit", LocationType.EXCEPTION_EXIT); + + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atBeforeInvoke", LocationType.INVOKE); + LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_COMPLETED); + LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); + groupLocationFilter.addFilter(invokeBeforeFilter); + groupLocationFilter.addFilter(invokeAfterFilter); + groupLocationFilter.addFilter(invokeExceptionFilter); + + for (MethodNode methodNode : matchedMethods) { + // 先查找是否有 atBeforeInvoke 函数,如果有,则说明已经有trace了,则直接不再尝试增强,直接插入 listener + if(AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) { + for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof MethodInsnNode) { + final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if(this.skipJDKTrace) { + if(methodInsnNode.owner.startsWith("java/")) { + continue; + } + } + // 原始类型的box类型相关的都跳过 + if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))) { + continue; + } + AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className, + methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener); + } } - if (c.isAssignableFrom(d)) { - return type1; - } - if (d.isAssignableFrom(c)) { - return type2; - } - if (c.isInterface() || d.isInterface()) { - return "java/lang/Object"; - } else { - do { - c = c.getSuperclass(); - } while (!c.isAssignableFrom(d)); - return c.getName().replace('.', '/'); + }else { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter); + for (InterceptorProcessor interceptor : interceptorProcessors) { + try { + List locations = interceptor.process(methodProcessor); + for (Location location : locations) { + if (location instanceof MethodInsnNodeWare) { + MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location; + MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode(); + + AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className, + methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener); + } + } + + } catch (Throwable e) { + logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e); + } } } - }; + // enter/exist 总是要插入 listener + AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc, + listener); + affect.mCnt(1); + } - // 生成增强字节码 - cr.accept(new AdviceWeaver(adviceId, isTracing, skipJDKTrace, cr.getClassName(), methodNameMatcher, affect, - cw), EXPAND_FRAMES); - final byte[] enhanceClassByteArray = cw.toByteArray(); + byte[] enhanceClassByteArray = AsmUtils.toBytes(classNode); - // 生成成功,推入缓存 - classBytesCache.put(classBeingRedefined, enhanceClassByteArray); + // 增强成功,记录类 + classBytesCache.put(classBeingRedefined, new Object()); // dump the class dumpClassIfNecessary(className, enhanceClassByteArray, affect); @@ -171,14 +309,6 @@ public class Enhancer implements ClassFileTransformer { // 成功计数 affect.cCnt(1); - // 派遣间谍 - try { - spy(inClassLoader); - } catch (Throwable t) { - logger.warn("print spy failed. classname={};loader={};", className, inClassLoader, t); - throw t; - } - return enhanceClassByteArray; } catch (Throwable t) { logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t); @@ -187,6 +317,21 @@ public class Enhancer implements ClassFileTransformer { return null; } + /** + * 是否抽象属性 + */ + private boolean isAbstract(int access) { + return (Opcodes.ACC_ABSTRACT & access) == Opcodes.ACC_ABSTRACT; + } + + /** + * 是否需要忽略 + */ + private boolean isIgnore(MethodNode methodNode, Matcher methodNameMatcher) { + return null == methodNode || isAbstract(methodNode.access) || !methodNameMatcher.matching(methodNode.name) + || ArthasCheckUtils.isEquals(methodNode.name, ""); + } + /** * dump class to file */ @@ -198,8 +343,7 @@ public class Enhancer implements ClassFileTransformer { final File classPath = new File(dumpClassFile.getParent()); // 创建类所在的包路径 - if (!classPath.mkdirs() - && !classPath.exists()) { + if (!classPath.mkdirs() && !classPath.exists()) { logger.warn("create dump classpath:{} failed.", classPath); return; } @@ -214,7 +358,6 @@ public class Enhancer implements ClassFileTransformer { } - /** * 是否需要过滤的类 * @@ -224,10 +367,7 @@ public class Enhancer implements ClassFileTransformer { final Iterator> it = classes.iterator(); while (it.hasNext()) { final Class clazz = it.next(); - if (null == clazz - || isSelf(clazz) - || isUnsafeClass(clazz) - || isUnsupportedClass(clazz)) { + if (null == clazz || isSelf(clazz) || isUnsafeClass(clazz) || isUnsupportedClass(clazz)) { it.remove(); } } @@ -237,25 +377,21 @@ public class Enhancer implements ClassFileTransformer { * 是否过滤Arthas加载的类 */ private static boolean isSelf(Class clazz) { - return null != clazz - && isEquals(clazz.getClassLoader(), Enhancer.class.getClassLoader()); + return null != clazz && isEquals(clazz.getClassLoader(), Enhancer.class.getClassLoader()); } /** * 是否过滤unsafe类 */ private static boolean isUnsafeClass(Class clazz) { - return !GlobalOptions.isUnsafe - && clazz.getClassLoader() == null; + return !GlobalOptions.isUnsafe && clazz.getClassLoader() == null; } /** * 是否过滤目前暂不支持的类 */ private static boolean isUnsupportedClass(Class clazz) { - return clazz.isArray() - || (clazz.isInterface() && !GlobalOptions.isSupportDefaultMethod) - || clazz.isEnum() + return clazz.isArray() || (clazz.isInterface() && !GlobalOptions.isSupportDefaultMethod) || clazz.isEnum() || clazz.equals(Class.class) || clazz.equals(Integer.class) || clazz.equals(Method.class); } @@ -271,12 +407,8 @@ public class Enhancer implements ClassFileTransformer { * @return 增强影响范围 * @throws UnmodifiableClassException 增强失败 */ - public static synchronized EnhancerAffect enhance( - final Instrumentation inst, - final int adviceId, - final boolean isTracing, - final boolean skipJDKTrace, - final Matcher classNameMatcher, + public static synchronized EnhancerAffect enhance(final Instrumentation inst, final AdviceListener listener, + final boolean isTracing, final boolean skipJDKTrace, final Matcher classNameMatcher, final Matcher methodNameMatcher) throws UnmodifiableClassException { final EnhancerAffect affect = new EnhancerAffect(); @@ -290,9 +422,13 @@ public class Enhancer implements ClassFileTransformer { filter(enhanceClassSet); // 构建增强器 - final Enhancer enhancer = new Enhancer(adviceId, isTracing, skipJDKTrace, enhanceClassSet, methodNameMatcher, affect); + final Enhancer enhancer = new Enhancer(listener, isTracing, skipJDKTrace, enhanceClassSet, methodNameMatcher, + affect); + affect.setTransformer(enhancer); + try { - inst.addTransformer(enhancer, true); + ArthasBootstrap.getInstance().getTransformerManager().addTransformer(enhancer, isTracing); + //inst.addTransformer(enhancer, true); // 批量增强 if (GlobalOptions.isBatchReTransform) { @@ -322,13 +458,12 @@ public class Enhancer implements ClassFileTransformer { } } } finally { - inst.removeTransformer(enhancer); + //inst.removeTransformer(enhancer); } return affect; } - /** * 重置指定的Class * @@ -337,9 +472,8 @@ public class Enhancer implements ClassFileTransformer { * @return 增强影响范围 * @throws UnmodifiableClassException */ - public static synchronized EnhancerAffect reset( - final Instrumentation inst, - final Matcher classNameMatcher) throws UnmodifiableClassException { + public static synchronized EnhancerAffect reset(final Instrumentation inst, final Matcher classNameMatcher) + throws UnmodifiableClassException { final EnhancerAffect affect = new EnhancerAffect(); final Set> enhanceClassSet = new HashSet>(); @@ -352,12 +486,8 @@ public class Enhancer implements ClassFileTransformer { final ClassFileTransformer resetClassFileTransformer = new ClassFileTransformer() { @Override - public byte[] transform( - ClassLoader loader, - String className, - Class classBeingRedefined, - ProtectionDomain protectionDomain, - byte[] classfileBuffer) throws IllegalClassFormatException { + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { return null; } }; diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java b/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java deleted file mode 100644 index 2723cb3a7..000000000 --- a/core/src/main/java/com/taobao/arthas/core/advisor/ReflectAdviceListenerAdapter.java +++ /dev/null @@ -1,233 +0,0 @@ -package com.taobao.arthas.core.advisor; - -import com.taobao.arthas.core.command.express.ExpressException; -import com.taobao.arthas.core.command.express.ExpressFactory; -import com.taobao.arthas.core.shell.command.CommandProcess; -import com.taobao.arthas.core.util.ArthasCheckUtils; -import com.taobao.arthas.core.util.Constants; -import com.taobao.arthas.core.util.StringUtils; -import org.objectweb.asm.Type; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -/** - * 反射通知适配器
- * 通过反射拿到对应的Class/Method类,而不是原始的ClassName/MethodName - * 当然性能开销要比普通监听器高许多 - */ -public abstract class ReflectAdviceListenerAdapter implements AdviceListener { - - @Override - public void create() { - // default no-op - } - - @Override - public void destroy() { - // default no-op - } - - private ClassLoader toClassLoader(ClassLoader loader) { - return null != loader - ? loader - : AdviceListener.class.getClassLoader(); - } - - private Class toClass(ClassLoader loader, String className) throws ClassNotFoundException { - return Class.forName(StringUtils.normalizeClassName(className), true, toClassLoader(loader)); - } - - private ArthasMethod toMethod(ClassLoader loader, Class clazz, String methodName, String methodDesc) - throws ClassNotFoundException, NoSuchMethodException { - final org.objectweb.asm.Type asmType = org.objectweb.asm.Type.getMethodType(methodDesc); - - // to arg types - final Class[] argsClasses = new Class[asmType.getArgumentTypes().length]; - for (int index = 0; index < argsClasses.length; index++) { - // asm class descriptor to jvm class - final Class argumentClass; - final Type argumentAsmType = asmType.getArgumentTypes()[index]; - switch (argumentAsmType.getSort()) { - case Type.BOOLEAN: { - argumentClass = boolean.class; - break; - } - case Type.CHAR: { - argumentClass = char.class; - break; - } - case Type.BYTE: { - argumentClass = byte.class; - break; - } - case Type.SHORT: { - argumentClass = short.class; - break; - } - case Type.INT: { - argumentClass = int.class; - break; - } - case Type.FLOAT: { - argumentClass = float.class; - break; - } - case Type.LONG: { - argumentClass = long.class; - break; - } - case Type.DOUBLE: { - argumentClass = double.class; - break; - } - case Type.ARRAY: { - argumentClass = toClass(loader, argumentAsmType.getInternalName()); - break; - } - case Type.VOID: { - argumentClass = void.class; - break; - } - case Type.OBJECT: - case Type.METHOD: - default: { - argumentClass = toClass(loader, argumentAsmType.getClassName()); - break; - } - } - - argsClasses[index] = argumentClass; - } - - // to method or constructor - if (ArthasCheckUtils.isEquals(methodName, "")) { - return ArthasMethod.newInit(toConstructor(clazz, argsClasses)); - } else { - return ArthasMethod.newMethod(toMethod(clazz, methodName, argsClasses)); - } - } - - private Method toMethod(Class clazz, String methodName, Class[] argClasses) throws NoSuchMethodException { - return clazz.getDeclaredMethod(methodName, argClasses); - } - - private Constructor toConstructor(Class clazz, Class[] argClasses) throws NoSuchMethodException { - return clazz.getDeclaredConstructor(argClasses); - } - - - @Override - final public void before( - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args) throws Throwable { - final Class clazz = toClass(loader, className); - before(loader, clazz, toMethod(loader, clazz, methodName, methodDesc), target, args); - } - - @Override - final public void afterReturning( - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args, Object returnObject) throws Throwable { - final Class clazz = toClass(loader, className); - afterReturning(loader, clazz, toMethod(loader, clazz, methodName, methodDesc), target, args, returnObject); - } - - @Override - final public void afterThrowing( - ClassLoader loader, String className, String methodName, String methodDesc, - Object target, Object[] args, Throwable throwable) throws Throwable { - final Class clazz = toClass(loader, className); - afterThrowing(loader, clazz, toMethod(loader, clazz, methodName, methodDesc), target, args, throwable); - } - - - /** - * 前置通知 - * - * @param loader 类加载器 - * @param clazz 类 - * @param method 方法 - * @param target 目标类实例 - * 若目标为静态方法,则为null - * @param args 参数列表 - * @throws Throwable 通知过程出错 - */ - public abstract void before( - ClassLoader loader, Class clazz, ArthasMethod method, - Object target, Object[] args) throws Throwable; - - /** - * 返回通知 - * - * @param loader 类加载器 - * @param clazz 类 - * @param method 方法 - * @param target 目标类实例 - * 若目标为静态方法,则为null - * @param args 参数列表 - * @param returnObject 返回结果 - * 若为无返回值方法(void),则为null - * @throws Throwable 通知过程出错 - */ - public abstract void afterReturning( - ClassLoader loader, Class clazz, ArthasMethod method, - Object target, Object[] args, - Object returnObject) throws Throwable; - - /** - * 异常通知 - * - * @param loader 类加载器 - * @param clazz 类 - * @param method 方法 - * @param target 目标类实例 - * 若目标为静态方法,则为null - * @param args 参数列表 - * @param throwable 目标异常 - * @throws Throwable 通知过程出错 - */ - public abstract void afterThrowing( - ClassLoader loader, Class clazz, ArthasMethod method, - Object target, Object[] args, - Throwable throwable) throws Throwable; - - - /** - * 判断条件是否满足,满足的情况下需要输出结果 - * @param conditionExpress 条件表达式 - * @param advice 当前的advice对象 - * @param cost 本次执行的耗时 - * @return true 如果条件表达式满足 - */ - protected boolean isConditionMet(String conditionExpress, Advice advice, double cost) throws ExpressException { - return StringUtils.isEmpty(conditionExpress) || - ExpressFactory.threadLocalExpress(advice).bind(Constants.COST_VARIABLE, cost).is(conditionExpress); - } - - protected Object getExpressionResult(String express, Advice advice, double cost) throws ExpressException { - return ExpressFactory.threadLocalExpress(advice) - .bind(Constants.COST_VARIABLE, cost).get(express); - } - - /** - * 是否超过了上限,超过之后,停止输出 - * @param limit 命令执行上限 - * @param currentTimes 当前执行次数 - * @return true 如果超过或者达到了上限 - */ - protected boolean isLimitExceeded(int limit, int currentTimes) { - return currentTimes >= limit; - } - - /** - * 超过次数上限,则不再输出,命令终止 - * @param process the process to be aborted - * @param limit the limit to be printed - */ - protected void abortProcess(CommandProcess process, int limit) { - process.write("Command execution times exceed limit: " + limit + ", so command will exit. You can set it with -n option.\n"); - process.end(); - } - -} diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java new file mode 100644 index 000000000..41e355e04 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java @@ -0,0 +1,208 @@ +package com.taobao.arthas.core.advisor; + +import java.arthas.SpyAPI.AbstractSpy; +import java.util.List; +import java.util.regex.Pattern; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.core.shell.system.ExecStatus; +import com.taobao.arthas.core.shell.system.ProcessAware; + +/** + *
+ * 怎么从 className|methodDesc 到 id 对应起来??
+ * 当id少时,可以id自己来判断是否符合?
+ * 
+ * 如果是每个 className|methodDesc 为 key ,是否
+ * 
+ * + * @author hengyunabc 2020-04-24 + * + */ +public class SpyImpl extends AbstractSpy { + private static final Logger logger = LoggerFactory.getLogger(SpyImpl.class); + + @Override + public void atEnter(Class clazz, String methodInfo, Object target, Object[] args) { + ClassLoader classLoader = clazz.getClassLoader(); + + String[] info = splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + // TODO listener 只用查一次,放到 thread local里保存起来就可以了! + List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.before(clazz, methodName, methodDesc, target, args); + } catch (Throwable e) { + if (logger.isDebugEnabled()) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); + } + } + } + } + + } + + @Override + public void atExit(Class clazz, String methodInfo, Object target, Object[] args, Object returnObject) { + ClassLoader classLoader = clazz.getClassLoader(); + + String[] info = splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + + List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.afterReturning(clazz, methodName, methodDesc, target, args, returnObject); + } catch (Throwable e) { + if (logger.isDebugEnabled()) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); + } + } + } + } + } + + @Override + public void atExceptionExit(Class clazz, String methodInfo, Object target, Object[] args, Throwable throwable) { + ClassLoader classLoader = clazz.getClassLoader(); + + String[] info = splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + + List listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(), + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.afterThrowing(clazz, methodName, methodDesc, target, args, throwable); + } catch (Throwable e) { + if (logger.isDebugEnabled()) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); + } + } + } + } + } + + @Override + public void atBeforeInvoke(Class clazz, String invokeInfo, Object target) { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = splitInvokeInfo(invokeInfo); + String owner = info[0]; + String methodName = info[1]; + String methodDesc = info[2]; + + List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), + owner, methodName, methodDesc); + + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + final InvokeTraceable listener = (InvokeTraceable) adviceListener; + listener.invokeBeforeTracing(owner, methodName, methodDesc, Integer.parseInt(info[3])); + } catch (Throwable e) { + if (logger.isDebugEnabled()) { + logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); + } + } + } + } + } + + @Override + public void atAfterInvoke(Class clazz, String invokeInfo, Object target) { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = splitInvokeInfo(invokeInfo); + String owner = info[0]; + String methodName = info[1]; + String methodDesc = info[2]; + List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), + owner, methodName, methodDesc); + + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + final InvokeTraceable listener = (InvokeTraceable) adviceListener; + listener.invokeAfterTracing(owner, methodName, methodDesc, Integer.parseInt(info[3])); + } catch (Throwable e) { + if (logger.isDebugEnabled()) { + logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); + } + } + } + } + + } + + @Override + public void atInvokeException(Class clazz, String invokeInfo, Object target, Throwable throwable) { + ClassLoader classLoader = clazz.getClassLoader(); + String[] info = splitInvokeInfo(invokeInfo); + String owner = info[0]; + String methodName = info[1]; + String methodDesc = info[2]; + + List listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(), + owner, methodName, methodDesc); + + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + final InvokeTraceable listener = (InvokeTraceable) adviceListener; + listener.invokeThrowTracing(owner, methodName, methodDesc, Integer.parseInt(info[3])); + } catch (Throwable e) { + if (logger.isDebugEnabled()) { + logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e); + } + } + } + } + } + + private String[] splitMethodInfo(String methodInfo) { + return methodInfo.split(Pattern.quote("|")); + } + + private String[] splitInvokeInfo(String invokeInfo) { + return invokeInfo.split(Pattern.quote("|")); + } + + private boolean skipAdviceListener(AdviceListener adviceListener) { + if (adviceListener instanceof ProcessAware) { + ProcessAware processAware = (ProcessAware) adviceListener; + ExecStatus status = processAware.getProcess().status(); + if (status.equals(ExecStatus.TERMINATED) || status.equals(ExecStatus.STOPPED)) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java new file mode 100644 index 000000000..492cfb085 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java @@ -0,0 +1,72 @@ +package com.taobao.arthas.core.advisor; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * + * @author hengyunabc 2020-05-18 + * + */ +public class TransformerManager { + + private Instrumentation instrumentation; + private List watchTransformers = new CopyOnWriteArrayList(); + private List traceTransformers = new CopyOnWriteArrayList(); + + private ClassFileTransformer classFileTransformer; + + public TransformerManager(Instrumentation instrumentation) { + this.instrumentation = instrumentation; + + classFileTransformer = new ClassFileTransformer() { + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, + ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + + for (ClassFileTransformer classFileTransformer : watchTransformers) { + byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined, + protectionDomain, classfileBuffer); + if (transformResult != null) { + classfileBuffer = transformResult; + } + } + + for (ClassFileTransformer classFileTransformer : traceTransformers) { + byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined, + protectionDomain, classfileBuffer); + if (transformResult != null) { + classfileBuffer = transformResult; + } + } + + return classfileBuffer; + } + + }; + instrumentation.addTransformer(classFileTransformer, true); + } + + public void addTransformer(ClassFileTransformer transformer, boolean isTracing) { + if (isTracing) { + traceTransformers.add(transformer); + } else { + watchTransformers.add(transformer); + } + } + + public void removeTransformer(ClassFileTransformer transformer) { + watchTransformers.remove(transformer); + traceTransformers.remove(transformer); + } + + public void destroy() { + instrumentation.removeTransformer(classFileTransformer); + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java index fbb2bb6f9..9f292e0f8 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java @@ -56,6 +56,7 @@ public class JadCommand extends AnnotatedCommand { private String methodName; private String code = null; private boolean isRegEx = false; + private boolean hideUnicode = false; /** * jad output source code only @@ -87,6 +88,12 @@ public class JadCommand extends AnnotatedCommand { isRegEx = regEx; } + @Option(longName = "hideUnicode", flag = true) + @Description("hide unicode, default value false") + public void setHideUnicode(boolean hideUnicode) { + this.hideUnicode = hideUnicode; + } + @Option(longName = "source-only", flag = true) @Description("Output source code only") public void setSourceOnly(boolean sourceOnly) { @@ -153,7 +160,7 @@ public class JadCommand extends AnnotatedCommand { Map, File> classFiles = transformer.getDumpResult(); File classFile = classFiles.get(c); - String source = Decompiler.decompile(classFile.getAbsolutePath(), methodName); + String source = Decompiler.decompile(classFile.getAbsolutePath(), methodName, hideUnicode); if (source != null) { source = pattern.matcher(source).replaceAll(""); } else { diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java index ce2e784f7..069d4e0f0 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/AbstractTraceAdviceListener.java @@ -4,7 +4,7 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.advisor.Advice; import com.taobao.arthas.core.advisor.ArthasMethod; -import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.ThreadLocalWatch; @@ -12,7 +12,7 @@ import com.taobao.arthas.core.util.ThreadLocalWatch; /** * @author ralf0131 2017-01-06 16:02. */ -public class AbstractTraceAdviceListener extends ReflectAdviceListenerAdapter { +public class AbstractTraceAdviceListener extends AdviceListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(AbstractTraceAdviceListener.class); protected final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch(); protected TraceCommand command; diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java index 8c421df3a..e7b1e8ec0 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java @@ -1,5 +1,6 @@ package com.taobao.arthas.core.command.monitor200; +import java.arthas.SpyAPI; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.util.Collections; @@ -7,6 +8,10 @@ import java.util.List; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.bytekit.asm.binding.Binding; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtEnter; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExceptionExit; +import com.taobao.arthas.bytekit.asm.interceptor.annotation.AtExit; import com.taobao.arthas.core.advisor.AdviceListener; import com.taobao.arthas.core.advisor.Enhancer; import com.taobao.arthas.core.advisor.InvokeTraceable; @@ -109,7 +114,7 @@ public abstract class EnhancerCommand extends AnnotatedCommand { skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace(); } - EnhancerAffect effect = Enhancer.enhance(inst, lock, listener instanceof InvokeTraceable, + EnhancerAffect effect = Enhancer.enhance(inst, listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher()); if (effect.cCnt() == 0 || effect.mCnt() == 0) { @@ -128,7 +133,7 @@ public abstract class EnhancerCommand extends AnnotatedCommand { // 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃 if (session.getLock() == lock) { // 注册通知监听器 - process.register(lock, listener); + process.register(lock, listener, effect.getTransformer()); if (process.isForeground()) { process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n"); } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/GroovyAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/GroovyAdviceListener.java index 8b97ca997..1f42f536d 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/GroovyAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/GroovyAdviceListener.java @@ -1,6 +1,6 @@ package com.taobao.arthas.core.command.monitor200; -import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; import com.taobao.arthas.core.command.ScriptSupportCommand; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.advisor.Advice; @@ -11,7 +11,7 @@ import com.taobao.arthas.core.advisor.ArthasMethod; * @author beiwei30 on 01/12/2016. */ @Deprecated -public class GroovyAdviceListener extends ReflectAdviceListenerAdapter { +public class GroovyAdviceListener extends AdviceListenerAdapter { private ScriptSupportCommand.ScriptListener scriptListener; private ScriptSupportCommand.Output output; diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java index eea9f8640..d92498fa8 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/MonitorAdviceListener.java @@ -1,6 +1,6 @@ package com.taobao.arthas.core.command.monitor200; -import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.advisor.ArthasMethod; import com.taobao.arthas.core.util.ThreadLocalWatch; @@ -66,7 +66,7 @@ import static com.taobao.text.ui.Element.label; * * @author beiwei30 on 28/11/2016. */ -class MonitorAdviceListener extends ReflectAdviceListenerAdapter { +class MonitorAdviceListener extends AdviceListenerAdapter { // 输出定时任务 private Timer timer; // 监控数据 diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java index a5f73aa7c..783d8b767 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/StackAdviceListener.java @@ -1,6 +1,6 @@ package com.taobao.arthas.core.command.monitor200; -import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; import com.taobao.arthas.core.shell.command.CommandProcess; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; @@ -14,7 +14,7 @@ import com.taobao.arthas.core.util.ThreadUtil; /** * @author beiwei30 on 29/11/2016. */ -public class StackAdviceListener extends ReflectAdviceListenerAdapter { +public class StackAdviceListener extends AdviceListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(StackAdviceListener.class); private final ThreadLocal stackThreadLocal = new ThreadLocal(); diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java index d0b41ccab..eb8a5f44b 100755 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/ThreadCommand.java @@ -52,7 +52,10 @@ public class ThreadCommand extends AnnotatedCommand { private int sampleInterval = 100; private String state; - { + private boolean lockedMonitors = false; + private boolean lockedSynchronizers = false; + + static { states = new HashSet(State.values().length); for (State state : State.values()) { states.add(state.name()); @@ -89,6 +92,18 @@ public class ThreadCommand extends AnnotatedCommand { this.state = state; } + @Option(longName = "lockedMonitors", flag = true) + @Description("Find the thread info with lockedMonitors flag, default value is false.") + public void setLockedMonitors(boolean lockedMonitors) { + this.lockedMonitors = lockedMonitors; + } + + @Option(longName = "lockedSynchronizers", flag = true) + @Description("Find the thread info with lockedSynchronizers flag, default value is false.") + public void setLockedSynchronizers(boolean lockedSynchronizers) { + this.lockedSynchronizers = lockedSynchronizers; + } + @Override public void process(CommandProcess process) { Affect affect = new RowAffect(); @@ -176,7 +191,7 @@ public class ThreadCommand extends AnnotatedCommand { int status = 0; Map topNThreads = ThreadUtil.getTopNThreads(sampleInterval, topNBusy); Long[] tids = topNThreads.keySet().toArray(new Long[0]); - ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ArrayUtils.toPrimitive(tids), true, true); + ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(ArrayUtils.toPrimitive(tids), lockedMonitors, lockedSynchronizers); if (threadInfos == null) { process.write("thread do not exist! id: " + id + "\n"); status = 1; @@ -192,7 +207,7 @@ public class ThreadCommand extends AnnotatedCommand { private int processThread(CommandProcess process) { int status = 0; String content; - ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(new long[]{id}, true, true); + ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(new long[]{id}, lockedMonitors, lockedSynchronizers); if (threadInfos == null || threadInfos[0] == null) { content = "thread do not exist! id: " + id + "\n"; status = 1; diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java index 09bc7c3ed..9566a3ddf 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/TimeTunnelAdviceListener.java @@ -1,28 +1,36 @@ package com.taobao.arthas.core.command.monitor200; -import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; -import com.taobao.arthas.core.command.express.ExpressException; -import com.taobao.arthas.core.shell.command.CommandProcess; -import com.alibaba.arthas.deps.org.slf4j.Logger; -import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; -import com.taobao.arthas.core.advisor.Advice; -import com.taobao.arthas.core.advisor.ArthasMethod; -import com.taobao.arthas.core.util.LogUtil; -import com.taobao.arthas.core.util.ThreadLocalWatch; -import com.taobao.text.ui.TableElement; -import com.taobao.text.util.RenderUtil; - -import java.util.Date; - import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.createTable; import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.fillTableHeader; import static com.taobao.arthas.core.command.monitor200.TimeTunnelTable.fillTableRow; +import java.util.Date; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.core.advisor.Advice; +import com.taobao.arthas.core.advisor.ArthasMethod; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; +import com.taobao.arthas.core.command.express.ExpressException; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.arthas.core.util.ThreadLocalWatch; +import com.taobao.text.ui.TableElement; +import com.taobao.text.util.RenderUtil; + /** * @author beiwei30 on 30/11/2016. + * @author hengyunabc 2020-05-20 */ -public class TimeTunnelAdviceListener extends ReflectAdviceListenerAdapter { +public class TimeTunnelAdviceListener extends AdviceListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(TimeTunnelAdviceListener.class); + private final ThreadLocal argsRef = new ThreadLocal() { + @Override + protected ObjectStack initialValue() { + return new ObjectStack(512); + } + }; + private TimeTunnelCommand command; private CommandProcess process; @@ -40,18 +48,23 @@ public class TimeTunnelAdviceListener extends ReflectAdviceListenerAdapter { @Override public void before(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args) throws Throwable { + argsRef.get().push(args); threadLocalWatch.start(); } @Override public void afterReturning(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Object returnObject) throws Throwable { + //取出入参时的 args,因为在函数执行过程中 args可能被修改 + args = (Object[]) argsRef.get().pop(); afterFinishing(Advice.newForAfterRetuning(loader, clazz, method, target, args, returnObject)); } @Override public void afterThrowing(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Throwable throwable) { + //取出入参时的 args,因为在函数执行过程中 args可能被修改 + args = (Object[]) argsRef.get().pop(); afterFinishing(Advice.newForAfterThrowing(loader, clazz, method, target, args, throwable)); } @@ -93,4 +106,53 @@ public class TimeTunnelAdviceListener extends ReflectAdviceListenerAdapter { abortProcess(process, command.getNumberOfLimit()); } } + + /** + * + *
+     * 一个特殊的stack,为了追求效率,避免扩容。
+     * 因为这个stack的push/pop 并不一定成对调用,比如可能push执行了,但是后面的流程被中断了,pop没有被执行。
+     * 如果不固定大小,一直增长的话,极端情况下可能应用有内存问题。
+     * 如果到达容量,pos会重置,循环存储数据。所以使用这个Stack如果在极端情况下统计的数据会不准确,只用于monitor/watch等命令的计时。
+     * 
+     * 
+ * + * @author hengyunabc 2019-11-20 + * + */ + static class ObjectStack { + private Object[] array; + private int pos = 0; + private int cap; + + public ObjectStack(int maxSize) { + array = new Object[maxSize]; + cap = array.length; + } + + public int size() { + return pos; + } + + public void push(Object value) { + if (pos < cap) { + array[pos++] = value; + } else { + // if array is full, reset pos + pos = 0; + array[pos++] = value; + } + } + + public Object pop() { + if (pos > 0) { + pos--; + return array[pos]; + } else { + pos = cap; + pos--; + return array[pos]; + } + } + } } diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java index a516fe59d..fe1c48ec3 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/WatchAdviceListener.java @@ -4,7 +4,7 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.advisor.Advice; import com.taobao.arthas.core.advisor.ArthasMethod; -import com.taobao.arthas.core.advisor.ReflectAdviceListenerAdapter; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; import com.taobao.arthas.core.shell.command.CommandProcess; import com.taobao.arthas.core.util.DateUtils; import com.taobao.arthas.core.util.LogUtil; @@ -15,7 +15,7 @@ import com.taobao.arthas.core.view.ObjectView; /** * @author beiwei30 on 29/11/2016. */ -class WatchAdviceListener extends ReflectAdviceListenerAdapter { +class WatchAdviceListener extends AdviceListenerAdapter { private static final Logger logger = LoggerFactory.getLogger(WatchAdviceListener.class); private final ThreadLocalWatch threadLocalWatch = new ThreadLocalWatch(); diff --git a/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java b/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java index c86153d8c..314cff3ee 100644 --- a/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java +++ b/core/src/main/java/com/taobao/arthas/core/env/PropertySource.java @@ -18,8 +18,6 @@ package com.taobao.arthas.core.env; import java.util.Arrays; -import com.sun.tools.javac.util.Log; - /** * Abstract base class representing a source of name/value property pairs. The * underlying {@linkplain #getSource() source object} may be of any type diff --git a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java index 9dd1c2efd..6fc3fa010 100644 --- a/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java +++ b/core/src/main/java/com/taobao/arthas/core/server/ArthasBootstrap.java @@ -1,6 +1,6 @@ package com.taobao.arthas.core.server; -import java.arthas.Spy; +import java.arthas.SpyAPI; import java.io.File; import java.io.IOException; import java.lang.instrument.Instrumentation; @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; +import java.util.Timer; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -25,7 +26,7 @@ import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.alibaba.arthas.tunnel.client.TunnelClient; import com.taobao.arthas.common.AnsiLog; import com.taobao.arthas.common.PidUtils; -import com.taobao.arthas.core.advisor.AdviceWeaver; +import com.taobao.arthas.core.advisor.TransformerManager; import com.taobao.arthas.core.command.BuiltinCommandPack; import com.taobao.arthas.core.config.BinderUtils; import com.taobao.arthas.core.config.Configure; @@ -42,7 +43,6 @@ import com.taobao.arthas.core.shell.impl.ShellServerImpl; import com.taobao.arthas.core.shell.term.impl.HttpTermServer; import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer; import com.taobao.arthas.core.util.ArthasBanner; -import com.taobao.arthas.core.util.Constants; import com.taobao.arthas.core.util.FileUtils; import com.taobao.arthas.core.util.LogUtil; import com.taobao.arthas.core.util.UserStatUtil; @@ -77,6 +77,10 @@ public class ArthasBootstrap { private static LoggerContext loggerContext; + private Timer timer = new Timer("arthas-timer", true); + + private TransformerManager transformerManager; + private ArthasBootstrap(Instrumentation instrumentation, String args) throws Throwable { this.instrumentation = instrumentation; @@ -111,21 +115,14 @@ public class ArthasBootstrap { } }; + transformerManager = new TransformerManager(instrumentation); Runtime.getRuntime().addShutdownHook(shutdown); } - private static void initSpy() throws ClassNotFoundException, NoSuchMethodException { - Class adviceWeaverClass = AdviceWeaver.class; - Method onBefore = adviceWeaverClass.getMethod(AdviceWeaver.ON_BEFORE, int.class, ClassLoader.class, String.class, - String.class, String.class, Object.class, Object[].class); - Method onReturn = adviceWeaverClass.getMethod(AdviceWeaver.ON_RETURN, Object.class); - Method onThrows = adviceWeaverClass.getMethod(AdviceWeaver.ON_THROWS, Throwable.class); - Method beforeInvoke = adviceWeaverClass.getMethod(AdviceWeaver.BEFORE_INVOKE, int.class, String.class, String.class, String.class, int.class); - Method afterInvoke = adviceWeaverClass.getMethod(AdviceWeaver.AFTER_INVOKE, int.class, String.class, String.class, String.class, int.class); - Method throwInvoke = adviceWeaverClass.getMethod(AdviceWeaver.THROW_INVOKE, int.class, String.class, String.class, String.class, int.class); - Spy.init(AdviceWeaver.class.getClassLoader(), onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke); + private static void initSpy() { + // TODO init SpyImpl ? } - + private void initArthasEnvironment(String args) throws IOException { if (arthasEnvironment == null) { arthasEnvironment = new ArthasEnvironment(); @@ -318,6 +315,7 @@ public class ArthasBootstrap { } public void destroy() { + timer.cancel(); if (this.tunnelClient != null) { try { tunnelClient.stop(); @@ -326,6 +324,7 @@ public class ArthasBootstrap { } } executorService.shutdownNow(); + transformerManager.destroy(); UserStatUtil.destroy(); // clear the reference in Spy class. cleanUpSpyReference(); @@ -368,18 +367,17 @@ public class ArthasBootstrap { } /** - * 清除spy中对classloader的引用,避免内存泄露 + * 清除SpyAPI里的引用 */ private void cleanUpSpyReference() { + SpyAPI.setNopSpy(); + // AgentBootstrap.resetArthasClassLoader(); try { - // 从ArthasClassLoader中加载Spy - Class spyClass = this.getClass().getClassLoader().loadClass(Constants.SPY_CLASSNAME); - Method agentDestroyMethod = spyClass.getMethod("destroy"); - agentDestroyMethod.invoke(null); - } catch (ClassNotFoundException e) { - logger().error("Spy load failed from ArthasClassLoader, which should not happen", e); - } catch (Exception e) { - logger().error("Spy destroy failed: ", e); + Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.taobao.arthas.agent3.AgentBootstrap"); + Method method = clazz.getDeclaredMethod("resetArthasClassLoader"); + method.invoke(null); + } catch (Throwable e) { + e.printStackTrace(); } } @@ -387,6 +385,18 @@ public class ArthasBootstrap { return tunnelClient; } + public Timer getTimer() { + return this.timer; + } + + public Instrumentation getInstrumentation() { + return this.instrumentation; + } + + public TransformerManager getTransformerManager() { + return this.transformerManager; + } + private Logger logger() { return LoggerFactory.getLogger(this.getClass()); } diff --git a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java index 6f85ca0b4..b3a12dbef 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/command/CommandProcess.java @@ -7,6 +7,7 @@ import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.term.Tty; import com.taobao.middleware.cli.CommandLine; +import java.lang.instrument.ClassFileTransformer; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -125,7 +126,7 @@ public interface CommandProcess extends Tty { * @param lock the lock for enhance class * @param listener */ - void register(int lock, AdviceListener listener); + void register(int lock, AdviceListener listener, ClassFileTransformer transformer); /** * Unregister listener diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/ProcessAware.java b/core/src/main/java/com/taobao/arthas/core/shell/system/ProcessAware.java new file mode 100644 index 000000000..9dc4baf2e --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/ProcessAware.java @@ -0,0 +1,14 @@ +package com.taobao.arthas.core.shell.system; + +/** + * + * @author hengyunabc 2020-05-18 + * + */ +public interface ProcessAware { + + public Process getProcess(); + + public void setProcess(Process process); + +} diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java index e408879f6..b32f8c879 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/GlobalJobControllerImpl.java @@ -4,18 +4,17 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; import com.taobao.arthas.core.GlobalOptions; +import com.taobao.arthas.core.server.ArthasBootstrap; import com.taobao.arthas.core.shell.cli.CliToken; import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.impl.ShellImpl; import com.taobao.arthas.core.shell.system.Job; -import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer; /** * 全局的Job Controller,不应该存在启停的概念,不需要在连接的断开时关闭, @@ -23,8 +22,6 @@ import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer; * @author gehui 2017年7月31日 上午11:55:41 */ public class GlobalJobControllerImpl extends JobControllerImpl { - - private Timer timer = new Timer("job-timeout", true); private Map jobTimeoutTaskMap = new HashMap(); private static final Logger logger = LoggerFactory.getLogger(GlobalJobControllerImpl.class); @@ -37,7 +34,6 @@ public class GlobalJobControllerImpl extends JobControllerImpl { @Override public void close() { - timer.cancel(); jobTimeoutTaskMap.clear(); for (Job job : jobs()) { job.terminate(); @@ -67,7 +63,7 @@ public class GlobalJobControllerImpl extends JobControllerImpl { } }; Date timeoutDate = new Date(System.currentTimeMillis() + (getJobTimeoutInSecond() * 1000)); - timer.schedule(jobTimeoutTask, timeoutDate); + ArthasBootstrap.getInstance().getTimer().schedule(jobTimeoutTask, timeoutDate); jobTimeoutTaskMap.put(job.id(), jobTimeoutTask); job.setTimeoutDate(timeoutDate); diff --git a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java index f145cf97c..b28b7875a 100644 --- a/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/shell/system/impl/ProcessImpl.java @@ -14,8 +14,8 @@ import com.taobao.arthas.core.shell.handlers.Handler; import com.taobao.arthas.core.shell.session.Session; import com.taobao.arthas.core.shell.system.ExecStatus; import com.taobao.arthas.core.shell.system.Process; +import com.taobao.arthas.core.shell.system.ProcessAware; import com.taobao.arthas.core.shell.term.Tty; -import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer; import com.taobao.arthas.core.util.usage.StyledUsageFormatter; import com.taobao.middleware.cli.CLIException; import com.taobao.middleware.cli.CommandLine; @@ -24,6 +24,7 @@ import com.taobao.text.Color; import io.termd.core.function.Function; +import java.lang.instrument.ClassFileTransformer; import java.util.Date; import java.util.LinkedList; import java.util.List; @@ -348,7 +349,7 @@ public class ProcessImpl implements Process { return; } - process = new CommandProcessImpl(args2, tty, cl); + process = new CommandProcessImpl(this, args2, tty, cl); if (cacheLocation() != null) { process.echoTips("job id : " + this.jobId + "\n"); process.echoTips("cache location : " + cacheLocation() + "\n"); @@ -371,22 +372,25 @@ public class ProcessImpl implements Process { handler.handle(process); } catch (Throwable t) { logger.error("Error during processing the command:", t); - process.write("Error during processing the command: " + t.getMessage() + "\n"); + process.write("Error during processing the command, exception type: " + t.getClass().getName() + ", message:" + t.getMessage() + + ", please check $HOME/logs/arthas/arthas.log for more details. \n"); terminate(1, null); } } } private class CommandProcessImpl implements CommandProcess { - + private final Process process; private final List args2; private final Tty tty; private final CommandLine commandLine; private int enhanceLock = -1; private AtomicInteger times = new AtomicInteger(); private AdviceListener suspendedListener = null; + private ClassFileTransformer transformer; - public CommandProcessImpl(List args2, Tty tty, CommandLine commandLine) { + public CommandProcessImpl(Process process, List args2, Tty tty, CommandLine commandLine) { + this.process = process; this.args2 = args2; this.tty = tty; this.commandLine = commandLine; @@ -524,13 +528,23 @@ public class ProcessImpl implements Process { } @Override - public void register(int enhanceLock, AdviceListener listener) { + public void register(int enhanceLock, AdviceListener listener, ClassFileTransformer transformer) { this.enhanceLock = enhanceLock; + + if (listener instanceof ProcessAware) { + ((ProcessAware) listener).setProcess(this.process); + } AdviceWeaver.reg(enhanceLock, listener); + + this.transformer = transformer; } @Override public void unregister() { + if (transformer != null) { + ArthasBootstrap.getInstance().getTransformerManager().removeTransformer(transformer); + } + AdviceWeaver.unReg(enhanceLock); } diff --git a/core/src/main/java/com/taobao/arthas/core/util/Constants.java b/core/src/main/java/com/taobao/arthas/core/util/Constants.java index 5beb6b75e..6bac42c89 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/Constants.java +++ b/core/src/main/java/com/taobao/arthas/core/util/Constants.java @@ -13,11 +13,6 @@ public class Constants { private Constants() { } - /** - * Spy的全类名 - */ - public static final String SPY_CLASSNAME = "java.arthas.Spy"; - /** * 中断提示 */ diff --git a/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java b/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java index 1e27e9757..3707a90ba 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java +++ b/core/src/main/java/com/taobao/arthas/core/util/Decompiler.java @@ -16,12 +16,17 @@ import org.benf.cfr.reader.api.OutputSinkFactory; */ public class Decompiler { + public static String decompile(String classFilePath, String methodName) { + return decompile(classFilePath, methodName, false); + } + /** * @param classFilePath * @param methodName + * @param hideUnicode * @return */ - public static String decompile(String classFilePath, String methodName) { + public static String decompile(String classFilePath, String methodName, boolean hideUnicode) { final StringBuilder result = new StringBuilder(8192); OutputSinkFactory mySink = new OutputSinkFactory() { @@ -52,6 +57,7 @@ public class Decompiler { * the cfr version is wrong. so disable show cfr version. */ options.put("showversion", "false"); + options.put("hideutf", String.valueOf(hideUnicode)); if (!StringUtils.isBlank(methodName)) { options.put("methodname", methodName); } diff --git a/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java b/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java index 131ec24df..81111c444 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java +++ b/core/src/main/java/com/taobao/arthas/core/util/affect/EnhancerAffect.java @@ -3,6 +3,7 @@ package com.taobao.arthas.core.util.affect; import com.taobao.arthas.core.GlobalOptions; import java.io.File; +import java.lang.instrument.ClassFileTransformer; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; @@ -18,19 +19,13 @@ public final class EnhancerAffect extends Affect { private final AtomicInteger cCnt = new AtomicInteger(); private final AtomicInteger mCnt = new AtomicInteger(); - + private ClassFileTransformer transformer; /** * dumpClass的文件存放集合 */ private final Collection classDumpFiles = new ArrayList(); public EnhancerAffect() { - - } - - public EnhancerAffect(int cCnt, int mCnt) { - this.cCnt(cCnt); - this.mCnt(mCnt); } /** @@ -80,6 +75,14 @@ public final class EnhancerAffect extends Affect { return classDumpFiles; } + public ClassFileTransformer getTransformer() { + return transformer; + } + + public void setTransformer(ClassFileTransformer transformer) { + this.transformer = transformer; + } + @Override public String toString() { final StringBuilder infoSB = new StringBuilder(); diff --git a/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java b/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java new file mode 100644 index 000000000..1468356c7 --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/advisor/EnhancerTest.java @@ -0,0 +1,104 @@ +package com.taobao.arthas.core.advisor; + +import java.arthas.SpyAPI; +import java.lang.instrument.Instrumentation; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarFile; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.mockito.Mockito; +import org.zeroturnaround.zip.ZipUtil; + +import com.alibaba.arthas.deps.org.objectweb.asm.Type; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.Decompiler; +import com.taobao.arthas.core.bytecode.TestHelper; +import com.taobao.arthas.core.server.ArthasBootstrap; +import com.taobao.arthas.core.util.affect.EnhancerAffect; +import com.taobao.arthas.core.util.matcher.EqualsMatcher; + +import demo.MathGame; +import net.bytebuddy.agent.ByteBuddyAgent; + +/** + * + * @author hengyunabc 2020-05-19 + * + */ +public class EnhancerTest { + + @Test + public void test() throws Throwable { + Instrumentation instrumentation = ByteBuddyAgent.install(); + + TestHelper.appendSpyJar(instrumentation); + + ArthasBootstrap.getInstance(instrumentation, ""); + + AdviceListener listener = Mockito.mock(AdviceListener.class); + + EnhancerAffect affect = new EnhancerAffect(); + + Set> matchingClasses = new HashSet>(); + matchingClasses.add(MathGame.class); + + EqualsMatcher matcher = new EqualsMatcher("print"); + + Enhancer enhancer = new Enhancer(listener, true, false, matchingClasses, matcher, affect); + + ClassLoader inClassLoader = MathGame.class.getClassLoader(); + String className = MathGame.class.getName(); + Class classBeingRedefined = MathGame.class; + + ClassNode classNode = AsmUtils.loadClass(MathGame.class); + + byte[] classfileBuffer = AsmUtils.toBytes(classNode); + + byte[] result = enhancer.transform(inClassLoader, className, classBeingRedefined, null, classfileBuffer); + + ClassNode resultClassNode1 = AsmUtils.toClassNode(result); + +// FileUtils.writeByteArrayToFile(new File("/tmp/MathGame1.class"), result); + + result = enhancer.transform(inClassLoader, className, classBeingRedefined, null, result); + + ClassNode resultClassNode2 = AsmUtils.toClassNode(result); + +// FileUtils.writeByteArrayToFile(new File("/tmp/MathGame2.class"), result); + + MethodNode resultMethodNode1 = AsmUtils.findMethods(resultClassNode1.methods, "print").get(0); + MethodNode resultMethodNode2 = AsmUtils.findMethods(resultClassNode2.methods, "print").get(0); + + Assertions + .assertThat(AsmUtils + .findMethodInsnNode(resultMethodNode1, Type.getInternalName(SpyAPI.class), "atEnter").size()) + .isEqualTo(AsmUtils.findMethodInsnNode(resultMethodNode2, Type.getInternalName(SpyAPI.class), "atEnter") + .size()); + + Assertions.assertThat(AsmUtils + .findMethodInsnNode(resultMethodNode1, Type.getInternalName(SpyAPI.class), "atExceptionExit").size()) + .isEqualTo(AsmUtils + .findMethodInsnNode(resultMethodNode2, Type.getInternalName(SpyAPI.class), "atExceptionExit") + .size()); + + Assertions.assertThat(AsmUtils + .findMethodInsnNode(resultMethodNode1, Type.getInternalName(SpyAPI.class), "atBeforeInvoke").size()) + .isEqualTo(AsmUtils + .findMethodInsnNode(resultMethodNode2, Type.getInternalName(SpyAPI.class), "atBeforeInvoke") + .size()); + Assertions.assertThat(AsmUtils + .findMethodInsnNode(resultMethodNode1, Type.getInternalName(SpyAPI.class), "atInvokeException").size()) + .isEqualTo(AsmUtils + .findMethodInsnNode(resultMethodNode2, Type.getInternalName(SpyAPI.class), "atInvokeException") + .size()); + + String string = Decompiler.decompile(result); + + System.err.println(string); + } + +} diff --git a/core/src/test/java/com/taobao/arthas/core/bytecode/TestHelper.java b/core/src/test/java/com/taobao/arthas/core/bytecode/TestHelper.java new file mode 100644 index 000000000..d5f9a19a4 --- /dev/null +++ b/core/src/test/java/com/taobao/arthas/core/bytecode/TestHelper.java @@ -0,0 +1,103 @@ +package com.taobao.arthas.core.bytecode; + +import java.io.File; +import java.io.IOException; +import java.lang.instrument.Instrumentation; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarFile; + +import org.zeroturnaround.zip.ZipUtil; + +import com.alibaba.arthas.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.arthas.deps.org.objectweb.asm.tree.MethodNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.InterceptorProcessor; +import com.taobao.arthas.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; +import com.taobao.arthas.bytekit.utils.AgentUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; +import com.taobao.arthas.bytekit.utils.MatchUtils; +import com.taobao.arthas.bytekit.utils.VerifyUtils; + +/** + * + * @author hengyunabc 2020-05-19 + * + */ +public class TestHelper { + + private Class interceptorClass; + + private boolean redefine; + + private String methodMatcher = "*"; + + private boolean asmVerity = true; + + public static TestHelper builder() { + return new TestHelper(); + } + + public TestHelper interceptorClass(Class interceptorClass) { + this.interceptorClass = interceptorClass; + return this; + } + + public TestHelper redefine(boolean redefine) { + this.redefine = redefine; + return this; + } + + public TestHelper methodMatcher(String methodMatcher) { + this.methodMatcher = methodMatcher; + return this; + } + + public byte[] process(Class transform) throws Exception { + DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + + List interceptorProcessors = defaultInterceptorClassParser.parse(interceptorClass); + + ClassNode classNode = AsmUtils.loadClass(transform); + + List matchedMethods = new ArrayList(); + for (MethodNode methodNode : classNode.methods) { + if (MatchUtils.wildcardMatch(methodNode.name, methodMatcher)) { + matchedMethods.add(methodNode); + } + } + + for (MethodNode methodNode : matchedMethods) { + MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode); + for (InterceptorProcessor interceptor : interceptorProcessors) { + interceptor.process(methodProcessor); + } + } + + byte[] bytes = AsmUtils.toBytes(classNode); + if (asmVerity) { + VerifyUtils.asmVerify(bytes); + } + + if (redefine) { + AgentUtils.redefine(transform, bytes); + } + + return bytes; + } + + public static void appendSpyJar(Instrumentation instrumentation) throws IOException { + // find spy target/classes directory + String file = TestHelper.class.getProtectionDomain().getCodeSource().getLocation().getFile(); + + File spyClassDir = new File(file, "../../../spy/target/classes").getAbsoluteFile(); + + File destJarFile = new File(file, "../../../spy/target/test-spy.jar").getAbsoluteFile(); + + ZipUtil.pack(spyClassDir, destJarFile); + + instrumentation.appendToBootstrapClassLoaderSearch(new JarFile(destJarFile)); + + } +} diff --git a/pom.xml b/pom.xml index 9faeb530d..4913746d1 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,7 @@ spy common tunnel-client + bytekit core agent client @@ -92,12 +93,12 @@ org.ow2.asm asm - 7.3.1 + 8.0.1 org.ow2.asm asm-commons - 7.3.1 + 8.0.1 org.benf @@ -132,7 +133,12 @@ com.alibaba.arthas arthas-repackage-logger - 0.0.1 + 0.0.3 + + + com.alibaba.arthas + arthas-repackage-asm + 0.0.3 com.alibaba @@ -166,6 +172,12 @@ 2.9.1 test + + org.mockito + mockito-core + 2.28.2 + test + io.netty @@ -286,7 +298,10 @@ prepare-agent - com/taobao/arthas/core/view/ObjectViewTest* + + com/taobao/arthas/core/view/ObjectViewTest* + com/taobao/arthas/bytekit/asm/interceptor* + diff --git a/spy/src/main/java/java/arthas/Spy.java b/spy/src/main/java/java/arthas/Spy.java deleted file mode 100644 index b05620894..000000000 --- a/spy/src/main/java/java/arthas/Spy.java +++ /dev/null @@ -1,131 +0,0 @@ -package java.arthas; - -import java.lang.reflect.Method; - -/** - * 间谍类
- * 藏匿在各个ClassLoader中 - * Created by vlinux on 15/8/23. - */ -public class Spy { - public static final String ON_BEFORE = "methodOnBegin"; - public static final String ON_RETURN = "methodOnReturnEnd"; - public static final String ON_THROWS = "methodOnThrowingEnd"; - public static final String BEFORE_INVOKE = "methodOnInvokeBeforeTracing"; - public static final String AFTER_INVOKE = "methodOnInvokeAfterTracing"; - public static final String THROW_INVOKE = "methodOnInvokeThrowTracing"; - - // -- 各种Advice的钩子引用 -- - public static volatile Method ON_BEFORE_METHOD; - public static volatile Method ON_RETURN_METHOD; - public static volatile Method ON_THROWS_METHOD; - public static volatile Method BEFORE_INVOKING_METHOD; - public static volatile Method AFTER_INVOKING_METHOD; - public static volatile Method THROW_INVOKING_METHOD; - - /** - * arthas's classloader 引用 - */ - public static volatile ClassLoader CLASSLOADER; - - /** - * 代理重设方法 - */ - public static volatile Method AGENT_RESET_METHOD; - - /** - * 用于普通的间谍初始化 - */ - public static void init( - ClassLoader classLoader, - Method onBeforeMethod, - Method onReturnMethod, - Method onThrowsMethod, - Method beforeInvokingMethod, - Method afterInvokingMethod, - Method throwInvokingMethod) { - CLASSLOADER = classLoader; - ON_BEFORE_METHOD = onBeforeMethod; - ON_RETURN_METHOD = onReturnMethod; - ON_THROWS_METHOD = onThrowsMethod; - BEFORE_INVOKING_METHOD = beforeInvokingMethod; - AFTER_INVOKING_METHOD = afterInvokingMethod; - THROW_INVOKING_METHOD = throwInvokingMethod; - } - - /** - * Clean up the reference to com.taobao.arthas.agent.AgentLauncher$1 - * to avoid classloader leak. - */ - public static void destroy() { - initEmptySpy(); - // clear the reference to ArthasClassLoader in AgentLauncher - if (AGENT_RESET_METHOD != null) { - try { - AGENT_RESET_METHOD.invoke(null); - } catch (Exception e) { - e.printStackTrace(); - } - } - AGENT_RESET_METHOD = null; - } - - private static void initEmptySpy() { - try { - Class adviceWeaverClass = Spy.class; - Method onBefore = adviceWeaverClass.getMethod(Spy.ON_BEFORE, int.class, ClassLoader.class, String.class, - String.class, String.class, Object.class, Object[].class); - Method onReturn = adviceWeaverClass.getMethod(Spy.ON_RETURN, Object.class); - Method onThrows = adviceWeaverClass.getMethod(Spy.ON_THROWS, Throwable.class); - Method beforeInvoke = adviceWeaverClass.getMethod(Spy.BEFORE_INVOKE, int.class, String.class, String.class, - String.class, int.class); - Method afterInvoke = adviceWeaverClass.getMethod(Spy.AFTER_INVOKE, int.class, String.class, String.class, - String.class, int.class); - Method throwInvoke = adviceWeaverClass.getMethod(Spy.THROW_INVOKE, int.class, String.class, String.class, - String.class, int.class); - Spy.init(null, onBefore, onReturn, onThrows, beforeInvoke, afterInvoke, throwInvoke); - } catch (Exception e) { - } - } - - /** - * empty method - * - * @see com.taobao.arthas.core.advisor.AdviceWeaver#methodOnBegin(int, - * ClassLoader, String, String, String, Object, Object[]) - * @param adviceId - * @param loader - * @param className - * @param methodName - * @param methodDesc - * @param target - * @param args - */ - public static void methodOnBegin(int adviceId, ClassLoader loader, String className, String methodName, - String methodDesc, Object target, Object[] args) { - } - - /** - * empty method - * - * @see com.taobao.arthas.core.advisor.AdviceWeaver#methodOnReturnEnd(Object) - * @param returnObject - */ - public static void methodOnReturnEnd(Object returnObject) { - } - - public static void methodOnThrowingEnd(Throwable throwable) { - } - - public static void methodOnInvokeBeforeTracing(int adviceId, String owner, String name, String desc, - int lineNumber) { - } - - public static void methodOnInvokeAfterTracing(int adviceId, String owner, String name, String desc, - int lineNumber) { - } - - public static void methodOnInvokeThrowTracing(int adviceId, String owner, String name, String desc, - int lineNumber) { - } -} diff --git a/spy/src/main/java/java/arthas/SpyAPI.java b/spy/src/main/java/java/arthas/SpyAPI.java new file mode 100644 index 000000000..5de04962b --- /dev/null +++ b/spy/src/main/java/java/arthas/SpyAPI.java @@ -0,0 +1,114 @@ +package java.arthas; + +/** + *
+ * 一个adviceId 是什么呢? 就是一个trace/monitor/watch命令能对应上的一个id,比如一个类某个函数,它的 enter/end/exception 统一是一个id,分配完了就不会再分配。
+ * 
+ * 同样一个method,如果它trace之后,也会有一个 adviceId, 这个method里的所有invoke都是统一处理,认为是一个 adviceId 。 但如果有匹配到不同的 invoke的怎么分配??
+ * 好像有点难了。。
+ * 
+ * 其实就是把所有可以插入的地方都分类好,那么怎么分类呢?? 或者是叫同一种匹配,就是同一种的 adviceId? 
+ * 
+ * 比如入参是有  class , method ,是固定的  ,  某个行号,或者 某个
+ * 
+ * aop插入的叫 adviceId , command插入的叫 ListenerId?
+ * 
+ * 
+ * 
+ * 
+ * + * @author hengyunabc + * + */ +public class SpyAPI { + private static final AbstractSpy NOPSPY = new NopSpy(); + private static volatile AbstractSpy spyInstance = new NopSpy(); + + public static AbstractSpy getSpy() { + return spyInstance; + } + + public static void setSpy(AbstractSpy spy) { + spyInstance = spy; + } + + public static void setNopSpy() { + setSpy(NOPSPY); + } + + public static void atEnter(Class clazz, String methodInfo, Object target, Object[] args) { + spyInstance.atEnter(clazz, methodInfo, target, args); + } + + public static void atExit(Class clazz, String methodInfo, Object target, Object[] args, + Object returnObject) { + spyInstance.atExit(clazz, methodInfo, target, args, returnObject); + } + + public static void atExceptionExit(Class clazz, String methodInfo, Object target, + Object[] args, Throwable throwable) { + spyInstance.atExceptionExit(clazz, methodInfo, target, args, throwable); + } + + public static void atBeforeInvoke(Class clazz, String invokeInfo, Object target) { + spyInstance.atBeforeInvoke(clazz, invokeInfo, target); + } + + public static void atAfterInvoke(Class clazz, String invokeInfo, Object target) { + spyInstance.atAfterInvoke(clazz, invokeInfo, target); + } + + public static void atInvokeException(Class clazz, String invokeInfo, Object target, Throwable throwable) { + spyInstance.atInvokeException(clazz, invokeInfo, target, throwable); + } + + public static abstract class AbstractSpy { + public abstract void atEnter(Class clazz, String methodInfo, Object target, + Object[] args); + + public abstract void atExit(Class clazz, String methodInfo, Object target, Object[] args, + Object returnObject); + + public abstract void atExceptionExit(Class clazz, String methodInfo, Object target, + Object[] args, Throwable throwable); + + public abstract void atBeforeInvoke(Class clazz, String invokeInfo, Object target); + + public abstract void atAfterInvoke(Class clazz, String invokeInfo, Object target); + + public abstract void atInvokeException(Class clazz, String invokeInfo, Object target, Throwable throwable); + } + + static class NopSpy extends AbstractSpy { + + @Override + public void atEnter(Class clazz, String methodInfo, Object target, Object[] args) { + } + + @Override + public void atExit(Class clazz, String methodInfo, Object target, Object[] args, + Object returnObject) { + } + + @Override + public void atExceptionExit(Class clazz, String methodInfo, Object target, Object[] args, + Throwable throwable) { + } + + @Override + public void atBeforeInvoke(Class clazz, String invokeInfo, Object target) { + + } + + @Override + public void atAfterInvoke(Class clazz, String invokeInfo, Object target) { + + } + + @Override + public void atInvokeException(Class clazz, String invokeInfo, Object target, Throwable throwable) { + + } + + } +} diff --git a/tunnel-server/pom.xml b/tunnel-server/pom.xml index f25abbc38..44dc94836 100644 --- a/tunnel-server/pom.xml +++ b/tunnel-server/pom.xml @@ -87,6 +87,7 @@ org.springframework.boot spring-boot-maven-plugin + 2.1.7.RELEASE package