当前位置:   article > 正文

java struct工作原理_☆JVMTI Agent 工作原理及核心源码分析

failed to find premain-class manifest attribute

0 前言

前一节讲述了基于JVMTI如何实现Agent,还有一种是基于Java Instrument API实现Agent,可以在Java代码层面编写Agent代码,而非基于C++/C的代码,具体使用可参考《Java Instrument 功能使用及原理》:

以 -javaagent:为开头的默认为instrument的agent;

那么以上这两种Agent实现方式,又是在JVMTI源码中如何运行工作呢?

1 初始化 Agent

JVM启动时,会读取JVM命令行参数 -agentlib -agentpath -javaagent,并构建了Agent Library链表。初始化 Agent 代码如下:

if (match_option(option, "-agentlib:", &tail) || (is_absolute_path = match_option(option, "-agentpath:", &tail))) {

if(tail != NULL) {

const char* pos = strchr(tail, '=');

size_t len = (pos == NULL) ? strlen(tail) : pos - tail;

char* name = strncpy(NEW_C_HEAP_ARRAY(char, len + 1), tail, len);

name[len] = '\0';

char *options = NULL;

if(pos != NULL) {

options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(pos + 1) + 1), pos + 1);

}

if ((strcmp(name, "hprof") == 0) || (strcmp(name, "jdwp") == 0)) {

warning("profiling and debugging agents are not supported with Kernel VM");

} else if

// JVMTI_KERNEL 构建Agent Library链表

add_init_agent(name, options, is_absolute_path);

}

} else if (match_option(option, "-javaagent:", &tail)) {

// -javaagent

if(tail != NULL) {

char *options = strcpy(NEW_C_HEAP_ARRAY(char, strlen(tail) + 1), tail);

// 构建Agent Library链表

add_init_agent("instrument", options, false);

}

// -Xnoclassgc

}

2 加载Agent链接库

在启动JVM create_vm时,对agent链表中的每个agent库,加载所指定的动态库, 并调用里面的Agent_OnLoad方法,比如:对于Java Instrument Agent加载就是对libinstrument的动态库instrument.so加载:

// Create agents for -agentlib: -agentpath: and converted -Xrun

void Threads::create_vm_init_agents() {

extern struct JavaVM_ main_vm;

AgentLibrary* agent;

JvmtiExport::enter_onload_phase();

for (agent = Arguments::agents(); agent != NULL; agent = agent->next()) {

OnLoadEntry_t on_load_entry = lookup_agent_on_load(agent);

if (on_load_entry != NULL) {

// 调用 Agent_OnLoad 函数

jint err = (*on_load_entry)(&main_vm, agent->options(), NULL);

if (err != JNI_OK) {

vm_exit_during_initialization("agent library failed to init", agent->name());

}

} else {

vm_exit_during_initialization("Could not find Agent_OnLoad function in the agent library", agent->name());

}

}

JvmtiExport::enter_primordial_phase();

}

3 创建 Instrument JPLISAgent

在方法Agent_OnLoad中创建一个新的 JPLISAgent(Java Programming Language Instrumentation Services Agent),初始化了类和包里的配置文件,并且同时从Vm环境中获取了 jvmtiEnv 的环境。

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *tail, void * reserved) {

JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;

jint result = JNI_OK;

JPLISAgent * agent = NULL;

// 创建一个新的JPLISAgent对象

initerror = createNewJPLISAgent(vm, &agent);

if ( initerror == JPLIS_INIT_ERROR_NONE ) {

if (parseArgumentTail(tail, &jarfile, &options) != 0) {

fprintf(stderr, "-javaagent: memory allocation failure.\n");

return JNI_ERR;

}

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 JNI_ERR;

}

premainClass = getAttribute(attributes, "Premain-Class");

if (premainClass == NULL) {

fprintf(stderr, "Failed to find Premain-Class manifest attribute in %s\n", jarfile);

free(jarfile);

if (options != NULL) free(options);

freeAttributes(attributes);

return JNI_ERR;

}

/*

* Add to the jarfile 把jar文件追加到agent的classpath中。

*/

appendClassPath(agent, jarfile);

……

}

在代码中,可以看到在 读取jar的配置文件MANIFEST里Premain-Class,并且把jar文件追加到agent的class path中。

