Commit 5d7c4150 authored by shengnan hu's avatar shengnan hu
Browse files

init

parents
Pipeline #5497 failed with stage
in 12 seconds
package com.taobao.arthas.core.advisor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.concurrent.ConcurrentWeakKeyHashMap;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.system.ExecStatus;
import com.taobao.arthas.core.shell.system.Process;
import com.taobao.arthas.core.shell.system.ProcessAware;
/**
*
* TODO line 的记录 listener方式? 还是有string为key,不过 classname|method|desc|num 这样子?
* 判断是否已插入了,可以在两行中间查询,有没有 SpyAPI 的invoke?
*
* TODO trace的怎么搞? trace 只记录一次就可以了 classname|method|desc|trace ? 怎么避免 trace 到
* SPY的invoke ?直接忽略?
*
* TODO trace命令可以动态的增加 新的函数进去不?只要关联上同一个 Listener应该是可以的。
*
* TODO 在SPY里放很多的 Object数组,然后动态的设置进去? 比如有新的 Listener来的时候。 这样子连查表都不用了。 甚至可以动态生成
* 存放这些 Listener数组的类? 这样子的话,只要有 Binding那里,查询到一个具体分配好的类, 这样子就可以了?
* 甚至每个ClassLoader里都动态生成这样子的 存放类,那么这样子不可以避免查 ClassLoader了么?
*
* 动态为每一个增强类,生成一个新的类,新的类里,有各种的 ID 数组,保存每一个类的每一种 trace 点的信息??
*
* 多个 watch命令 对同一个类,现在的逻辑是,每个watch都有一个自己的 TransForm,但不会重复增强,因为做了判断。
* watch命令停止时,也没有去掉增强的代码。 只有reset时 才会去掉。
*
* 其实用户想查看局部变量,并不是想查看哪一行! 而是想看某个函数里子调用时的 局部变量的值! 所以实际上是想要一个新的命令,比如 watchinmethod
* , 可以 在某个子调用里,
*
* TODO 现在的trace 可以输出行号,可能不是很精确,但是可以对应上的。 这个在新的方式里怎么支持? 增加一个 linenumber binding?
* 从mehtodNode,向上查找到最近的行号?
*
* TODO 防止重复增强,最重要的应该还是动态增加 annotation,这个才是真正可以做到某一行,某一个子 invoke 都能识别出来的! 无论是
* transform多少次! 字节码怎么动态加 annotation ? annotation里签名用 url ?的key/value方式表达!
* 这样子可以有效还原信息
*
* TODO 是否考虑一个 trace /watch命令之后,得到一个具体的 Listener ID, 允许在另外的窗口里,再次
* trace/watch时指定这个ID,就会查找到,并处理。 这样子的话,真正达到了动态灵活的,一层一层增加的trace !
*
*
* @author hengyunabc 2020-04-24
*
*/
public class AdviceListenerManager {
private static final Logger logger = LoggerFactory.getLogger(AdviceListenerManager.class);
private static final FakeBootstrapClassLoader FAKEBOOTSTRAPCLASSLOADER = new FakeBootstrapClassLoader();
static {
// 清理失效的 AdviceListener
ArthasBootstrap.getInstance().getScheduledExecutorService().scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
try {
for (Entry<ClassLoader, ClassLoaderAdviceListenerManager> entry : adviceListenerMap.entrySet()) {
ClassLoaderAdviceListenerManager adviceListenerManager = entry.getValue();
synchronized (adviceListenerManager) {
for (Entry<String, List<AdviceListener>> eee : adviceListenerManager.map.entrySet()) {
List<AdviceListener> listeners = eee.getValue();
List<AdviceListener> newResult = new ArrayList<AdviceListener>();
for (AdviceListener listener : listeners) {
if (listener instanceof ProcessAware) {
ProcessAware processAware = (ProcessAware) listener;
Process process = processAware.getProcess();
if (process == null) {
continue;
}
ExecStatus status = process.status();
if (!status.equals(ExecStatus.TERMINATED)) {
newResult.add(listener);
}
}
}
if (newResult.size() != listeners.size()) {
adviceListenerManager.map.put(eee.getKey(), newResult);
}
}
}
}
} catch (Throwable e) {
try {
logger.error("clean AdviceListener error", e);
} catch (Throwable t) {
// ignore
}
}
}
}, 3, 3, TimeUnit.SECONDS);
}
private static final ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager> adviceListenerMap = new ConcurrentWeakKeyHashMap<ClassLoader, ClassLoaderAdviceListenerManager>();
static class ClassLoaderAdviceListenerManager {
private ConcurrentHashMap<String, List<AdviceListener>> map = new ConcurrentHashMap<String, List<AdviceListener>>();
private String key(String className, String methodName, String methodDesc) {
return className + methodName + methodDesc;
}
private String keyForTrace(String className, String owner, String methodName, String methodDesc) {
return className + owner + methodName + methodDesc;
}
public void registerAdviceListener(String className, String methodName, String methodDesc,
AdviceListener listener) {
synchronized (this) {
className = className.replace('/', '.');
String key = key(className, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
if (listeners == null) {
listeners = new ArrayList<AdviceListener>();
map.put(key, listeners);
}
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
}
public List<AdviceListener> queryAdviceListeners(String className, String methodName, String methodDesc) {
className = className.replace('/', '.');
String key = key(className, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
return listeners;
}
public void registerTraceAdviceListener(String className, String owner, String methodName, String methodDesc,
AdviceListener listener) {
className = className.replace('/', '.');
String key = keyForTrace(className, owner, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
if (listeners == null) {
listeners = new ArrayList<AdviceListener>();
map.put(key, listeners);
}
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
public List<AdviceListener> queryTraceAdviceListeners(String className, String owner, String methodName,
String methodDesc) {
className = className.replace('/', '.');
String key = keyForTrace(className, owner, methodName, methodDesc);
List<AdviceListener> listeners = map.get(key);
return listeners;
}
}
public static void registerAdviceListener(ClassLoader classLoader, String className, String methodName,
String methodDesc, AdviceListener listener) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager == null) {
manager = new ClassLoaderAdviceListenerManager();
adviceListenerMap.put(classLoader, manager);
}
manager.registerAdviceListener(className, methodName, methodDesc, listener);
}
public static void updateAdviceListeners() {
}
public static List<AdviceListener> queryAdviceListeners(ClassLoader classLoader, String className,
String methodName, String methodDesc) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager != null) {
return manager.queryAdviceListeners(className, methodName, methodDesc);
}
return null;
}
public static void registerTraceAdviceListener(ClassLoader classLoader, String className, String owner,
String methodName, String methodDesc, AdviceListener listener) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager == null) {
manager = new ClassLoaderAdviceListenerManager();
adviceListenerMap.put(classLoader, manager);
}
manager.registerTraceAdviceListener(className, owner, methodName, methodDesc, listener);
}
public static List<AdviceListener> queryTraceAdviceListeners(ClassLoader classLoader, String className,
String owner, String methodName, String methodDesc) {
classLoader = wrap(classLoader);
className = className.replace('/', '.');
ClassLoaderAdviceListenerManager manager = adviceListenerMap.get(classLoader);
if (manager != null) {
return manager.queryTraceAdviceListeners(className, owner, methodName, methodDesc);
}
return null;
}
private static ClassLoader wrap(ClassLoader classLoader) {
if (classLoader != null) {
return classLoader;
}
return FAKEBOOTSTRAPCLASSLOADER;
}
private static class FakeBootstrapClassLoader extends ClassLoader {
}
}
package com.taobao.arthas.core.advisor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
/**
* 通知编织者<br/>
* <p/>
* <h2>线程帧栈与执行帧栈</h2>
* 编织者在执行通知的时候有两个重要的栈:线程帧栈(threadFrameStack),执行帧栈(frameStack)
* <p/>
* Created by vlinux on 15/5/17.
*/
public class AdviceWeaver {
private static final Logger logger = LoggerFactory.getLogger(AdviceWeaver.class);
// 通知监听器集合
private final static Map<Long/*ADVICE_ID*/, AdviceListener> advices
= new ConcurrentHashMap<Long, AdviceListener>();
/**
* 注册监听器
*
* @param listener 通知监听器
*/
public static void reg(AdviceListener listener) {
// 触发监听器创建
listener.create();
// 注册监听器
advices.put(listener.id(), listener);
}
/**
* 注销监听器
*
* @param listener 通知监听器
*/
public static void unReg(AdviceListener listener) {
if (null != listener) {
// 注销监听器
advices.remove(listener.id());
// 触发监听器销毁
listener.destroy();
}
}
public static AdviceListener listener(long id) {
return advices.get(id);
}
/**
* 恢复监听
*
* @param listener 通知监听器
*/
public static void resume(AdviceListener listener) {
// 注册监听器
advices.put(listener.id(), listener);
}
/**
* 暂停监听
*
* @param adviceId 通知ID
*/
public static AdviceListener suspend(long adviceId) {
// 注销监听器
return advices.remove(adviceId);
}
}
package com.taobao.arthas.core.advisor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.alibaba.deps.org.objectweb.asm.Type;
import com.taobao.arthas.core.util.StringUtils;
/**
*
* 主要用于 tt 命令重放使用
*
* @author vlinux on 15/5/24
* @author hengyunabc 2020-05-20
*
*/
public class ArthasMethod {
private final Class<?> clazz;
private final String methodName;
private final String methodDesc;
private Constructor<?> constructor;
private Method method;
private void initMethod() {
if (constructor != null || method != null) {
return;
}
try {
ClassLoader loader = this.clazz.getClassLoader();
final Type asmType = Type.getMethodType(methodDesc);
// to arg types
final Class<?>[] argsClasses = new Class<?>[asmType.getArgumentTypes().length];
for (int index = 0; index < argsClasses.length; index++) {
// asm class descriptor to jvm class
final Class<?> argumentClass;
final Type argumentAsmType = asmType.getArgumentTypes()[index];
switch (argumentAsmType.getSort()) {
case Type.BOOLEAN: {
argumentClass = boolean.class;
break;
}
case Type.CHAR: {
argumentClass = char.class;
break;
}
case Type.BYTE: {
argumentClass = byte.class;
break;
}
case Type.SHORT: {
argumentClass = short.class;
break;
}
case Type.INT: {
argumentClass = int.class;
break;
}
case Type.FLOAT: {
argumentClass = float.class;
break;
}
case Type.LONG: {
argumentClass = long.class;
break;
}
case Type.DOUBLE: {
argumentClass = double.class;
break;
}
case Type.ARRAY: {
argumentClass = toClass(loader, argumentAsmType.getInternalName());
break;
}
case Type.VOID: {
argumentClass = void.class;
break;
}
case Type.OBJECT:
case Type.METHOD:
default: {
argumentClass = toClass(loader, argumentAsmType.getClassName());
break;
}
}
argsClasses[index] = argumentClass;
}
if ("<init>".equals(this.methodName)) {
this.constructor = clazz.getDeclaredConstructor(argsClasses);
} else {
this.method = clazz.getDeclaredMethod(methodName, argsClasses);
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private Class<?> toClass(ClassLoader loader, String className) throws ClassNotFoundException {
return Class.forName(StringUtils.normalizeClassName(className), true, toClassLoader(loader));
}
private ClassLoader toClassLoader(ClassLoader loader) {
return null != loader ? loader : ArthasMethod.class.getClassLoader();
}
/**
* 获取方法名称
*
* @return 返回方法名称
*/
public String getName() {
return this.methodName;
}
@Override
public String toString() {
initMethod();
if (constructor != null) {
return constructor.toString();
} else if (method != null) {
return method.toString();
}
return "ERROR_METHOD";
}
public boolean isAccessible() {
initMethod();
if (this.method != null) {
return method.isAccessible();
} else if (this.constructor != null) {
return constructor.isAccessible();
}
return false;
}
public void setAccessible(boolean accessFlag) {
initMethod();
if (constructor != null) {
constructor.setAccessible(accessFlag);
} else if (method != null) {
method.setAccessible(accessFlag);
}
}
public Object invoke(Object target, Object... args)
throws IllegalAccessException, InvocationTargetException, InstantiationException {
initMethod();
if (method != null) {
return method.invoke(target, args);
} else if (this.constructor != null) {
return constructor.newInstance(args);
}
return null;
}
public ArthasMethod(Class<?> clazz, String methodName, String methodDesc) {
this.clazz = clazz;
this.methodName = methodName;
this.methodDesc = methodDesc;
}
}
package com.taobao.arthas.core.advisor;
import static com.taobao.arthas.core.util.ArthasCheckUtils.isEquals;
import static java.lang.System.arraycopy;
import java.arthas.SpyAPI;
import java.io.File;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import com.alibaba.deps.org.objectweb.asm.ClassReader;
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.ClassNode;
import com.alibaba.deps.org.objectweb.asm.tree.MethodInsnNode;
import com.alibaba.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
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;
import com.alibaba.bytekit.asm.location.filter.LocationFilter;
import com.alibaba.bytekit.utils.AsmOpUtils;
import com.alibaba.bytekit.utils.AsmUtils;
import com.taobao.arthas.common.Pair;
import com.taobao.arthas.core.GlobalOptions;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyInterceptor1;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyInterceptor2;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyInterceptor3;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyTraceExcludeJDKInterceptor1;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyTraceExcludeJDKInterceptor2;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyTraceExcludeJDKInterceptor3;
import com.taobao.arthas.core.advisor.SpyInterceptors.SpyTraceInterceptor1;
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.affect.EnhancerAffect;
import com.taobao.arthas.core.util.matcher.Matcher;
/**
* 对类进行通知增强 Created by vlinux on 15/5/17.
* @author hengyunabc
*/
public class Enhancer implements ClassFileTransformer {
private static final Logger logger = LoggerFactory.getLogger(Enhancer.class);
private final AdviceListener listener;
private final boolean isTracing;
private final boolean skipJDKTrace;
private final Matcher classNameMatcher;
private final Matcher classNameExcludeMatcher;
private final Matcher methodNameMatcher;
private final EnhancerAffect affect;
private Set<Class<?>> matchingClasses = null;
private static final ClassLoader selfClassLoader = Enhancer.class.getClassLoader();
// 被增强的类的缓存
private final static Map<Class<?>/* Class */, Object> classBytesCache = new WeakHashMap<Class<?>, Object>();
private static SpyImpl spyImpl = new SpyImpl();
static {
SpyAPI.setSpy(spyImpl);
}
/**
* @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.skipJDKTrace = skipJDKTrace;
this.classNameMatcher = classNameMatcher;
this.classNameExcludeMatcher = classNameExcludeMatcher;
this.methodNameMatcher = methodNameMatcher;
this.affect = new EnhancerAffect();
affect.setListenerId(listener.id());
}
@Override
public byte[] transform(final ClassLoader inClassLoader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
// 检查classloader能否加载到 SpyAPI,如果不能,则放弃增强
try {
if (inClassLoader != null) {
inClassLoader.loadClass(SpyAPI.class.getName());
}
} catch (Throwable e) {
logger.error("the classloader can not load SpyAPI, ignore it. classloader: {}, className: {}",
inClassLoader.getClass().getName(), className, e);
return null;
}
// 这里要再次过滤一次,为啥?因为在transform的过程中,有可能还会再诞生新的类
// 所以需要将之前需要转换的类集合传递下来,再次进行判断
if (matchingClasses != null && !matchingClasses.contains(classBeingRedefined)) {
return null;
}
//keep origin class reader for bytecode optimizations, avoiding JVM metaspace OOM.
ClassNode classNode = new ClassNode(Opcodes.ASM9);
ClassReader classReader = AsmUtils.toClassNode(classfileBuffer, classNode);
// remove JSR https://github.com/alibaba/arthas/issues/1304
classNode = AsmUtils.removeJSRInstructions(classNode);
// 生成增强字节码
DefaultInterceptorClassParser defaultInterceptorClassParser = new DefaultInterceptorClassParser();
final List<InterceptorProcessor> interceptorProcessors = new ArrayList<InterceptorProcessor>();
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));
}
}
List<MethodNode> matchedMethods = new ArrayList<MethodNode>();
for (MethodNode methodNode : classNode.methods) {
if (!isIgnore(methodNode, methodNameMatcher)) {
matchedMethods.add(methodNode);
}
}
// https://github.com/alibaba/arthas/issues/1690
if (AsmUtils.isEnhancerByCGLIB(className)) {
for (MethodNode methodNode : matchedMethods) {
if (AsmUtils.isConstructor(methodNode)) {
AsmUtils.fixConstructorExceptionTable(methodNode);
}
}
}
// 用于检查是否已插入了 spy函数,如果已有则不重复处理
GroupLocationFilter groupLocationFilter = new GroupLocationFilter();
LocationFilter enterFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atEnter",
LocationType.ENTER);
LocationFilter existFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class), "atExit",
LocationType.EXIT);
LocationFilter exceptionFilter = new InvokeContainLocationFilter(Type.getInternalName(SpyAPI.class),
"atExceptionExit", LocationType.EXCEPTION_EXIT);
groupLocationFilter.addFilter(enterFilter);
groupLocationFilter.addFilter(existFilter);
groupLocationFilter.addFilter(exceptionFilter);
LocationFilter invokeBeforeFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
"atBeforeInvoke", LocationType.INVOKE);
LocationFilter invokeAfterFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
"atInvokeException", LocationType.INVOKE_COMPLETED);
LocationFilter invokeExceptionFilter = new InvokeCheckLocationFilter(Type.getInternalName(SpyAPI.class),
"atInvokeException", LocationType.INVOKE_EXCEPTION_EXIT);
groupLocationFilter.addFilter(invokeBeforeFilter);
groupLocationFilter.addFilter(invokeAfterFilter);
groupLocationFilter.addFilter(invokeExceptionFilter);
for (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")) {
for (AbstractInsnNode insnNode = methodNode.instructions.getFirst(); insnNode != null; insnNode = insnNode
.getNext()) {
if (insnNode instanceof MethodInsnNode) {
final MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if(this.skipJDKTrace) {
if(methodInsnNode.owner.startsWith("java/")) {
continue;
}
}
// 原始类型的box类型相关的都跳过
if(AsmOpUtils.isBoxType(Type.getObjectType(methodInsnNode.owner))) {
continue;
}
AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
}
}
}else {
MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode, groupLocationFilter);
for (InterceptorProcessor interceptor : interceptorProcessors) {
try {
List<Location> locations = interceptor.process(methodProcessor);
for (Location location : locations) {
if (location instanceof MethodInsnNodeWare) {
MethodInsnNodeWare methodInsnNodeWare = (MethodInsnNodeWare) location;
MethodInsnNode methodInsnNode = methodInsnNodeWare.methodInsnNode();
AdviceListenerManager.registerTraceAdviceListener(inClassLoader, className,
methodInsnNode.owner, methodInsnNode.name, methodInsnNode.desc, listener);
}
}
} catch (Throwable e) {
logger.error("enhancer error, class: {}, method: {}, interceptor: {}", classNode.name, methodNode.name, interceptor.getClass().getName(), e);
}
}
}
// enter/exist 总是要插入 listener
AdviceListenerManager.registerAdviceListener(inClassLoader, className, methodNode.name, methodNode.desc,
listener);
affect.addMethodAndCount(inClassLoader, className, methodNode.name, methodNode.desc);
}
// https://github.com/alibaba/arthas/issues/1223 , V1_5 的major version是49
if (AsmUtils.getMajorVersion(classNode.version) < 49) {
classNode.version = AsmUtils.setMajorVersion(classNode.version, 49);
}
byte[] enhanceClassByteArray = AsmUtils.toBytes(classNode, inClassLoader, classReader);
// 增强成功,记录类
classBytesCache.put(classBeingRedefined, new Object());
// dump the class
dumpClassIfNecessary(className, enhanceClassByteArray, affect);
// 成功计数
affect.cCnt(1);
return enhanceClassByteArray;
} catch (Throwable t) {
logger.warn("transform loader[{}]:class[{}] failed.", inClassLoader, className, t);
affect.setThrowable(t);
}
return null;
}
/**
* 是否抽象属性
*/
private boolean isAbstract(int access) {
return (Opcodes.ACC_ABSTRACT & access) == Opcodes.ACC_ABSTRACT;
}
/**
* 是否需要忽略
*/
private boolean isIgnore(MethodNode methodNode, Matcher methodNameMatcher) {
return null == methodNode || isAbstract(methodNode.access) || !methodNameMatcher.matching(methodNode.name)
|| ArthasCheckUtils.isEquals(methodNode.name, "<clinit>");
}
/**
* dump class to file
*/
private static void dumpClassIfNecessary(String className, byte[] data, EnhancerAffect affect) {
if (!GlobalOptions.isDump) {
return;
}
final File dumpClassFile = new File("./arthas-class-dump/" + className + ".class");
final File classPath = new File(dumpClassFile.getParent());
// 创建类所在的包路径
if (!classPath.mkdirs() && !classPath.exists()) {
logger.warn("create dump classpath:{} failed.", classPath);
return;
}
// 将类字节码写入文件
try {
FileUtils.writeByteArrayToFile(dumpClassFile, data);
affect.addClassDumpFile(dumpClassFile);
if (GlobalOptions.verbose) {
logger.info("dump enhanced class: {}, path: {}", className, dumpClassFile);
}
} catch (IOException e) {
logger.warn("dump class:{} to file {} failed.", className, dumpClassFile, e);
}
}
/**
* 是否需要过滤的类
*
* @param classes 类集合
*/
private List<Pair<Class<?>, String>> filter(Set<Class<?>> classes) {
List<Pair<Class<?>, String>> filteredClasses = new ArrayList<Pair<Class<?>, String>>();
final Iterator<Class<?>> it = classes.iterator();
while (it.hasNext()) {
final Class<?> clazz = it.next();
boolean removeFlag = false;
if (null == clazz) {
removeFlag = true;
} else if (isSelf(clazz)) {
filteredClasses.add(new Pair<Class<?>, String>(clazz, "class loaded by arthas itself"));
removeFlag = true;
} else if (isUnsafeClass(clazz)) {
filteredClasses.add(new Pair<Class<?>, String>(clazz, "class loaded by Bootstrap Classloader, try to execute `options unsafe true`"));
removeFlag = true;
} else if (isExclude(clazz)) {
filteredClasses.add(new Pair<Class<?>, String>(clazz, "class is excluded"));
removeFlag = true;
} else {
Pair<Boolean, String> unsupportedResult = isUnsupportedClass(clazz);
if (unsupportedResult.getFirst()) {
filteredClasses.add(new Pair<Class<?>, String>(clazz, unsupportedResult.getSecond()));
removeFlag = true;
}
}
if (removeFlag) {
it.remove();
}
}
return filteredClasses;
}
private boolean isExclude(Class<?> clazz) {
if (this.classNameExcludeMatcher != null) {
return classNameExcludeMatcher.matching(clazz.getName());
}
return false;
}
/**
* 是否过滤Arthas加载的类
*/
private static boolean isSelf(Class<?> clazz) {
return null != clazz && isEquals(clazz.getClassLoader(), selfClassLoader);
}
/**
* 是否过滤unsafe类
*/
private static boolean isUnsafeClass(Class<?> clazz) {
return !GlobalOptions.isUnsafe && clazz.getClassLoader() == null;
}
/**
* 是否过滤目前暂不支持的类
*/
private static Pair<Boolean, String> isUnsupportedClass(Class<?> clazz) {
if (ClassUtils.isLambdaClass(clazz)) {
return new Pair<Boolean, String>(Boolean.TRUE, "class is lambda");
}
if (clazz.isInterface() && !GlobalOptions.isSupportDefaultMethod) {
return new Pair<Boolean, String>(Boolean.TRUE, "class is interface");
}
if (clazz.equals(Integer.class)) {
return new Pair<Boolean, String>(Boolean.TRUE, "class is java.lang.Integer");
}
if (clazz.equals(Class.class)) {
return new Pair<Boolean, String>(Boolean.TRUE, "class is java.lang.Class");
}
if (clazz.equals(Method.class)) {
return new Pair<Boolean, String>(Boolean.TRUE, "class is java.lang.Method");
}
if (clazz.isArray()) {
return new Pair<Boolean, String>(Boolean.TRUE, "class is array");
}
return new Pair<Boolean, String>(Boolean.FALSE, "");
}
/**
* 对象增强
*
* @param inst inst
* @param adviceId 通知ID
* @param isTracing 可跟踪方法调用
* @param skipJDKTrace 是否忽略对JDK内部方法的跟踪
* @param classNameMatcher 类名匹配
* @param methodNameMatcher 方法名匹配
* @return 增强影响范围
* @throws UnmodifiableClassException 增强失败
*/
public synchronized EnhancerAffect enhance(final Instrumentation inst) throws UnmodifiableClassException {
// 获取需要增强的类集合
this.matchingClasses = GlobalOptions.isDisableSubClass
? SearchUtils.searchClass(inst, classNameMatcher)
: SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher));
// 过滤掉无法被增强的类
List<Pair<Class<?>, String>> filtedList = filter(matchingClasses);
if (!filtedList.isEmpty()) {
for (Pair<Class<?>, String> filted : filtedList) {
logger.info("ignore class: {}, reason: {}", filted.getFirst().getName(), filted.getSecond());
}
}
logger.info("enhance matched classes: {}", matchingClasses);
affect.setTransformer(this);
try {
ArthasBootstrap.getInstance().getTransformerManager().addTransformer(this, isTracing);
// 批量增强
if (GlobalOptions.isBatchReTransform) {
final int size = matchingClasses.size();
final Class<?>[] classArray = new Class<?>[size];
arraycopy(matchingClasses.toArray(), 0, classArray, 0, size);
if (classArray.length > 0) {
inst.retransformClasses(classArray);
logger.info("Success to batch transform classes: " + Arrays.toString(classArray));
}
} else {
// for each 增强
for (Class<?> clazz : matchingClasses) {
try {
inst.retransformClasses(clazz);
logger.info("Success to transform class: " + clazz);
} catch (Throwable t) {
logger.warn("retransform {} failed.", clazz, t);
if (t instanceof UnmodifiableClassException) {
throw (UnmodifiableClassException) t;
} else if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new RuntimeException(t);
}
}
}
}
} catch (Throwable e) {
logger.error("Enhancer error, matchingClasses: {}", matchingClasses, e);
affect.setThrowable(e);
}
return affect;
}
/**
* 重置指定的Class
*
* @param inst inst
* @param classNameMatcher 类名匹配
* @return 增强影响范围
* @throws UnmodifiableClassException
*/
public static synchronized EnhancerAffect reset(final Instrumentation inst, final Matcher classNameMatcher)
throws UnmodifiableClassException {
final EnhancerAffect affect = new EnhancerAffect();
final Set<Class<?>> enhanceClassSet = new HashSet<Class<?>>();
for (Class<?> classInCache : classBytesCache.keySet()) {
if (classNameMatcher.matching(classInCache.getName())) {
enhanceClassSet.add(classInCache);
}
}
try {
enhance(inst, enhanceClassSet);
logger.info("Success to reset classes: " + enhanceClassSet);
} finally {
for (Class<?> resetClass : enhanceClassSet) {
classBytesCache.remove(resetClass);
affect.cCnt(1);
}
}
return affect;
}
// 批量增强
private static void enhance(Instrumentation inst, Set<Class<?>> classes)
throws UnmodifiableClassException {
int size = classes.size();
Class<?>[] classArray = new Class<?>[size];
arraycopy(classes.toArray(), 0, classArray, 0, size);
if (classArray.length > 0) {
inst.retransformClasses(classArray);
}
}
}
package com.taobao.arthas.core.advisor;
/**
* 方法调用跟踪<br/>
* 当一个方法内部调用另外一个方法时,会触发此跟踪方法
* Created by vlinux on 15/5/27.
*/
public interface InvokeTraceable {
/**
* 调用之前跟踪
*
* @param tracingClassName 调用类名
* @param tracingMethodName 调用方法名
* @param tracingMethodDesc 调用方法描述
* @param tracingLineNumber 执行调用行数
* @throws Throwable 通知过程出错
*/
void invokeBeforeTracing(
ClassLoader classLoader,
String tracingClassName,
String tracingMethodName,
String tracingMethodDesc,
int tracingLineNumber) throws Throwable;
/**
* 抛异常后跟踪
*
* @param tracingClassName 调用类名
* @param tracingMethodName 调用方法名
* @param tracingMethodDesc 调用方法描述
* @param tracingLineNumber 执行调用行数
* @throws Throwable 通知过程出错
*/
void invokeThrowTracing(
ClassLoader classLoader,
String tracingClassName,
String tracingMethodName,
String tracingMethodDesc,
int tracingLineNumber) throws Throwable;
/**
* 调用之后跟踪
*
* @param tracingClassName 调用类名
* @param tracingMethodName 调用方法名
* @param tracingMethodDesc 调用方法描述
* @param tracingLineNumber 执行调用行数
* @throws Throwable 通知过程出错
*/
void invokeAfterTracing(
ClassLoader classLoader,
String tracingClassName,
String tracingMethodName,
String tracingMethodDesc,
int tracingLineNumber) throws Throwable;
}
package com.taobao.arthas.core.advisor;
import java.arthas.SpyAPI.AbstractSpy;
import java.util.List;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.shell.system.ExecStatus;
import com.taobao.arthas.core.shell.system.ProcessAware;
import com.taobao.arthas.core.util.StringUtils;
/**
* <pre>
* 怎么从 className|methodDesc 到 id 对应起来??
* 当id少时,可以id自己来判断是否符合?
*
* 如果是每个 className|methodDesc 为 key ,是否
* </pre>
*
* @author hengyunabc 2020-04-24
*
*/
public class SpyImpl extends AbstractSpy {
private static final Logger logger = LoggerFactory.getLogger(SpyImpl.class);
@Override
public void atEnter(Class<?> clazz, String methodInfo, Object target, Object[] args) {
ClassLoader classLoader = clazz.getClassLoader();
String[] info = StringUtils.splitMethodInfo(methodInfo);
String methodName = info[0];
String methodDesc = info[1];
// TODO listener 只用查一次,放到 thread local里保存起来就可以了!
List<AdviceListener> listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
methodName, methodDesc);
if (listeners != null) {
for (AdviceListener adviceListener : listeners) {
try {
if (skipAdviceListener(adviceListener)) {
continue;
}
adviceListener.before(clazz, methodName, methodDesc, target, args);
} catch (Throwable e) {
logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
}
}
}
}
@Override
public void atExit(Class<?> clazz, String methodInfo, Object target, Object[] args, Object returnObject) {
ClassLoader classLoader = clazz.getClassLoader();
String[] info = StringUtils.splitMethodInfo(methodInfo);
String methodName = info[0];
String methodDesc = info[1];
List<AdviceListener> listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
methodName, methodDesc);
if (listeners != null) {
for (AdviceListener adviceListener : listeners) {
try {
if (skipAdviceListener(adviceListener)) {
continue;
}
adviceListener.afterReturning(clazz, methodName, methodDesc, target, args, returnObject);
} catch (Throwable e) {
logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
}
}
}
}
@Override
public void atExceptionExit(Class<?> clazz, String methodInfo, Object target, Object[] args, Throwable throwable) {
ClassLoader classLoader = clazz.getClassLoader();
String[] info = StringUtils.splitMethodInfo(methodInfo);
String methodName = info[0];
String methodDesc = info[1];
List<AdviceListener> listeners = AdviceListenerManager.queryAdviceListeners(classLoader, clazz.getName(),
methodName, methodDesc);
if (listeners != null) {
for (AdviceListener adviceListener : listeners) {
try {
if (skipAdviceListener(adviceListener)) {
continue;
}
adviceListener.afterThrowing(clazz, methodName, methodDesc, target, args, throwable);
} catch (Throwable e) {
logger.error("class: {}, methodInfo: {}", clazz.getName(), methodInfo, e);
}
}
}
}
@Override
public void atBeforeInvoke(Class<?> clazz, String invokeInfo, Object target) {
ClassLoader classLoader = clazz.getClassLoader();
String[] info = StringUtils.splitInvokeInfo(invokeInfo);
String owner = info[0];
String methodName = info[1];
String methodDesc = info[2];
List<AdviceListener> listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
owner, methodName, methodDesc);
if (listeners != null) {
for (AdviceListener adviceListener : listeners) {
try {
if (skipAdviceListener(adviceListener)) {
continue;
}
final InvokeTraceable listener = (InvokeTraceable) adviceListener;
listener.invokeBeforeTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3]));
} catch (Throwable e) {
logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
}
}
}
}
@Override
public void atAfterInvoke(Class<?> clazz, String invokeInfo, Object target) {
ClassLoader classLoader = clazz.getClassLoader();
String[] info = StringUtils.splitInvokeInfo(invokeInfo);
String owner = info[0];
String methodName = info[1];
String methodDesc = info[2];
List<AdviceListener> listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
owner, methodName, methodDesc);
if (listeners != null) {
for (AdviceListener adviceListener : listeners) {
try {
if (skipAdviceListener(adviceListener)) {
continue;
}
final InvokeTraceable listener = (InvokeTraceable) adviceListener;
listener.invokeAfterTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3]));
} catch (Throwable e) {
logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
}
}
}
}
@Override
public void atInvokeException(Class<?> clazz, String invokeInfo, Object target, Throwable throwable) {
ClassLoader classLoader = clazz.getClassLoader();
String[] info = StringUtils.splitInvokeInfo(invokeInfo);
String owner = info[0];
String methodName = info[1];
String methodDesc = info[2];
List<AdviceListener> listeners = AdviceListenerManager.queryTraceAdviceListeners(classLoader, clazz.getName(),
owner, methodName, methodDesc);
if (listeners != null) {
for (AdviceListener adviceListener : listeners) {
try {
if (skipAdviceListener(adviceListener)) {
continue;
}
final InvokeTraceable listener = (InvokeTraceable) adviceListener;
listener.invokeThrowTracing(classLoader, owner, methodName, methodDesc, Integer.parseInt(info[3]));
} catch (Throwable e) {
logger.error("class: {}, invokeInfo: {}", clazz.getName(), invokeInfo, e);
}
}
}
}
private static boolean skipAdviceListener(AdviceListener adviceListener) {
if (adviceListener instanceof ProcessAware) {
ProcessAware processAware = (ProcessAware) adviceListener;
ExecStatus status = processAware.getProcess().status();
if (status.equals(ExecStatus.TERMINATED) || status.equals(ExecStatus.STOPPED)) {
return true;
}
}
return false;
}
}
\ No newline at end of file
package com.taobao.arthas.core.advisor;
import java.arthas.SpyAPI;
import com.alibaba.bytekit.asm.binding.Binding;
import com.alibaba.bytekit.asm.interceptor.annotation.AtEnter;
import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit;
import com.alibaba.bytekit.asm.interceptor.annotation.AtExit;
import com.alibaba.bytekit.asm.interceptor.annotation.AtInvoke;
import com.alibaba.bytekit.asm.interceptor.annotation.AtInvokeException;
/**
*
* @author hengyunabc 2020-06-05
*
*/
public class SpyInterceptors {
public static class SpyInterceptor1 {
@AtEnter(inline = true)
public static void atEnter(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.MethodInfo String methodInfo, @Binding.Args Object[] args) {
SpyAPI.atEnter(clazz, methodInfo, target, args);
}
}
public static class SpyInterceptor2 {
@AtExit(inline = true)
public static void atExit(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.MethodInfo String methodInfo, @Binding.Args Object[] args, @Binding.Return Object returnObj) {
SpyAPI.atExit(clazz, methodInfo, target, args, returnObj);
}
}
public static class SpyInterceptor3 {
@AtExceptionExit(inline = true)
public static void atExceptionExit(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.MethodInfo String methodInfo, @Binding.Args Object[] args,
@Binding.Throwable Throwable throwable) {
SpyAPI.atExceptionExit(clazz, methodInfo, target, args, throwable);
}
}
public static class SpyTraceInterceptor1 {
@AtInvoke(name = "", inline = true, whenComplete = false, excludes = {"java.arthas.SpyAPI", "java.lang.Byte"
, "java.lang.Boolean"
, "java.lang.Short"
, "java.lang.Character"
, "java.lang.Integer"
, "java.lang.Float"
, "java.lang.Long"
, "java.lang.Double"})
public static void onInvoke(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.InvokeInfo String invokeInfo) {
SpyAPI.atBeforeInvoke(clazz, invokeInfo, target);
}
}
public static class SpyTraceInterceptor2 {
@AtInvoke(name = "", inline = true, whenComplete = true, excludes = {"java.arthas.SpyAPI", "java.lang.Byte"
, "java.lang.Boolean"
, "java.lang.Short"
, "java.lang.Character"
, "java.lang.Integer"
, "java.lang.Float"
, "java.lang.Long"
, "java.lang.Double"})
public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.InvokeInfo String invokeInfo) {
SpyAPI.atAfterInvoke(clazz, invokeInfo, target);
}
}
public static class SpyTraceInterceptor3 {
@AtInvokeException(name = "", inline = true, excludes = {"java.arthas.SpyAPI", "java.lang.Byte"
, "java.lang.Boolean"
, "java.lang.Short"
, "java.lang.Character"
, "java.lang.Integer"
, "java.lang.Float"
, "java.lang.Long"
, "java.lang.Double"})
public static void onInvokeException(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) {
SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable);
}
}
public static class SpyTraceExcludeJDKInterceptor1 {
@AtInvoke(name = "", inline = true, whenComplete = false, excludes = "java.**")
public static void onInvoke(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.InvokeInfo String invokeInfo) {
SpyAPI.atBeforeInvoke(clazz, invokeInfo, target);
}
}
public static class SpyTraceExcludeJDKInterceptor2 {
@AtInvoke(name = "", inline = true, whenComplete = true, excludes = "java.**")
public static void onInvokeAfter(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.InvokeInfo String invokeInfo) {
SpyAPI.atAfterInvoke(clazz, invokeInfo, target);
}
}
public static class SpyTraceExcludeJDKInterceptor3 {
@AtInvokeException(name = "", inline = true, excludes = "java.**")
public static void onInvokeException(@Binding.This Object target, @Binding.Class Class<?> clazz,
@Binding.InvokeInfo String invokeInfo, @Binding.Throwable Throwable throwable) {
SpyAPI.atInvokeException(clazz, invokeInfo, target, throwable);
}
}
}
package com.taobao.arthas.core.advisor;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
*
* <pre>
* * 统一管理 ClassFileTransformer
* * 每个增强命令对应一个 Enhancer ,也统一在这里管理
* </pre>
*
* @see com.taobao.arthas.core.advisor.Enhancer
* @author hengyunabc 2020-05-18
*
*/
public class TransformerManager {
private Instrumentation instrumentation;
private List<ClassFileTransformer> watchTransformers = new CopyOnWriteArrayList<ClassFileTransformer>();
private List<ClassFileTransformer> traceTransformers = new CopyOnWriteArrayList<ClassFileTransformer>();
/**
* 先于 watch/trace的 Transformer TODO 改进为全部用 order 排序?
*/
private List<ClassFileTransformer> reTransformers = new CopyOnWriteArrayList<ClassFileTransformer>();
private ClassFileTransformer classFileTransformer;
public TransformerManager(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
classFileTransformer = new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
for (ClassFileTransformer classFileTransformer : reTransformers) {
byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
protectionDomain, classfileBuffer);
if (transformResult != null) {
classfileBuffer = transformResult;
}
}
for (ClassFileTransformer classFileTransformer : watchTransformers) {
byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
protectionDomain, classfileBuffer);
if (transformResult != null) {
classfileBuffer = transformResult;
}
}
for (ClassFileTransformer classFileTransformer : traceTransformers) {
byte[] transformResult = classFileTransformer.transform(loader, className, classBeingRedefined,
protectionDomain, classfileBuffer);
if (transformResult != null) {
classfileBuffer = transformResult;
}
}
return classfileBuffer;
}
};
instrumentation.addTransformer(classFileTransformer, true);
}
public void addTransformer(ClassFileTransformer transformer, boolean isTracing) {
if (isTracing) {
traceTransformers.add(transformer);
} else {
watchTransformers.add(transformer);
}
}
public void addRetransformer(ClassFileTransformer transformer) {
reTransformers.add(transformer);
}
public void removeTransformer(ClassFileTransformer transformer) {
reTransformers.remove(transformer);
watchTransformers.remove(transformer);
traceTransformers.remove(transformer);
}
public void destroy() {
reTransformers.clear();
watchTransformers.clear();
traceTransformers.clear();
instrumentation.removeTransformer(classFileTransformer);
}
}
package com.taobao.arthas.core.command;
import java.util.ArrayList;
import java.util.List;
import com.taobao.arthas.core.command.basic1000.AuthCommand;
import com.taobao.arthas.core.command.basic1000.Base64Command;
import com.taobao.arthas.core.command.basic1000.CatCommand;
import com.taobao.arthas.core.command.basic1000.ClsCommand;
import com.taobao.arthas.core.command.basic1000.EchoCommand;
import com.taobao.arthas.core.command.basic1000.GrepCommand;
import com.taobao.arthas.core.command.basic1000.HelpCommand;
import com.taobao.arthas.core.command.basic1000.HistoryCommand;
import com.taobao.arthas.core.command.basic1000.KeymapCommand;
import com.taobao.arthas.core.command.basic1000.OptionsCommand;
import com.taobao.arthas.core.command.basic1000.PwdCommand;
import com.taobao.arthas.core.command.basic1000.ResetCommand;
import com.taobao.arthas.core.command.basic1000.SessionCommand;
import com.taobao.arthas.core.command.basic1000.StopCommand;
import com.taobao.arthas.core.command.basic1000.SystemEnvCommand;
import com.taobao.arthas.core.command.basic1000.SystemPropertyCommand;
import com.taobao.arthas.core.command.basic1000.TeeCommand;
import com.taobao.arthas.core.command.basic1000.VMOptionCommand;
import com.taobao.arthas.core.command.basic1000.VersionCommand;
import com.taobao.arthas.core.command.hidden.JulyCommand;
import com.taobao.arthas.core.command.hidden.ThanksCommand;
import com.taobao.arthas.core.command.klass100.ClassLoaderCommand;
import com.taobao.arthas.core.command.klass100.DumpClassCommand;
import com.taobao.arthas.core.command.klass100.GetStaticCommand;
import com.taobao.arthas.core.command.klass100.JadCommand;
import com.taobao.arthas.core.command.klass100.MemoryCompilerCommand;
import com.taobao.arthas.core.command.klass100.OgnlCommand;
import com.taobao.arthas.core.command.klass100.RedefineCommand;
import com.taobao.arthas.core.command.klass100.RetransformCommand;
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.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.Command;
import com.taobao.arthas.core.shell.command.CommandResolver;
import com.taobao.middleware.cli.annotations.Name;
/**
* TODO automatically discover the built-in commands.
* @author beiwei30 on 17/11/2016.
*/
public class BuiltinCommandPack implements CommandResolver {
private List<Command> commands = new ArrayList<Command>();
public BuiltinCommandPack(List<String> disabledCommands) {
initCommands(disabledCommands);
}
@Override
public List<Command> commands() {
return commands;
}
private void initCommands(List<String> disabledCommands) {
List<Class<? extends AnnotatedCommand>> commandClassList = new ArrayList<Class<? extends AnnotatedCommand>>(32);
commandClassList.add(HelpCommand.class);
commandClassList.add(AuthCommand.class);
commandClassList.add(KeymapCommand.class);
commandClassList.add(SearchClassCommand.class);
commandClassList.add(SearchMethodCommand.class);
commandClassList.add(ClassLoaderCommand.class);
commandClassList.add(JadCommand.class);
commandClassList.add(GetStaticCommand.class);
commandClassList.add(MonitorCommand.class);
commandClassList.add(StackCommand.class);
commandClassList.add(ThreadCommand.class);
commandClassList.add(TraceCommand.class);
commandClassList.add(WatchCommand.class);
commandClassList.add(TimeTunnelCommand.class);
commandClassList.add(JvmCommand.class);
commandClassList.add(MemoryCommand.class);
commandClassList.add(PerfCounterCommand.class);
// commandClassList.add(GroovyScriptCommand.class);
commandClassList.add(OgnlCommand.class);
commandClassList.add(MemoryCompilerCommand.class);
commandClassList.add(RedefineCommand.class);
commandClassList.add(RetransformCommand.class);
commandClassList.add(DashboardCommand.class);
commandClassList.add(DumpClassCommand.class);
commandClassList.add(HeapDumpCommand.class);
commandClassList.add(JulyCommand.class);
commandClassList.add(ThanksCommand.class);
commandClassList.add(OptionsCommand.class);
commandClassList.add(ClsCommand.class);
commandClassList.add(ResetCommand.class);
commandClassList.add(VersionCommand.class);
commandClassList.add(SessionCommand.class);
commandClassList.add(SystemPropertyCommand.class);
commandClassList.add(SystemEnvCommand.class);
commandClassList.add(VMOptionCommand.class);
commandClassList.add(LoggerCommand.class);
commandClassList.add(HistoryCommand.class);
commandClassList.add(CatCommand.class);
commandClassList.add(Base64Command.class);
commandClassList.add(EchoCommand.class);
commandClassList.add(PwdCommand.class);
commandClassList.add(MBeanCommand.class);
commandClassList.add(GrepCommand.class);
commandClassList.add(TeeCommand.class);
commandClassList.add(ProfilerCommand.class);
commandClassList.add(VmToolCommand.class);
commandClassList.add(StopCommand.class);
for (Class<? extends AnnotatedCommand> clazz : commandClassList) {
Name name = clazz.getAnnotation(Name.class);
if (name != null && name.value() != null) {
if (disabledCommands.contains(name.value())) {
continue;
}
}
commands.add(Command.create(clazz));
}
}
}
package com.taobao.arthas.core.command;
/**
* @author ralf0131 2016-12-14 17:21.
* @author hengyunabc 2018-12-03
*/
public interface Constants {
/**
* TODO improve the description
*/
String EXPRESS_DESCRIPTION = " The express may be one of the following expression (evaluated dynamically):\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" +
" returnObj : the returned object of method\n" +
" throwExp : the throw exception of method\n" +
" isReturn : the method ended by return\n" +
" isThrow : the method ended by throwing exception\n" +
" #cost : the execution time in ms of method invocation";
String EXAMPLE = "\nEXAMPLES:\n";
String WIKI = "\nWIKI:\n";
String WIKI_HOME = " https://arthas.aliyun.com/doc/";
String EXPRESS_EXAMPLES = "Examples:\n" +
" params\n" +
" params[0]\n" +
" 'params[0]+params[1]'\n" +
" '{params[0], target, returnObj}'\n" +
" returnObj\n" +
" throwExp\n" +
" target\n" +
" clazz\n" +
" method\n";
String CONDITION_EXPRESS = "Conditional expression in ognl style, for example:\n" +
" TRUE : 1==1\n" +
" TRUE : true\n" +
" FALSE : false\n" +
" TRUE : 'params.length>=0'\n" +
" FALSE : 1==2\n" +
" '#cost>100'\n";
}
package com.taobao.arthas.core.command;
import com.taobao.arthas.core.advisor.Advice;
/**
* 脚本支持命令
* Created by vlinux on 15/6/1.
*/
public interface ScriptSupportCommand {
/**
* 增强脚本监听器
*/
interface ScriptListener {
/**
* 脚本创建
*
* @param output 输出器
*/
void create(Output output);
/**
* 脚本销毁
*
* @param output 输出器
*/
void destroy(Output output);
/**
* 方法执行前
*
* @param output 输出器
* @param advice 通知点
*/
void before(Output output, Advice advice);
/**
* 方法正常返回
*
* @param output 输出器
* @param advice 通知点
*/
void afterReturning(Output output, Advice advice);
/**
* 方法异常返回
*
* @param output 输出器
* @param advice 通知点
*/
void afterThrowing(Output output, Advice advice);
}
/**
* 脚本监听器适配器
*/
class ScriptListenerAdapter implements ScriptListener {
@Override
public void create(Output output) {
}
@Override
public void destroy(Output output) {
}
@Override
public void before(Output output, Advice advice) {
}
@Override
public void afterReturning(Output output, Advice advice) {
}
@Override
public void afterThrowing(Output output, Advice advice) {
}
}
/**
* 输出器
*/
interface Output {
/**
* 输出字符串(不换行)
*
* @param string 待输出字符串
* @return this
*/
Output print(String string);
/**
* 输出字符串(换行)
*
* @param string 待输出字符串
* @return this
*/
Output println(String string);
/**
* 结束当前脚本
*
* @return this
*/
Output finish();
}
}
package com.taobao.arthas.core.command.basic1000;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.ArthasConstants;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.security.BasicPrincipal;
import com.taobao.arthas.core.security.SecurityAuthenticator;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.DefaultValue;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
/**
* TODO 支持更多的鉴权方式。目前只支持 username/password的方式
*
* @author hengyunabc 2021-03-03
*
*/
// @formatter:off
@Name(ArthasConstants.AUTH)
@Summary("Authenticates the current session")
@Description(Constants.EXAMPLE +
" auth" +
" auth <password>\n" +
" auth --username <username> <password>\n"
+ Constants.WIKI + Constants.WIKI_HOME + ArthasConstants.AUTH)
//@formatter:on
public class AuthCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(AuthCommand.class);
private String username;
private String password;
private SecurityAuthenticator authenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator();
@Argument(argName = "password", index = 0, required = false)
@Description("password")
public void setPassword(String password) {
this.password = password;
}
@Option(shortName = "n", longName = "username")
@Description("username, default value 'arthas'")
@DefaultValue(ArthasConstants.DEFAULT_USERNAME)
public void setUsername(String username) {
this.username = username;
}
@Override
public void process(CommandProcess process) {
int status = 0;
String message = "";
try {
Session session = process.session();
if (username == null) {
status = 1;
message = "username can not be empty!";
return;
}
if (password == null) { // 没有传入passowrd参数时,打印当前结果
boolean authenticated = session.get(ArthasConstants.SUBJECT_KEY) != null;
boolean needLogin = this.authenticator.needLogin();
message = "Authentication result: " + authenticated + ", Need authentication: " + needLogin;
if (needLogin && !authenticated) {
status = 1;
}
return;
} else {
// 尝试进行鉴权
BasicPrincipal principal = new BasicPrincipal(username, password);
try {
Subject subject = authenticator.login(principal);
if (subject != null) {
// 把subject 保存到 session里,后续其它命令则可以正常执行
session.put(ArthasConstants.SUBJECT_KEY, subject);
message = "Authentication result: " + true + ", username: " + username;
} else {
status = 1;
message = "Authentication result: " + false + ", username: " + username;
}
} catch (LoginException e) {
logger.error("Authentication error, username: {}", username, e);
}
}
} finally {
process.end(status, message);
}
}
@Override
public void complete(Completion completion) {
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
}
package com.taobao.arthas.core.command.basic1000;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.IOUtils;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.Base64Model;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.CharsetUtil;
/**
*
* @author hengyunabc 2021-01-05
*
*/
@Name("base64")
@Summary("Encode and decode using Base64 representation")
@Description(Constants.EXAMPLE +
" base64 /tmp/test.txt\n" +
" base64 --input /tmp/test.txt --output /tmp/result.txt\n" +
" base64 -d /tmp/result.txt\n"
+ Constants.WIKI + Constants.WIKI_HOME + "base64")
public class Base64Command extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(Base64Command.class);
private String file;
private int sizeLimit = 128 * 1024;
private static final int MAX_SIZE_LIMIT = 8 * 1024 * 1024;
private boolean decode;
private String input;
private String output;
@Argument(argName = "file", index = 0, required = false)
@Description("file")
public void setFiles(String file) {
this.file = file;
}
@Option(shortName = "d", longName = "decode", flag = true)
@Description("decodes input")
public void setDecode(boolean decode) {
this.decode = decode;
}
@Option(shortName = "i", longName = "input")
@Description("input file")
public void setInput(String input) {
this.input = input;
}
@Option(shortName = "o", longName = "output")
@Description("output file")
public void setOutput(String output) {
this.output = output;
}
@Option(shortName = "M", longName = "sizeLimit")
@Description("Upper size limit in bytes for the result (128 * 1024 by default, the maximum value is 8 * 1024 * 1024)")
public void setSizeLimit(Integer sizeLimit) {
this.sizeLimit = sizeLimit;
}
@Override
public void process(CommandProcess process) {
if (!verifyOptions(process)) {
return;
}
// 确认输入
if (file == null) {
if (this.input != null) {
file = input;
} else {
process.end(-1, ": No file, nor input");
return;
}
}
File f = new File(file);
if (!f.exists()) {
process.end(-1, file + ": No such file or directory");
return;
}
if (f.isDirectory()) {
process.end(-1, file + ": Is a directory");
return;
}
if (f.length() > sizeLimit) {
process.end(-1, file + ": Is too large, size: " + f.length());
return;
}
InputStream input = null;
try {
input = new FileInputStream(f);
byte[] bytes = IOUtils.getBytes(input);
ByteBuf convertResult = null;
if (this.decode) {
convertResult = Base64.decode(Unpooled.wrappedBuffer(bytes));
} else {
convertResult = Base64.encode(Unpooled.wrappedBuffer(bytes));
}
if (this.output != null) {
int readableBytes = convertResult.readableBytes();
OutputStream out = new FileOutputStream(this.output);
convertResult.readBytes(out, readableBytes);
process.appendResult(new Base64Model(null));
} else {
String base64Str = convertResult.toString(CharsetUtil.UTF_8);
process.appendResult(new Base64Model(base64Str));
}
} catch (IOException e) {
logger.error("read file error. name: " + file, e);
process.end(1, "read file error: " + e.getMessage());
return;
} finally {
IOUtils.close(input);
}
process.end();
}
private boolean verifyOptions(CommandProcess process) {
if(this.file == null && this.input == null) {
process.end(-1);
return false;
}
if (sizeLimit > MAX_SIZE_LIMIT) {
process.end(-1, "sizeLimit cannot be large than: " + MAX_SIZE_LIMIT);
return false;
}
// 目前不支持过滤,限制http请求执行的文件大小
int maxSizeLimitOfNonTty = 128 * 1024;
if (!process.session().isTty() && sizeLimit > maxSizeLimitOfNonTty) {
process.end(-1,
"When executing in non-tty session, sizeLimit cannot be large than: " + maxSizeLimitOfNonTty);
return false;
}
return true;
}
@Override
public void complete(Completion completion) {
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
}
package com.taobao.arthas.core.command.basic1000;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import com.taobao.arthas.core.command.model.CatModel;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.util.FileUtils;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
@Name("cat")
@Summary("Concatenate and print files")
public class CatCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(CatCommand.class);
private List<String> files;
private String encoding;
private Integer sizeLimit = 128 * 1024;
private int maxSizeLimit = 8 * 1024 * 1024;
@Argument(argName = "files", index = 0)
@Description("files")
public void setFiles(List<String> files) {
this.files = files;
}
@Option(longName = "encoding")
@Description("File encoding")
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Option(shortName = "M", longName = "sizeLimit")
@Description("Upper size limit in bytes for the result (128 * 1024 by default, the maximum value is 8 * 1024 * 1024)")
public void setSizeLimit(Integer sizeLimit) {
this.sizeLimit = sizeLimit;
}
@Override
public void process(CommandProcess process) {
if (!verifyOptions(process)) {
return;
}
for (String file : files) {
File f = new File(file);
if (!f.exists()) {
process.end(-1, "cat " + file + ": No such file or directory");
return;
}
if (f.isDirectory()) {
process.end(-1, "cat " + file + ": Is a directory");
return;
}
}
for (String file : files) {
File f = new File(file);
if (f.length() > sizeLimit) {
process.end(-1, "cat " + file + ": Is too large, size: " + f.length());
return;
}
try {
String fileToString = FileUtils.readFileToString(f,
encoding == null ? Charset.defaultCharset() : Charset.forName(encoding));
process.appendResult(new CatModel(file, fileToString));
} catch (IOException e) {
logger.error("cat read file error. name: " + file, e);
process.end(1, "cat read file error: " + e.getMessage());
return;
}
}
process.end();
}
private boolean verifyOptions(CommandProcess process) {
if (sizeLimit > maxSizeLimit) {
process.end(-1, "sizeLimit cannot be large than: " + maxSizeLimit);
return false;
}
//目前不支持过滤,限制http请求执行的文件大小
int maxSizeLimitOfNonTty = 128 * 1024;
if (!process.session().isTty() && sizeLimit > maxSizeLimitOfNonTty) {
process.end(-1, "When executing in non-tty session, sizeLimit cannot be large than: " + maxSizeLimitOfNonTty);
return false;
}
return true;
}
@Override
public void complete(Completion completion) {
if (!CompletionUtils.completeFilePath(completion)) {
super.complete(completion);
}
}
}
package com.taobao.arthas.core.command.basic1000;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Summary;
import com.taobao.text.util.RenderUtil;
@Name("cls")
@Summary("Clear the screen")
public class ClsCommand extends AnnotatedCommand {
@Override
public void process(CommandProcess process) {
if (!process.session().isTty()) {
process.end(-1, "Command 'cls' is only support tty session.");
return;
}
process.write(RenderUtil.cls()).write("\n");
process.end();
}
}
package com.taobao.arthas.core.command.basic1000;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.EchoModel;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Summary;
/**
*
* @author hengyunabc
*
*/
@Name("echo")
@Summary("write arguments to the standard output")
@Description("\nExamples:\n" +
" echo 'abc'\n" +
Constants.WIKI + Constants.WIKI_HOME + "echo")
public class EchoCommand extends AnnotatedCommand {
private String message;
@Argument(argName = "message", index = 0, required = false)
@Description("message")
public void setMessage(String message) {
this.message = message;
}
@Override
public void process(CommandProcess process) {
if (message != null) {
process.appendResult(new EchoModel(message));
}
process.end();
}
}
package com.taobao.arthas.core.command.basic1000;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.DefaultValue;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
/**
* @see com.taobao.arthas.core.shell.command.internal.GrepHandler
*/
@Name("grep")
@Summary("grep command for pipes." )
@Description(Constants.EXAMPLE +
" sysprop | grep java \n" +
" sysprop | grep java -n\n" +
" sysenv | grep -v JAVA\n" +
" sysenv | grep -e \"(?i)(JAVA|sun)\" -m 3 -C 2\n" +
" sysenv | grep JAVA -A2 -B3\n" +
" thread | grep -m 10 -e \"TIMED_WAITING|WAITING\"\n"
+ Constants.WIKI + Constants.WIKI_HOME + "grep")
public class GrepCommand extends AnnotatedCommand {
private String pattern;
private boolean ignoreCase;
/**
* select non-matching lines
*/
private boolean invertMatch;
private boolean isRegEx = false;
/**
* print line number with output lines
*/
private boolean showLineNumber = false;
private boolean trimEnd;
/**
* print NUM lines of leading context
*/
private int beforeLines;
/**
* print NUM lines of trailing context
*/
private int afterLines;
/**
* print NUM lines of output context
*/
private int context;
/**
* stop after NUM selected lines
*/
private int maxCount;
@Argument(index = 0, argName = "pattern", required = true)
@Description("Pattern")
public void setOptionName(String pattern) {
this.pattern = pattern;
}
@Option(shortName = "e", longName = "regex", flag = true)
@Description("Enable regular expression to match")
public void setRegEx(boolean regEx) {
isRegEx = regEx;
}
@Option(shortName = "i", longName = "ignore-case", flag = true)
@Description("Perform case insensitive matching. By default, grep is case sensitive.")
public void setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
}
@Option(shortName = "v", longName = "invert-match", flag = true)
@Description("Select non-matching lines")
public void setInvertMatch(boolean invertMatch) {
this.invertMatch = invertMatch;
}
@Option(shortName = "n", longName = "line-number", flag = true)
@Description("Print line number with output lines")
public void setShowLineNumber(boolean showLineNumber) {
this.showLineNumber = showLineNumber;
}
@Option(longName = "trim-end", flag = false)
@DefaultValue("true")
@Description("Remove whitespaces at the end of the line, default value true")
public void setTrimEnd(boolean trimEnd) {
this.trimEnd = trimEnd;
}
@Option(shortName = "B", longName = "before-context")
@Description("Print NUM lines of leading context)")
public void setBeforeLines(int beforeLines) {
this.beforeLines = beforeLines;
}
@Option(shortName = "A", longName = "after-context")
@Description("Print NUM lines of trailing context)")
public void setAfterLines(int afterLines) {
this.afterLines = afterLines;
}
@Option(shortName = "C", longName = "context")
@Description("Print NUM lines of output context)")
public void setContext(int context) {
this.context = context;
}
@Option(shortName = "m", longName = "max-count")
@Description("stop after NUM selected lines)")
public void setMaxCount(int maxCount) {
this.maxCount = maxCount;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
public boolean isIgnoreCase() {
return ignoreCase;
}
public boolean isInvertMatch() {
return invertMatch;
}
public boolean isRegEx() {
return isRegEx;
}
public boolean isShowLineNumber() {
return showLineNumber;
}
public boolean isTrimEnd() {
return trimEnd;
}
public int getBeforeLines() {
return beforeLines;
}
public int getAfterLines() {
return afterLines;
}
public int getContext() {
return context;
}
public int getMaxCount() {
return maxCount;
}
@Override
public void process(CommandProcess process) {
process.end(-1, "The grep command only for pipes. See 'grep --help'\n");
}
}
package com.taobao.arthas.core.command.basic1000;
import com.taobao.arthas.core.command.model.ArgumentVO;
import com.taobao.arthas.core.command.model.CommandOptionVO;
import com.taobao.arthas.core.command.model.CommandVO;
import com.taobao.arthas.core.command.model.HelpModel;
import com.taobao.arthas.core.shell.cli.Completion;
import com.taobao.arthas.core.shell.cli.CompletionUtils;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.Command;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.command.CommandResolver;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.util.usage.StyledUsageFormatter;
import com.taobao.middleware.cli.CLI;
import com.taobao.middleware.cli.Option;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Summary;
import java.util.ArrayList;
import java.util.List;
/**
* @author vlinux on 14/10/26.
*/
@Name("help")
@Summary("Display Arthas Help")
@Description("Examples:\n" + " help\n" + " help sc\n" + " help sm\n" + " help watch")
public class HelpCommand extends AnnotatedCommand {
private String cmd;
@Argument(index = 0, argName = "cmd", required = false)
@Description("command name")
public void setCmd(String cmd) {
this.cmd = cmd;
}
@Override
public void process(CommandProcess process) {
List<Command> commands = allCommands(process.session());
Command targetCmd = findCommand(commands);
if (targetCmd == null) {
process.appendResult(createHelpModel(commands));
} else {
process.appendResult(createHelpDetailModel(targetCmd));
}
process.end();
}
public HelpModel createHelpDetailModel(Command targetCmd) {
return new HelpModel(createCommandVO(targetCmd, true));
}
private HelpModel createHelpModel(List<Command> commands) {
HelpModel helpModel = new HelpModel();
for (Command command : commands) {
if(command.cli() == null || command.cli().isHidden()){
continue;
}
helpModel.addCommandVO(createCommandVO(command, false));
}
return helpModel;
}
private CommandVO createCommandVO(Command command, boolean withDetail) {
CLI cli = command.cli();
CommandVO commandVO = new CommandVO();
commandVO.setName(command.name());
if (cli!=null){
commandVO.setSummary(cli.getSummary());
if (withDetail){
commandVO.setCli(cli);
StyledUsageFormatter usageFormatter = new StyledUsageFormatter(null);
String usageLine = usageFormatter.computeUsageLine(null, cli);
commandVO.setUsage(usageLine);
commandVO.setDescription(cli.getDescription());
//以线程安全的方式遍历options
List<Option> options = cli.getOptions();
for (int i = 0; i < options.size(); i++) {
Option option = options.get(i);
if (option.isHidden()){
continue;
}
commandVO.addOption(createOptionVO(option));
}
//arguments
List<com.taobao.middleware.cli.Argument> arguments = cli.getArguments();
for (int i = 0; i < arguments.size(); i++) {
com.taobao.middleware.cli.Argument argument = arguments.get(i);
if (argument.isHidden()){
continue;
}
commandVO.addArgument(createArgumentVO(argument));
}
}
}
return commandVO;
}
private ArgumentVO createArgumentVO(com.taobao.middleware.cli.Argument argument) {
ArgumentVO argumentVO = new ArgumentVO();
argumentVO.setArgName(argument.getArgName());
argumentVO.setMultiValued(argument.isMultiValued());
argumentVO.setRequired(argument.isRequired());
return argumentVO;
}
private CommandOptionVO createOptionVO(Option option) {
CommandOptionVO optionVO = new CommandOptionVO();
if (!isEmptyName(option.getLongName())) {
optionVO.setLongName(option.getLongName());
}
if (!isEmptyName(option.getShortName())) {
optionVO.setShortName(option.getShortName());
}
optionVO.setDescription(option.getDescription());
optionVO.setAcceptValue(option.acceptValue());
return optionVO;
}
private boolean isEmptyName(String name) {
return name == null || name.equals(Option.NO_NAME);
}
@Override
public void complete(Completion completion) {
List<Command> commands = allCommands(completion.session());
List<String> names = new ArrayList<String>(commands.size());
for (Command command : commands) {
CLI cli = command.cli();
if (cli == null || cli.isHidden()) {
continue;
}
names.add(command.name());
}
CompletionUtils.complete(completion, names);
}
private List<Command> allCommands(Session session) {
List<CommandResolver> commandResolvers = session.getCommandResolvers();
List<Command> commands = new ArrayList<Command>();
for (CommandResolver commandResolver : commandResolvers) {
commands.addAll(commandResolver.commands());
}
return commands;
}
private Command findCommand(List<Command> commands) {
for (Command command : commands) {
if (command.name().equals(cmd)) {
return command;
}
}
return null;
}
}
package com.taobao.arthas.core.command.basic1000;
import java.util.ArrayList;
import java.util.List;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.command.model.HistoryModel;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.history.HistoryManager;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.shell.term.impl.TermImpl;
import com.taobao.middleware.cli.annotations.Argument;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Option;
import com.taobao.middleware.cli.annotations.Summary;
import io.termd.core.readline.Readline;
import io.termd.core.util.Helper;
/**
*
* @author hengyunabc 2018-11-18
*
*/
@Name("history")
@Summary("Display command history")
@Description(Constants.EXAMPLE + " history\n" + " history -c\n" + " history 5\n")
public class HistoryCommand extends AnnotatedCommand {
boolean clear = false;
int n = -1;
@Option(shortName = "c", longName = "clear", flag = true , acceptValue = false)
@Description("clear history")
public void setClear(boolean clear) {
this.clear = clear;
}
@Argument(index = 0, argName = "n", required = false)
@Description("how many history commnads to display")
public void setNumber(int n) {
this.n = n;
}
@Override
public void process(CommandProcess process) {
Session session = process.session();
//TODO 修改term history实现方式,统一使用HistoryManager
Object termObject = session.get(Session.TTY);
if (termObject instanceof TermImpl) {
TermImpl term = (TermImpl) termObject;
Readline readline = term.getReadline();
List<int[]> history = readline.getHistory();
if (clear) {
readline.setHistory(new ArrayList<int[]>());
} else {
StringBuilder sb = new StringBuilder();
int size = history.size();
if (n < 0 || n > size) {
n = size;
}
for (int i = 0; i < n; ++i) {
int[] line = history.get(n - i - 1);
sb.append(String.format("%5s ", size - (n - i - 1)));
Helper.appendCodePoints(line, sb);
sb.append('\n');
}
process.write(sb.toString());
}
} else {
//http api
HistoryManager historyManager = ArthasBootstrap.getInstance().getHistoryManager();
if (clear) {
historyManager.clearHistory();
} else {
List<String> history = historyManager.getHistory();
process.appendResult(new HistoryModel(new ArrayList<String>(history)));
}
}
process.end();
}
}
package com.taobao.arthas.core.command.basic1000;
import com.alibaba.arthas.deps.org.slf4j.Logger;
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
import com.taobao.arthas.common.IOUtils;
import com.taobao.arthas.core.command.Constants;
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
import com.taobao.arthas.core.shell.command.CommandProcess;
import com.taobao.arthas.core.shell.term.impl.Helper;
import com.taobao.middleware.cli.annotations.Description;
import com.taobao.middleware.cli.annotations.Name;
import com.taobao.middleware.cli.annotations.Summary;
import com.taobao.text.Decoration;
import com.taobao.text.ui.TableElement;
import com.taobao.text.util.RenderUtil;
import static com.taobao.text.ui.Element.label;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* A command to display all the keymap for the specified connection.
*
* @author ralf0131 2016-12-15 17:27.
* @author hengyunabc 2019-01-18
*/
@Name("keymap")
@Summary("Display all the available keymap for the specified connection.")
@Description(Constants.WIKI + Constants.WIKI_HOME + "keymap")
public class KeymapCommand extends AnnotatedCommand {
private static final Logger logger = LoggerFactory.getLogger(KeymapCommand.class);
@Override
public void process(CommandProcess process) {
if (!process.session().isTty()) {
process.end(-1, "Command 'keymap' is only support tty session.");
return;
}
InputStream inputrc = Helper.loadInputRcFile();
try {
TableElement table = new TableElement(1, 1, 2).leftCellPadding(1).rightCellPadding(1);
table.row(true, label("Shortcut").style(Decoration.bold.bold()),
label("Description").style(Decoration.bold.bold()),
label("Name").style(Decoration.bold.bold()));
BufferedReader br = new BufferedReader(new InputStreamReader(inputrc));
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || "".equals(line)) {
continue;
}
String[] strings = line.split(":");
if (strings.length == 2) {
table.row(strings[0], translate(strings[0]), strings[1]);
} else {
table.row(line);
}
}
process.write(RenderUtil.render(table, process.width()));
} catch (IOException e) {
logger.error("read inputrc file error.", e);
} finally {
IOUtils.close(inputrc);
process.end();
}
}
private String translate(String key) {
if (key.length() == 6 && key.startsWith("\"\\C-") && key.endsWith("\"")) {
char ch = key.charAt(4);
if ((ch >= 'a' && ch <= 'z') || ch == '?') {
return "Ctrl + " + ch;
}
}
if (key.equals("\"\\e[D\"")) {
return "Left arrow";
} else if (key.equals("\"\\e[C\"")) {
return "Right arrow";
} else if (key.equals("\"\\e[B\"")) {
return "Down arrow";
} else if (key.equals("\"\\e[A\"")) {
return "Up arrow";
}
return key;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment