From e330bdc2f6bddbff63e634c3469fdd6c397cf231 Mon Sep 17 00:00:00 2001 From: hengyunabc Date: Sun, 12 Apr 2020 03:42:51 +0800 Subject: [PATCH] add bytekit module --- bytekit/pom.xml | 100 +++ .../com/taobao/arthas/bytekit/ByteKit.java | 22 + .../arthas/bytekit/asm/InliningAdapter.java | 86 ++ .../arthas/bytekit/asm/MethodCallInliner.java | 97 +++ .../taobao/arthas/bytekit/asm/MethodInfo.java | 48 ++ .../arthas/bytekit/asm/MethodProcessor.java | 766 +++++++++++++++++ .../arthas/bytekit/asm/MyTryCatchBlock.java | 64 ++ .../arthas/bytekit/asm/TryCatchBlock.java | 66 ++ .../taobao/arthas/bytekit/asm/TypeHelper.java | 431 ++++++++++ .../bytekit/asm/binding/ArgNamesBinding.java | 33 + .../bytekit/asm/binding/ArgsBinding.java | 20 + .../bytekit/asm/binding/ArrayBinding.java | 50 ++ .../arthas/bytekit/asm/binding/Binding.java | 323 +++++++ .../bytekit/asm/binding/BindingContext.java | 41 + .../bytekit/asm/binding/ClassBinding.java | 21 + .../bytekit/asm/binding/FieldBinding.java | 95 +++ .../bytekit/asm/binding/IntBinding.java | 36 + .../asm/binding/InvokeArgsBinding.java | 47 + .../InvokeMethodDeclarationBinding.java | 38 + .../asm/binding/InvokeReturnBinding.java | 62 ++ .../bytekit/asm/binding/LineBinding.java | 30 + .../asm/binding/LocalVarNamesBinding.java | 38 + .../bytekit/asm/binding/LocalVarsBinding.java | 49 ++ .../bytekit/asm/binding/MethodBinding.java | 52 ++ .../asm/binding/MethodDeclarationBinding.java | 32 + .../bytekit/asm/binding/MonitorBinding.java | 41 + .../bytekit/asm/binding/ReturnBinding.java | 43 + .../bytekit/asm/binding/StackSaver.java | 23 + .../bytekit/asm/binding/ThisBinding.java | 18 + .../bytekit/asm/binding/ThrowableBinding.java | 30 + .../asm/binding/annotation/BindingParser.java | 11 + .../annotation/BindingParserHandler.java | 15 + .../arthas/bytekit/asm/inst/Instrument.java | 35 + .../bytekit/asm/inst/InstrumentApi.java | 32 + .../arthas/bytekit/asm/inst/NewField.java | 10 + .../bytekit/asm/inst/impl/InstrumentImpl.java | 125 +++ .../asm/inst/impl/MethodReplaceResult.java | 52 ++ .../asm/interceptor/EnterInteceptor.java | 5 + .../asm/interceptor/ExceptionInterceptor.java | 5 + .../asm/interceptor/ExitInterceptor.java | 5 + .../bytekit/asm/interceptor/Inteceptor.java | 5 + .../interceptor/InterceptorMethodConfig.java | 71 ++ .../asm/interceptor/InterceptorProcessor.java | 253 ++++++ .../asm/interceptor/annotation/AtEnter.java | 64 ++ .../annotation/AtExceptionExit.java | 69 ++ .../asm/interceptor/annotation/AtExit.java | 63 ++ .../interceptor/annotation/AtFieldAccess.java | 91 ++ .../asm/interceptor/annotation/AtInvoke.java | 94 ++ .../asm/interceptor/annotation/AtLine.java | 67 ++ .../interceptor/annotation/AtSyncEnter.java | 70 ++ .../interceptor/annotation/AtSyncExit.java | 70 ++ .../asm/interceptor/annotation/AtThrow.java | 67 ++ .../annotation/BindingParserUtils.java | 36 + .../annotation/EmptySuppressHandler.java | 10 + .../annotation/ExceptionHandler.java | 13 + .../annotation/ExceptionHandlerUtils.java | 85 ++ .../annotation/InterceptorParserHander.java | 17 + .../asm/interceptor/annotation/None.java | 13 + .../annotation/PrintSuppressHandler.java | 11 + .../parser/DefaultInterceptorClassParser.java | 50 ++ .../parser/InterceptorClassParser.java | 10 + .../parser/InterceptorProcessorParser.java | 11 + .../asm/location/AccessLocationMatcher.java | 24 + .../asm/location/EnterLocationMatcher.java | 21 + .../ExceptionExitLocationMatcher.java | 32 + .../asm/location/ExitLocationMatcher.java | 46 + .../location/FieldAccessLocationMatcher.java | 96 +++ .../asm/location/InvokeLocationMatcher.java | 161 ++++ .../asm/location/LineLocationMatcher.java | 60 ++ .../arthas/bytekit/asm/location/Location.java | 646 ++++++++++++++ .../bytekit/asm/location/LocationMatcher.java | 11 + .../bytekit/asm/location/LocationType.java | 86 ++ .../bytekit/asm/location/MatchResult.java | 9 + .../asm/location/SyncExitLocationMatcher.java | 46 + .../asm/location/SyncLocationMatcher.java | 53 ++ .../asm/location/ThrowLocationMatcher.java | 47 + .../VariableAccessLocationMatcher.java | 34 + .../bytekit/asm/matcher/ClassMatcher.java | 7 + .../bytekit/asm/matcher/MethodMatcher.java | 8 + .../arthas/bytekit/utils/AgentUtils.java | 21 + .../arthas/bytekit/utils/AnnotationUtils.java | 12 + .../arthas/bytekit/utils/AsmOpUtils.java | 454 ++++++++++ .../taobao/arthas/bytekit/utils/AsmUtils.java | 499 +++++++++++ .../arthas/bytekit/utils/Decompiler.java | 271 ++++++ .../arthas/bytekit/utils/InstanceUtils.java | 13 + .../arthas/bytekit/utils/MatchUtils.java | 169 ++++ .../arthas/bytekit/utils/MethodUtils.java | 5 + .../arthas/bytekit/utils/ReflectionUtils.java | 807 ++++++++++++++++++ .../arthas/bytekit/utils/VerifyUtils.java | 69 ++ .../arthas/bytekit/asm/inst/InstDemo.java | 18 + .../arthas/bytekit/asm/inst/InstDemoTest.java | 102 +++ .../arthas/bytekit/asm/inst/InstDemo_APM.java | 29 + .../bytekit/asm/inst/InvokeOriginDemo.java | 77 ++ .../asm/inst/InvokeOriginDemo_APM.java | 85 ++ .../bytekit/asm/inst/InvokeOriginTest.java | 178 ++++ .../bytekit/asm/interceptor/AtEnterTest.java | 89 ++ .../asm/interceptor/AtExceptionExitTest.java | 84 ++ .../bytekit/asm/interceptor/AtExitTest.java | 93 ++ .../asm/interceptor/AtFieldAccessTest.java | 60 ++ .../bytekit/asm/interceptor/AtInvokeTest.java | 101 +++ .../bytekit/asm/interceptor/AtLineTest.java | 85 ++ .../asm/interceptor/AtSyncEnterTest.java | 89 ++ .../asm/interceptor/AtSyncExitTest.java | 89 ++ .../bytekit/asm/interceptor/AtThrowTest.java | 76 ++ .../bytekit/asm/interceptor/TestHelper.java | 78 ++ .../arthas/bytekit/utils/AsmUtilsTest.java | 144 ++++ .../arthas/bytekit/utils/EmptyClass.java | 9 + 107 files changed, 9530 insertions(+) create mode 100644 bytekit/pom.xml create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/ByteKit.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/InliningAdapter.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodCallInliner.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodInfo.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MyTryCatchBlock.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TryCatchBlock.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/TypeHelper.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgNamesBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArgsBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ArrayBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/Binding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/BindingContext.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ClassBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/FieldBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/IntBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeArgsBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodDeclarationBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeReturnBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LineBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarNamesBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LocalVarsBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodDeclarationBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MonitorBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ReturnBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/StackSaver.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThisBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/ThrowableBinding.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParser.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/annotation/BindingParserHandler.java 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/main/java/com/taobao/arthas/bytekit/asm/inst/impl/InstrumentImpl.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/inst/impl/MethodReplaceResult.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/EnterInteceptor.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExceptionInterceptor.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/ExitInterceptor.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/Inteceptor.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorMethodConfig.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorProcessor.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtEnter.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExceptionExit.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtExit.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtFieldAccess.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvoke.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtLine.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncEnter.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtSyncExit.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtThrow.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/BindingParserUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/EmptySuppressHandler.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandler.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/ExceptionHandlerUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/InterceptorParserHander.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/None.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/PrintSuppressHandler.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/DefaultInterceptorClassParser.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorClassParser.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/parser/InterceptorProcessorParser.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/AccessLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/EnterLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExceptionExitLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExitLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/FieldAccessLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/InvokeLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LineLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/Location.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationType.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/MatchResult.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncExitLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ThrowLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/VariableAccessLocationMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/ClassMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/MethodMatcher.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AnnotationUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/Decompiler.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/InstanceUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MatchUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/MethodUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/ReflectionUtils.java create mode 100644 bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.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 create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InvokeOriginTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExceptionExitTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtExitTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtFieldAccessTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtLineTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncEnterTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtSyncExitTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtThrowTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/utils/AsmUtilsTest.java create mode 100644 bytekit/src/test/java/com/taobao/arthas/bytekit/utils/EmptyClass.java diff --git a/bytekit/pom.xml b/bytekit/pom.xml new file mode 100644 index 00000000..bfea386a --- /dev/null +++ b/bytekit/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + com.taobao.arthas + arthas-all + 3.1.9-SNAPSHOT + ../pom.xml + + arthas-bytekit + arthas-bytekit + + + + com.alibaba.arthas + arthas-repackage-asm + + + + org.benf + cfr + 0.132 + provided + true + + + + net.bytebuddy + byte-buddy + 1.7.10 + provided + true + + + + net.bytebuddy + byte-buddy-agent + 1.7.10 + provided + true + + + + commons-io + commons-io + 2.6 + provided + + + + org.apache.commons + commons-lang3 + 3.7 + provided + + + + + junit + junit + test + + + + org.assertj + assertj-core + 3.9.1 + test + true + + + + org.springframework.boot + spring-boot-starter-test + 1.5.9.RELEASE + test + true + + + + + + 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 00000000..281ce743 --- /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 00000000..58741283 --- /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.ASM7, 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 00000000..2d0876a1 --- /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.ASM7, 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 00000000..8188d4d7 --- /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 00000000..d4f97d5f --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/MethodProcessor.java @@ -0,0 +1,766 @@ +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.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; + + public MethodProcessor(final ClassNode classNode, final MethodNode methodNode) { + this(classNode, methodNode, false); + } + + public MethodProcessor(final ClassNode classNode, final MethodNode methodNode, boolean keepLocalVariableNames) { + this(classNode.name, methodNode, keepLocalVariableNames); + this.classNode = classNode; + } + + public MethodProcessor(final String owner, final MethodNode methodNode, boolean keepLocalVariableNames) { + this.owner = owner; + this.methodNode = methodNode; + 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; + } + /** + * 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 00000000..3c90ec99 --- /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 00000000..9068fa2a --- /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 00000000..0e8d35f8 --- /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 00000000..b752daf1 --- /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 00000000..8b31c84a --- /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 00000000..363ef892 --- /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 00000000..93414262 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/Binding.java @@ -0,0 +1,323 @@ +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 @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 @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 @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 @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 @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 @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 @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 @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 = InvokeMethodDeclarationBindingParser.class) + public @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 = MethodBindingParser.class) + public @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 = ReturnBindingParser.class) + public @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 @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 @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 @interface Line { + + boolean optional() default false; + + } + + public static class LineBindingParser implements BindingParser { + @Override + public Binding parse(Annotation annotation) { + return new LineBinding(); + } + + } + + + @Documented + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.PARAMETER) + @BindingParserHandler(parser = MonitorBindingParser.class) + public @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 00000000..2fcfb3be --- /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 00000000..360afcf7 --- /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 00000000..9c20b266 --- /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 00000000..7400da63 --- /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 00000000..0b7532ab --- /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/InvokeMethodDeclarationBinding.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodDeclarationBinding.java new file mode 100644 index 00000000..b05d3f22 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/InvokeMethodDeclarationBinding.java @@ -0,0 +1,38 @@ +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.MethodInsnNode; + +import com.taobao.arthas.bytekit.utils.AsmOpUtils; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +/** + * + * @author hengyunabc + * + */ +public class InvokeMethodDeclarationBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + AbstractInsnNode insnNode = bindingContext.getLocation().getInsnNode(); + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + String methodDeclaration = AsmUtils.methodDeclaration(methodInsnNode); + AsmOpUtils.push(instructions, methodDeclaration); + + } else { + throw new IllegalArgumentException( + "InvokeMethodDeclarationBinding location is not MethodInsnNode, insnNode: " + insnNode); + } + + } + + @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 00000000..d36d786c --- /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 00000000..cd57149c --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/LineBinding.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.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; + +public class LineBinding extends Binding { + + @Override + public void pushOntoStack(InsnList instructions, BindingContext bindingContext) { + Location location = bindingContext.getLocation(); + AbstractInsnNode insnNode = location.getInsnNode(); + + if (insnNode instanceof LineNumberNode) { + AsmOpUtils.push(instructions, ((LineNumberNode) insnNode).line); + } else { + throw new IllegalArgumentException("LineBinding location is not LineNumberNode, insnNode: " + insnNode); + } + } + + @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 00000000..56bffb23 --- /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 00000000..3953fdc4 --- /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 00000000..d3ba3c78 --- /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 00000000..7d4e9ca8 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/binding/MethodDeclarationBinding.java @@ -0,0 +1,32 @@ +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; +import com.taobao.arthas.bytekit.utils.AsmUtils; + +/** + * 提供一个完整的 method 的string,包含类名,并不是desc?用户可以自己提取descs method的定义,前面是 public + * /static 这些关键字,是有限的几个。后面是 throws ,的异常信息 + * + * @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())); + + } + + @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 00000000..8dcffb6e --- /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 00000000..a7121860 --- /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.storeVar(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 00000000..2f5b22aa --- /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 00000000..1e90140a --- /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 00000000..f05f5399 --- /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 00000000..c507e384 --- /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 00000000..b898f600 --- /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 00000000..1dc51dba --- /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 00000000..d9e48a5d --- /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 00000000..3e0707a0 --- /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 00000000..977a4205 --- /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 00000000..b45d22eb --- /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 00000000..5ff6fc0f --- /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 00000000..9c44d2bf --- /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 00000000..894cf199 --- /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 00000000..ac690943 --- /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 00000000..33fba23e --- /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 00000000..f67d41b7 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/InterceptorProcessor.java @@ -0,0 +1,253 @@ +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 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(); + + 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())); + } + } + + 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 00000000..163f7d1e --- /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 00000000..644e8d17 --- /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 00000000..6bc354c4 --- /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 00000000..a2888fe4 --- /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 00000000..448c96c5 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtInvoke.java @@ -0,0 +1,94 @@ +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; + + 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/AtLine.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/interceptor/annotation/AtLine.java new file mode 100644 index 00000000..e5eb9867 --- /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 00000000..b2e81da0 --- /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 00000000..42007b92 --- /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 00000000..d2a7c681 --- /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 00000000..5228c301 --- /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 00000000..4568199d --- /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 00000000..38490683 --- /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 00000000..65bfce33 --- /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 00000000..82603f0d --- /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 00000000..f4c87c17 --- /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 00000000..191cdb0d --- /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 00000000..f1c70344 --- /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 00000000..e28c66eb --- /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 00000000..233ff4d2 --- /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 00000000..e773b781 --- /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 00000000..22da4116 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/EnterLocationMatcher.java @@ -0,0 +1,21 @@ +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; + +public class EnterLocationMatcher implements LocationMatcher { + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode enterInsnNode = methodProcessor.getEnterInsnNode(); + 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 00000000..a1e31865 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExceptionExitLocationMatcher.java @@ -0,0 +1,32 @@ +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.taobao.arthas.bytekit.asm.MethodProcessor; +import com.taobao.arthas.bytekit.asm.TryCatchBlock; +import com.taobao.arthas.bytekit.asm.location.Location.ExceptionExitLocation; + +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); + 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 00000000..b103e138 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/ExitLocationMatcher.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.ExitLocation; + +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)) { + 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 00000000..fa537e8f --- /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 00000000..65a611b0 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/InvokeLocationMatcher.java @@ -0,0 +1,161 @@ +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.Type; +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.MethodProcessor; +import com.taobao.arthas.bytekit.asm.location.Location.InvokeLocation; +import com.taobao.arthas.bytekit.utils.MatchUtils; + +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 class, such as java.* to exclude jdk invoke. + */ + private List excludes = new ArrayList(); + + public InvokeLocationMatcher(String owner, String methodName, String desc, int count, boolean whenComplete, + List excludes) { + super(); + this.owner = owner; + this.methodName = methodName; + this.desc = desc; + this.count = count; + this.whenComplete = whenComplete; + this.excludes = excludes; + } + + 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) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + + int matchedCount = 0; + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + + if (matchCall(methodInsnNode)) { + matchedCount++; + if (count <= 0 || count == matchedCount) { + InvokeLocation invokeLocation = new InvokeLocation(methodInsnNode, count, + whenComplete); + locations.add(invokeLocation); + } + } + } + insnNode = insnNode.getNext(); + } + + return locations; + } + + private boolean matchCall(MethodInsnNode methodInsnNode) { + + if(methodName == null || methodName.isEmpty()) { + return true; + } + + 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 00000000..a4ee285e --- /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 00000000..a4ce75c7 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/Location.java @@ -0,0 +1,646 @@ +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 { + + /** + * 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 = true; + } + + @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; + } + + } + + /** + * 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; + } + } + +} \ 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 00000000..c3774624 --- /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 00000000..0d652802 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/LocationType.java @@ -0,0 +1,86 @@ + +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, + /** + * 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 00000000..77c31c01 --- /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/SyncExitLocationMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/location/SyncExitLocationMatcher.java new file mode 100644 index 00000000..bd38e7de --- /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 00000000..11957312 --- /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 00000000..3b74e920 --- /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 00000000..590f40a7 --- /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/matcher/ClassMatcher.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/asm/matcher/ClassMatcher.java new file mode 100644 index 00000000..e9a4b4fb --- /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 00000000..2edc7248 --- /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 00000000..28c9438e --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AgentUtils.java @@ -0,0 +1,21 @@ +package com.taobao.arthas.bytekit.utils; + +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; + +import net.bytebuddy.agent.ByteBuddyAgent; + +public class AgentUtils { + + private static class InstrumentationHolder { + static final Instrumentation instance = ByteBuddyAgent.install(); + } + + public static void redefine(Class theClass, byte[] theClassFile) + throws ClassNotFoundException, UnmodifiableClassException { + ClassDefinition classDefinition = new ClassDefinition(theClass, theClassFile); + InstrumentationHolder.instance.redefineClasses(classDefinition); + } + +} 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 00000000..327b610a --- /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/AsmOpUtils.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java new file mode 100644 index 00000000..d557c332 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmOpUtils.java @@ -0,0 +1,454 @@ +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 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 00000000..b85ceb7d --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/AsmUtils.java @@ -0,0 +1,499 @@ +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; + +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.ASM7); + 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.ASM7, 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.ASM7, 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, + String desc) { + List result = new ArrayList(); + for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode + .getNext()) { + if (insnNode instanceof MethodInsnNode) { + final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.owner.equals(owner) && methodInsnNode.name.equals(name) + && methodInsnNode.desc.equals(desc)) { + result.add(methodInsnNode); + } + } + } + return result; + } + + public static boolean isStatic(MethodNode methodNode) { + return (methodNode.access & Opcodes.ACC_STATIC) != 0; + } + + 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.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); + + 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/Decompiler.java b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/Decompiler.java new file mode 100644 index 00000000..421e84a0 --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/Decompiler.java @@ -0,0 +1,271 @@ +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.Collections; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.benf.cfr.reader.api.ClassFileSource; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.entities.Method; +import org.benf.cfr.reader.relationship.MemberNameResolver; +import org.benf.cfr.reader.state.ClassFileSourceImpl; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageCollector; +import org.benf.cfr.reader.state.TypeUsageInformation; +import org.benf.cfr.reader.util.AnalysisType; +import org.benf.cfr.reader.util.CannotLoadClassException; +import org.benf.cfr.reader.util.ConfusedCFRException; +import org.benf.cfr.reader.util.ListFactory; +import org.benf.cfr.reader.util.getopt.GetOptParser; +import org.benf.cfr.reader.util.getopt.Options; +import org.benf.cfr.reader.util.getopt.OptionsImpl; +import org.benf.cfr.reader.util.output.Dumper; +import org.benf.cfr.reader.util.output.DumperFactory; +import org.benf.cfr.reader.util.output.DumperFactoryImpl; +import org.benf.cfr.reader.util.output.IllegalIdentifierDump; +import org.benf.cfr.reader.util.output.StreamDumper; +import org.benf.cfr.reader.util.output.ToStringDumper; +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; + +public class Decompiler { + + public static String decompile(byte[] bytecode) throws IOException { + String result = ""; + + File tempDirectory = FileUtils.getTempDirectory(); + File file = new File(tempDirectory, RandomStringUtils.randomAlphabetic(8)); + 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(); + } + + + /** + * @see org.benf.cfr.reader.Main#main(String[]) + * @param classFilePath + * @param methodName + * @return + */ + public static String decompile(String classFilePath, String methodName) { + StringBuilder result = new StringBuilder(8192); + + List argList = new ArrayList(); + argList.add(classFilePath); + if (methodName != null) { + argList.add("--methodname"); + argList.add(methodName); + } + String args[] = argList.toArray(new String[0]); + + GetOptParser getOptParser = new GetOptParser(); + + Options options = null; + List files = null; + try { + Pair processedArgs = getOptParser.parse(args, OptionsImpl.getFactory()); + files = (List) processedArgs.getFirst(); + options = (Options) processedArgs.getSecond(); + } catch (Exception e) { + getOptParser.showHelp(OptionsImpl.getFactory(), e); + System.exit(1); + } + + if ((options.optionIsSet(OptionsImpl.HELP)) || (files.isEmpty())) { + getOptParser.showOptionHelp(OptionsImpl.getFactory(), options, OptionsImpl.HELP); + return ""; + } + + ClassFileSourceImpl classFileSource = new ClassFileSourceImpl(options); + + boolean skipInnerClass = (files.size() > 1) + && (((Boolean) options.getOption(OptionsImpl.SKIP_BATCH_INNER_CLASSES)).booleanValue()); + + Collections.sort(files); + + for (String path : files) { + classFileSource.clearConfiguration(); + DCCommonState dcCommonState = new DCCommonState(options, classFileSource); + DumperFactory dumperFactory = new DumperFactoryImpl(options); + + path = classFileSource.adjustInputPath(path); + + AnalysisType type = (AnalysisType) options.getOption(OptionsImpl.ANALYSE_AS); + if (type == null) + type = dcCommonState.detectClsJar(path); + + if (type == AnalysisType.JAR) { + // doJar(dcCommonState, path, dumperFactory); + } + if (type == AnalysisType.CLASS) + result.append(doClass(dcCommonState, path, skipInnerClass, dumperFactory)); + } + return result.toString(); + } + + public static String doClass(DCCommonState dcCommonState, String path, boolean skipInnerClass, + DumperFactory dumperFactory) { + StringBuilder result = new StringBuilder(8192); + Options options = dcCommonState.getOptions(); + IllegalIdentifierDump illegalIdentifierDump = IllegalIdentifierDump.Factory.get(options); + Dumper d = new ToStringDumper(); + try { + ClassFile c = dcCommonState.getClassFileMaybePath(path); + if ((skipInnerClass) && (c.isInnerClass())) + return ""; + dcCommonState.configureWith(c); + dumperFactory.getProgressDumper().analysingType(c.getClassType()); + try { + c = dcCommonState.getClassFile(c.getClassType()); + } catch (CannotLoadClassException e) { + } + if (((Boolean) options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)).booleanValue()) { + c.loadInnerClasses(dcCommonState); + } + if (((Boolean) options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)).booleanValue()) { + MemberNameResolver.resolveNames(dcCommonState, + ListFactory.newList(dcCommonState.getClassCache().getLoadedTypes())); + } + + c.analyseTop(dcCommonState); + + TypeUsageCollector collectingDumper = new TypeUsageCollector(c); + c.collectTypeUsages(collectingDumper); + + d = new StringDumper(collectingDumper.getTypeUsageInformation(), options, illegalIdentifierDump); + + // d = dumperFactory.getNewTopLevelDumper(c.getClassType(), summaryDumper, + // collectingDumper.getTypeUsageInformation(), illegalIdentifierDump); + + String methname = (String) options.getOption(OptionsImpl.METHODNAME); + if (methname == null) + c.dump(d); + else { + try { + for (Method method : c.getMethodByName(methname)) + method.dump(d, true); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("No such method '" + methname + "'."); + } + } + d.print(""); + result.append(d.toString()); + } catch (ConfusedCFRException e) { + result.append(e.toString()).append("\n"); + for (Object x : e.getStackTrace()) + result.append(x).append("\n"); + } catch (CannotLoadClassException e) { + result.append("Can't load the class specified:").append("\n"); + result.append(e.toString()).append("\n"); + } catch (RuntimeException e) { + result.append(e.toString()).append("\n"); + for (Object x : e.getStackTrace()) + result.append(x).append("\n"); + } finally { + if (d != null) + d.close(); + } + return result.toString(); + } + + public static class StringDumper extends StreamDumper { + private StringWriter sw = new StringWriter(); + + public StringDumper(TypeUsageInformation typeUsageInformation, Options options, + IllegalIdentifierDump illegalIdentifierDump) { + super(typeUsageInformation, options, illegalIdentifierDump); + } + + public void addSummaryError(Method paramMethod, String paramString) { + + } + + public void close() { + try { + sw.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + protected void write(String source) { + sw.write(source); + } + + public String toString() { + return sw.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 00000000..a1526b8f --- /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 00000000..07704052 --- /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 00000000..8023d3b2 --- /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 00000000..b96f30b3 --- /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 00000000..f60bac4b --- /dev/null +++ b/bytekit/src/main/java/com/taobao/arthas/bytekit/utils/VerifyUtils.java @@ -0,0 +1,69 @@ +package com.taobao.arthas.bytekit.utils; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +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) 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(); + + URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); + + @SuppressWarnings("resource") + ClassbyteClassLoader cl = new ClassbyteClassLoader(systemClassLoader.getURLs(), + 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/inst/InstDemo.java b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/inst/InstDemo.java new file mode 100644 index 00000000..2daf425f --- /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 00000000..dc25eb0a --- /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 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.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 00000000..75cfd3ae --- /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 00000000..041d26ea --- /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 00000000..a5d41a27 --- /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 00000000..4410b1a3 --- /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 00000000..5dead641 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtEnterTest.java @@ -0,0 +1,89 @@ +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 + ) { + System.err.println("onEnter, object:" + object); + return 123L; + } + + } + + + + @Test + public void testEnter() throws Exception { + TestHelper helper = TestHelper.builder().interceptorClass(EnterInterceptor.class).methodMatcher("hello") + .redefine(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 00000000..65adc4ad --- /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") + .redefine(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 00000000..74282e6f --- /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") + .redefine(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") + .redefine(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 00000000..ca75af6c --- /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") + .redefine(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 00000000..94067901 --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/AtInvokeTest.java @@ -0,0 +1,101 @@ +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.InvokeArgs Object[] args + ) { + 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 + 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 00000000..c0210078 --- /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("*") + .redefine(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 00000000..27d8ef7c --- /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("*") + .redefine(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 00000000..c9c23c6b --- /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("*") + .redefine(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 00000000..c2d07da5 --- /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") + .redefine(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 00000000..4fd32efa --- /dev/null +++ b/bytekit/src/test/java/com/taobao/arthas/bytekit/asm/interceptor/TestHelper.java @@ -0,0 +1,78 @@ +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; + +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; + } +} 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 00000000..eef1d9ab --- /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 00000000..4e5399ae --- /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() { + + } + +}