4 JVMTI 回调接口注册与执行

以下是JVMTI的一些回调接口,通过这些回调接口设置回调函数指针:

typedef struct {

/* 50 : VM Initialization Event */

jvmtiEventVMInit VMInit;

/* 51 : VM Death Event */

jvmtiEventVMDeath VMDeath;

/* 52 : Thread Start */

jvmtiEventThreadStart ThreadStart;

/* 53 : Thread End */

jvmtiEventThreadEnd ThreadEnd;

/* 54 : Class File Load Hook */

jvmtiEventClassFileLoadHook ClassFileLoadHook;

/* 55 : Class Load */

jvmtiEventClassLoad ClassLoad;

/* 56 : Class Prepare */

jvmtiEventClassPrepare ClassPrepare;

/* 57 : VM Start Event */

jvmtiEventVMStart VMStart;

/* 58 : Exception */

jvmtiEventException Exception;

/* 59 : Exception Catch */

jvmtiEventExceptionCatch ExceptionCatch;

/* 60 : Single Step */

jvmtiEventSingleStep SingleStep;

/* 61 : Frame Pop */

jvmtiEventFramePop FramePop;

/* 62 : Breakpoint */

jvmtiEventBreakpoint Breakpoint;

/* 63 : Field Access */

jvmtiEventFieldAccess FieldAccess;

/* 64 : Field Modification */

jvmtiEventFieldModification FieldModification;

/* 65 : Method Entry */

jvmtiEventMethodEntry MethodEntry;

/* 66 : Method Exit */

jvmtiEventMethodExit MethodExit;

/* 67 : Native Method Bind */

jvmtiEventNativeMethodBind NativeMethodBind;

/* 68 : Compiled Method Load */

jvmtiEventCompiledMethodLoad CompiledMethodLoad;

/* 69 : Compiled Method Unload */

jvmtiEventCompiledMethodUnload CompiledMethodUnload;

/* 70 : Dynamic Code Generated */

jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;

/* 71 : Data Dump Request */

jvmtiEventDataDumpRequest DataDumpRequest;

/* 72 */

jvmtiEventReserved reserved72;

/* 73 : Monitor Wait */

jvmtiEventMonitorWait MonitorWait;

/* 74 : Monitor Waited */

jvmtiEventMonitorWaited MonitorWaited;

/* 75 : Monitor Contended Enter */

jvmtiEventMonitorContendedEnter MonitorContendedEnter;

/* 76 : Monitor Contended Entered */

jvmtiEventMonitorContendedEntered MonitorContendedEntered;

/* 77 */

jvmtiEventReserved reserved77;

/* 78 */

jvmtiEventReserved reserved78;

/* 79 */

jvmtiEventReserved reserved79;

/* 80 : Resource Exhausted */

jvmtiEventResourceExhausted ResourceExhausted;

/* 81 : Garbage Collection Start */

jvmtiEventGarbageCollectionStart GarbageCollectionStart;

/* 82 : Garbage Collection Finish */

jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;

/* 83 : Object Free */

jvmtiEventObjectFree ObjectFree;

/* 84 : VM Object Allocation */

jvmtiEventVMObjectAlloc VMObjectAlloc;

} jvmtiEventCallbacks;

4.1 执行jvmtiEventVMInit的回调函数

虚拟机在创建create_vm的时候,初始化了JVMTI环境后会执行JvmtiExport::post_vm_initialized(); 方法,代码如下:

void JvmtiExport::post_vm_initialized() {

EVT_TRIG_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Trg VM init event triggered" ));

// can now enable events

JvmtiEventController::vm_init();

JvmtiEnvIterator it;

for (JvmtiEnv* env = it.first(); env != NULL; env = it.next(env)) {

if (env->is_enabled(JVMTI_EVENT_VM_INIT)) {

EVT_TRACE(JVMTI_EVENT_VM_INIT, ("JVMTI Evt VM init event sent" ));

JavaThread *thread = JavaThread::current();

JvmtiThreadEventMark jem(thread);

JvmtiJavaThreadEventTransition jet(thread);

jvmtiEventVMInit callback = env->callbacks()->VMInit;

if (callback != NULL) {

// 调用了VMInit的回调函数

(*callback)(env->jvmti_external(), jem.jni_env(), jem.jni_thread());

}

}

}

}

