前言
在前两篇文章Jvm-Sandbox源码分析--启动简析)和Jvm-Sandbox源码分析--启动时加载模块中我们分析了jvm-sandbox启动及启动时加载模块的过程,这一篇我们来看一下如何使用模块增强目标类的流程。
执行脚本
在启动沙箱流程之后,执行如下命令。
sh sandbox/bin/sandbox.sh -p pid -d "broken-clock-tinker/repairCheckState"
# -d debug
if [[ ! -z ${OP_DEBUG} ]]; then
sandbox_debug_curl "module/http/${ARG_DEBUG}"
exit
fi
通过ModuleHttpServlet doMethod方法进行http路由
ModuleHttpServlet是在启动流程中提到的 JettyCoreServer 类中通过 initJettyContextHandler 操作加入到jetty上下文的。
// module-http-servlet
final String pathSpec = "/module/http/*";
logger.info("initializing http-handler. path={}", contextPath + pathSpec);
context.addServlet(
new ServletHolder(new ModuleHttpServlet(jvmSandbox.getCoreModuleManager())),
pathSpec
);
进入 ModuleHttpServlet 中,我们可以看到所有 http 相关请求都会进入 doMethod 方法。
关键步骤:
1.获取请求路径
2.获取模块ID
3.通过模块ID,获取模块
// 获取请求路径
final String path = req.getPathInfo();
// 获取模块ID
final String uniqueId = parseUniqueId(path);
if (StringUtils.isBlank(uniqueId)) {
logger.warn("http request value={} was not found.", path);
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// 获取模块
final CoreModule coreModule = coreModuleManager.get(uniqueId);
if (null == coreModule) {
logger.warn("module[id={}] was not existed, value={};", uniqueId, path);
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
4.获取目标类(模块)上加了@Command注解的方法
private Method matchingModuleMethod(final String path,
final Http.Method httpMethod,
final String uniqueId,
final Class<?> classOfModule) {
// 查找@Command注解的方法
for (final Method method : MethodUtils.getMethodsListWithAnnotation(classOfModule, Command.class)) {
final Command commandAnnotation = method.getAnnotation(Command.class);
if (null == commandAnnotation) {
continue;
}
final String pathOfCmd = "/" + uniqueId + "/" + commandAnnotation.value();
if (StringUtils.equals(path, pathOfCmd)) {
return method;
}
}
// 找不到匹配方法,返回null
return null;
}
5.使用ModuleJarClassLoader invoke目标模块的方法
//关键代码
// 生成方法调用参数
final Object[] parameterObjectArray = generateParameterObjectArray(method, req, resp);
//使用ModuleJarClassLoader invoke目标模块的方法
final ClassLoader oriThreadContextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(coreModule.getLoader());
method.invoke(coreModule.getModule(), parameterObjectArray);
Thread.currentThread().setContextClassLoader(oriThreadContextClassLoader);
观察调用链
我们可以看到调用进入了我们自定义的Module中。
官方示例模块代码
@MetaInfServices(Module.class)
@Information(id = "broken-clock-tinker")
public class BrokenClockTinkerModule implements Module {
@Resource
private ModuleEventWatcher moduleEventWatcher;
@Command("repairCheckState")
public void repairCheckState() {
new EventWatchBuilder(moduleEventWatcher)
.onClass("com.taobao.demo.Clock")
.onBehavior("checkState")
.onWatch(new AdviceListener() {
/**
* 拦截{@code com.taobao.demo.Clock#checkState()}方法,当这个方法抛出异常时将会被
* AdviceListener#afterThrowing()所拦截
*/
@Override
protected void afterThrowing(Advice advice) throws Throwable {
// 在此,你可以通过ProcessController来改变原有方法的执行流程
// 这里的代码意义是:改变原方法抛出异常的行为,变更为立即返回;void返回值用null表示
ProcessController.returnImmediately(null);
}
});
}
}
行为匹配实现器BuildingForBehavior
这是事件观察者类构建器 EventWatchBuilder的一个内部类。
@Override
public EventWatcher onWatch(final AdviceListener adviceListener) {
return build(new AdviceAdapterListener(adviceListener), null, BEFORE, RETURN, THROWS, IMMEDIATELY_RETURN, IMMEDIATELY_THROWS);
}
事件观察者类构建器 EventWatchBuilder的build方法
其实到这里可以看出通过 HTTP 请求来调用模块的方法增强目标类,最终是调用了 moduleEventWatcher的watch方法。
关键步骤:
1.调用 moduleEventWatcher.watch 去修改目标类的字节码
2.创建事件观察条件,过滤类和方法
private EventWatcher build(final EventListener listener,
final Progress progress,
final Event.Type... eventTypes) {
// 调用 moduleEventWatcher.watch 去修改目标类的字节码
final int watchId = moduleEventWatcher.watch(
toEventWatchCondition(),// 创建事件观察条件,过滤类和方法
listener,
progress,
eventTypes
);
return new EventWatcher() {
final List<Progress> progresses = new ArrayList<Progress>();
@Override
public int getWatchId() {
return watchId;
}
@Override
// 流程监控回调类
public IBuildingForUnWatching withProgress(Progress progress) {
if (null != progress) {
progresses.add(progress);
}
return this;
}
@Override
// 取消监听操作
public void onUnWatched() {
moduleEventWatcher.delete(watchId, toProgressGroup(progresses));
}
};
}
// 创建事件观察条件,过滤类和方法
private EventWatchCondition toEventWatchCondition() {
final List<Filter> filters = new ArrayList<Filter>();
for (final BuildingForClass bfClass : bfClasses) {
final Filter filter = new Filter() {
@Override
public boolean doClassFilter(final int access,
final String javaClassName,
final String superClassTypeJavaClassName,
final String[] interfaceTypeJavaClassNameArray,
final String[] annotationTypeJavaClassNameArray) {
return (access & bfClass.withAccess) == bfClass.withAccess
&& patternMatching(javaClassName, bfClass.pattern, patternType)
&& bfClass.hasInterfaceTypes.patternHas(interfaceTypeJavaClassNameArray)
&& bfClass.hasAnnotationTypes.patternHas(annotationTypeJavaClassNameArray);
}
@Override
public boolean doMethodFilter(final int access,
final String javaMethodName,
final String[] parameterTypeJavaClassNameArray,
final String[] throwsTypeJavaClassNameArray,
final String[] annotationTypeJavaClassNameArray) {
// nothing to matching
if (bfClass.bfBehaviors.isEmpty()) {
return false;
}
// matching any behavior
for (final BuildingForBehavior bfBehavior : bfClass.bfBehaviors) {
if ((access & bfBehavior.withAccess) == bfBehavior.withAccess
&& patternMatching(javaMethodName, bfBehavior.pattern, patternType)
&& bfBehavior.withParameterTypes.patternWith(parameterTypeJavaClassNameArray)
&& bfBehavior.hasExceptionTypes.patternHas(throwsTypeJavaClassNameArray)
&& bfBehavior.hasAnnotationTypes.patternHas(annotationTypeJavaClassNameArray)) {
return true;
}//if
}//for
// non matched
return false;
}
};//filter
filters.add(makeExtFilter(filter, bfClass));
}
return new EventWatchCondition() {
@Override
public Filter[] getOrFilterArray() {
return filters.toArray(new Filter[0]);
}
};
}
ModuleEventWatcher.watch方法逻辑
public int watch(final EventWatchCondition condition,
final EventListener listener,
final Progress progress,
final Event.Type... eventType)
1.生成watchId,其本质是使用了一个全局的AtomicInteger
final int watchId = watchIdSequencer.next();
2.给对应的模块追加 ClassFileTransformer
这是 JDK 提供的一个接口ClassFileTransformer,其主要实现了 transform 方法用于修改类的字节码,通过这个方法,可以得到虚拟机载入的类的字节码。
final SandboxClassFileTransformer sandClassFileTransformer = new SandboxClassFileTransformer(
watchId, coreModule.getUniqueId(), matcher, listener, isEnableUnsafe, eventType, namespace);
3.将 SandboxClassFileTransformer 注册到 coreModule 中
这个coreModule 就是我们自己定义的BrokenClockTinkerModule
// 注册到CoreModule中
coreModule.getSandboxClassFileTransformers().add(sandClassFileTransformer);
4.注册到JVM加载上ClassFileTransformer处理新增的类
inst是最开始启动时候通过 sandbox-agent 所传递的Instrumentation,通过其 addTransformer方法来修改类的字节码
// 注册到JVM加载上ClassFileTransformer处理新增的类
inst.addTransformer(sandClassFileTransformer, true);
5.查找需要渲染的类集合
这里会调用iteratorForLoadedClasses这个方法,这个方法中执行了inst.getAllLoadedClasses()这个方法,最终会调用sandbox自己实现的类加载器SandboxClassLoader的loadClass方法。
// 查找需要渲染的类集合
final List<Class<?>> waitingReTransformClasses = classDataSource.findForReTransform(matcher);
@Override
public List<Class<?>> findForReTransform(final Matcher matcher) {
return find(matcher, true);
}
private List<Class<?>> find(final Matcher matcher,
final boolean isRemoveUnsupported) {
final List<Class<?>> classes = new ArrayList<Class<?>>();
if (null == matcher) {
return classes;
}
final Iterator<Class<?>> itForLoaded = iteratorForLoadedClasses();
while (itForLoaded.hasNext()) {
final Class<?> clazz = itForLoaded.next();
// 过滤掉对于JVM认为不可修改的类
if (isRemoveUnsupported
&& !inst.isModifiableClass(clazz)) {
// logger.debug("remove from findForReTransform, because class:{} is unModifiable", clazz.getName());
continue;
}
try {
if (isRemoveUnsupported) {
if (new UnsupportedMatcher(clazz.getClassLoader(), isEnableUnsafe)
.and(matcher)
.matching(ClassStructureFactory.createClassStructure(clazz))
.isMatched()) {
classes.add(clazz);
}
} else {
if (matcher.matching(ClassStructureFactory.createClassStructure(clazz)).isMatched()) {
classes.add(clazz);
}
}
} catch (Throwable cause) {
// 在这里可能会遇到非常坑爹的模块卸载错误
// 当一个URLClassLoader被动态关闭之后,但JVM已经加载的类并不知情(因为没有GC)
// 所以当尝试获取这个类更多详细信息的时候会引起关联类的ClassNotFoundException等未知的错误(取决于底层ClassLoader的实现)
// 这里没有办法穷举出所有的异常情况,所以catch Throwable来完成异常容灾处理
// 当解析类出现异常的时候,直接简单粗暴的认为根本没有这个类就好了
logger.debug("remove from findForReTransform, because loading class:{} occur an exception", clazz.getName(), cause);
}
}
return classes;
}
@Override
public Iterator<Class<?>> iteratorForLoadedClasses() {
return new Iterator<Class<?>>() {
final Class<?>[] loaded = inst.getAllLoadedClasses();
int pos = 0;
@Override
public boolean hasNext() {
return pos < loaded.length;
}
@Override
public Class<?> next() {
return loaded[pos++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
6.调用reTransformClasses方法
// 应用JVM
reTransformClasses(watchId, waitingReTransformClasses, progress);
//reTransformClasses 中重要步骤
//转换待转换的类
inst.retransformClasses(waitingReTransformClass);
7.到这一步我们来看一下调用路径
8.我们看到经过一系列调用,最终会执行SandboxClassFileTransformer的transform方法。
这里会调用SandboxClassFileTransformer 实现ClassFileTransformer接口的transform方法。
这个方法主要实现过滤sandbox相关的类和增强符合条件的字节码文件。
@Override
public byte[] transform(final ClassLoader loader,
final String internalClassName,
final Class<?> classBeingRedefined,
final ProtectionDomain protectionDomain,
final byte[] srcByteCodeArray) {
try {
// 这里过滤掉Sandbox所需要的类,防止ClassCircularityError的发生
if (null != internalClassName
&& internalClassName.startsWith("com/alibaba/jvm/sandbox/")) {
return null;
}
// 这里过滤掉来自SandboxClassLoader的类,防止ClassCircularityError的发生
if (loader == SandboxClassFileTransformer.class.getClassLoader()) {
return null;
}
// 过滤掉来自ModuleJarClassLoader加载的类
if (loader instanceof ModuleJarClassLoader) {
return null;
}
return _transform(
loader,
internalClassName,
classBeingRedefined,
srcByteCodeArray
);
} catch (Throwable cause) {
logger.warn("sandbox transform {} in loader={}; failed, module={} at watch={}, will ignore this transform.",
internalClassName,
loader,
uniqueId,
watchId,
cause
);
return null;
}
}
private byte[] _transform(final ClassLoader loader,
final String internalClassName,
final Class<?> classBeingRedefined,
final byte[] srcByteCodeArray) {
// 如果未开启unsafe开关,是不允许增强来自BootStrapClassLoader的类
if (!isEnableUnsafe
&& null == loader) {
logger.debug("transform ignore {}, class from bootstrap but unsafe.enable=false.", internalClassName);
return null;
}
final ClassStructure classStructure = getClassStructure(loader, classBeingRedefined, srcByteCodeArray);
final MatchingResult matchingResult = new UnsupportedMatcher(loader, isEnableUnsafe).and(matcher).matching(classStructure);
final Set<String> behaviorSignCodes = matchingResult.getBehaviorSignCodes();
// 如果一个行为都没匹配上也不用继续了
if (!matchingResult.isMatched()) {
logger.debug("transform ignore {}, no behaviors matched in loader={}", internalClassName, loader);
return null;
}
// 开始进行类匹配
try {
// 类增强
final byte[] toByteCodeArray = new EventEnhancer().toByteCodeArray(
loader,
srcByteCodeArray,
behaviorSignCodes,
namespace,
listenerId,
eventTypeArray
);
if (srcByteCodeArray == toByteCodeArray) {
logger.debug("transform ignore {}, nothing changed in loader={}", internalClassName, loader);
return null;
}
// statistic affect
affectStatistic.statisticAffect(loader, internalClassName, behaviorSignCodes);
logger.info("transform {} finished, by module={} in loader={}", internalClassName, uniqueId, loader);
return toByteCodeArray;
} catch (Throwable cause) {
logger.warn("transform {} failed, by module={} in loader={}", internalClassName, uniqueId, loader, cause);
return null;
}
}
9.经过一系列的过滤逻辑,最终会通过EventEnhancer对符合条件对类进行字节码的修改,这里也是jvm-sandbox的核心能力。
// 类增强
final byte[] toByteCodeArray = new EventEnhancer().toByteCodeArray(
loader,
srcByteCodeArray,
behaviorSignCodes,
namespace,
listenerId,
eventTypeArray
);
10.asm操作字节码
asm扫盲参考
接下来我们看jvm-sandbox是如何实现字节码修改的。
ClassReader读取字节码数据。
final ClassReader cr = new ClassReader(byteCodeArray);
ClassWriter 继承 ClassVisitor 抽象类,负责将对象化的 class 文件内容重构成一个二进制格式的 class 字节码文件。
final ClassWriter cw = createClassWriter(targetClassLoader, cr);
映射Java对象为对象ID(JVM唯一)
final int targetClassLoaderObjectID = ObjectIDs.instance.identity(targetClassLoader);
调用ClassReader的accept方法,接收一个实现了抽象类 ClassVisitor的EventWeaver(方法事件编织者)对象实例作为参数,EventWeaver对象实现了ClassVisitor 的各个visitxxxx方法。
cr.accept(
new EventWeaver(
ASM7, cw, namespace, listenerId,
targetClassLoaderObjectID,
cr.getClassName(),
signCodes,
eventTypeArray
),
EXPAND_FRAMES
);
最终得到的cw.toByteArray() 即是我们重新transform之后的字节码。