JVM wide preprocessor hook

This document explains the detail of the JVM wide preprocessor hook of beSee. This mechanism allows to plug any class preprocessor component in the whole hierarchy of java class loaders, thus allowing runtime instrumentation of any component.

A class preprocessor component is the component which intercepts all class loading and allows instrumentation. This is the low level part of a weave engine.

beSee JVM wide preprocessor hook architecture is based on a dedicated class loader preprocessor which has the responsability to instrument the java.lang.ClassLoader bytecode to allow class preprocessor behavior. Two implementations are provided, one with BCEL, the other with Javassist. beSee provides also a simple way to use any other bytecode instrumentation layer.

  1. Supported environments
  2. Overview
    1. Class loader preprocessor
    2. Class preprocessor
  3. Usage
  4. Samples

Supported environments

beSee mechanism is optimally based on java 1.4 HotSwap features, but provides a transparent support for java 1.3 and non HotSwap enabled JVM.

HotSwap allows class and class method redefinition at runtime. This is widely used in modern debugging and profiling application, and even in java IDE.

HotSwap is part of Java Platform Debugger Architecture . See SUN official site .

beSee brings JVM wide instrumentation in the following environments:

  • java 1.3
  • java 1.4
  • J2SE applications, supporting user defined class loaders
  • J2EE applications, supporting user defined class loaders
There is no differences in using beSee in those environements. beSee detects the HotSwap supports and adapts its behavior at runtime.

An important feature of beSee is that it is completely transparent when plugged in: you can turn on remote debugging in the launched application with JDWP even if beSee makes itself uses of it.

Overview

Class loader preprocessor

beSee provides a standard way to instrument the java.lang.ClassLoader.

Use the optional -Dbesee.classloader.clpreprocessor to specify the full qualified name (package + class name) of the component that has the responsability to instrument the java.lang.ClassLoader.

public MyClassLoaderPatcher implements com.gnilux.besee.hook.ClassLoaderPreProcessor {

	public byte[] preProcess(byte[] classLoaderBytecode) {
		// transform classLoaderBytecode using your favorite bytecode API
		// could be BCEL or Javassist, or ...
	}
	
}

beSee comes with a default javassit implementation which hooks in the java class loading mechanims a class preprocessor component.

When no option is given, this implementation is used.

beSee provides the same mechanism with a BCEL implementation.

Note for java 1.3 and JVM not supporting HotSwap: In such case, beSee still generates at runtime a modified java.lang.ClassLoader thru the class loader preprocessor implementation, but add it transparently in the -Xbootclasspath option of the target JVM running the instrumented application.

The modified java.lang.ClassLoader is generated and kept in the directory specified with the -Dbesee.classloader.clbootclasspath option. This can also be used to deactivate HotSwapping for debugging purpose.

Class preprocessor

The most natural way to provide runtime class instrumentation is the class preprocessor mechanism: the class preprocessor component is responsible for modifying bytecode just before any class is loaded in the JVM (except classes in the bootclasspath like Object, String, ...).

beSee supports this mechanism for the whole hierarchy of class loaders.

Note that this depends on how the class loader preprocessor modify the class loading mechanism.

A standard class preprocessor is something like

public class StdoutPreProcessor implements com.gnilux.besee.hook.ClassPreProcessor {

	public byte[] preProcess(String klass, byte bytecode[], ClassLoader caller) {
        	log("preprocess " + klass + " for ClassLoader " + caller);
        	// modify bytecode as needed
        	return bytecode;
        }
        
}

When the beSee default class loader preprocessor implementation is used (BCEL or Javassist based), the effective class preprocessor is specified using its full qualified name with beSee option (not JVM option, see Usage section) -Dbesee.classloader.preprocessor

beSee distribution comes with a very basic class preprocessor which just prints out name of the class loaded without doing instrumentation.

For demo purpose, a BCEL based preprocessor adding a marker interface is also provided. This can be used to test the offline mode (post-processing of compiled class / jar)

See beSee WebLogic extension for another use bringing WebLogic instrumentation JVM wide, based on the beSee 1-x family project which provides a linear instrumentation mechanism (insert this snip of code before this method ...).

A work in progress is done to integrate an aspect oriented programming (AOP) layer.

Usage

beSee is a regular java main class. It can be considered as a "java" command replacement.

The following invocation

java [target jvm option] [target classpath] targetMainClass [targetMainClass args]
is then written
java [jvm option] [classpath] com.gnilux.besee.hook.ProcessStarter ...
	... [target jvm option] [target classpath] targetMainClass [targetMainClass args]
For an executable jar you have the same syntax:
java [target jvm option] [target classpath] -jar targetExecutable.jar [targetMainClass args]
is then written
java [jvm option] [classpath] com.gnilux.besee.hook.ProcessStarter ...
	... [target jvm option] [target classpath] -jar targetExecutable.jar [targetMainClass args]