4.2 执行jvmtiEventClassFileLoadHook的回调函数

钩子方法是jvmtiEventClassFileLoadHook的回调方法,代码在classFileParser的文件中:

instanceKlassHandle ClassFileParser::parseClassFile(symbolHandle name, Handle class_loader,Handle protection_domain, KlassHandle host_klass, GrowableArray* cp_patches, symbolHandle& parsed_name,bool verify, TRAPS) {

……

if (JvmtiExport::should_post_class_file_load_hook()) {

unsigned char* ptr = cfs->buffer();

unsigned char* end_ptr = cfs->buffer() + cfs->length();

JvmtiExport::post_class_file_load_hook(name, class_loader, protection_domain,

&ptr, &end_ptr,

&cached_class_file_bytes,

&cached_class_file_length);

if (ptr != cfs->buffer()) {

// JVMTI agent has modified class file data.

// Set new class file stream using JVMTI agent modified

// class file data.

cfs = new ClassFileStream(ptr, end_ptr - ptr, cfs->source());

set_stream(cfs);

}

}

}

在jvmtiexport::post_class_file_load_hook函数最后 调用了post_to_env()函数:

void post_to_env(JvmtiEnv* env, bool caching_needed) {

unsigned char *new_data = NULL;

jint new_len = 0;

JvmtiClassFileLoadEventMark jem(_thread, _h_name, _class_loader,

_h_protection_domain,

_h_class_being_redefined);

JvmtiJavaThreadEventTransition jet(_thread);

JNIEnv* jni_env = (JvmtiEnv::get_phase() == JVMTI_PHASE_PRIMORDIAL)? NULL : jem.jni_env();

jvmtiEventClassFileLoadHook callback = env->callbacks()->ClassFileLoadHook;

if (callback != NULL) {

(*callback)(env->jvmti_external(), jni_env,

jem.class_being_redefined(),

jem.jloader(), jem.class_name(),

jem.protection_domain(),

_curr_len, _curr_data,

&new_len, &new_data);

}

......

}

函数中调用了jvmtiEventClassFileLoadHook的回调函数,也就是刚才在结构体中定义的jvmtiEventCallbacks。钩子函数eventHandlerClassFileLoadHook:

void JNICALL 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);

也就是调用了InstrumentationImpl里的transform方法,在InstrumentationImpl类里通过TransformerManager的transform的方法最终调用我们自定义的MyTransformer的类的transform方法。

private byte[] transform(ClassLoader var1, String var2, Class var3, ProtectionDomain var4, byte[] var5, boolean var6) {

TransformerManager var7 = var6 ? this.mRetransfomableTransformerManager : this.mTransformerManager;

return var7 == null ? null : var7.transform(var1, var2, var3, var4, var5);

}

4.3 注册钩子函数jvmtiEventClassFileLoadHook

如上,那么钩子函数jvmtiEventClassFileLoadHook是何时注册的,回到刚才创建新的JPLISAgent代码中:

JPLISInitializationError createNewJPLISAgent(JavaVM * vm, JPLISAgent **agent_ptr) {

JPLISInitializationError initerror = JPLIS_INIT_ERROR_NONE;

jvmtiEnv * jvmtienv = NULL;

jint jnierror = JNI_OK;

*agent_ptr = NULL;

jnierror = (*vm)->GetEnv(vm,(void **) &jvmtienv,JVMTI_VERSION);

if (jnierror != JNI_OK) {

initerror = JPLIS_INIT_ERROR_CANNOT_CREATE_NATIVE_AGENT;

} else {

JPLISAgent * agent = allocateJPLISAgent(jvmtienv);

if (agent == NULL) {

initerror = JPLIS_INIT_ERROR_ALLOCATION_FAILURE;

} else {

initerror = initializeJPLISAgent(agent, vm, jvmtienv);

if (initerror == JPLIS_INIT_ERROR_NONE) {

*agent_ptr = agent;

} else {

deallocateJPLISAgent(jvmtienv, agent);

}

}

/* don't leak envs */

if ( initerror != JPLIS_INIT_ERROR_NONE ) {

jvmtiError jvmtierror = (*jvmtienv)->DisposeEnvironment(jvmtienv);

jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

}

}

return initerror;

}

