0 Предисловие
В предыдущем разделе описано, как реализовать агент на основе JVMTI. Другой раздел — это реализация агента на основе Java Instrument API. Код агента может быть написан на уровне кода Java вместо кода на основе C++/C. Подробнее см.«Использование и принцип функции инструмента Java»:
от-javaagent: значение по умолчанию для начала инструмента;
Итак, как две приведенные выше реализации агента работают в исходном коде JVMTI?
1 Инициализировать агент
При запуске JVM считываются параметры командной строки JVM.-agentlib -agentpath -javaagent
,И создайте связанный список библиотеки агентов.Код для инициализации агента выглядит следующим образом.:
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 Загрузите библиотеку ссылок агента
При запуске JVM create_vm для каждой библиотеки агентов в связанном списке агентовЗагрузите указанную динамическую библиотеку и вызовите метод Agent_OnLoad внутри,Например:Для загрузки агента инструментов Java необходимо загрузить динамическую библиотеку Instrument.so из libinstrument.:
// 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 Создать инструмент jplisagent
Создайте новый в методе Agent_OnLoadJPLISAgent (агент служб инструментария языка программирования Java) инициализирует файлы конфигурации в классе и пакете и одновременно получает среду jvmtiEnv из среды Vm..
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);
……
}
В коде вы можете видеть, что вПрочитайте Premain-Class в файле конфигурации jar MANIFEST и добавьте файл jar в путь к классу агента..
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<Handle>* 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,В классе InstrumentationImpl метод преобразования нашего пользовательского класса MyTransformer, наконец, вызывается через метод преобразования TransformerManager..
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 Структура агента JPLISA
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: среда агента;
- mRetransformEnvironment:преобразовать окружающую среду;
- mInstrumentationImpl: Объект-инструмент, предоставленный самим солнцем;
- mPremainCaller:
sun.instrument.InstrumentationImpl.loadClassAndCallPremain
метод, который будет вызван при загрузке агента;- mAgentmainCaller:
sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain
метод, который будет вызываться при подключении агента, динамически загружает агент;- mTransform:
sun.instrument.InstrumentationImpl.transform
метод;- mAgentClassName: указано в MANIFEST.MF javaagent
Agent-Class
;- mOptionsString: агент начальные параметры;
- mRedefineAvailable: Параметры в MANIFEST.MF
Can-Redefine-Classes:true
;- mNativeMethodPrefixAvailable: параметры в MANIFEST.MF
Can-Set-Native-Method-Prefix:true
;- mIsRetransformer: параметры в MANIFEST.MF
Can-Retransform-Classes:true
;
Метод запуска JPLISAgent вызывается в методе startJavaAgentМы смотрим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;
}
В функции фактически вызывается метод loadClassAndCallPremain в java-классе sun.instrument.InstrumentationImpl.
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);
}
Продолжайте просматривать метод loadClassAndStartAgent класса Java sun.instrument.InstrumentationImpl:
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);
}
Предварительный метод нашего пользовательского Transformer инициализируется в классе InstrumentationImpl:
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
6.2 VMINIT Выполните функцию обратного вызова, функция обратного вызова зарегистрирована JVMTieventClassFileLoadhook, загружает и инициализирует агент прибора
6.3 Загрузите и проанализируйте файл класса и выполните функцию обратного вызова jvmtiEventClassFileLoadHook.
6.4 Взяв в качестве примера -javaagent, принцип работы
- Когда JVM запускается,Передайте jar агента через параметр JVM -javaagent, агент инструмента загружается;
- Когда инструментальный агент инициализируется,JVMTI Зарегистрированная функция инициализации EventHandLervminit;
- Когда JVM запускается,Будет вызвана функция инициализации eventHandlerVMinit, запускает агент инструмента,Используйте метод loadClassAndCallPremain в классе sun.instrument.instrumentationImpl для инициализации метода premain указанного класса Premain-Class.;
- Функция инициализации EventHandLervminit,Функция ClassFileLoadHook зарегистрирована для разрешения класса;
- Перед разбором классаJVM вызывает функцию ClassFileLoadHook JVMTI, функция ловушки вызывает метод преобразования в классе sun.instrument.instrumentationImpl,Наконец, вызовите метод преобразования нашего пользовательского класса Transformer через метод преобразования TransformerManager.;
- Поскольку Bytecode изменяется перед анализом класса, поток данных модифицированного Bytecode напрямую используется вместо этого, и, наконец, входит в анализ класса, который не влияет на все расставание класса;
- Перезагрузить класс все равно заново сделать 5-6 шагов;