From 55ee992d09924fda519c0b1e2ffc3096c10fa253 Mon Sep 17 00:00:00 2001 From: hengyunabc Date: Fri, 1 Mar 2019 19:29:57 +0800 Subject: [PATCH] bytekit InterceptorProcessor support ClassLoader --- .../arthas/bytekit/asm/inst/Instrument.java | 35 ++++++ .../bytekit/asm/inst/InstrumentApi.java | 32 ++++++ .../arthas/bytekit/asm/inst/NewField.java | 10 ++ .../asm/interceptor/InterceptorProcessor.java | 85 ++++++++------- .../asm/interceptor/annotation/AtEnter.java | 3 +- .../annotation/AtExceptionExit.java | 6 +- .../asm/interceptor/annotation/AtExit.java | 4 +- .../interceptor/annotation/AtFieldAccess.java | 4 +- .../asm/interceptor/annotation/AtInvoke.java | 2 +- .../asm/interceptor/annotation/AtLine.java | 2 +- .../interceptor/annotation/AtSyncEnter.java | 8 +- .../interceptor/annotation/AtSyncExit.java | 8 +- .../asm/interceptor/annotation/AtThrow.java | 6 +- .../taobao/arthas/bytekit/utils/AsmUtils.java | 80 ++++++++++++++ .../arthas/bytekit/asm/inst/InstDemo.java | 21 ++++ .../arthas/bytekit/asm/inst/InstDemoTest.java | 102 ++++++++++++++++++ .../arthas/bytekit/asm/inst/InstDemo_APM.java | 29 +++++ .../bytekit/asm/inst/InvokeOriginDemo.java | 5 + .../asm/inst/InvokeOriginDemo_APM.java | 5 + .../bytekit/asm/interceptor/AtEnterTest.java | 18 ++-- .../arthas/bytekit/utils/AsmUtilsTest.java | 15 +++ 21 files changed, 413 insertions(+), 67 deletions(-) create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/Instrument.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/InstrumentApi.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/NewField.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo_APM.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java 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/interceptor/InterceptorProcessor.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorProcessor.java index e51158fb1..d87f6d514 100644 --- 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 @@ -24,28 +24,37 @@ import com.taobao.arthas.bytekit.utils.Decompiler; public class InterceptorProcessor { private LocationMatcher locationMatcher; - + /** * 插入的回调函数的配置 */ - private InterceptorMethodConfig interceptorMethodConfig; - + private InterceptorMethodConfig interceptorMethodConfig; + /** * 插入的代码被 try/catch 包围的配置,注意有一些location插入try/catch会可能失败,因为不能确切知道栈上的情况 */ - private InterceptorMethodConfig exceptionHandlerConfig; + private InterceptorMethodConfig exceptionHandlerConfig; + + /** + * 加载inlne类所需要的ClassLoader + */ + private ClassLoader classLoader; + + public InterceptorProcessor(ClassLoader classLoader) { + this.classLoader = classLoader; + } public void 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(); @@ -54,33 +63,33 @@ public class InterceptorProcessor { 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++; + 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); @@ -88,15 +97,15 @@ public class InterceptorProcessor { // 检查 回调函数的参数类型,看是否要box一下 ,检查是否原始类型就可以了。 // 只有类型不一样时,才需要判断。比如两个都是 long,则不用判断 Type bindingType = binding.getType(bindingContext); - if(!bindingType.equals(argumentTypes[i])) { + 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)); @@ -120,27 +129,27 @@ public class InterceptorProcessor { } } } - - + + 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)); @@ -151,36 +160,40 @@ public class InterceptorProcessor { }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 = 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 = 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())); } } - + private void errorHandler(MethodProcessor methodProcessor, InsnList insnList) { // MethodInsnNode printStackTrace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable", "printStackTrace", "()V", false); // insnList.add(printStackTrace); - // 第一个参数要求是 throwable ,或者一个exception ? + // 第一个参数要求是 throwable ,或者一个exception ? // 有很多 binding 并不能使用的,因为location不生效 - BindingContext bindingContext = new BindingContext(null, methodProcessor, null); + BindingContext bindingContext = new BindingContext(null, methodProcessor, null); Type methodType = Type.getMethodType(this.exceptionHandlerConfig.getMethodDesc()); Type[] argumentTypes = methodType.getArgumentTypes(); List bindings = this.exceptionHandlerConfig.getBindings(); @@ -201,10 +214,10 @@ public class InterceptorProcessor { 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); 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 index ca5fe5a61..af121ace2 100644 --- 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 @@ -33,8 +33,7 @@ public @interface AtEnter { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); 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 index 3703da67c..85561d034 100644 --- 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 @@ -36,7 +36,7 @@ public @interface AtExceptionExit { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); @@ -47,9 +47,9 @@ public @interface AtExceptionExit { AtExceptionExit atExceptionExit = (AtExceptionExit) annotationOnMethod; interceptorMethodConfig.setInline(atExceptionExit.inline()); - + LocationMatcher locationMatcher = new ExceptionExitLocationMatcher(Type.getInternalName(atExceptionExit.onException()));; - + interceptorProcessor.setLocationMatcher(locationMatcher); List bindings = BindingParserUtils.parseBindings(method); 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 index c8f03e449..5bc60d0e0 100644 --- 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 @@ -26,13 +26,13 @@ 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(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); 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 index 084763183..d7e602dbe 100644 --- 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 @@ -47,7 +47,7 @@ public @interface AtFieldAccess { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); @@ -65,7 +65,7 @@ public @interface AtFieldAccess { if(!atFieldAccess.type().equals(Void.class)) { fieldDesc = Type.getType(atFieldAccess.type()).getDescriptor(); } - + LocationMatcher locationMatcher = new FieldAccessLocationMatcher( ownerClass, fieldDesc, atFieldAccess.name(), atFieldAccess.count(), 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 index 7d2580229..6305eceae 100644 --- 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 @@ -47,7 +47,7 @@ public @interface AtInvoke { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); 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 index e8847b187..e16d44753 100644 --- 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 @@ -36,7 +36,7 @@ public @interface AtLine { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); 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 index 0e6bbb1ac..c541d0c9a 100644 --- 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 @@ -29,7 +29,7 @@ public @interface AtSyncEnter { Class suppress() default None.class; Class suppressHandler() default Void.class; - + int count() default -1; boolean whenComplete() default false; @@ -38,7 +38,7 @@ public @interface AtSyncEnter { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); @@ -47,10 +47,10 @@ public @interface AtSyncEnter { 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); 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 index e24522a49..b139bb180 100644 --- 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 @@ -29,7 +29,7 @@ public @interface AtSyncExit { Class suppress() default None.class; Class suppressHandler() default Void.class; - + int count() default -1; boolean whenComplete() default false; @@ -38,7 +38,7 @@ public @interface AtSyncExit { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); @@ -47,10 +47,10 @@ public @interface AtSyncExit { 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); 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 index 04c47ad57..75e7a5e47 100644 --- 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 @@ -28,7 +28,7 @@ public @interface AtThrow { Class suppress() default None.class; Class suppressHandler() default Void.class; - + int count() default -1; class ThrowInterceptorProcessorParser implements InterceptorProcessorParser { @@ -36,7 +36,7 @@ public @interface AtThrow { @Override public InterceptorProcessor parse(Method method, Annotation annotationOnMethod) { - InterceptorProcessor interceptorProcessor = new InterceptorProcessor(); + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); @@ -47,7 +47,7 @@ public @interface AtThrow { AtThrow atThrow = (AtThrow) annotationOnMethod; LocationMatcher locationMatcher = new ThrowLocationMatcher(atThrow.count()); interceptorProcessor.setLocationMatcher(locationMatcher); - + interceptorMethodConfig.setInline(atThrow.inline()); List bindings = BindingParserUtils.parseBindings(method); diff --git a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java index e213c2857..7daed4df1 100644 --- a/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java @@ -12,12 +12,15 @@ import java.util.Comparator; import java.util.List; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; +import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.JSRInlinerAdapter; +import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; @@ -52,6 +55,42 @@ public class AsmUtils { 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); @@ -131,6 +170,10 @@ public class AsmUtils { 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)) { @@ -163,6 +206,20 @@ public class AsmUtils { 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 boolean isStatic(MethodNode methodNode) { return (methodNode.access & Opcodes.ACC_STATIC) != 0; } @@ -238,6 +295,20 @@ public class AsmUtils { return result; } + public static ClassNode copy(ClassNode source) { + ClassNode result = new ClassNode(Opcodes.ASM7); + source.accept(new ClassVisitor(Opcodes.ASM7, 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); @@ -369,6 +440,15 @@ public class AsmUtils { 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); 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..672ed14fa --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java @@ -0,0 +1,21 @@ +package com.taobao.arthas.bytekit.asm.inst; + +import java.util.Date; + +public class InstDemo { + + public int returnInt(int i) { + return 9998; + } + + public static int returnIntStatic(int i) { + return 9998; + } + + public static void main(String[] args) { + Date date = new Date(1551168643); + + System.err.println(date.toLocaleString()); + } + +} 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..3be246c2b --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemoTest.java @@ -0,0 +1,102 @@ +package com.taobao.arthas.bytekit.asm.inst; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.junit.Test; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import com.taobao.arthas.bytekit.asm.MethodProcessor; +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; + +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)); + + FileUtils.writeByteArrayToFile(new File("/tmp/ttt/InstDemo.class"), 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..cdfb5f79d --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.asm.inst; + +public class InvokeOriginDemo { + +} 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..0b15a7713 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginDemo_APM.java @@ -0,0 +1,5 @@ +package com.taobao.arthas.bytekit.asm.inst; + +public class InvokeOriginDemo_APM { + +} 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 index f39d50d5d..5dead641f 100644 --- 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 @@ -21,7 +21,7 @@ public class AtEnterTest { public OutputCapture capture = new OutputCapture(); public static class Sample { - + long longField; String strField; static int intField; @@ -32,20 +32,20 @@ public class AtEnterTest { } 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 = false) + @ExceptionHandler(inline = true) public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) { System.err.println("exception handler: " + clazz); e.printStackTrace(); @@ -54,14 +54,14 @@ public class AtEnterTest { public static class EnterInterceptor { - @AtEnter(inline = false + @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 = "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 ) { 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 index a8cb68f81..f46210956 100644 --- a/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java @@ -126,4 +126,19 @@ public class AsmUtilsTest { } + + @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"); + } + }