历史文章:
- OGNL语法规范
- 消失的堆栈
- Arthas原理系列(一):利用JVM的attach机制实现一个极简的watch命令
- Arthas原理系列(二):总体架构和项目入口
- Arthas原理系列(三):服务端启动流程
- Arthas原理系列(四):字节码插装让一切变得有可能
前言
在前面的文章中我们可以看到watch
命令对原来的字节码进行了插装,并且我们以此为思路实现了一个简易版的watch
命令,但真实的watch
提供的能力远不止统计方法的运行时间,我们最常用他的一个功能还是观察方法的入参返回值等运行时的变量。所有需要插装字节码的命令都继承了EnhancerCommand
,总共有 5 个命令,分别是monitor,stack,tt,watch,trace
,本片文章将会展示 arthas 为这么多的命令如何设计一个统一的插装流程的。
插装的主流程
在上篇文章中,我们看了watch
命令的process
方法:
@Override
public void process(final CommandProcess process) {
// ctrl-C support
process.interruptHandler(new CommandInterruptHandler(process));
// q exit support
process.stdinHandler(new QExitHandler(process));
// start to enhance
enhance(process);
}
可以看到整个插装的入口是enhance
这个方法,这个方法在父类EnhancerCommand
中实现:
protected void enhance(CommandProcess process) {
Session session = process.session();
if (!session.tryLock()) {
String msg = "someone else is enhancing classes, pls. wait.";
process.appendResult(new EnhancerModel(null, false, msg));
process.end(-1, msg);
return;
}
EnhancerAffect effect = null;
int lock = session.getLock();
try {
Instrumentation inst = session.getInstrumentation();
// 获取每个命令特有的AdviceListener,插装的时候会把这个方法注入到目标JVM中
AdviceListener listener = getAdviceListenerWithId(process);
if (listener == null) {
logger.error("advice listener is null");
String msg = "advice listener is null, check arthas log";
process.appendResult(new EnhancerModel(effect, false, msg));
process.end(-1, msg);
return;
}
boolean skipJDKTrace = false;
if(listener instanceof AbstractTraceAdviceListener) {
// 如果是trace命令,判断是否需要跳过jdk提供的方法
skipJDKTrace = ((AbstractTraceAdviceListener) listener).getCommand().isSkipJDKTrace();
}
Enhancer enhancer = new Enhancer(listener, listener instanceof InvokeTraceable, skipJDKTrace, getClassNameMatcher(), getMethodNameMatcher());
// 注册通知监听器
process.register(listener, enhancer);
// 开始插装
effect = enhancer.enhance(inst);
if (effect.getThrowable() != null) {
String msg = "error happens when enhancing class: "+effect.getThrowable().getMessage();
process.appendResult(new EnhancerModel(effect, false, msg));
process.end(1, msg + ", check arthas log: " + LogUtil.loggingFile());
return;
}
if (effect.cCnt() == 0 || effect.mCnt() == 0) {
// no class effected
// might be method code too large
process.appendResult(new EnhancerModel(effect, false, "No class or method is affected"));
String msg = "No class or method is affected, try:\n"
+ "1. sm CLASS_NAME METHOD_NAME to make sure the method you are tracing actually exists (it might be in your parent class).\n"
+ "2. reset CLASS_NAME and try again, your method body might be too large.\n"
+ "3. check arthas log: " + LogUtil.loggingFile() + "\n"
+ "4. visit https://github.com/alibaba/arthas/issues/47 for more details.";
process.end(-1, msg);
return;
}
// 这里做个补偿,如果在enhance期间,unLock被调用了,则补偿性放弃
if (session.getLock() == lock) {
if (process.isForeground()) {
process.echoTips(Constants.Q_OR_CTRL_C_ABORT_MSG + "\n");
}
}
process.appendResult(new EnhancerModel(effect, true));
//异步执行,在AdviceListener中结束
} catch (Throwable e) {
String msg = "error happens when enhancing class: "+e.getMessage();
logger.error(msg, e);
process.appendResult(new EnhancerModel(effect, false, msg));
process.end(-1, msg);
} finally {
if (session.getLock() == lock) {
// enhance结束后解锁
process.session().unLock();
}
}
}
这段代码稍微有点长,里面最重要的是两个地方,一个是调用getAdviceListenerWithId
获得了一个AdviceListener
,这个类实现了befor
,arterReturning
,afterThrowing
等方法,会被插装函数注入到目标JVM
中。另一个是创建了一个Enhancer
对象,并开始对目标 JVM 的类和方法进行插装。
public synchronized EnhancerAffect enhance(final Instrumentation inst) throws UnmodifiableClassException {
// 获取需要增强的类集合
this.matchingClasses = GlobalOptions.isDisableSubClass
? SearchUtils.searchClass(inst, classNameMatcher)
: SearchUtils.searchSubClass(inst, SearchUtils.searchClass(inst, classNameMatcher));
// 过滤掉无法被增强的类
filter(matchingClasses);
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;
}
enhance
这个方法的实现如果看过之前实现watch
命令那一篇应该不会陌生,在这个方法中最重要的事情就是注册了本类作为转换类,并且调用Instrumentation
的retransformClasses
方法对类进行转换。在这个机制下,真正干活的就是transform
方法了。
@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 (