函数initializeJPLISAgent初始化了JPLISAgent:

JPLISInitializationError initializeJPLISAgent( JPLISAgent * agent,JavaVM * vm,jvmtiEnv * jvmtienv) {

……

checkCapabilities(agent);

jvmtierror == (*jvmtienv)->GetPhase(jvmtienv, &phase);

jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

if (phase == JVMTI_PHASE_LIVE) {

return JPLIS_INIT_ERROR_NONE;

}

/* now turn on the VMInit event */

if ( jvmtierror == JVMTI_ERROR_NONE ) {

jvmtiEventCallbacks callbacks;

memset(&callbacks, 0, sizeof(callbacks));

callbacks.VMInit = &eventHandlerVMInit;

jvmtierror = (*jvmtienv)->SetEventCallbacks( jvmtienv, &callbacks, sizeof(callbacks));

jplis_assert(jvmtierror == JVMTI_ERROR_NONE);

}

……

}

JPLISAgent里首先 注册了一个VMInit的初始化函数eventHandlerVMInit,跟踪eventHandlerVMInit函数:

void JNICALL eventHandlerVMInit( jvmtiEnv * jvmtienv,

JNIEnv * jnienv,

jthread thread) {

JPLISEnvironment * environment = NULL;

jboolean success = JNI_FALSE;

environment = getJPLISEnvironment(jvmtienv);

/* process the premain calls on the all the JPL agents */

if ( environment != NULL ) {

jthrowable outstandingException = preserveThrowable(jnienv);

success = processJavaStart( environment->mAgent,

jnienv);

restoreThrowable(jnienv, outstandingException);

}

/* if we fail to start cleanly, bring down the JVM */

if ( !success ) {

abortJVM(jnienv, JPLIS_ERRORMESSAGE_CANNOTSTART);

}

}

在processJavaStart里:

jboolean processJavaStart(JPLISAgent * agent, JNIEnv * jnienv) {

jboolean result;

result = initializeFallbackError(jnienv);

jplis_assert(result);

if ( result ) {

result = createInstrumentationImpl(jnienv, agent);

jplis_assert(result);

}

if ( result ) {

result = setLivePhaseEventHandlers(agent);

jplis_assert(result);

}

if ( result ) {

result = startJavaAgent(agent, jnienv, agent->mAgentClassName, agent->mOptionsString,agent->mPremainCaller);

}

if ( result ) {

deallocateCommandLineData(agent);

}

return result;

}

在setLivePhaseEventHandler函数中注册了,代码如下:

callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook;

5 JPLISAgent结构体

struct _JPLISAgent {

JavaVM * mJVM; /* handle to the JVM */

JPLISEnvironment mNormalEnvironment; /* for every thing but retransform stuff */

JPLISEnvironment mRetransformEnvironment;/* for retransform stuff only */

jobject mInstrumentationImpl; /* handle to the Instrumentation instance */

jmethodID mPremainCaller; /* method on the InstrumentationImpl that does the premain stuff (cached to save lots of lookups) */

jmethodID mAgentmainCaller; /* method on the InstrumentationImpl for agents loaded via attach mechanism */

jmethodID mTransform; /* method on the InstrumentationImpl that does the class file transform */

jboolean mRedefineAvailable; /* cached answer to "does this agent support redefine" */

jboolean mRedefineAdded; /* indicates if can_redefine_classes capability has been added */

jboolean mNativeMethodPrefixAvailable; /* cached answer to "does this agent support prefixing" */

jboolean mNativeMethodPrefixAdded; /* indicates if can_set_native_method_prefix capability has been added */

char const * mAgentClassName; /* agent class name */

char const * mOptionsString; /* -javaagent options string */

};

struct _JPLISEnvironment {

jvmtiEnv * mJVMTIEnv; /* the JVM TI environment */

JPLISAgent * mAgent; /* corresponding agent */

jboolean mIsRetransformer; /* indicates if special environment */

};

mNormalEnvironment:agent环境;

mRetransformEnvironment:retransform环境;

