diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AccessPoint.java b/core/src/main/java/com/taobao/arthas/core/advisor/AccessPoint.java index 1d361c4edcf..20a6ac9631f 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AccessPoint.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AccessPoint.java @@ -1,7 +1,7 @@ package com.taobao.arthas.core.advisor; public enum AccessPoint { - ACCESS_BEFORE(1, "AtEnter"), ACCESS_AFTER_RETUNING(1 << 1, "AtExit"), ACCESS_AFTER_THROWING(1 << 2, "AtExceptionExit"); + ACCESS_BEFORE(1, "AtEnter"), ACCESS_AFTER_RETUNING(1 << 1, "AtExit"), ACCESS_AFTER_THROWING(1 << 2, "AtExceptionExit"), ACCESS_LOOK_LOCATION(1 << 3, "atLookLocation"); private int value; diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java b/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java index 51f31b9534f..8292812ef95 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/Advice.java @@ -1,5 +1,7 @@ package com.taobao.arthas.core.advisor; +import java.util.Map; + /** * 通知点 Created by vlinux on 15/5/20. */ @@ -12,6 +14,7 @@ public class Advice { private final Object[] params; private final Object returnObj; private final Throwable throwExp; + private final Map varMap; private final boolean isBefore; private final boolean isThrow; private final boolean isReturn; @@ -76,6 +79,7 @@ private Advice( Object[] params, Object returnObj, Throwable throwExp, + Map varMap, int access) { this.loader = loader; this.clazz = clazz; @@ -84,6 +88,7 @@ private Advice( this.params = params; this.returnObj = returnObj; this.throwExp = throwExp; + this.varMap = varMap; isBefore = (access & AccessPoint.ACCESS_BEFORE.getValue()) == AccessPoint.ACCESS_BEFORE.getValue(); isThrow = (access & AccessPoint.ACCESS_AFTER_THROWING.getValue()) == AccessPoint.ACCESS_AFTER_THROWING.getValue(); isReturn = (access & AccessPoint.ACCESS_AFTER_RETUNING.getValue()) == AccessPoint.ACCESS_AFTER_RETUNING.getValue(); @@ -102,6 +107,7 @@ public static Advice newForBefore(ClassLoader loader, params, null, //returnObj null, //throwExp + null,//varMap AccessPoint.ACCESS_BEFORE.getValue() ); } @@ -120,6 +126,7 @@ public static Advice newForAfterReturning(ClassLoader loader, params, returnObj, null, //throwExp + null,//varMap AccessPoint.ACCESS_AFTER_RETUNING.getValue() ); } @@ -138,9 +145,30 @@ public static Advice newForAfterThrowing(ClassLoader loader, params, null, //returnObj throwExp, + null,//varMap AccessPoint.ACCESS_AFTER_THROWING.getValue() ); } + public static Advice newForLooking(ClassLoader loader, + Class clazz, + ArthasMethod method, + Object target, + Object[] params, + Map varMap) { + return new Advice( + loader, + clazz, + method, + target, + params, + null, //returnObj + null, //throwExp + varMap, + AccessPoint.ACCESS_LOOK_LOCATION.getValue() + ); + + } + } diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java index f9e24980014..592fa91b69e 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListener.java @@ -70,4 +70,18 @@ void afterThrowing( Object target, Object[] args, Throwable throwable) throws Throwable; + /** + * look命令中使用,查看本地变量 + * @param clazz 类 + * @param methodName 方法名 + * @param methodDesc 方法描述 + * @param target 目标类实例,若目标为静态方法,则为null + * @param args 参数列表 + * @param lookLocation LineNumber 或 LocationCode + * @param vars 本地变量数组 + * @param varNames 本地变量名数组 + * @throws Throwable 通知过程出错 + */ + void atLookLocation(Class clazz, String methodName, String methodDesc, Object target, Object[] args, String lookLocation, Object[] vars, String[] varNames) throws Throwable; + } diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java index 724414d4373..bb54b7ea3e1 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerAdapter.java @@ -65,6 +65,11 @@ final public void afterThrowing(Class clazz, String methodName, String method throwable); } + @Override + public void atLookLocation(Class clazz, String methodName, String methodDesc, Object target, Object[] args, String lookLocation, Object[] vars, String[] varNames) throws Throwable { + atLookLocation(clazz.getClassLoader(), clazz, new ArthasMethod(clazz, methodName, methodDesc), target, args, lookLocation, vars, varNames); + } + /** * 前置通知 * @@ -106,6 +111,24 @@ public abstract void afterReturning(ClassLoader loader, Class clazz, ArthasMe public abstract void afterThrowing(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, Throwable throwable) throws Throwable; + + /** + * look命令中使用,查看本地变量 + * + * @param loader 类加载器 + * @param clazz 类 + * @param method 方法 + * @param target 目标类实例,若目标为静态方法,则为null + * @param args 参数列表 + * @param location look命令使用的location + * @param vars 本地变量数组 + * @param varNames 本地变量名数组 + * @throws Throwable 通知过程出错 + */ + public void atLookLocation(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, String location, Object[] vars, String[] varNames) throws Throwable{ + //doing nothing by default + } + /** * 判断条件是否满足,满足的情况下需要输出结果 * diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java index 13c1896c4b5..5f4314738fe 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/AdviceListenerManager.java @@ -111,6 +111,10 @@ private String keyForTrace(String className, String owner, String methodName, St return className + owner + methodName + methodDesc; } + private String keyForLook(String className, String lookLocation, String methodName, String methodDesc) { + return className + lookLocation + methodName + methodDesc; + } + public void registerAdviceListener(String className, String methodName, String methodDesc, AdviceListener listener) { synchronized (this) { @@ -162,6 +166,33 @@ public List queryTraceAdviceListeners(String className, String o return listeners; } + + public void registerLookAdviceListener(String className, String lookLocation, String methodName, String methodDesc, + AdviceListener listener) { + + className = className.replace('/', '.'); + String key = keyForLook(className, lookLocation, methodName, methodDesc); + + List listeners = map.get(key); + if (listeners == null) { + listeners = new ArrayList(); + map.put(key, listeners); + } + if (!listeners.contains(listener)) { + listeners.add(listener); + } + } + + public List queryLookAdviceListeners(String className, String lookLocation, String methodName, + String methodDesc) { + className = className.replace('/', '.'); + String key = keyForLook(className, lookLocation, methodName, methodDesc); + + List listeners = map.get(key); + + return listeners; + } + } public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName, @@ -222,6 +253,33 @@ public static List queryTraceAdviceListeners(ClassLoader classLo return null; } + public static void registerLookAdviceListener(ClassLoader classLoader, String className, String lookLocation, + String methodName, String methodDesc, AdviceListener listener) { + classLoader = wrap(classLoader); + className = className.replace('/', '.'); + + ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader); + + if (manager == null) { + manager = new ClassLoaderAdviceListenerManager(); + adviceListenerMap.put(classLoader, manager); + } + manager.registerLookAdviceListener(className, lookLocation, methodName, methodDesc, listener); + } + + public static List queryLookAdviceListeners(ClassLoader classLoader, String className, + String loolLoc, String methodName, String methodDesc) { + classLoader = wrap(classLoader); + className = className.replace('/', '.'); + ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader); + + if (manager != null) { + return manager.queryLookAdviceListeners(className, loolLoc, methodName, methodDesc); + } + + return null; + } + private static ClassLoader wrap(ClassLoader classLoader) { if (classLoader != null) { return classLoader; diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java index 7a0ecc0f5b1..112e66a541b 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/Enhancer.java @@ -21,6 +21,7 @@ import java.util.Set; import java.util.WeakHashMap; +import com.alibaba.bytekit.asm.location.*; import com.alibaba.deps.org.objectweb.asm.ClassReader; import com.alibaba.deps.org.objectweb.asm.Opcodes; import com.alibaba.deps.org.objectweb.asm.Type; @@ -33,9 +34,6 @@ import com.alibaba.bytekit.asm.MethodProcessor; import com.alibaba.bytekit.asm.interceptor.InterceptorProcessor; import com.alibaba.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser; -import com.alibaba.bytekit.asm.location.Location; -import com.alibaba.bytekit.asm.location.LocationType; -import com.alibaba.bytekit.asm.location.MethodInsnNodeWare; import com.alibaba.bytekit.asm.location.filter.GroupLocationFilter; import com.alibaba.bytekit.asm.location.filter.InvokeCheckLocationFilter; import com.alibaba.bytekit.asm.location.filter.InvokeContainLocationFilter; @@ -54,11 +52,9 @@ import com.taobao.arthas.core.advisor.SpyInterceptors.SpyTraceInterceptor2; import com.taobao.arthas.core.advisor.SpyInterceptors.SpyTraceInterceptor3; import com.taobao.arthas.core.server.ArthasBootstrap; -import com.taobao.arthas.core.util.ArthasCheckUtils; -import com.taobao.arthas.core.util.ClassUtils; -import com.taobao.arthas.core.util.FileUtils; -import com.taobao.arthas.core.util.SearchUtils; +import com.taobao.arthas.core.util.*; import com.taobao.arthas.core.util.affect.EnhancerAffect; +import com.taobao.arthas.core.util.look.LookInterceptorParserUtils; import com.taobao.arthas.core.util.matcher.Matcher; /** @@ -71,6 +67,8 @@ public class Enhancer implements ClassFileTransformer { private final AdviceListener listener; private final boolean isTracing; + private boolean isLooking; + private String lookLocation; private final boolean skipJDKTrace; private final Matcher classNameMatcher; private final Matcher classNameExcludeMatcher; @@ -88,18 +86,16 @@ public class Enhancer implements ClassFileTransformer { } /** - * @param adviceId 通知编号 * @param isTracing 可跟踪方法调用 * @param skipJDKTrace 是否忽略对JDK内部方法的跟踪 - * @param matchingClasses 匹配中的类 * @param methodNameMatcher 方法名匹配 - * @param affect 影响统计 */ public Enhancer(AdviceListener listener, boolean isTracing, boolean skipJDKTrace, Matcher classNameMatcher, Matcher classNameExcludeMatcher, Matcher methodNameMatcher) { this.listener = listener; this.isTracing = isTracing; + this.isLooking = false; this.skipJDKTrace = skipJDKTrace; this.classNameMatcher = classNameMatcher; this.classNameExcludeMatcher = classNameExcludeMatcher; @@ -138,21 +134,26 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class // 生成增强字节码 DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser(); + //收集 InterceptorProcessor final List interceptorProcessors = new ArrayList(); - - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor1.class)); - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor2.class)); - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor3.class)); - - if (this.isTracing) { - if (!this.skipJDKTrace) { - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor1.class)); - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor2.class)); - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor3.class)); - } else { - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor1.class)); - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor2.class)); - interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor3.class)); + if (this.isLooking) { + //手动创建一个 InterceptorProcessor + InterceptorProcessor lookInterceptorProcessor = LookInterceptorParserUtils.createLookInterceptorProcessor(this.lookLocation); + interceptorProcessors.add(lookInterceptorProcessor); + } else { + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor1.class)); + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor2.class)); + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyInterceptor3.class)); + if (this.isTracing) { + if (!this.skipJDKTrace) { + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor1.class)); + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor2.class)); + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceInterceptor3.class)); + } else { + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor1.class)); + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor2.class)); + interceptorProcessors.addAll(defaultInterceptorClassParser.parse(SpyTraceExcludeJDKInterceptor3.class)); + } } } @@ -174,36 +175,52 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class // 用于检查是否已插入了 spy函数,如果已有则不重复处理 GroupLocationFilter groupLocationFilter = new GroupLocationFilter(); + if (isLooking) { + LocationFilter locationLineFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atLookLocationLine", LocationType.LINE); + LocationFilter locationCodeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atLookLocationCode", LocationType.USER_DEFINE); + groupLocationFilter.addFilter(locationLineFilter); + groupLocationFilter.addFilter(locationCodeFilter); + } else { + LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter", + LocationType.ENTER); + LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit", + LocationType.EXIT); + LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), + "atExceptionExit", LocationType.EXCEPTION_EXIT); + + groupLocationFilter.addFilter(enterFilter); + groupLocationFilter.addFilter(existFilter); + groupLocationFilter.addFilter(exceptionFilter); + + LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atBeforeInvoke", LocationType.INVOKE); + LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_COMPLETED); + LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), + "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); + + groupLocationFilter.addFilter(invokeBeforeFilter); + groupLocationFilter.addFilter(invokeAfterFilter); + groupLocationFilter.addFilter(invokeExceptionFilter); + } - LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter", - LocationType.ENTER); - LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit", - LocationType.EXIT); - LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), - "atExceptionExit", LocationType.EXCEPTION_EXIT); - - groupLocationFilter.addFilter(enterFilter); - groupLocationFilter.addFilter(existFilter); - groupLocationFilter.addFilter(exceptionFilter); - - LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), - "atBeforeInvoke", LocationType.INVOKE); - LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), - "atInvokeException", LocationType.INVOKE_COMPLETED); - LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class), - "atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT); - groupLocationFilter.addFilter(invokeBeforeFilter); - groupLocationFilter.addFilter(invokeAfterFilter); - groupLocationFilter.addFilter(invokeExceptionFilter); - + //记录location匹配的数量,主要针对的是look命令,因为其它命令似乎是不会失败的,但是look不同,如果没找到对应的插入点,那就没有增强 + //同时在bytekit的LocationMatcher上也进行了处理,之前如果被filter过滤掉的话,是不会返回location的,现在针对特定的Location做了处理 + //即使在LocationFilter被判定为filtered,依旧会返回该location,但是在location中做了标记,根据该标记决定是否增强 + //如果不这样做的话,这里就没法判定该行之前是否有增强过,也就没法判定要不要注册listener + int matchLocationCount = 0; for (MethodNode methodNode : matchedMethods) { if (AsmUtils.isNative(methodNode)) { logger.info("ignore native method: {}", AsmUtils.methodDeclaration(Type.getObjectType(classNode.name), methodNode)); continue; } + // 先查找是否有 atBeforeInvoke 函数,如果有,则说明已经有trace了,则直接不再尝试增强,直接插入 listener - if(AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke")) { + // 这里需要排除下look命令,因为即使进行了trace跟watch,look命令依旧需要增强 + if (AsmUtils.containsMethodInsnNode(methodNode, Type.getInternalName(SpyAPI.class), "atBeforeInvoke") && !isLooking) { for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode .getNext()) { if (insnNode instanceof MethodInsnNode) { @@ -235,6 +252,7 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener); } } + matchLocationCount += locations.size(); } catch (Throwable e) { logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e); @@ -242,10 +260,18 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class } } - // enter/exist 总是要插入 listener - AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc, - listener); - affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc); + if (isLooking){ + if (matchLocationCount > 0){ + AdviceListenerManager.registerLookAdviceListener(inClassLoader, className, + this.lookLocation, methodNode.name, methodNode.desc, listener); + affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc); + } + }else { + // enter/exist 总是要插入 listener + AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc, + listener); + affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc); + } } // https://github.com/alibaba/arthas/issues/1223 , V1_5 的major version是49 @@ -262,7 +288,11 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class dumpClassIfNecessary(className, enhanceClassByteArray, affect); // 成功计数 - affect.cCnt(1); + if (isLooking) { + if (matchLocationCount > 0) affect.cCnt(1); + } else { + affect.cCnt(1); + } return enhanceClassByteArray; } catch (Throwable t) { @@ -273,6 +303,22 @@ public byte[] transform(final ClassLoader inClassLoader, String className, Class return null; } + public boolean isLooking() { + return isLooking; + } + + public void setLooking(boolean looking) { + isLooking = looking; + } + + public String getLookLocation() { + return lookLocation; + } + + public void setLookLocation(String lookLocation) { + this.lookLocation = lookLocation; + } + /** * 是否抽象属性 */ @@ -413,8 +459,11 @@ private static Pair isUnsupportedClass(Class clazz) { * @throws UnmodifiableClassException 增强失败 */ public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNumOfMatchedClass) throws UnmodifiableClassException { + //判定是否需要增强子类,look命令对子类是没有意义的,所以需要做前置判定 + boolean isDisableSubClass = isLooking || GlobalOptions.isDisableSubClass; + // 获取需要增强的类集合 - this.matchingClasses = GlobalOptions.isDisableSubClass + this.matchingClasses = isDisableSubClass ? SearchUtils.searchClass(inst, classNameMatcher) : SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher)); @@ -435,7 +484,7 @@ public synchronized EnhancerAffect enhance(final Instrumentation inst, int maxNu affect.setTransformer(this); try { - ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing); + ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing, isLooking); // 批量增强 if (GlobalOptions.isBatchReTransform) { diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java index 8cf54962326..c3f8cd41689 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/SpyImpl.java @@ -174,6 +174,30 @@ public void atInvokeException(Class clazz, String invokeInfo, Object target, } } + @Override + public void atLookLocation(Class clazz, String methodInfo, Object target, Object[] args, String lookLocation, Object[] vars, String[] varNames) { + ClassLoader classLoader = clazz.getClassLoader(); + + String[] info = StringUtils.splitMethodInfo(methodInfo); + String methodName = info[0]; + String methodDesc = info[1]; + + List listeners = AdviceListenerManager.queryLookAdviceListeners(classLoader, clazz.getName(), lookLocation, + methodName, methodDesc); + if (listeners != null) { + for (AdviceListener adviceListener : listeners) { + try { + if (skipAdviceListener(adviceListener)) { + continue; + } + adviceListener.atLookLocation(clazz, methodName, methodDesc, target, args, lookLocation, vars, varNames); + } catch (Throwable e) { + logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e); + } + } + } + } + private static boolean skipAdviceListener(AdviceListener adviceListener) { if (adviceListener instanceof ProcessAware) { ProcessAware processAware = (ProcessAware) adviceListener; diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/SpyInterceptors.java b/core/src/main/java/com/taobao/arthas/core/advisor/SpyInterceptors.java index 027d1044dba..77b4a825cde 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/SpyInterceptors.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/SpyInterceptors.java @@ -8,6 +8,7 @@ import com.alibaba.bytekit.asm.interceptor.annotation.AtExit; import com.alibaba.bytekit.asm.interceptor.annotation.AtInvoke; import com.alibaba.bytekit.asm.interceptor.annotation.AtInvokeException; +import com.taobao.arthas.core.util.look.LookUtils; /** * @@ -87,6 +88,33 @@ public static void onInvokeException(@Binding.This Object target, @Binding.Class } } + /** + * 为什么要用两个"一模一样"的方法? + * 因为在避免重复增强时,需要有标记作为判定是否重复的依据,而look入参数有 LineNumber 和 LocationCode ,所以这个处理起来会非常麻烦 + * 并且 LineNumber 和 LocationCode 不能一对一映射(匹配方法退出时) + */ + public static class SpyLookInterceptor { + + public static void atLookLocationCode(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, + //转成binding之后再赋值,这里默认是空串 + @Binding.StringValue("LocationPlaceholder") String location, + @Binding.LocalVars(excludePattern = LookUtils.LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER) Object[] vars, + @Binding.LocalVarNames(excludePattern = LookUtils.LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER) String[] varNames) { + SpyAPI.atLookLocationCode(clazz, methodInfo, target, args, location, vars, varNames); + } + + public static void atLookLocationLine(@Binding.This Object target, @Binding.Class Class clazz, + @Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, + //转成binding之后再赋值,这里默认是空串 + @Binding.StringValue("LocationPlaceholder") String location, + @Binding.LocalVars(excludePattern = LookUtils.LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER) Object[] vars, + @Binding.LocalVarNames(excludePattern = LookUtils.LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER) String[] varNames) { + SpyAPI.atLookLocationLine(clazz, methodInfo, target, args, location, vars, varNames); + } + + } + public static class SpyTraceExcludeJDKInterceptor1 { @AtInvoke(name = "", inline = true, whenComplete = false, excludes = "java.**") public static void onInvoke(@Binding.This Object target, @Binding.Class Class clazz, diff --git a/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java index 5f0947598fe..1ae284a8164 100644 --- a/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java +++ b/core/src/main/java/com/taobao/arthas/core/advisor/TransformerManager.java @@ -23,7 +23,8 @@ public class TransformerManager { private Instrumentation instrumentation; private List watchTransformers = new CopyOnWriteArrayList(); private List traceTransformers = new CopyOnWriteArrayList(); - + private List lookTransformers = new CopyOnWriteArrayList(); + /** * 先于 watch/trace的 Transformer TODO 改进为全部用 order 排序? */ @@ -55,6 +56,14 @@ public byte[] transform(ClassLoader loader, String className, Class classBein } } + for (ClassFileTransformer classFileTransformer : lookTransformers) { + byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined, + protectionDomain, classfileBuffer); + if (transformResult != null) { + classfileBuffer = transformResult; + } + } + for (ClassFileTransformer classFileTransformer : traceTransformers) { byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer); @@ -70,9 +79,11 @@ public byte[] transform(ClassLoader loader, String className, Class classBein instrumentation.addTransformer(classFileTransformer, true); } - public void addTransformer(ClassFileTransformer transformer, boolean isTracing) { + public void addTransformer(ClassFileTransformer transformer, boolean isTracing, boolean isLooking) { if (isTracing) { traceTransformers.add(transformer); + } else if (isLooking) { + lookTransformers.add(transformer); } else { watchTransformers.add(transformer); } @@ -86,12 +97,14 @@ public void removeTransformer(ClassFileTransformer transformer) { reTransformers.remove(transformer); watchTransformers.remove(transformer); traceTransformers.remove(transformer); + lookTransformers.remove(transformer); } public void destroy() { reTransformers.clear(); watchTransformers.clear(); traceTransformers.clear(); + lookTransformers.clear(); instrumentation.removeTransformer(classFileTransformer); } diff --git a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java index 262793812e1..07752c766e2 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java +++ b/core/src/main/java/com/taobao/arthas/core/command/BuiltinCommandPack.java @@ -20,20 +20,7 @@ import com.taobao.arthas.core.command.klass100.SearchClassCommand; import com.taobao.arthas.core.command.klass100.SearchMethodCommand; import com.taobao.arthas.core.command.logger.LoggerCommand; -import com.taobao.arthas.core.command.monitor200.DashboardCommand; -import com.taobao.arthas.core.command.monitor200.HeapDumpCommand; -import com.taobao.arthas.core.command.monitor200.JvmCommand; -import com.taobao.arthas.core.command.monitor200.MBeanCommand; -import com.taobao.arthas.core.command.monitor200.MemoryCommand; -import com.taobao.arthas.core.command.monitor200.MonitorCommand; -import com.taobao.arthas.core.command.monitor200.PerfCounterCommand; -import com.taobao.arthas.core.command.monitor200.ProfilerCommand; -import com.taobao.arthas.core.command.monitor200.StackCommand; -import com.taobao.arthas.core.command.monitor200.ThreadCommand; -import com.taobao.arthas.core.command.monitor200.TimeTunnelCommand; -import com.taobao.arthas.core.command.monitor200.TraceCommand; -import com.taobao.arthas.core.command.monitor200.VmToolCommand; -import com.taobao.arthas.core.command.monitor200.WatchCommand; +import com.taobao.arthas.core.command.monitor200.*; import com.taobao.arthas.core.shell.command.AnnotatedCommand; import com.taobao.arthas.core.shell.command.Command; import com.taobao.arthas.core.shell.command.CommandResolver; @@ -105,6 +92,7 @@ private void initCommands(List disabledCommands) { commandClassList.add(ProfilerCommand.class); commandClassList.add(VmToolCommand.class); commandClassList.add(StopCommand.class); + commandClassList.add(LookCommand.class); try { if (ClassLoader.getSystemClassLoader().getResource("jdk/jfr/Recording.class") != null) { commandClassList.add(JFRCommand.class); diff --git a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java index f218e873e1d..4d4e7b4a2c2 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/klass100/JadCommand.java @@ -2,13 +2,13 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.alibaba.bytekit.utils.AsmUtils; +import com.alibaba.deps.org.objectweb.asm.tree.ClassNode; +import com.alibaba.deps.org.objectweb.asm.tree.MethodNode; +import com.taobao.arthas.common.FileUtils; import com.taobao.arthas.common.Pair; import com.taobao.arthas.core.command.Constants; -import com.taobao.arthas.core.command.model.ClassVO; -import com.taobao.arthas.core.command.model.ClassLoaderVO; -import com.taobao.arthas.core.command.model.JadModel; -import com.taobao.arthas.core.command.model.MessageModel; -import com.taobao.arthas.core.command.model.RowAffectModel; +import com.taobao.arthas.core.command.model.*; import com.taobao.arthas.core.shell.cli.Completion; import com.taobao.arthas.core.shell.cli.CompletionUtils; import com.taobao.arthas.core.shell.command.AnnotatedCommand; @@ -21,6 +21,7 @@ import com.taobao.arthas.core.util.InstrumentationUtils; import com.taobao.arthas.core.util.SearchUtils; import com.taobao.arthas.core.util.affect.RowAffect; +import com.taobao.arthas.core.util.look.LookUtils; import com.taobao.middleware.cli.annotations.Argument; import com.taobao.middleware.cli.annotations.DefaultValue; import com.taobao.middleware.cli.annotations.Description; @@ -30,12 +31,7 @@ import java.io.File; import java.lang.instrument.Instrumentation; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Set; -import java.util.Collection; +import java.util.*; import java.util.regex.Pattern; /** @@ -68,6 +64,8 @@ public class JadCommand extends AnnotatedCommand { */ private boolean sourceOnly = false; + private boolean displayLookLocation = false; + @Argument(argName = "class-pattern", index = 0) @Description("Class name pattern, use either '.' or '/' as separator") public void setClassPattern(String classPattern) { @@ -118,6 +116,12 @@ public void setLineNumber(boolean lineNumber) { this.lineNumber = lineNumber; } + @Option(shortName = "L", longName = "displayLookLocation", flag = true) + @Description("Output the look location list, default value false") + public void setLookLocation(boolean displayLookLocation) { + this.displayLookLocation = displayLookLocation; + } + @Override public void process(CommandProcess process) { RowAffect affect = new RowAffect(); @@ -196,6 +200,16 @@ private ExitStatus processExactMatch(CommandProcess process, RowAffect affect, I } process.appendResult(jadModel); + //简单处理 + if (displayLookLocation){ + ClassNode classNode = AsmUtils.toClassNode(FileUtils.readFileToByteArray(classFile)); + for (MethodNode methodNode : classNode.methods) { + if (methodNode.name.equals(methodName)){ + process.appendResult(new EchoModel(LookUtils.renderMethodLocation(methodNode))); + } + } + } + affect.rCnt(classFiles.keySet().size()); return ExitStatus.success(); } catch (Throwable t) { diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/LookModel.java b/core/src/main/java/com/taobao/arthas/core/command/model/LookModel.java new file mode 100644 index 00000000000..d056a062b10 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/LookModel.java @@ -0,0 +1,74 @@ +package com.taobao.arthas.core.command.model; + +import java.util.Date; + +/** + * Look command result model + * + */ +public class LookModel extends ResultModel { + + private Date ts; + private ObjectVO value; + + private Integer sizeLimit; + private String className; + private String methodName; + private String accessPoint; + + public LookModel() { + } + + @Override + public String getType() { + return "look"; + } + + public Date getTs() { + return ts; + } + + public void setTs(Date ts) { + this.ts = ts; + } + + public ObjectVO getValue() { + return value; + } + + public void setValue(ObjectVO value) { + this.value = value; + } + + public void setSizeLimit(Integer sizeLimit) { + this.sizeLimit = sizeLimit; + } + + public Integer getSizeLimit() { + return sizeLimit; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getMethodName() { + return methodName; + } + + public void setMethodName(String methodName) { + this.methodName = methodName; + } + + public String getAccessPoint() { + return accessPoint; + } + + public void setAccessPoint(String accessPoint) { + this.accessPoint = accessPoint; + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/model/LookView.java b/core/src/main/java/com/taobao/arthas/core/command/model/LookView.java new file mode 100644 index 00000000000..f9c6b9a5f04 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/model/LookView.java @@ -0,0 +1,23 @@ +package com.taobao.arthas.core.command.model; + +import com.taobao.arthas.core.command.view.ResultView; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.DateUtils; +import com.taobao.arthas.core.util.StringUtils; +import com.taobao.arthas.core.view.ObjectView; + +/** + * Term view for LookModel + * + */ +public class LookView extends ResultView { + + @Override + public void draw(CommandProcess process, LookModel model) { + ObjectVO objectVO = model.getValue(); + String result = StringUtils.objectToString( + objectVO.needExpand() ? new ObjectView(model.getSizeLimit(), objectVO).draw() : objectVO.getObject()); + process.write("method=" + model.getClassName() + "." + model.getMethodName() + " location=" + model.getAccessPoint() + "\n"); + process.write("ts=" + DateUtils.formatDate(model.getTs()) + "; result=" + result + "\n"); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java index 48615a19f5d..33acd00f36f 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/EnhancerCommand.java @@ -37,6 +37,9 @@ public abstract class EnhancerCommand extends AnnotatedCommand { protected static final List EMPTY = Collections.emptyList(); public static final String[] EXPRESS_EXAMPLES = { "params", "returnObj", "throwExp", "target", "clazz", "method", "{params,returnObj}", "params[0]" }; + + public static final String[] LOOK_EXPRESS_EXAMPLES = {"target", "clazz", "method", "params", "varMap"}; + private String excludeClassPattern; protected Matcher classNameMatcher; @@ -168,6 +171,11 @@ protected void enhance(CommandProcess process) { } Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getClassNameExcludeMatcher(), getMethodNameMatcher()); + if(listener instanceof LookAdviceListener) { + String lookLocation = ((LookAdviceListener) listener).getCommand().getLocation(); + enhancer.setLooking(true); + enhancer.setLookLocation(lookLocation); + } // 注册通知监听器 process.register(listener, enhancer); effect = enhancer.enhance(inst, this.maxNumOfMatchedClass); diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/LookAdviceListener.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/LookAdviceListener.java new file mode 100644 index 00000000000..2eb38588460 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/LookAdviceListener.java @@ -0,0 +1,132 @@ +package com.taobao.arthas.core.command.monitor200; + +import com.alibaba.arthas.deps.org.slf4j.Logger; +import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.core.advisor.AccessPoint; +import com.taobao.arthas.core.advisor.Advice; +import com.taobao.arthas.core.advisor.AdviceListenerAdapter; +import com.taobao.arthas.core.advisor.ArthasMethod; +import com.taobao.arthas.core.command.express.ExpressException; +import com.taobao.arthas.core.command.express.ExpressFactory; +import com.taobao.arthas.core.command.model.LookModel; +import com.taobao.arthas.core.command.model.ObjectVO; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.LogUtil; +import com.taobao.arthas.core.util.StringUtils; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +class LookAdviceListener extends AdviceListenerAdapter { + + private static final Logger logger = LoggerFactory.getLogger(LookAdviceListener.class); + private LookCommand command; + private CommandProcess process; + + private static final String VARIABLE_RENAME = "-renamed-"; + + private static final String EXCLUDE_VARIABLE_THIS = "this"; + + public LookAdviceListener(LookCommand command, CommandProcess process, boolean verbose) { + this.command = command; + this.process = process; + super.setVerbose(verbose); + } + + @Override + public void before(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args) + throws Throwable { + + } + + @Override + public void afterReturning(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, + Object returnObject) throws Throwable { + + } + + @Override + public void afterThrowing(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, + Throwable throwable) { + + } + + /** + * 解析变量名 + * 处理重复的变量名 + */ + private String determinedVarName(Set nameSet, String varName) { + String tmpVarName = varName; + for (int i = 1; i < Integer.MAX_VALUE; i++) { + if (nameSet.contains(tmpVarName)) { + tmpVarName = varName + VARIABLE_RENAME + i; + } else { + return tmpVarName; + } + } + throw new IllegalArgumentException("illegal varName:" + varName); + } + + @Override + public void atLookLocation(ClassLoader loader, Class clazz, ArthasMethod method, Object target, Object[] args, String location, Object[] vars, String[] varNames) throws Throwable { + try { + + Map varMap = new HashMap(vars.length); + for (int i = 0; i < vars.length; i++) { + //不放入this + if (EXCLUDE_VARIABLE_THIS.equals(varNames[i])) continue; + String varName = determinedVarName(varMap.keySet(), varNames[i]); + varMap.put(varName, vars[i]); + } + + Advice advice = Advice.newForLooking(loader, clazz, method, target, args, varMap); + boolean conditionResult = isConditionMet(command.getConditionExpress(), advice); + if (this.isVerbose()) { + process.write("Condition express: " + command.getConditionExpress() + " , result: " + conditionResult + "\n"); + } + if (conditionResult) { + Object value = getExpressionResult(command.getExpress(), advice); + + LookModel model = new LookModel(); + model.setTs(new Date()); + model.setValue(new ObjectVO(value, command.getExpand())); + model.setSizeLimit(command.getSizeLimit()); + model.setClassName(advice.getClazz().getName()); + model.setMethodName(advice.getMethod().getName()); + model.setAccessPoint(AccessPoint.ACCESS_LOOK_LOCATION.getKey() + ":" + location); + + process.appendResult(model); + process.times().incrementAndGet(); + if (isLimitExceeded(command.getNumberOfLimit(), process.times().get())) { + abortProcess(process, command.getNumberOfLimit()); + } + } + } catch (Throwable e) { + logger.warn("look failed.", e); + process.end(-1, "look failed, condition is: " + command.getConditionExpress() + ", express is: " + + command.getExpress() + ", " + e.getMessage() + ", visit " + LogUtil.loggingFile() + + " for more details."); + } + } + + boolean isConditionMet(String conditionExpress, Advice advice) throws ExpressException { + return StringUtils.isEmpty(conditionExpress) + || ExpressFactory.threadLocalExpress(advice).is(conditionExpress); + + } + + Object getExpressionResult(String express, Advice advice) throws ExpressException { + return ExpressFactory.threadLocalExpress(advice).get(express); + } + + public LookCommand getCommand() { + return command; + } + + public void setCommand(LookCommand command) { + this.command = command; + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/monitor200/LookCommand.java b/core/src/main/java/com/taobao/arthas/core/command/monitor200/LookCommand.java new file mode 100644 index 00000000000..3aa92d9e78b --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/command/monitor200/LookCommand.java @@ -0,0 +1,202 @@ +package com.taobao.arthas.core.command.monitor200; + +import com.taobao.arthas.core.GlobalOptions; +import com.taobao.arthas.core.advisor.AdviceListener; +import com.taobao.arthas.core.command.Constants; +import com.taobao.arthas.core.shell.cli.Completion; +import com.taobao.arthas.core.shell.cli.CompletionUtils; +import com.taobao.arthas.core.shell.command.CommandProcess; +import com.taobao.arthas.core.util.SearchUtils; +import com.taobao.arthas.core.util.look.LookUtils; +import com.taobao.arthas.core.util.matcher.Matcher; +import com.taobao.arthas.core.view.ObjectView; +import com.taobao.middleware.cli.annotations.*; + +import java.arthas.SpyAPI; +import java.lang.reflect.Method; +import java.util.Arrays; + +@Name("look") +@Summary("Display the local variables, input parameter of method specified with LineNumber or LocationCode(found in jad command)") +@Description( + " The express may be one of the following expression:\n" + + " target : the object\n" + + " clazz : the object's class\n" + + " method : the constructor or method\n" + + " params : the parameters array of method\n" + + " params[0..n] : the element of parameters array\n" + + " varMap : the local variables map\n" + + " varMap[\"varName\"] : the local variable value of varName\n" + + "\nExamples:\n" + + " look com.arthas.controller.Controller doSomething -1 \n" + + " look com.arthas.controller.Controller doSomething -1 'varMap'\n" + + " look com.arthas.controller.Controller doSomething 35 'varMap' 'varMap[\"varName\"].equals(\"123\")'\n" + + " look OuterClass$InnerClass doSomething 128 '{params,varMap}' \n" + + " look com.arthas.controller.Controller doSomething abcd-1 'varMap'\n" + + Constants.WIKI + Constants.WIKI_HOME + "look") + +public class LookCommand extends EnhancerCommand { + + private String classPattern; + private String methodPattern; + private String express; + private String location; + private String conditionExpress; + private Integer expand = 1; + private Integer sizeLimit = 10 * 1024 * 1024; + private boolean isRegEx = false; + private int numberOfLimit = 100; + + @Argument(index = 0, argName = "class-pattern") + @Description("The full qualified class name you want to look") + public void setClassPattern(String classPattern) { + this.classPattern = classPattern; + } + + @Argument(index = 1, argName = "method-pattern") + @Description("The method name you want to look in") + public void setMethodPattern(String methodPattern) { + this.methodPattern = methodPattern; + } + + @Argument(index = 2, argName = "location") + @Description("The location will be look before(LineNumber) or after(LocationCode).") + public void setLocation(String location) { + this.location = location; + } + + @Argument(index = 3, argName = "express", required = false) + @DefaultValue("varMap") + @Description("The content you want to look, written by ognl. Default value is 'varMap'\n") + public void setExpress(String express) { + this.express = express; + } + + @Argument(index = 4, argName = "condition-express", required = false) + @Description(Constants.CONDITION_EXPRESS) + public void setConditionExpress(String conditionExpress) { + this.conditionExpress = conditionExpress; + } + + @Option(shortName = "M", longName = "sizeLimit") + @Description("Upper size limit in bytes for the result (10 * 1024 * 1024 by default)") + public void setSizeLimit(Integer sizeLimit) { + this.sizeLimit = sizeLimit; + } + + @Option(shortName = "x", longName = "expand") + @Description("Expand level of object (1 by default), the max value is " + ObjectView.MAX_DEEP) + public void setExpand(Integer expand) { + this.expand = expand; + } + + @Option(shortName = "E", longName = "regex", flag = true) + @Description("Enable regular expression to match (wildcard matching by default)") + public void setRegEx(boolean regEx) { + isRegEx = regEx; + } + + @Option(shortName = "n", longName = "limits") + @Description("Threshold of execution times") + public void setNumberOfLimit(int numberOfLimit) { + this.numberOfLimit = numberOfLimit; + } + + public String getClassPattern() { + return classPattern; + } + + public String getMethodPattern() { + return methodPattern; + } + + public String getExpress() { + return express; + } + + public String getConditionExpress() { + return conditionExpress; + } + + public Integer getExpand() { + return expand; + } + + public Integer getSizeLimit() { + return sizeLimit; + } + + public boolean isRegEx() { + return isRegEx; + } + + public int getNumberOfLimit() { + return numberOfLimit; + } + + public String getLocation() { + return location; + } + + @Override + protected Matcher getClassNameMatcher() { + if (classNameMatcher == null) { + classNameMatcher = SearchUtils.classNameMatcher(getClassPattern(), isRegEx()); + } + return classNameMatcher; + } + + @Override + protected Matcher getClassNameExcludeMatcher() { + if (classNameExcludeMatcher == null && getExcludeClassPattern() != null) { + classNameExcludeMatcher = SearchUtils.classNameMatcher(getExcludeClassPattern(), isRegEx()); + } + return classNameExcludeMatcher; + } + + @Override + protected Matcher getMethodNameMatcher() { + if (methodNameMatcher == null) { + methodNameMatcher = SearchUtils.classNameMatcher(getMethodPattern(), isRegEx()); + } + return methodNameMatcher; + } + + @Override + protected AdviceListener getAdviceListener(CommandProcess process) { + return new LookAdviceListener(this, process, GlobalOptions.verbose || this.verbose); + } + + /** + * 命令补齐 + */ + @Override + protected void completeArgument3(Completion completion) { + CompletionUtils.complete(completion, Arrays.asList(EnhancerCommand.LOOK_EXPRESS_EXAMPLES)); + } + + @Override + public void process(final CommandProcess process) { + //兼容旧版,如果没有那些方法,就不要用这个命令 + boolean hasLookMethod = false; + try { + Class clazz = Class.forName("java.arthas.SpyAPI"); // 加载不到会抛异常 + for (Method declaredMethod : clazz.getDeclaredMethods()) { + if (declaredMethod.getName().equals("atLookLocationCode")){ + hasLookMethod = true; + break; + } + } + } catch (Throwable e) { + // ignore + } + if (!hasLookMethod){ + throw new IllegalArgumentException("this version not support look command!"); + } + //check arg,只是简单的格式校验 + if (!LookUtils.validLocation(location)) { + throw new IllegalArgumentException("location is invalid! " + location); + } + super.process(process); + } +} diff --git a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java index 947b60a1ee6..13004f702b3 100644 --- a/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java +++ b/core/src/main/java/com/taobao/arthas/core/command/view/ResultViewResolver.java @@ -2,6 +2,7 @@ import com.alibaba.arthas.deps.org.slf4j.Logger; import com.alibaba.arthas.deps.org.slf4j.LoggerFactory; +import com.taobao.arthas.core.command.model.LookView; import com.taobao.arthas.core.command.model.ResultModel; import com.taobao.arthas.core.shell.command.CommandProcess; @@ -80,6 +81,7 @@ private void initResultViews() { registerView(WatchView.class); registerView(VmToolView.class); registerView(JFRView.class); + registerView(LookView.class); } catch (Throwable e) { logger.error("register result view failed", e); diff --git a/core/src/main/java/com/taobao/arthas/core/util/EncryptUtils.java b/core/src/main/java/com/taobao/arthas/core/util/EncryptUtils.java new file mode 100644 index 00000000000..9dba716b11d --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/util/EncryptUtils.java @@ -0,0 +1,48 @@ +package com.taobao.arthas.core.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * 加密算法相关工具 + */ +public abstract class EncryptUtils { + + private static final String MD5_ALGORITHM_NAME = "MD5"; + + private static final char[] HEX_CHARS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + + /** + * md5 as hex + */ + public static String md5DigestAsHex(byte[] bytes) { + MessageDigest messageDigest; + try { + messageDigest = MessageDigest.getInstance(MD5_ALGORITHM_NAME); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("Could not find MessageDigest with algorithm \"" + MD5_ALGORITHM_NAME + "\"", ex); + } + byte[] digest = messageDigest.digest(bytes); + char[] hexDigest = encodeHex(digest); + return new String(hexDigest); + } + + /** + * bytes转换成hex + */ + private static char[] encodeHex(byte[] bytes) { + char[] chars = new char[32]; + for (int i = 0; i < chars.length; i = i + 2) { + byte b = bytes[i / 2]; + chars[i] = HEX_CHARS[(b >>> 0x4) & 0xf]; + chars[i + 1] = HEX_CHARS[b & 0xf]; + } + return chars; + } + +} + + + diff --git a/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java b/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java index d243687b3bc..a50eb171aa1 100644 --- a/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java +++ b/core/src/main/java/com/taobao/arthas/core/util/StringUtils.java @@ -994,4 +994,21 @@ public static List toStringList(URL[] urls) { } return Collections.emptyList(); } + + /** + * 判断字符串是否全为数字 + */ + public static boolean isNumeric(final CharSequence cs) { + if (isEmpty(cs)) { + return false; + } + final int sz = cs.length(); + for (int i = 0; i < sz; i++) { + if (!Character.isDigit(cs.charAt(i))) { + return false; + } + } + return true; + } + } diff --git a/core/src/main/java/com/taobao/arthas/core/util/look/LookInterceptorParserUtils.java b/core/src/main/java/com/taobao/arthas/core/util/look/LookInterceptorParserUtils.java new file mode 100644 index 00000000000..f9b978a5b67 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/util/look/LookInterceptorParserUtils.java @@ -0,0 +1,199 @@ +package com.taobao.arthas.core.util.look; + +import com.alibaba.bytekit.asm.MethodProcessor; +import com.alibaba.bytekit.asm.binding.Binding; +import com.alibaba.bytekit.asm.binding.StringBinding; +import com.alibaba.bytekit.asm.interceptor.InterceptorMethodConfig; +import com.alibaba.bytekit.asm.interceptor.InterceptorProcessor; +import com.alibaba.bytekit.asm.interceptor.annotation.BindingParserUtils; +import com.alibaba.bytekit.asm.location.Location; +import com.alibaba.bytekit.asm.location.LocationMatcher; +import com.alibaba.bytekit.asm.location.LocationType; +import com.alibaba.bytekit.asm.location.filter.LocationFilter; +import com.alibaba.bytekit.utils.ReflectionUtils; +import com.alibaba.deps.org.objectweb.asm.Opcodes; +import com.alibaba.deps.org.objectweb.asm.Type; +import com.alibaba.deps.org.objectweb.asm.tree.AbstractInsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.InsnNode; +import com.alibaba.deps.org.objectweb.asm.tree.LineNumberNode; +import com.taobao.arthas.core.advisor.SpyInterceptors; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class LookInterceptorParserUtils { + + public static InterceptorProcessor createLookInterceptorProcessor(String lookLocation) { + Method method = ReflectionUtils.findMethod(SpyInterceptors.SpyLookInterceptor.class, "atLookLocationLine", null); + if (LookUtils.isLocationCode(lookLocation)) { + method = ReflectionUtils.findMethod(SpyInterceptors.SpyLookInterceptor.class, "atLookLocationCode", null); + } + + InterceptorProcessor interceptorProcessor = new InterceptorProcessor(method.getDeclaringClass().getClassLoader()); + + //locationMatcher + if (LookUtils.isLocationCode(lookLocation)) { + interceptorProcessor.setLocationMatcher(new LookLocationCodeMatcher(lookLocation)); + } else { + interceptorProcessor.setLocationMatcher(new LookLineNumberMatcher(lookLocation)); + } + + //interceptorMethodConfig + InterceptorMethodConfig interceptorMethodConfig = new InterceptorMethodConfig(); + interceptorProcessor.setInterceptorMethodConfig(interceptorMethodConfig); + interceptorMethodConfig.setOwner(Type.getInternalName(method.getDeclaringClass())); + interceptorMethodConfig.setMethodName(method.getName()); + interceptorMethodConfig.setMethodDesc(Type.getMethodDescriptor(method)); + + //inline + interceptorMethodConfig.setInline(true); + + //bindings + List bindings = BindingParserUtils.parseBindings(method); + for (Binding binding : bindings) { + //因为注解值不能动态变化,所以需要在这里进行重新赋值,其实在这里生成binding也可以,但是方法注解会比较方便 + if (binding instanceof StringBinding) { + StringBinding stringBinding = (StringBinding) binding; + if (stringBinding.getValue().equals("LocationPlaceholder")) { + stringBinding.setValue(lookLocation); + } + } + } + interceptorMethodConfig.setBindings(bindings); + + return interceptorProcessor; + } + + /** + * 定义一个LookLocation + * 为什么?因为其它已经定义的好的Location不太适用 + */ + public static class LookLocation extends Location { + + private String location; + + public LookLocation(AbstractInsnNode insnNode, String location, boolean whenComplete, boolean filtered) { + super(insnNode, whenComplete, filtered); + this.location = location; + } + + @Override + public LocationType getLocationType() { + return LocationType.USER_DEFINE; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + } + + + /** + * 行号匹配 + */ + public static class LookLineNumberMatcher implements LocationMatcher { + + /** + * 目标行号 + * -1则代表方法的退出之前 + */ + private Integer targetLine; + + public LookLineNumberMatcher(String locationCode) { + this.targetLine = Integer.valueOf(locationCode); + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + AbstractInsnNode insnNode = methodProcessor.getEnterInsnNode(); + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + + while (insnNode != null) { + + if (targetLine == -1) { + //匹配方法退出之前,可能会有多个return语句 + if (insnNode instanceof InsnNode) { + InsnNode node = (InsnNode) insnNode; + if (matchExit(node)) { + //行前匹配 + boolean filtered = !locationFilter.allow(node, LocationType.LINE, false); + Location location = new LookLocation(node, targetLine.toString(), false, filtered); + locations.add(location); + } + } + } else { + //匹配具体的行 + if (insnNode instanceof LineNumberNode) { + LineNumberNode lineNumberNode = (LineNumberNode) insnNode; + if (matchLine(lineNumberNode.line)) { + //行前匹配 + boolean filtered = !locationFilter.allow(lineNumberNode, LocationType.LINE, false); + //目前因为如果直接返回lineNumberNode,增强完之后会导致行号丢失,暂时没找到原因,因此取上一个节点 + Location location = new LookLocation(lineNumberNode.getPrevious(), targetLine.toString(), false, filtered); + locations.add(location); + //存在一个方法内会有多个相同行号的情况,这里只取第一个 + break; + } + } + } + insnNode = insnNode.getNext(); + } + return locations; + } + + private boolean matchLine(int line) { + return line == targetLine; + } + + 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; + } + + } + + + /** + * 位置代码匹配 + */ + public static class LookLocationCodeMatcher implements LocationMatcher { + + private String locationCode = ""; + + public LookLocationCodeMatcher(String locationCode) { + this.locationCode = locationCode; + } + + @Override + public List match(MethodProcessor methodProcessor) { + List locations = new ArrayList(); + LocationFilter locationFilter = methodProcessor.getLocationFilter(); + + AbstractInsnNode insnNode = LookUtils.findInsnNodeByLocationCode(methodProcessor.getMethodNode(), locationCode); + if (insnNode == null) { + throw new IllegalStateException("invalid locationCode:" + locationCode); + } + boolean filtered = !locationFilter.allow(insnNode, LocationType.USER_DEFINE, false); + Location location = new LookLocation(insnNode, locationCode, false, filtered); + locations.add(location); + return locations; + } + + } + +} diff --git a/core/src/main/java/com/taobao/arthas/core/util/look/LookUtils.java b/core/src/main/java/com/taobao/arthas/core/util/look/LookUtils.java new file mode 100644 index 00000000000..074181f3555 --- /dev/null +++ b/core/src/main/java/com/taobao/arthas/core/util/look/LookUtils.java @@ -0,0 +1,304 @@ +package com.taobao.arthas.core.util.look; + +import com.alibaba.bytekit.utils.MatchUtils; +import com.alibaba.deps.org.objectweb.asm.Opcodes; +import com.alibaba.deps.org.objectweb.asm.tree.*; +import com.taobao.arthas.common.Pair; +import com.taobao.arthas.core.util.EncryptUtils; +import com.taobao.arthas.core.util.StringUtils; + +import java.util.*; + +/** + * look命令工具 + * look相关的定义中需要特别注意的是LocationCode,它的形式如 abcd-1 这是用来标记一个位置用的, + * 而它对应的明文则称之为LocationContent,形如 assign-variable: it 或者 invoke-method: java.util.Iterator#hasNext:()Z + */ +public class LookUtils { + + private static final String LOCATION_CODE_SPLITTER = "-"; + + public static final String LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER = "*$*"; + private static final String LOCATION_CONTENT_VARIABLE_FORMATTER = "assign-variable: %s"; + private static final String LOCATION_CONTENT_METHOD_FORMATTER = "invoke-method: %s#%s:%s"; + private static final String LOCATION_VIEW_LINE_FORMATTER = "/*%s*/ %s %s"; + + + /** + * 根据locationCode找到对应的InsnNode + */ + public static AbstractInsnNode findInsnNodeByLocationCode(MethodNode methodNode, String locationCode) { + Pair locationCodePair = convertToLocationCode(locationCode); + Map uniqMap = genLocationCodeMapNode(methodNode, locationCodePair.getFirst().length()); + return uniqMap.get(locationCode); + } + + /** + * 生成方法的look视图 + */ + public static String renderMethodLocation(MethodNode methodNode) { + Object[] lineArray = renderMethodView(methodNode).toArray(); + return StringUtils.join(lineArray, "\n"); + } + + /** + * 将传入的location(形如abcd-1)转换成 结构化的Pair(first=abcd,second=1) + */ + public static Pair convertToLocationCode(String location) { + String[] arr = location.split(LOCATION_CODE_SPLITTER); + return new Pair(arr[0], Integer.valueOf(arr[1])); + } + + /** + * 判断look使用的location是否合法 + * 1.LineNumber类型的location,如12 + * 2.LocationCode类型的location,如abcd=-1 + */ + public static boolean validLocation(String locationCode) { + if (locationCode == null || locationCode.isEmpty()) { + return false; + } + if (locationCode.contains("-")) { + String[] arr = locationCode.split(LOCATION_CODE_SPLITTER); + if (arr.length != 2) { + return false; + } + return StringUtils.isNumeric(arr[1]) && arr[1].length() < 8; + } + return StringUtils.isNumeric(locationCode) && locationCode.length() < 8; + } + + /** + * 判断是否为LocatonCode类型 + * 依据:是否包含 "-" + */ + public static boolean isLocationCode(String location) { + return location.contains(LOCATION_CODE_SPLITTER); + } + + /** + * 判断 InsnNode 类型是否是look想要的 + * 依据:因为look监测的对象是变量,而变量值一般发生在 赋值、作为参数被方法调用 + * 为什么方法调用只过滤了基础类型的box方法? + * 1.首先已经明确这些方法不会改变变量值 + * 2.其次在插桩的时候,生成的代码里边会有这些方法,如果不排除掉,会影响LocationCode的匹配和生成 + * 3.目前要判断一个变量是否作为该方法的参数较为复杂,先不做 + */ + private static boolean matchInsnNode(AbstractInsnNode abstractInsnNode, Set allowVariableSet) { + if (abstractInsnNode instanceof VarInsnNode) { + //赋值 + switch (abstractInsnNode.getOpcode()) { + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: + case Opcodes.ASTORE: + return allowVariableSet.contains(((VarInsnNode) abstractInsnNode).var); + } + return false; + } else if (abstractInsnNode instanceof MethodInsnNode) { + //box方法,及arthas内部方法 + MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode; + if (methodInsnNode.owner.equals("java/arthas/SpyAPI")) return false; + if (methodInsnNode.owner.equals("java/lang/Byte") || + methodInsnNode.owner.equals("java/lang/Short") || + methodInsnNode.owner.equals("java/lang/Integer") || + methodInsnNode.owner.equals("java/lang/Long") || + methodInsnNode.owner.equals("java/lang/Boolean") || + methodInsnNode.owner.equals("java/lang/Float") + ) + return !methodInsnNode.name.equals(""); + return true; + } + return false; + } + + /** + * 向前寻找离insnNode最近的LineNumber节点并返回行号 + * 如果找不到,则返回 0 + */ + private static int findPreLineNumber(AbstractInsnNode insnNode) { + while (insnNode != null) { + if (insnNode instanceof LineNumberNode) { + return ((LineNumberNode) insnNode).line; + } + insnNode = insnNode.getPrevious(); + } + return 0; + } + + /** + * 生成InsnNode对应的LocationContent,也就是方便肉眼理解的形式 + */ + private static List genLocationContentList(List nodeList, Map varIdxMap) { + List contentList = new LinkedList(); + for (AbstractInsnNode abstractInsnNode : nodeList) { + String content = ""; + if (abstractInsnNode instanceof VarInsnNode) { + VarInsnNode varInsnNode = (VarInsnNode) abstractInsnNode; + content = String.format(LOCATION_CONTENT_VARIABLE_FORMATTER, varIdxMap.get(varInsnNode.var)); + } else if (abstractInsnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode; + content = String.format(LOCATION_CONTENT_METHOD_FORMATTER, methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc); + } + contentList.add(content); + } + return contentList; + } + + /** + * 生成InsnNode对应的LineNumber + */ + private static List genLineNumberList(List nodeList) { + List preLineNumberList = new LinkedList(); + for (AbstractInsnNode abstractInsnNode : nodeList) { + int preLineNumber = findPreLineNumber(abstractInsnNode); + preLineNumberList.add(preLineNumber); + } + return preLineNumberList; + } + + /** + * 过滤出只会被look命令关注的InsnNode + */ + private static List filterNodeList(InsnList insnList, Map varIdxMap) { + List noteList = new LinkedList(); + for (AbstractInsnNode abstractInsnNode : insnList) { + if (matchInsnNode(abstractInsnNode, varIdxMap.keySet())) { + noteList.add(abstractInsnNode); + } + } + return noteList; + } + + /** + * 生成方法的jad视图,形如: + * /*82* / f0bd-1 invoke-method: java.util.ArrayList#:(I)V + * /*83* / 7cda-1 invoke-method: java.lang.Iterable#iterator:()Ljava/util/Iterator; + * /*83* / ad72-1 invoke-method: java.util.Iterator#hasNext:()Z + * /*83* / b105-1 invoke-method: java.util.Iterator#next:()Ljava/lang/Object; + * /*84* / 11a1-1 assign-variable: it + * /*84* / f9e5-1 invoke-method: java.lang.Number#intValue:()I + */ + private static List renderMethodView(MethodNode methodNode) { + List printLines = new LinkedList(); + + Map varIdxMap = new HashMap(); + for (LocalVariableNode localVariable : methodNode.localVariables) { + if (!MatchUtils.wildcardMatch(localVariable.name, LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER)) { + varIdxMap.put(localVariable.index, localVariable.name); + } + } + + List noteList = filterNodeList(methodNode.instructions, varIdxMap); + + List contentList = genLocationContentList(noteList, varIdxMap); + List preLineNumberList = genLineNumberList(noteList); + + List> contentAndCode = genLocationCode(contentList); + + for (int i = 0; i < contentAndCode.size(); i++) { + Pair contentCodePair = contentAndCode.get(i); + Integer preLineNumber = preLineNumberList.get(i); + String printLine = String.format(LOCATION_VIEW_LINE_FORMATTER, preLineNumber, contentCodePair.getSecond(), contentCodePair.getFirst()); + printLines.add(printLine); + } + + return printLines; + } + + /** + * 生成LocationCode及其匹配的InsnNode + */ + private static Map genLocationCodeMapNode(MethodNode methodNode, int uniqLength) { + //本地变量表 + Map varIdxMap = new HashMap(); + for (LocalVariableNode localVariable : methodNode.localVariables) { + if (!MatchUtils.wildcardMatch(localVariable.name, LOCAL_VARIABLES_NAME_EXCLUDE_MATCHER)) { + varIdxMap.put(localVariable.index, localVariable.name); + } + } + //filter node + List nodeList = new LinkedList(); + for (AbstractInsnNode abstractInsnNode : methodNode.instructions) { + if (matchInsnNode(abstractInsnNode, varIdxMap.keySet())) { + nodeList.add(abstractInsnNode); + } + } + //拼凑出content + List contentList = genLocationContentList(nodeList, varIdxMap); + //构建拼凑map + Map uniqMapNode = new HashMap(); + Map contentMapIdx = new HashMap(); + for (int i = 0; i < contentList.size(); i++) { + String c = contentList.get(i); + Integer lastIdx = contentMapIdx.get(c); + int curIdx = lastIdx == null ? 1 : ++lastIdx; + contentMapIdx.put(c, curIdx); + + String md5 = EncryptUtils.md5DigestAsHex(c.getBytes()).substring(0, uniqLength); + + String locationCode = md5 + LOCATION_CODE_SPLITTER + curIdx; + uniqMapNode.put(locationCode, nodeList.get(i)); + } + return uniqMapNode; + } + + /** + * 生成LocationContent及其映射的LocationCode + */ + private static List> genLocationCode(List locationContentList) { + int preSize = locationContentList.size(); + List> locationContentAndCodeList = new LinkedList>(); + Set contentSet = new HashSet(locationContentList); + //采集md5 + Map contentMapMd5 = new HashMap(preSize); + for (String content : contentSet) { + String project = EncryptUtils.md5DigestAsHex(content.getBytes()); + contentMapMd5.put(content, project); + } + //寻找合适长度 + int length = determineLocationCodeLength(contentMapMd5.values()); + //生成map + Map contentMapIdx = new HashMap(); + for (String locationContent : locationContentList) { + //维护idx + Integer lastIdx = contentMapIdx.get(locationContent); + int curIdx = lastIdx == null ? 1 : ++lastIdx; + contentMapIdx.put(locationContent, curIdx); + + String locationCode = locationContent + LOCATION_CODE_SPLITTER + curIdx; + if (length != -1) { + String md5 = contentMapMd5.get(locationContent); + locationCode = md5.substring(0, length) + LOCATION_CODE_SPLITTER + curIdx; + } + locationContentAndCodeList.add(new Pair(locationContent, locationCode)); + } + return locationContentAndCodeList; + } + + /** + * 获取出LocationCode的长度 + * 目前摘要算法用md5,初始长度给4为,如果有出现重复,则递增 + */ + private static int determineLocationCodeLength(Collection md5List) { + Set md5Set = new HashSet(md5List); + if (md5Set.size() != md5List.size()) { + return -1; + } + for (int i = 4; i < 32; i++) { + Set uniqSet = new HashSet(md5Set.size()); + for (String md5 : md5Set) { + String uniq = md5.substring(0, i); + if (!uniqSet.add(uniq)) { + break; + } + } + if (uniqSet.size() == md5Set.size()) { + return i; + } + } + return -1; + } + +} diff --git a/pom.xml b/pom.xml index 463cfc3edd1..1e01e8dbf8e 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ com.alibaba bytekit-core - 0.0.7 + 0.0.8-SNAPSHOT org.benf diff --git a/spy/src/main/java/java/arthas/SpyAPI.java b/spy/src/main/java/java/arthas/SpyAPI.java index 32762def92e..b9758303947 100644 --- a/spy/src/main/java/java/arthas/SpyAPI.java +++ b/spy/src/main/java/java/arthas/SpyAPI.java @@ -81,6 +81,17 @@ public static void atInvokeException(Class clazz, String invokeInfo, Object t spyInstance.atInvokeException(clazz, invokeInfo, target, throwable); } + public static void atLookLocationCode(Class clazz, String methodInfo, Object target, Object[] args, + String lookLocation, Object[] vars, String[] varNames){ + spyInstance.atLookLocation(clazz, methodInfo, target, args, lookLocation, vars, varNames); + } + + public static void atLookLocationLine(Class clazz, String methodInfo, Object target, Object[] args, + String lookLocation, Object[] vars, String[] varNames){ + spyInstance.atLookLocation(clazz, methodInfo, target, args, lookLocation, vars, varNames); + } + + public static abstract class AbstractSpy { public abstract void atEnter(Class clazz, String methodInfo, Object target, Object[] args); @@ -96,6 +107,10 @@ public abstract void atExceptionExit(Class clazz, String methodInfo, Object t public abstract void atAfterInvoke(Class clazz, String invokeInfo, Object target); public abstract void atInvokeException(Class clazz, String invokeInfo, Object target, Throwable throwable); + + public abstract void atLookLocation(Class clazz, String methodInfo, Object target, Object[] args, + String lookLocation, Object[] vars, String[] varNames); + } static class NopSpy extends AbstractSpy { @@ -129,5 +144,10 @@ public void atInvokeException(Class clazz, String invokeInfo, Object target, } + @Override + public void atLookLocation(Class clazz, String methodInfo, Object target, Object[] args, String lookLocation, Object[] vars, String[] varNames) { + + } + } }