当前位置:网站首页>Understand the principle of bytecode enhancement technology through the jvm-sandbox source code
Understand the principle of bytecode enhancement technology through the jvm-sandbox source code
2022-07-04 04:17:00 【InfoQ】
Introduce
Architecture design

- Module control management : Responsible for managing the sandbox Own module and user-defined module , For example, module loading , Activate , frozen , uninstall
- Event monitoring processing : User defined module implementation Event Interface for customized processing of enhanced events , Wait for the event distribution processor to trigger .
- Sandbox event distribution processor : After enhancing the target method, three links will be added to the target method , Before method call
BEFORE
、After callingRETURN
、Call throw exceptionTHROWS
、When the code is executed to these three phases, the dispatcher will assign it to the corresponding event listening executor for execution .
- Code weaving framework : adopt ASM The framework relies on JVMTI Modify the bytecode of the target method , So as to complete the ability of code enhancement .
- Retrieve filter : When the user creates an enhanced event on the target method , The sandbox will be right for the target jvm Match and filter the target classes and methods in . Match to the target classes and methods set by the user for enhancement , To filter out sandbox Internal classes and jvm Classes that are considered immutable .
- Load class retrieval : Get the class collection that needs to be enhanced and rely on the retrieval filter module
- HTTP The server : adopt http The protocol communicates with the client (sandbox.sh It can be understood as client ) The essence is through curl Command to send instructions to the sandbox http The server . For example, module loading , Activate , frozen , Uninstall and other instructions
Related technology
JVM TI
Instrumentation
- Add... To the startup script -javaagent Parameter this way will be accompanied by JVM Start together ,agent We need to provide famous premain Method , seeing the name of a thing one thinks of its function premain Is in main Method is executed before it runs , Then I will run the main program main Method , This requires developers to confirm the processing logic and parameter content of the agent before the application starts . This kind of hanging on agent The advantage of this method is if agent Starting requires loading a large number of classes , With jvm Loading directly at startup will not cause JVM Stuck or CPU shake , The disadvantage is that it is not flexible enough .
- utilize Attach API stay JVM The mount can be completed without restarting during operation ,agent Need to provide agentmain Method , Plug and play mode is very flexible ,Attach API Provides an add-on to Java The mechanism of virtual machine , Use this API Attach to the target virtual machine and load its tool agent into the virtual machine , Essentially, it provides and goals jvm The ability to communicate . For example, we often use jastck,jmap Such orders are all used attach api First with the goal jvm Establish communication and then execute the command . But if agent Starting requires loading a large number of classes, which may lead to the target jvm There's a Caton ,cpu Jitter, etc
- addTransformer: Register bytecode converter , After registering a bytecode converter , All class loads are processed by bytecode converters .
- retransformClasses Back to the JVM Bytecode conversion of loaded classes
- removeTransformer Delete the registered bytecode converter , After deletion, the newly loaded class will not be processed by bytecode converter , But already “ enhance ” The used classes will still be preserved
ClassFileTransformer
byte[] transform( ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
- When loading a new class , for example ClassLoader.defineClass
- When redefining classes , for example Instrumentation.redefineClasses
- When re converting classes , for example Instrumentation.retransformClasses
Bytecode generation
- ClassReader: The main function of this class is to read bytecode files , And then inform... Of the read data ClassVisitor;
- ClassVisitor: For generating and transforming compiled classes ASM API be based on ClassVisitor abstract class , receive ClassReader Send a message to method Access requests for , And replace it with another customized MethodVisitor
- ClassWriter: It is inherited from ClassVisitor, It is mainly used to generate classes ;

ClassLoader
Types of Loaders
- Start class loader (Bootstrap Class Loader): This class loader is responsible for loading and storing in <JAVA_HOME>\lib Catalog , Or be -Xbootclasspath Stored in the path specified by the , And it's Java Virtual machines can Identification of the ( Identify by file name , Such as rt .jar、t ools.jar, The name does not match the class library even if placed in lib The directory will not be loaded ) class The library is loaded into the memory of the virtual machine
- Extend the classloader (Extension Class Loader): This class loader is in class sun.misc.Launcher$ExtClassLoader China and Israel Java In the form of code . It's responsible for loading <JAVA_HOM E>\lib\ext Directory , Or be java.ext.dirs The system variables are All class libraries in the specified path .
- Application class loader (Application Class Loader): This class loader consists of sun.misc.Launcher$App ClassLoader To achieve . Because the application class loader is ClassLoader Class getSystem- ClassLoader() Return value of method , So in some cases, it is also called “ system class loader ”. It is responsible for loading the user classpath (ClassPath) All the class libraries on , Developers can also use this classloader directly in the code . If there is no... In the application I have customized my own class loader , In general, this is the default classloader in the program .
- Custom class loaders : As the name suggests, it is a class loader customized by developers
Parent delegation model

Custom class loaders
Sandbox isolation strategy

SPI
The boot process
mount Agent
- -javaagent Mount directly in the startup script
- utilize attach api Mount at runtime
function attach_jvm() {
# attach target jvm
"${SANDBOX_JAVA_HOME}/bin/java" \
${SANDBOX_JVM_OPS} \
-jar "${SANDBOX_LIB_DIR}/sandbox-core.jar" \
"${TARGET_JVM_PID}" \
"${SANDBOX_LIB_DIR}/sandbox-agent.jar" \
"home=${SANDBOX_HOME_DIR};token=${token};server.ip=${TARGET_SERVER_IP};server.port=${TARGET_SERVER_PORT};namespace=${TARGET_NAMESPACE}" ||
exit_on_err 1 "attach JVM ${TARGET_JVM_PID} fail."
}
<manifest>
<mainClass>com.alibaba.jvm.sandbox.core.CoreLauncher</mainClass>
</manifest>
private void attachAgent(final String targetJvmPid,
final String agentJarPath,
final String cfg) throws Exception {
vmObj = VirtualMachine.attach(targetJvmPid);
if (vmObj != null) {
vmObj.loadAgent(agentJarPath, cfg);
}
}
load Agent
<manifestEntries>
<Premain-Class>com.alibaba.jvm.sandbox.agent.AgentLauncher</Premain-Class>
<Agent-Class>com.alibaba.jvm.sandbox.agent.AgentLauncher</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
- String featureString Is the execution script execution in the script attach Parameters passed during Such as sandbox Path address ,token,namspace( Tenant ) etc.
- Instrumentation yes JVM The basic library provided can dynamically modify the loaded classes at run time , obtain Instrumentation Instances can only pass premain perhaps agentmian Method parameters .
// Start loading -javaagent
public static void premain(String featureString, Instrumentation inst) {
LAUNCH_MODE = LAUNCH_MODE_AGENT;
install(toFeatureMap(featureString), inst);
}
// Dynamic loading attach api
public static void agentmain(String featureString, Instrumentation inst) {
LAUNCH_MODE = LAUNCH_MODE_ATTACH;
final Map<String, String> featureMap = toFeatureMap(featureString); // Parse parameters
writeAttachResult(
getNamespace(featureMap), // Get the ownership of the tenant namespace
getToken(featureMap), // obtain token
install(featureMap, inst)
);
}
initialization Agent
Spy Spy class
// take Spy Injection into BootstrapClassLoader
inst.appendToBootstrapClassLoaderSearch(new JarFile(new File(
getSandboxSpyJarPath(home)
// SANDBOX_SPY_JAR_PATH
)));
// BEFORE
try {
/*
* do something...
*/
// RETURN
return;
} catch (Throwable cause) {
// THROWS
}

SandBoxClassLoader
final ClassLoader sandboxClassLoader = loadOrDefineClassLoader(
namespace,
getSandboxCoreJarPath(home)
// SANDBOX_CORE_JAR_PATH
);
@Override
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
final Class<?> loadedClass = findLoadedClass(name);
if (loadedClass != null) {
return loadedClass;
}
try {
Class<?> aClass = findClass(name);
if (resolve) {
resolveClass(aClass);
}
return aClass;
} catch (Exception e) {
return super.loadClass(name, resolve);
}
}
start-up HTTP service
@Override
public synchronized void bind(final CoreConfigure cfg, final Instrumentation inst) throws IOException {
this.cfg = cfg;
try {
initializer.initProcess(new Initializer.Processor() {
@Override
public void process() throws Throwable {
logger.info("initializing server. cfg={}", cfg);
jvmSandbox = new JvmSandbox(cfg, inst);
initHttpServer(); // initialization http server
initJettyContextHandler(); // initialization jetty context processor
httpServer.start();// start-up http server
}
});
// Initialize and load all modules
try {
jvmSandbox.getCoreModuleManager().reset();
} catch (Throwable cause) {
logger.warn("reset occur error when initializing.", cause);
}
......
}
private void initJettyContextHandler() {
......
// module-http-servlet
final String pathSpec = "/module/http/*";
logger.info("initializing http-handler. path={}", contextPath + pathSpec);
context.addServlet(
new ServletHolder(new ModuleHttpServlet(cfg, jvmSandbox.getCoreModuleManager())),
pathSpec
);
httpServer.setHandler(context);
}
Summary

Module management
- $sandbox_home/module/Sandbox system module directory , By configuration item system_module Define . A general management module for storing sandboxes , For example, for the sandbox module management function module-mgr modular , Future module operation quality monitoring module 、 The security verification modules will also be stored here , Distribute with the release of sandbox . The system module is not refreshed (-f)、** Forced to refresh (-F) The impact of function , Only container reset (-R)** It enables the sandbox to reload all modules under the system module directory .
- $sandbox_home/sandbox-module/Sandbox user module directory , from sandbox.properties Configuration item for user_module Define , The default is ${HOME}/.sandbox-module/. It is generally used to store modules developed by users . Self developed modules often face frequent version upgrades , When dynamic hot plug replacement of modules is required , Can pass ** Refresh (-f) Or force refresh (-F)** To finish reloading .
Module life cycle
- 【 Load module 】 Loaded correctly by sandbox , The sandbox will allow the module to command accordingly 、 Code insertion and other actions
- 【 Activation module 】 After loading successfully, the default is frozen , The code needs to be activated actively . The module can only listen to sandbox events when it is active
- 【 Freeze module 】 After entering the frozen state , All sandbox events previously listened to will be masked . It should be noted that , The frozen module will not return the code plug of event listening , Only delete()、wathcing() Or the plug-in code will be cleaned up when the module is unloaded
- 【 Unload sandbox 】 I won't see this module again , All resources previously allocated to this module will be recycled , Classes that include modules that have been listening for events will be removed from listening instrumentation , Clean and tidy without sequelae
Custom module
@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() {
/**
* Intercept {@code com.taobao.demo.Clock#checkState()} Method , When this method throws an exception, it will be
* AdviceListener#afterThrowing() Intercepted
*/
@Override
protected void afterThrowing(Advice advice) throws Throwable {
// Here it is , You can go through ProcessController To change the execution process of the original method
// The meaning of the code here is : Change the behavior of throwing exceptions in the original method , Change to return immediately ;void Return value with null Express
ProcessController.returnImmediately(null);
}
});
}
}
Module loading
jvmSandbox.getCoreModuleManager().reset();
public synchronized CoreModuleManager reset() throws ModuleException {
logger.info("resetting all loaded modules:{}", loadedModuleBOMap.keySet());
// 1. Force uninstall of all modules
unloadAll();
// 2. Load all modules
for (final File moduleLibDir : moduleLibDirArray) {
// User module loading Directory , Load all modules in the user module directory
// Verify the access rights of the module
if (moduleLibDir.exists() && moduleLibDir.canRead()) {
new ModuleLibLoader(moduleLibDir, cfg.getLaunchMode())
.load(
new InnerModuleJarLoadCallback(),
new InnerModuleLoadCallback()
);
} else {
logger.warn("module-lib not access, ignore flush load this lib. path={}", moduleLibDir);
}
}
return this;
}
void load(final ModuleLoadCallback mCb) throws IOException {
boolean hasModuleLoadedSuccessFlag = false;
ModuleJarClassLoader moduleJarClassLoader = null;
logger.info("prepare loading module-jar={};", moduleJarFile);
try {
moduleJarClassLoader = new ModuleJarClassLoader(moduleJarFile);
final ClassLoader preTCL = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(moduleJarClassLoader);
try {
hasModuleLoadedSuccessFlag = loadingModules(moduleJarClassLoader, mCb);
} finally {
Thread.currentThread().setContextClassLoader(preTCL);
}
ModuleJarClassLoader
public class ModuleJarClassLoader extends RoutingURLClassLoader {
private ModuleJarClassLoader(final File moduleJarFile,
final File tempModuleJarFile) throws IOException {
super(
new URL[]{new URL("file:" + tempModuleJarFile.getPath())},
new Routing(
ModuleJarClassLoader.class.getClassLoader(),
"^com\\.alibaba\\.jvm\\.sandbox\\.api\\..*",
"^javax\\.servlet\\..*",
"^javax\\.annotation\\.Resource.*$"
)
);
}
RoutingURLClassLoader
public class RoutingURLClassLoader extends URLClassLoader {
private static final Logger logger = LoggerFactory.getLogger(RoutingURLClassLoader.class);
private final ClassLoadingLock classLoadingLock = new ClassLoadingLock();
private final Routing[] routingArray;
public RoutingURLClassLoader(final URL[] urls,
final Routing... routingArray) {
super(urls);
this.routingArray = routingArray;
}
@Override
protected Class<?> loadClass(final String javaClassName, final boolean resolve) throws ClassNotFoundException {
return classLoadingLock.loadingInLock(javaClassName, new ClassLoadingLock.ClassLoading() {
@Override
public Class<?> loadClass(String javaClassName) throws ClassNotFoundException {
// The priority query class loads the routing table , If you hit the routing rule , From the... In the routing table first ClassLoader Complete class loading
if (ArrayUtils.isNotEmpty(routingArray)) {
for (final Routing routing : routingArray) {
if (!routing.isHit(javaClassName)) {
continue;
}
final ClassLoader routingClassLoader = routing.classLoader;
try {
return routingClassLoader.loadClass(javaClassName);
} catch (Exception cause) {
// If at present routingClassLoader Could not find the class that should be loaded first ( There is no possibilities , But it does not rule out that there is a class named with the same name )
// Exceptions should be ignored at this time , Continue loading
// ignore...
}
}
}
// First go through the cache of loaded classes , If you don't hit , Then continue to load
final Class<?> loadedClass = findLoadedClass(javaClassName);
if (loadedClass != null) {
return loadedClass;
}
try {
Class<?> aClass = findClass(javaClassName);
if (resolve) {
resolveClass(aClass);
}
return aClass;
} catch (Exception cause) {
DelegateBizClassLoader delegateBizClassLoader = BusinessClassLoaderHolder.getBussinessClassLoader();
try {
if(null != delegateBizClassLoader){
return delegateBizClassLoader.loadClass(javaClassName,resolve);
}
} catch (Exception e) {
// Ignore exceptions , Continue loading
}
return RoutingURLClassLoader.super.loadClass(javaClassName, resolve);
}
}
});
}
}
private boolean loadingModules(final ModuleJarClassLoader moduleClassLoader,
final ModuleLoadCallback mCb) {
final Set<String> loadedModuleUniqueIds = new LinkedHashSet<String>();
final ServiceLoader<Module> moduleServiceLoader = ServiceLoader.load(Module.class, moduleClassLoader);
final Iterator<Module> moduleIt = moduleServiceLoader.iterator();
while (moduleIt.hasNext()) {
final Module module;
try {
module = moduleIt.next();
} catch (Throwable cause) {
logger.warn("loading module instance failed: instance occur error, will be ignored. module-jar={}", moduleJarFile, cause);
continue;
}
final Class<?> classOfModule = module.getClass();
// Determine whether the module implements @Information Mark
if (!classOfModule.isAnnotationPresent(Information.class)) {
logger.warn("loading module instance failed: not implements @Information, will be ignored. class={};module-jar={};",
classOfModule,
moduleJarFile
);
continue;
}
final Information info = classOfModule.getAnnotation(Information.class);
final String uniqueId = info.id();
// Judgment module ID Is it legal
if (StringUtils.isBlank(uniqueId)) {
logger.warn("loading module instance failed: @Information.id is missing, will be ignored. class={};module-jar={};",
classOfModule,
moduleJarFile
);
continue;
}
// Judge whether the startup mode required by the module matches the startup mode of the container
if (!ArrayUtils.contains(info.mode(), mode)) {
logger.warn("loading module instance failed: launch-mode is not match module required, will be ignored. module={};launch-mode={};required-mode={};class={};module-jar={};",
uniqueId,
mode,
StringUtils.join(info.mode(), ","),
classOfModule,
moduleJarFile
);
continue;
}
try {
if (null != mCb) {
mCb.onLoad(uniqueId, classOfModule, module, moduleJarFile, moduleClassLoader);
}
}
......
}
@Infomation
@Information(id = "broken-clock-tinker")
public class BrokenClockTinkerModule implements Module
onLoad( Real load )
- Initialization module information CoreModule stay coreModule There is module Implementation class of ,moduleJar,ModuleClassLoader, And the most important set of module class converters sandboxClassFileTransformers, Class converter is the key to bytecode enhancement , The code enhancement chapter will introduce in detail
- Note injection @Resource resources , for example ModuleEventWatcher Event observer . About ModuleEventWatcher The code enhancement chapter will introduce in detail
- Notify the corresponding implementation of module loading in the life cycle (module Implementation classes can implement Module Interface and sandbox module life cycle interface ModuleLifecyle Interface ), stay ModuleLifecyle In the method corresponding to the interface , Users can customize and implement business logic .
- Activate the module and notify the corresponding implementation of the module activation in the lifecycle , Only the activated module can respond to the enhanced event of the module .
- Make the module unique id And current coreModule Instance stored in module list , The module list is a global ConcurrentHashMap. Remember when Httpserver It will be initialized at startup ModuleHttpServlet, stay ModuleHttpServlet When receiving the request, the module will be parsed from the parameters id, So as to get the corresponding coreModule.
- Notify the corresponding implementation of module loading completion in the life cycle
private synchronized void load(final String uniqueId,
final Module module,
final File moduleJarFile,
final ModuleJarClassLoader moduleClassLoader) throws ModuleException {
......
// Initialization module information
final CoreModule coreModule = new CoreModule(uniqueId, moduleJarFile, moduleClassLoader, module);
// Inject @Resource resources
injectResourceOnLoadIfNecessary(coreModule);
// Notification lifecycle , Module loading
callAndFireModuleLifeCycle(coreModule, MODULE_LOAD);
// Set to loaded
coreModule.markLoaded(true);
// If the module is marked, it is automatically activated when loaded , You need to activate the module after loading
markActiveOnLoadIfNecessary(coreModule);
// Register in the module list
loadedModuleBOMap.put(uniqueId, coreModule);
// Notification lifecycle , Module loading completed
callAndFireModuleLifeCycle(coreModule, MODULE_LOAD_COMPLETED);
}
Summary

Module activation
# -a active module
[[ -n ${OP_MODULE_ACTIVE} ]] &&
sandbox_curl_with_exit "sandbox-module-mgr/active" "&ids=${ARG_MODULE_ACTIVE}"
@Command("active")
public void active(final Map<String, String> param,
final PrintWriter writer) throws ModuleException {
int total = 0;
final String idsStringPattern = getParamWithDefault(param, "ids", EMPTY);
for (final Module module : search(idsStringPattern)) {
final Information info = module.getClass().getAnnotation(Information.class);
final boolean isActivated = moduleManager.isActivated(info.id());
if (!isActivated) {
try {
moduleManager.active(info.id());
total++;
} catch (ModuleException me) {
logger.warn("active module[id={};] occur error={}.", me.getUniqueId(), me.getErrorCode(), me);
}// try
} else {
total++;
}
}// for
output(writer, "total %s module activated.", total);
}
Summary

Module freeze
# -A frozen module
[[ -n ${OP_MODULE_FROZEN} ]] &&
sandbox_curl_with_exit "sandbox-module-mgr/frozen" "&ids=${ARG_MODULE_FROZEN}
Summary

Module uninstall
# -u unload module
[[ -n ${OP_MODULE_UNLOAD} ]] &&
sandbox_curl_with_exit "sandbox-module-mgr/unload" "&action=unload&ids=${ARG_MODULE_UNLOAD}"
- Try to freeze the module , Let the event handler pause from executing user-defined enhanced logic .
- Notify the corresponding implementation of the lifecycle module uninstall
- Delete the corresponding module from the module registry
- Mark uninstall ,isLoaded=true
- Release resources , One step in the module loading process is injection @Resource resources , At injection ModuleEventWatcher Resource time , It's actually built ReleaseResource object , And implemented release Method . stay ModuleEventWatcher.delete Will use Instrumentation Class library delete class enhancement converter SandboxClassFileTransformer, In this way, when new classes are loaded, they will not be implanted spy Spies . But the loaded classes always have spyware code . adopt Instrumentation The class library re renders the bytecode of the target class .
@Override
public void delete(final int watcherId,
final Progress progress) {
final Set<Matcher> waitingRemoveMatcherSet = new LinkedHashSet<Matcher>();
// Find out what to delete SandboxClassFileTransformer
final Iterator<SandboxClassFileTransformer> cftIt = coreModule.getSandboxClassFileTransformers().iterator();
int cCnt = 0, mCnt = 0;
while (cftIt.hasNext()) {
final SandboxClassFileTransformer sandboxClassFileTransformer = cftIt.next();
if (watcherId == sandboxClassFileTransformer.getWatchId()) {
// Freeze all associated code enhancements
EventListenerHandler.getSingleton()
.frozen(sandboxClassFileTransformer.getListenerId());
// stay JVM Remove the hit ClassFileTransformer
inst.removeTransformer(sandboxClassFileTransformer);
// Count
cCnt += sandboxClassFileTransformer.getAffectStatistic().cCnt();
mCnt += sandboxClassFileTransformer.getAffectStatistic().mCnt();
// Append to the filter set to be deleted
waitingRemoveMatcherSet.add(sandboxClassFileTransformer.getMatcher());
// Clear the SandboxClassFileTransformer
cftIt.remove();
}
}
// Find the class collection that needs to be deleted and re rendered
final List<Class<?>> waitingReTransformClasses = classDataSource.findForReTransform(
new GroupMatcher.Or(waitingRemoveMatcherSet.toArray(new Matcher[0]))
);
logger.info("watch={} in module={} found {} classes for delete.",
watcherId,
coreModule.getUniqueId(),
waitingReTransformClasses.size()
);
beginProgress(progress, waitingReTransformClasses.size());
try {
// application JVM
reTransformClasses(watcherId, waitingReTransformClasses, progress);
} finally {
finishProgress(progress, cCnt, mCnt);
}
}
- close ModuleJarClassLoader, You can load all the module classes from jvm Clean up . It will be used before closing SPI Find the module ModuleJarUnloadSPI Implementation class of interface , The notification module has been uninstalled and can do some custom business processing .
public void closeIfPossible() {
onJarUnLoadCompleted();
try {
// If it is JDK7+ Version of , URLClassLoader Realized Closeable Interface , Just call it directly
if (this instanceof Closeable) {
logger.debug("JDK is 1.7+, use URLClassLoader[file={}].close()", moduleJarFile);
try {
((Closeable)this).close();
} catch (Throwable cause) {
logger.warn("close ModuleJarClassLoader[file={}] failed. JDK7+", moduleJarFile, cause);
}
return;
}
.......
}
}
Summary

Code enhancements
Custom module
@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() {
/**
* Intercept {@code com.taobao.demo.Clock#checkState()} Method , When this method throws an exception, it will be
* AdviceListener#afterThrowing() Intercepted
*/
@Override
protected void afterThrowing(Advice advice) throws Throwable {
// Here it is , You can go through ProcessController To change the execution process of the original method
// The meaning of the code here is : Change the behavior of throwing exceptions in the original method , Change to return immediately ;void Return value with null Express
ProcessController.returnImmediately(null);
}
});
}
}
watch
- stay watch Method will first create SandboxClassFileTransformer Sandbox converter , And register it in CoreModule in
- utilize Instrumentation The class library addTransformer api register SandboxClassFileTransformer After the sandbox class converter instance registers the class converter , All subsequent class loads will go through SandboxClassFileTransformer
- Find a collection of classes to render , utilize matcher Object to find the current jvm Loaded classes matcher The object is EventWatchBuilder The wrapper object of the target class and target method specified in , stay matcher Object specifies the matching rule .
- Render the found class ( Code enhancements ), utilize Instrumentation Of retransformClasses Method re pair JVM Bytecode conversion of loaded classes .
private int watch(final Matcher matcher,
final EventListener listener,
final Progress progress,
final Event.Type... eventType) {
final int watchId = watchIdSequencer.next();
// Add... To the corresponding module ClassFileTransformer
final SandboxClassFileTransformer sandClassFileTransformer = new SandboxClassFileTransformer(
watchId, coreModule.getUniqueId(), matcher, listener, isEnableUnsafe, eventType, namespace);
// Sign up to CoreModule in
coreModule.getSandboxClassFileTransformers().add(sandClassFileTransformer);
// here addTransformer after , The next class loading will go through sandClassFileTransformer
inst.addTransformer(sandClassFileTransformer, true);
// Find a collection of classes to render
final List<Class<?>> waitingReTransformClasses = classDataSource.findForReTransform(matcher);
logger.info("watch={} in module={} found {} classes for watch(ing).",
watchId,
coreModule.getUniqueId(),
waitingReTransformClasses.size()
);
int cCnt = 0, mCnt = 0;
// Progress notification start
beginProgress(progress, waitingReTransformClasses.size());
try {
// application JVM
reTransformClasses(watchId,waitingReTransformClasses, progress);
// Count
cCnt += sandClassFileTransformer.getAffectStatistic().cCnt();
mCnt += sandClassFileTransformer.getAffectStatistic().mCnt();
// Activate the enhanced class
if (coreModule.isActivated()) {
final int listenerId = sandClassFileTransformer.getListenerId();
EventListenerHandler.getSingleton()
.active(listenerId, listener, eventType);
}
} finally {
finishProgress(progress, cCnt, mCnt);
}
return watchId;
Code enhancements
private byte[] _transform(final ClassLoader loader,
final String internalClassName,
final Class<?> classBeingRedefined,
final byte[] srcByteCodeArray) {
// If not on unsafe switch , It is not allowed to enhance from BootStrapClassLoader Class
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 a behavior doesn't match, you don't have to continue
if (!matchingResult.isMatched()) {
logger.debug("transform ignore {}, no behaviors matched in loader={}", internalClassName, loader);
return null;
}
// Start class matching
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;
}
}
@Override
public byte[] toByteCodeArray(final ClassLoader targetClassLoader,
final byte[] byteCodeArray,
final Set<String> signCodes,
final String namespace,
final int listenerId,
final Event.Type[] eventTypeArray) {
// Returns the enhanced bytecode
final ClassReader cr = new ClassReader(byteCodeArray);
final ClassWriter cw = createClassWriter(targetClassLoader, cr);
final int targetClassLoaderObjectID = ObjectIDs.instance.identity(targetClassLoader);
cr.accept(
new EventWeaver(
ASM7, cw, namespace, listenerId,
targetClassLoaderObjectID,
cr.getClassName(),
signCodes,
eventTypeArray
),
EXPAND_FRAMES
);
return dumpClassIfNecessary(cr.getClassName(), cw.toByteArray());
}

Summary
Event handling
public static Ret spyMethodOnBefore(final Object[] argumentArray,
final String namespace,
final int listenerId,
final int targetClassLoaderObjectID,
final String javaClassName,
final String javaMethodName,
final String javaMethodDesc,
final Object target) throws Throwable {
final Thread thread = Thread.currentThread();
if (selfCallBarrier.isEnter(thread)) {
return Ret.RET_NONE;
}
final SelfCallBarrier.Node node = selfCallBarrier.enter(thread);
try {
final SpyHandler spyHandler = namespaceSpyHandlerMap.get(namespace);
if (null == spyHandler) {
return Ret.RET_NONE;
}
return spyHandler.handleOnBefore(
listenerId, targetClassLoaderObjectID, argumentArray,
javaClassName,
javaMethodName,
javaMethodDesc,
target
);
} catch (Throwable cause) {
handleException(cause);
return Ret.RET_NONE;
} finally {
selfCallBarrier.exit(thread, node);
}
}
EventListenHandler
@Override
public Spy.Ret handleOnBefore(int listenerId, int targetClassLoaderObjectID, Object[] argumentArray, String javaClassName, String javaMethodName, String javaMethodDesc, Object target) throws Throwable {
// Events generated in the guard area do not need to be responded
if (SandboxProtector.instance.isInProtecting()) {
logger.debug("listener={} is in protecting, ignore processing before-event", listenerId);
return newInstanceForNone();
}
// Get event handler
final EventProcessor processor = mappingOfEventProcessor.get(listenerId);
// If not already registered , Then return directly , Do nothing
if (null == processor) {
logger.debug("listener={} is not activated, ignore processing before-event.", listenerId);
return newInstanceForNone();
}
// Get call trace information
final EventProcessor.Process process = processor.processRef.get();
// If the current processing ID Be ignored , Return immediately
if (process.isIgnoreProcess()) {
logger.debug("listener={} is marked ignore process!", listenerId);
return newInstanceForNone();
}
// call ID
final int invokeId = invokeIdSequencer.getAndIncrement();
process.pushInvokeId(invokeId);
// Call the process ID
final int processId = process.getProcessId();
final ClassLoader javaClassLoader = ObjectIDs.instance.getObject(targetClassLoaderObjectID);
// Place the business class loader
BusinessClassLoaderHolder.setBussinessClassLoader(javaClassLoader);
final BeforeEvent event = process.getEventFactory().makeBeforeEvent(
processId,
invokeId,
javaClassLoader,
javaClassName,
javaMethodName,
javaMethodDesc,
target,
argumentArray
);
try {
return handleEvent(listenerId, processId, invokeId, event, processor);
} finally {
process.getEventFactory().returnEvent(event);
}
}
EventProcessor
public void active(final int listenerId,
final EventListener listener,
final Event.Type[] eventTypes) {
mappingOfEventProcessor.put(listenerId, new EventProcessor(listenerId, listener, eventTypes));
logger.info("activated listener[id={};target={};] event={}",
listenerId,
listener,
join(eventTypes, ",")
);
}
listenerId
listener
Summary

summary
- JVM-SANDBOX be based on JVM TI(JVM Tool interface ) Realization agent To the goal jvm Mount , utilize Instrumentation Class libraries register custom class converters (SandboxClassFileTransformer), Use in class converters ASM The framework weaves methods of the target class Spy Buried point method in spy class , So as to realize byte code enhancement .
- JVM-SANDBOX utilize ClassLoader Parent delegation model , Customize SandboxClassLoader Load sandbox inner class (spy-core), Use customization ModulerJarClassLoader and SPI Mechanism to load custom modules , So as to realize class isolation .
- Pluggable and multi tenant features are more inclined to JVM-SANDBOX Internal business logic , It is described in detail in the module management chapter module loading and unloading .
边栏推荐
- Illustrated network: what is the hot backup router protocol HSRP?
- 线程常用的方法
- 新型数据中心,助力加快构建以数据为关键要素的数字经济
- CesiumJS 2022^ 源码解读[0] - 文章目录与源码工程结构
- LevelDB源码解读-SkipList
- laravel admin里百度编辑器自定义路径和文件名
- LNK2038 检测到“RuntimeLibrary”的不匹配项: 值“MD_DynamicRelease”不匹配值“MDd_DynamicDebug”(main.obj 中)
- Objective-C string class, array class
- [paddleseg source code reading] paddleseg calculation dice
- 三菱M70宏变量读取三菱M80公共变量采集三菱CNC变量读取采集三菱CNC远程刀补三菱机床在线刀补三菱数控在线测量
猜你喜欢
随机推荐
Pytest multi process / multi thread execution test case
Is it safe to buy insurance for your children online? Do you want to buy a million dollar medical insurance for your children?
【华为云IoT】读书笔记之《万物互联:物联网核心技术与安全》第3章(上)
[Huawei cloud IOT] reading notes, "Internet of things: core technology and security of the Internet of things", Chapter 3 (I)
2022-07-03:数组里有0和1,一定要翻转一个区间,翻转:0变1,1变0。 请问翻转后可以使得1的个数最多是多少? 来自小红书。3.13笔试。
idea修改主体颜色
STM32 external DHT11 display temperature and humidity
Katalon框架测试web(二十一)获取元素属性断言
【读书会第十三期】视频文件的封装格式
Epidemic strikes -- Thinking about telecommuting | community essay solicitation
02 ls 命令的具体实现
Wechat official account web page authorization
Smart subway | cloud computing injects wisdom into urban subway transportation
MySQL one master multiple slaves + linear replication
Calculate the odd sum of 1~n (1~100 as an example)
程序员远程办公喜忧参半| 社区征文
量子力学习题
Lnk2038 detected a mismatch of "runtimelibrary": the value "md_dynamicrelease" does not match the value "mdd_dynamicdebug" (in main.obj)
Spa in SDP
Balance between picture performance of unity mobile game performance optimization spectrum and GPU pressure