mInstrumentationImpl:sun自己提供的instrument对象;

mPremainCaller:sun.instrument.InstrumentationImpl.loadClassAndCallPremain方法,agent启动时加载会被调用该方法;

mAgentmainCaller:sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain方法,agent attach动态加载agent的时会被调用该方法;

mTransform:sun.instrument.InstrumentationImpl.transform方法;

mAgentClassName:javaagent的MANIFEST.MF里指定的Agent-Class;

mOptionsString:agent初始参数;

mRedefineAvailable:MANIFEST.MF里的参数Can-Redefine-Classes:true;

mNativeMethodPrefixAvailable:MANIFEST.MF里的参数Can-Set-Native-Method-Prefix:true;

mIsRetransformer:MANIFEST.MF里的参数Can-Retransform-Classes:true;

在startJavaAgent的方法中调用了启动JPLISAgent的方式,我们来看invokeJavaAgentMainMethod:

jboolean invokeJavaAgentMainMethod(JNIEnv * jnienv,

jobject instrumentationImpl,

jmethodID mainCallingMethod,

jstring className,

jstring optionsString) {

jboolean errorOutstanding = JNI_FALSE;

jplis_assert(mainCallingMethod != NULL);

if (mainCallingMethod != NULL ) {

(*jnienv)->CallVoidMethod(jnienv,

instrumentationImpl,

mainCallingMethod,

className,

optionsString);

errorOutstanding = checkForThrowable(jnienv);

if ( errorOutstanding ) {

logThrowable(jnienv);

}

checkForAndClearThrowable(jnienv);

}

return !errorOutstanding;

}

在函数里,实际上是调用java类sun.instrument.InstrumentationImpl 类里的方法loadClassAndCallPremain。

private void loadClassAndCallPremain(String var1, String var2) throws Throwable {

this.loadClassAndStartAgent(var1, "premain", var2);

}

private void loadClassAndCallAgentmain(String var1, String var2) throws Throwable {

this.loadClassAndStartAgent(var1, "agentmain", var2);

}

继续查看Java的sun.instrument.InstrumentationImpl类的方法loadClassAndStartAgent:

private void loadClassAndStartAgent(String classname,

String methodname,

String optionsString) throws Throwable {

...

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);

}

在InstrumentationImpl的类中初始化了我们自定义的Transformer的premain方法:

public class MyInjectTransformer implements ClassFileTransformer{

public static void premain(String options, Instrumentation ins) {

ins.addTransformer(new SQLInjectTransformer());

}

@Override

public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined,

ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

return null;

}

}

6 工作原理及运行时序图

6.1 启动并创建JVM,注册vmInit回调函数

7b2072513819

启动并创建JVM

6.2 执行vmInit回调函数,注册jvmtiEventClassFileLoadHook回调函数,加载并初始化 Instrument Agent

7b2072513819

执行vmInit回调函数,注册jvmtiEventClassFileLoadHook回调函数,加载并初始化 Instrument Agent

6.3 加载解析Class文件,执行jvmtiEventClassFileLoadHook回调函数

7b2072513819

加载解析Class文件,执行jvmtiEventClassFileLoadHook回调函数

6.4 以-javaagent为例,工作原理

在JVM启动时,通过JVM参数-javaagent,传入agent jar,Instrument Agent被加载;

在Instrument Agent 初始化时,注册了JVMTI初始化函数eventHandlerVMinit;

在JVM启动时,会调用初始化函数eventHandlerVMinit,启动了Instrument Agent,用sun.instrument.instrumentationImpl类里的方法loadClassAndCallPremain方法去初始化Premain-Class指定类的premain方法;

初始化函数eventHandlerVMinit,注册了class解析的ClassFileLoadHook函数;

在解析Class之前,JVM调用JVMTI的ClassFileLoadHook函数,钩子函数调用sun.instrument.instrumentationImpl类里的transform方法,通过TransformerManager的transformer方法最终调用我们自定义的Transformer类的transform方法;

因为字节码在解析Class之前改的,直接使用修改后的字节码的数据流替代,最后进入Class解析,对整个Class解析无影响;

重新加载Class依然重新走5-6步骤;

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/450783
推荐阅读
相关标签
  

闽ICP备14008679号