with the following requirements:
  • [classpath] must contain %JAVA_HOME%/tools.jar for HotSwap support
  • [classpath] must contain beSee jar (of course)
  • use -Dbesee.classloader.clpreprocessor=... in [jvm option] to use another class loader preprocessor than the default one
  • if the default one is used, put bcel-5.1.jar in [classpath], else put what the class loader preprocessor implementation requires (could be javassist.jar)
  • if specified to com.gnilux.besee.hook.impl.JavassistClassLoaderPreProcessorImpl, the Javassist implementation is used instead of the BCEL default one. You will need to add in [classpath] javassist.jar and beSee Javassist extension jar (besee-ext-javassist- version .jar)
Optionnally:
  • use -Dbesee.classloader.clbootclasspath=... in [jvm ption] to disable HotSwap and specify a target directory for the modified java.lang.ClassLoader bytecode.

    It defaults to "boot" if beSee detects by its own HotSwap is not supported (java 1.3).
  • If HotSwap is enabled, target JVM is launched with a listening JPDA connector on transport=dt_socket,address=9300 unless specified in [target jvm option] thru -Xdebug -Xrunjdwp:... .

    If you used to launch your application with specific -Xrunjdwp options to allow remote debugging thru shared memory, or thru another port, it is possible. Just do it as usual.
  • If -Dbesee.classloader.clbootclasspath is used, target JVM running your application will not run in debug mode. (note that debug mode has been improved in java 1.4, so their might be no really performance improvement by using this option with java 1.4 instead of default HotSwap)


For the beSee BCEL/javassist class preprocessor standard implementation consider also:
  • [target classpath] must contain beSee jar in the -Xbootclasspath section but all other jar needed by the class preprocessor implementation are only needed in the [target classpath] option (in detail, the class preprocessor effective loading is delegated to the system classloader).
  • use -Dbesee.classloader.preprocessor=... in [target jvm option] to specify the class preprocessor. This is a mandatory parameter without default value.

    If not specified, no runtime instrumentation will occur

Samples

The following runs a HelloWorld sample with the standard implementation and a simple class preprocessor that prints out some information. You can reproduce this samples by calling the ant targets demo.hook.nohotswap , demo.hook.hotswap .

For a more realistic sample, refer to the WebLogic extension .

// regular java usage
java -classpath besee.test.jar com.gnilux.besee.hook.HelloWorldMain

	// output
	HelloWorld !

// disable HotSwap and put modified ClassLoader in "demo-boot" directory
java -classpath bcel-5.1.jar:besee.jar;%JAVA_HOME%/tools.jar -Dbesee.classloader.clbootclasspath=demo-boot \
	com.gnilux.besee.hook.ProcessStarter -Xbootclasspath/a:besee.jar \
		-Dbesee.classloader.preprocessor=com.gnilux.besee.hook.impl.StdoutPreProcessor \
		-classpath besee.test.jar com.gnilux.besee.hook.HelloWorldMain
		
	// output
	HotSwap deactivated, using bootclasspath: demo-boot
	com.gnilux.besee.hook.impl.StdoutPreProcessor: initialize
	besee - INFO - Pre-processor com.gnilux.besee.hook.impl.StdoutPreProcessor loaded and initialized
	com.gnilux.besee.hook.impl.StdoutPreProcessor: preprocess com.gnilux.besee.hook.HelloWorldMain \
		for CL sun.misc.Launcher$AppClassLoader@7d8483
	HelloWorld !

// enable HotSwap if available for java version used
java -classpath bcel-5.1.jar:besee.jar;%JAVA_HOME%/tools.jar \
	com.gnilux.besee.hook.ProcessStarter -Xbootclasspath/a:besee.jar \
		-Dbesee.classloader.preprocessor=com.gnilux.besee.hook.impl.StdoutPreProcessor \
		-classpath besee.test.jar com.gnilux.besee.hook.HelloWorldMain
		
	// output for java 1.3
	HotSwap not supported by this java version, using bootclasspath: .\boot
	com.gnilux.besee.hook.impl.StdoutPreProcessor: initialize
	com.gnilux.besee.hook.impl.StdoutPreProcessor: loaded by null
	besee - INFO - Pre-processor com.gnilux.besee.hook.impl.StdoutPreProcessor loaded and initialized
	com.gnilux.besee.hook.impl.StdoutPreProcessor: preprocess com.gnilux.besee.hook.HelloWorldMain \
		for CL sun.misc.Launcher$AppClassLoader@7d8483
	HelloWorld !
	
	// Note: autodetection and adaptive behavior for jvm not supporting HotSwap