在介绍instrument是什么之前,先来看几个定义:
JVMTI(JVM tool interface):它是JVM提供的一系列native编程接口。
Agent:与JVM进行通信的外部进程,它们通过调用JVMTI进行交互,可进行的操作包括设置JVM回调函数、获取当前虚拟机状态信息等
Instrument:Jdk提供的"java.lang.instrument"包,与JVM进行交互,可以看作为一个Instrument Agent。
https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/index.html?ca=drs-
接上文,当JVM接收到execute(“load”,“instrument”,“false”,options)命令之后,开始进行动态链接库的加载。
从Agent_OnAttach开始,是libinstrument.so中的内容:
关于libInstrument.so库,我认为可以当做是一个底层Agent。
它直接调用了JVMTI提供的回调方法,然后加载我们自定义的agent,避免了我们自己的agent与JVMTI直接通信。
// Implementation of "load" command.
static jint load_agent(AttachOperation* op, outputStream* out) {
// get agent name and options
const char* agent = op->arg(0);
const char* absParam = op->arg(1);
const char* options = op->arg(2);
// If loading a java agent then need to ensure that the java.instrument module is loaded
if (strcmp(agent, "instrument") == 0) {
Thread* THREAD = Thread::current();
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
JavaValue result(T_OBJECT);
Handle h_module_name = java_lang_String::create_from_str("java.instrument", THREAD);
JavaCalls::call_static(&result,
SystemDictionary::module_Modules_klass(),
vmSymbols::loadModule_name(),
vmSymbols::loadModule_signature(),
h_module_name,
THREAD);
if (HAS_PENDING_EXCEPTION) {
java_lang_Throwable::print(PENDING_EXCEPTION, out);
CLEAR_PENDING_EXCEPTION;
return JNI_ERR;
}
}
return JvmtiExport::load_agent_library(agent, absParam, options, out);
}
接收到的三个参数对应我们发送命令时的三个:“instrument”、是否绝对路径、agentpath=args;
先加载java.instrument模块,再加载Agent,调用JvmtiExport::load_agent_library(agent, absParam, options, out),当成功时返回码为0.
jint JvmtiExport::load_agent_library(const char *agent, const char *absParam,
const char *options, outputStream* st) {
char ebuf[1024];
char buffer[JVM_MAXPATHLEN];
void* library = NULL;
jint result = JNI_ERR;
const char *on_attach_symbols[] = AGENT_ONATTACH_SYMBOLS;
size_t num_symbol_entries = ARRAY_SIZE(on_attach_symbols);
// The abs paramter should be "true" or "false"
bool is_absolute_path = (absParam != NULL) && (strcmp(absParam,"true")==0);
// Initially marked as invalid. It will be set to valid if we can find the agent
AgentLibrary *agent_lib = new AgentLibrary(agent, options, is_absolute_path, NULL);
// Check for statically linked in agent. If not found then if the path is
// absolute we attempt to load the library. Otherwise we try to load it
// from the standard dll directory.
if (!os::find_builtin_agent(agent_lib, on_attach_symbols, num_symbol_entries)) {
if (is_absolute_path) {
library = os::dll_load(agent, ebuf, sizeof ebuf);
} else {
// Try to load the agent from the standard dll directory
if (os::dll_locate_lib(buffer, sizeof(buffer), Arguments::get_dll_dir(),
agent)) {
library = os::dll_load(buffer, ebuf, sizeof ebuf);
}
if (library == NULL) {
// not found - try OS default library path
if (os::dll_build_name(buffer, sizeof(buffer), agent)) {
library = os::dll_load(buffer, ebuf, sizeof ebuf);
}
}
}
if (library != NULL) {
agent_lib->set_os_lib(library);
agent_lib->set_valid();
}
}
// If the library was loaded then we attempt to invoke the Agent_OnAttach
// function
if (agent_lib->valid()) {
// Lookup the Agent_OnAttach function
OnAttachEntry_t on_attach_entry = NULL;
on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t,
os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries));
if (on_attach_entry == NULL) {
// Agent_OnAttach missing - unload library
if (!agent_lib->is_static_lib()) {
os::dll_unload(library);
}
delete agent_lib;
} else {
// Invoke the Agent_OnAttach function
JavaThread* THREAD = JavaThread::current();
{
extern struct JavaVM_ main_vm;
JvmtiThreadEventMark jem(THREAD);
JvmtiJavaThreadEventTransition jet(THREAD);
result = (*on_attach_entry)(&main_vm, (char*)options, NULL);
}
// Agent_OnAttach may have used JNI
if (HAS_PENDING_EXCEPTION) {
CLEAR_PENDING_EXCEPTION;
}
// If OnAttach returns JNI_OK then we add it to the list of
// agent libraries so that we can call Agent_OnUnload later.
if (result == JNI_OK) {
Arguments::add_loaded_agent(agent_lib);
} else {
delete agent_lib;
}
// Agent_OnAttach executed so completion status is JNI_OK
st->print_cr("%d", result);
result = JNI_OK;
}
}
return result;
}
在linux中,首先要加载动态库,库的名字由dll_build_name(buffer, sizeof(buffer), agent)获得:“lib”+传入的agentlib名字+".so",即"libinstrument.so"。
on_attach_entry = CAST_TO_FN_PTR(OnAttachEntry_t, os::find_agent_function(agent_lib, false, on_attach_symbols, num_symbol_entries));这行代码负责在库中找Agent_OnAttach方法。
(find_agent_function支持找的方法:Agent_On(Un)Load/Attach)
libinstrument.so中的instrument agent,实现了Agent_OnLoad和Agent_OnAttach两方法,javaAgent功能就是由此实现的:
Agent_OnLoad对应虚拟机启动时加载,Agent_OnAttach对应启动后加载。
加载类库成功后,调用Agent_OnAttach方法。传参:vm、options(agentpath=args)、null
Agent_OnAttach:
/*
* This will be called once each time a tool attaches to the VM and loads
* the JPLIS library.
*/
JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char *args, void * reserved) {
JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;
jint result = JNI_OK;
JPLISAgent * agent = NULL;
JNIEnv * jni_env = NULL;
/*
* Need JNIEnv - guaranteed to be called from thread that is already
* attached to VM
*/
result = (*vm)->GetEnv(vm, (void**)&jni_env, JNI_VERSION_1_2);
jplis_assert(result==JNI_OK);
initerror = createNewJPLISAgent(vm, &agent);
if ( initerror == JPLIS_INIT_ERROR_NONE ) {
int oldLen, newLen;
char * jarfile;
char * options;
jarAttribute* attributes;
char * agentClass;
char * bootClassPath;
jboolean success;
/*
* Parse [=options] into jarfile and options
*/
if (parseArgumentTail(args, &jarfile, &options) != 0) {
return JNI_ENOMEM;
}
/*
* Open the JAR file and parse the manifest
*/
attributes = readAttributes( jarfile );
if (attributes == NULL) {
fprintf(stderr, "Error opening zip file or JAR manifest missing: %s\n", jarfile);
free(jarfile);
if (options != NULL) free(options);
return AGENT_ERROR_BADJAR;
}
agentClass = getAttribute(attributes, "Agent-Class");
if (agentClass == NULL) {
fprintf(stderr, "Failed to find Agent-Class manifest attribute from %s\n",
jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return AGENT_ERROR_BADJAR;
}
/*
* Add the jarfile to the system class path
*/
if (appendClassPath(agent, jarfile)) {
fprintf(stderr, "Unable to add %s to system class path "
"- not supported by system class loader or configuration error!\n",
jarfile);
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return AGENT_ERROR_NOTONCP;
}
/*
* The value of the Agent-Class attribute becomes the agent
* class name. The manifest is in UTF8 so need to convert to
* modified UTF8 (see JNI spec).
*/
oldLen = strlen(agentClass);
newLen = modifiedUtf8LengthOfUtf8(agentClass, oldLen);
if (newLen == oldLen) {
agentClass = strdup(agentClass);
} else {
char* str = (char*)malloc( newLen+1 );
if (str != NULL) {
convertUtf8ToModifiedUtf8(agentClass, oldLen, str, newLen);
}
agentClass = str;
}
if (agentClass == NULL) {
free(jarfile);
if (options != NULL) free(options);
freeAttributes(attributes);
return JNI_ENOMEM;
}
/*
* If the Boot-Class-Path attribute is specified then we process
* each URL - in the live phase only JAR files will be added.
*/
bootClassPath = getAttribute(attributes, "Boot-Class-Path");
if (bootClassPath != NULL) {
appendBootClassPath(agent, jarfile, bootClassPath);
}
/*
* Convert JAR attributes into agent capabilities
*/
convertCapabilityAtrributes(attributes, agent);
/*
* Create the java.lang.instrument.Instrumentation instance
*/
success = createInstrumentationImpl(jni_env, agent);
jplis_assert(success);
/*
* Turn on the ClassFileLoadHook.
*/
if (success) {
success = setLivePhaseEventHandlers(agent);
jplis_assert(success);
}
/*
* Start the agent
*/
if (success) {
success = startJavaAgent(agent,
jni_env,
agentClass,
options,
agent->mAgentmainCaller);
}
if (!success) {
fprintf(stderr, "Agent failed to start!\n");
result = AGENT_ERROR_STARTFAIL;
}
/*
* Clean-up
*/
free(jarfile);
if (options != NULL) free(options);
free(agentClass);
freeAttributes(attributes);
}
return result;
}
首先(parseArgumentTail(args, &jarfile, &options)解析传来的参数:将agentPath 与 args通过"="分隔;
第五步:setLivePhaseEventHandlers
jboolean
setLivePhaseEventHandlers( JPLISAgent * agent) {
jvmtiEventCallbacks callbacks;
jvmtiEnv * jvmtienv = jvmti(agent);
jvmtiError jvmtierror;
/* first swap out the handlers (switch from the VMInit handler, which we do not need,
* to the ClassFileLoadHook handler, which is what the agents need from now on)
*/
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;
jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv,
&callbacks,
sizeof(callbacks));
check_phase_ret_false(jvmtierror);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
if ( jvmtierror == JVMTI_ERROR_NONE ) {
/* turn off VMInit */
jvmtierror = (*jvmtienv)->SetEventNotificationMode(
jvmtienv,
JVMTI_DISABLE,
JVMTI_EVENT_VM_INIT,
NULL /* all threads */);
check_phase_ret_false(jvmtierror);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
if ( jvmtierror == JVMTI_ERROR_NONE ) {
/* turn on ClassFileLoadHook */
jvmtierror = (*jvmtienv)->SetEventNotificationMode(
jvmtienv,
JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
NULL /* all threads */);
check_phase_ret_false(jvmtierror);
jplis_assert(jvmtierror == JVMTI_ERROR_NONE);
}
return (jvmtierror == JVMTI_ERROR_NONE);
}
JVM中定义的关于类加载的回调:钩子函数eventHandlerClassFileLoadHook
eventHandlerClassFileLoadHook( jvmtiEnv * jvmtienv,
JNIEnv * jnienv,
jclass class_being_redefined,
jobject loader,
const char* name,
jobject protectionDomain,
jint class_data_len,
const unsigned char* class_data,
jint* new_class_data_len,
unsigned char** new_class_data) {
JPLISEnvironment * environment = NULL;
environment = getJPLISEnvironment(jvmtienv);
/* if something is internally inconsistent (no agent), just silently return without touching the buffer */
if ( environment != NULL ) {
jthrowable outstandingException = preserveThrowable(jnienv);
transformClassFile( environment->mAgent,
jnienv,
loader,
name,
class_being_redefined,
protectionDomain,
class_data_len,
class_data,
new_class_data_len,
new_class_data,
environment->mIsRetransformer);
restoreThrowable(jnienv, outstandingException);
}
}
transformClassFile方法中:
transformedBufferObject = (*jnienv)->CallObjectMethod(
jnienv,
agent->mInstrumentationImpl,
agent->mTransform,
loaderObject,
classNameStringObject,
classBeingRedefined,
protectionDomain,
classFileBufferObject,
is_retransformer);
第六步:
startJavaAgent( JPLISAgent * agent,
JNIEnv * jnienv,
const char * classname,
const char * optionsString,
jmethodID agentMainMethod) {
jboolean success = JNI_FALSE;
jstring classNameObject = NULL;
jstring optionsStringObject = NULL;
success = commandStringIntoJavaStrings( jnienv,
classname,
optionsString,
&classNameObject,
&optionsStringObject);
if (success) {
success = invokeJavaAgentMainMethod( jnienv,
agent->mInstrumentationImpl,
agentMainMethod,
classNameObject,
optionsStringObject);
}
return success;
}
其中的invokeJavaAgentMainMethod,通过调用mInstrumentationImpl.loadClassAndStartAgent实现初始化调用我们自定义的agentMain方法。
loadClassAndCallAgentmain( String classname,
String optionsString)
throws Throwable {
loadClassAndStartAgent( classname, "agentmain", optionsString );
}
// Attempt to load and start an agent
private void
loadClassAndStartAgent( String classname,
String methodname,
String optionsString)
throws Throwable {
ClassLoader mainAppLoader = ClassLoader.getSystemClassLoader();
Class<?> javaAgentClass = mainAppLoader.loadClass(classname);
Method m = null;
NoSuchMethodException firstExc = null;
boolean twoArgAgent = false;
// The agent class must have a premain or agentmain method that
// has 1 or 2 arguments. We check in the following order:
//
// 1) declared with a signature of (String, Instrumentation)
// 2) declared with a signature of (String)
// 3) inherited with a signature of (String, Instrumentation)
// 4) inherited with a signature of (String)
//
// So the declared version of either 1-arg or 2-arg always takes
// primary precedence over an inherited version. After that, the
// 2-arg version takes precedence over the 1-arg version.
//
// If no method is found then we throw the NoSuchMethodException
// from the first attempt so that the exception text indicates
// the lookup failed for the 2-arg method (same as JDK5.0).
try {
m = javaAgentClass.getDeclaredMethod( methodname,
new Class<?>[] {
String.class,
java.lang.instrument.Instrumentation.class
}
);
twoArgAgent = true;
} catch (NoSuchMethodException x) {
// remember the NoSuchMethodException
firstExc = x;
}
if (m == null) {
// now try the declared 1-arg method
try {
m = javaAgentClass.getDeclaredMethod(methodname,
new Class<?>[] { String.class });
} catch (NoSuchMethodException x) {
// ignore this exception because we'll try
// two arg inheritance next
}
}
if (m == null) {
// now try the inherited 2-arg method
try {
m = javaAgentClass.getMethod( methodname,
new Class<?>[] {
String.class,
java.lang.instrument.Instrumentation.class
}
);
twoArgAgent = true;
} catch (NoSuchMethodException x) {
// ignore this exception because we'll try
// one arg inheritance next
}
}
if (m == null) {
// finally try the inherited 1-arg method
try {
m = javaAgentClass.getMethod(methodname,
new Class<?>[] { String.class });
} catch (NoSuchMethodException x) {
// none of the methods exists so we throw the
// first NoSuchMethodException as per 5.0
throw firstExc;
}
}
// the premain method should not be required to be public,
// make it accessible so we can call it
// Note: The spec says the following:
// The agent class must implement a public static premain method...
setAccessible(m, true);
// invoke the 1 or 2-arg method
if (twoArgAgent) {
m.invoke(null, new Object[] { optionsString, this });
} else {
m.invoke(null, new Object[] { optionsString });
}
// don't let others access a non-public premain method
setAccessible(m, false);
}