本文重點講述javaagent的具體實現,因為它面向的是我們java程序員,而且agent都是用java編寫的,不需要太多的c/c++編程基礎,不過這篇文章里也會講到JVMTIAgent(c實現的),因為javaagent的運行還是依賴於一個特殊的JVMTIAgent。
對於javaagent或許大家都聽過,甚至使用過,常見的用法大致如下:
java -javaagent:myagent.jar=mode=test Test
我們通過-javaagent來指定我們編寫的agent的jar路徑(./myagent.jar)及要傳給agent的參數(mode=test),這樣在啟動的時候這個agent就可以做一些我們想要它做的事了。
javaagent的主要的功能如下:
- 可以在加載java文件之前做攔截把字節碼做修改
- 可以在運行期將已經加載的類的字節碼做變更,但是這種情況下會有很多的限制,后面會詳細說
- 還有其他的一些小眾的功能
- 獲取所有已經被加載過的類
- 獲取所有已經被初始化過了的類(執行過了clinit方法,是上面的一個子集)
- 獲取某個對象的大小
- 將某個jar加入到bootstrapclasspath里作為高優先級被bootstrapClassloader加載
- 將某個jar加入到classpath里供AppClassloard去加載
- 設置某些native方法的前綴,主要在查找native方法的時候做規則匹配
JVMTI
JVM Tool Interface,是jvm暴露出來的一些供用戶擴展的接口集合,JVMTI是基於事件驅動的,JVM每執行到一定的邏輯就會調用一些事件的回調接口(如果有的話),這些接口可以供開發者去擴展自己的邏輯。
比如說我們最常見的想在某個類的字節碼文件讀取之后類定義之前能修改相關的字節碼,從而使創建的class對象是我們修改之后的字節碼內容,那我們就可以實現一個回調函數賦給JvmtiEnv(JVMTI的運行時,通常一個JVMTIAgent對應一個jvmtiEnv,但是也可以對應多個)的回調方法集合里的ClassFileLoadHook,這樣在接下來的類文件加載過程中都會調用到這個函數里來了,大致實現如下:
-
jvmtiEventCallbacks callbacks ;
-
jvmtiEnv * jvmtienv = jvmti(agent);
-
jvmtiError jvmtierror;
-
memset(&callbacks, 0, sizeof(callbacks)) ;
-
callbacks.ClassFileLoadHook = &eventHandlerClassFileLoadHook ;
-
jvmtierror = (* jvmtienv)->SetEventCallbacks( jvmtienv,
-
&callbacks,
-
sizeof(callbacks)) ;
JVMTIAgent
JVMTIAgent其實就是一個動態庫,利用JVMTI暴露出來的一些接口來干一些我們想做但是正常情況下又做不到的事情,不過為了和普通的動態庫進行區分,它一般會實現如下的一個或者多個函數:
-
JNIEXPORT jint JNICALL
-
Agent_OnLoad( JavaVM *vm, char *options, void *reserved) ;
-
-
JNIEXPORT jint JNICALL
-
Agent_OnAttach( JavaVM* vm, char* options, void* reserved) ;
-
-
JNIEXPORT void JNICALL
-
Agent_OnUnload( JavaVM *vm) ;
-
Agent_OnLoad函數,如果agent是在啟動的時候加載的,也就是在vm參數里通過-agentlib來指定,那在啟動過程中就會去執行這個agent里的Agent_OnLoad函數。Agent_OnAttach函數,如果agent不是在啟動的時候加載的,是我們先attach到目標進程上,然后給對應的目標進程發送load命令來加載agent,在加載過程中就會調用Agent_OnAttach函數。Agent_OnUnload函數,在agent做卸載的時候調用,不過貌似基本上很少實現它。
其實我們每天都在和JVMTIAgent打交道,只是你可能沒有意識到而已,比如我們經常使用eclipse等工具對java代碼做調試,其實就利用了jre自帶的jdwp agent來實現的,只是由於eclipse等工具在沒讓你察覺的情況下將相關參數(類似-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:61349)給自動加到程序啟動參數列表里了,其中agentlib參數就是用來跟要加載的agent的名字,比如這里的jdwp(不過這不是動態庫的名字,而JVM是會做一些名稱上的擴展,比如在linux下會去找libjdwp.so的動態庫進行加載,也就是在名字的基礎上加前綴lib,再加后綴.so),接下來會跟一堆相關的參數,會將這些參數傳給Agent_OnLoad或者Agent_OnAttach函數里對應的options參數。
javaagent
說到javaagent必須要講的是一個叫做instrument的JVMTIAgent(linux下對應的動態庫是libinstrument.so),因為就是它來實現javaagent的功能的,另外instrument agent還有個別名叫JPLISAgent(Java Programming Language Instrumentation Services Agent),從這名字里也完全體現了其最本質的功能:就是專門為java語言編寫的插樁服務提供支持的。
instrument agent
instrument agent實現了Agent_OnLoad和Agent_OnAttach兩方法,也就是說我們在用它的時候既支持啟動的時候來加載agent,也支持在運行期來動態來加載這個agent,其中啟動時加載agent還可以通過類似-javaagent:myagent.jar的方式來間接加載instrument agent,運行期動態加載agent依賴的是jvm的attach機制JVM Attach機制實現,通過發送load命令來加載agent。
instrument agent的核心數據結構如下:
-
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:主要提供正常的類transform及redefine功能的。
- mRetransformEnvironment:主要提供類retransform功能的。
- mInstrumentationImpl:這個對象非常重要,也是我們java agent和JVM進行交互的入口,或許寫過javaagent的人在寫
premain以及agentmain方法的時候注意到了有個Instrumentation的參數,這個參數其實就是這里的對象。 - mPremainCaller:指向
sun.instrument.InstrumentationImpl.loadClassAndCallPremain方法,如果agent是在啟動的時候加載的,那該方法會被調用。 - mAgentmainCaller:指向
sun.instrument.InstrumentationImpl.loadClassAndCallAgentmain方法,該方法在通過attach的方式動態加載agent的時候調用。 - mTransform:指向
sun.instrument.InstrumentationImpl.transform方法。 - mAgentClassName:在我們javaagent的MANIFEST.MF里指定的
Agent-Class。 - mOptionsString:傳給agent的一些參數。
- mRedefineAvailable:是否開啟了redefine功能,在javaagent的MANIFEST.MF里設置
Can-Redefine-Classes:true。 - mNativeMethodPrefixAvailable:是否支持native方法前綴設置,通樣在javaagent的MANIFEST.MF里設置
Can-Set-Native-Method-Prefix:true。 - mIsRetransformer:如果在javaagent的MANIFEST.MF文件里定義了
Can-Retransform-Classes:true,那將會設置mRetransformEnvironment的mIsRetransformer為true。
啟動時加載instrument agent
正如『概述』里提到的方式,就是啟動的時候加載instrument agent,具體過程都在InvocationAdapter.c的Agent_OnLoad方法里,簡單描述下過程:
- 創建並初始化JPLISAgent
- 監聽VMInit事件,在vm初始化完成之后做下面的事情:
- 創建InstrumentationImpl對象
- 監聽ClassFileLoadHook事件
- 調用InstrumentationImpl的
loadClassAndCallPremain方法,在這個方法里會去調用javaagent里MANIFEST.MF里指定的Premain-Class類的premain方法
- 解析javaagent里MANIFEST.MF里的參數,並根據這些參數來設置JPLISAgent里的一些內容
運行時加載instrument agent
運行時加載的方式,大致按照下面的方式來操作:
-
VirtualMachine vm = VirtualMachine.attach(pid) ;
-
vm.loadAgent(agentPath, agentArgs) ;
上面會通過jvm的attach機制來請求目標jvm加載對應的agent,過程大致如下:
- 創建並初始化JPLISAgent
- 解析javaagent里MANIFEST.MF里的參數
- 創建InstrumentationImpl對象
- 監聽ClassFileLoadHook事件
- 調用InstrumentationImpl的
loadClassAndCallAgentmain方法,在這個方法里會去調用javaagent里MANIFEST.MF里指定的Agent-Class類的agentmain方法
instrument agent的ClassFileLoadHook回調實現
不管是啟動時還是運行時加載的instrument agent都關注着同一個jvmti事件---ClassFileLoadHook,這個事件是在讀取字節碼文件之后回調時用的,這樣可以對原來的字節碼做修改,那這里面究竟是怎樣實現的呢?
-
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) ;
-
}
-
}
先根據jvmtiEnv取得對應的JPLISEnvironment,因為上面我已經說到其實有兩個JPLISEnvironment(並且有兩個jvmtiEnv),其中一個專門做retransform的,而另外一個用來做其他的事情,根據不同的用途我們在注冊具體的ClassFileTransformer的時候也是分開的,對於作為retransform用的ClassFileTransformer我們會注冊到一個單獨的TransformerManager里。
接着調用transformClassFile方法,由於函數實現比較長,我這里就不貼代碼了,大致意思就是調用InstrumentationImpl對象的transform方法,根據最后那個參數來決定選哪個TransformerManager里的ClassFileTransformer對象們做transform操作。
-
private byte[]
-
transform( ClassLoader loader,
-
String classname,
-
Class classBeingRedefined,
-
ProtectionDomain protectionDomain,
-
byte[] classfileBuffer,
-
boolean isRetransformer) {
-
TransformerManager mgr = isRetransformer?
-
mRetransfomableTransformerManager :
-
mTransformerManager;
-
if (mgr == null) {
-
return null; // no manager, no transform
-
} else {
-
return mgr.transform( loader,
-
classname,
-
classBeingRedefined,
-
protectionDomain,
-
classfileBuffer);
-
}
-
}
-
-
public byte[]
-
transform( ClassLoader loader,
-
String classname,
-
Class classBeingRedefined,
-
ProtectionDomain protectionDomain,
-
byte[] classfileBuffer) {
-
boolean someoneTouchedTheBytecode = false;
-
-
TransformerInfo[] transformerList = getSnapshotTransformerList();
-
-
byte[] bufferToUse = classfileBuffer;
-
-
// order matters, gotta run 'em in the order they were added
-
for ( int x = 0; x < transformerList.length; x++ ) {
-
TransformerInfo transformerInfo = transformerList[x];
-
ClassFileTransformer transformer = transformerInfo.transformer();
-
byte[] transformedBytes = null;
-
-
try {
-
transformedBytes = transformer.transform( loader,
-
classname,
-
classBeingRedefined,
-
protectionDomain,
-
bufferToUse);
-
}
-
catch ( Throwable t) {
-
// don't let any one transformer mess it up for the others.
-
// This is where we need to put some logging. What should go here? FIXME
-
}
-
-
if ( transformedBytes != null ) {
-
someoneTouchedTheBytecode = true;
-
bufferToUse = transformedBytes;
-
}
-
}
-
-
// if someone modified it, return the modified buffer.
-
// otherwise return null to mean "no transforms occurred"
-
byte [] result;
-
if ( someoneTouchedTheBytecode ) {
-
result = bufferToUse;
-
}
-
else {
-
result = null;
-
}
-
-
return result;
-
}
以上是最終調到的java代碼,可以看到已經調用到我們自己編寫的javaagent代碼里了,我們一般是實現一個ClassFileTransformer類,然后創建一個對象注冊了對應的TransformerManager里。
Class Transform的實現
這里說的class transform其實是狹義的,主要是針對第一次類文件加載的時候就要求被transform的場景,在加載類文件的時候發出ClassFileLoad的事件,然后交給instrumenat agent來調用javaagent里注冊的ClassFileTransformer實現字節碼的修改。
Class Redefine的實現
類重新定義,這是Instrumentation提供的基礎功能之一,主要用在已經被加載過的類上,想對其進行修改,要做這件事,我們必須要知道兩個東西,一個是要修改哪個類,另外一個是那個類你想修改成怎樣的結構,有了這兩信息之后於是你就可以通過InstrumentationImpl的下面的redefineClasses方法去操作了:
-
publicvoid
-
redefineClasses (ClassDefinition[] definitions)
-
throws ClassNotFoundException {
-
if (!isRedefineClassesSupported()) {
-
throw new UnsupportedOperationException( "redefineClasses is not supported in this environment");
-
}
-
if (definitions == null) {
-
throw new NullPointerException( "null passed as 'definitions' in redefineClasses");
-
}
-
for ( int i = 0; i < definitions.length; ++i) {
-
if (definitions[i] == null) {
-
throw new NullPointerException( "element of 'definitions' is null in redefineClasses");
-
}
-
}
-
if (definitions.length == 0) {
-
return; // short-circuit if there are no changes requested
-
}
-
-
redefineClasses0(mNativeAgent, definitions);
-
}
在JVM里對應的實現是創建一個VM_RedefineClasses的VM_Operation,注意執行它的時候會stop the world的:
-
jvmtiError
-
JvmtiEnv::RedefineClasses(jint class _count, const jvmtiClassDefinition* class _definitions) {
-
//TODO: add locking
-
VM _ RedefineClasses op( class _ count, class _ definitions, jvmti _ class _ load _k ind _ redefine);
-
VMThread::execute(&op);
-
return (op.check _error());
-
} /* end RedefineClasses */
這個過程我盡量用語言來描述清楚,不詳細貼代碼了,因為代碼量實在有點大:
- 挨個遍歷要批量重定義的jvmtiClassDefinition
- 然后讀取新的字節碼,如果有關注ClassFileLoadHook事件的,還會走對應的transform來對新的字節碼再做修改
- 字節碼解析好,創建一個klassOop對象
- 對比新老類,並要求如下:
- 父類是同一個
- 實現的接口數也要相同,並且是相同的接口
- 類訪問符必須一致
- 字段數和字段名要一致
- 新增或刪除的方法必須是private static/final的
- 可以修改方法
- 對新類做字節碼校驗
- 合並新老類的常量池
- 如果老類上有斷點,那都清除掉
- 對老類做jit去優化
- 對新老方法匹配的方法的jmethodid做更新,將老的jmethodId更新到新的method上
- 新類的常量池的holer指向老的類
- 將新類和老類的一些屬性做交換,比如常量池,methods,內部類
- 初始化新的vtable和itable
- 交換annotation的method,field,paramenter
- 遍歷所有當前類的子類,修改他們的vtable及itable
上面是基本的過程,總的來說就是只更新了類里內容,相當於只更新了指針指向的內容,並沒有更新指針,避免了遍歷大量已有類對象對它們進行更新帶來的開銷。
Class Retransform的實現
retransform class可以簡單理解為回滾操作,具體回滾到哪個版本,這個需要看情況而定,下面不管那種情況都有一個前提,那就是javaagent已經要求要有retransform的能力了:
- 如果類是在第一次加載的的時候就做了transform,那么做retransform的時候會將代碼回滾到transform之后的代碼
- 如果類是在第一次加載的的時候沒有任何變化,那么做retransform的時候會將代碼回滾到最原始的類文件里的字節碼
- 如果類已經被加載了,期間類可能做過多次redefine(比如被另外一個agent做過),但是接下來加載一個新的agent要求有retransform的能力了,然后對類做redefine的動作,那么retransform的時候會將代碼回滾到上一個agent最后一次做redefine后的字節碼
我們從InstrumentationImpl的retransformClasses方法參數看猜到應該是做回滾操作,因為我們只指定了class
-
publicvoid
-
retransformClasses (Class<?>[] classes){
-
if (!isRetransformClassesSupported()) {
-
throw new UnsupportedOperationException(
-
"retransformClasses is not supported in this environment");
-
}
-
retransformClasses0(mNativeAgent, classes);
-
}
-
不過retransform的實現其實也是通過redefine的功能來實現,在類加載的時候有比較小的差別,主要體現在究竟會走哪些transform上,如果當前是做retransform的話,那將忽略那些注冊到正常的TransformerManager里的ClassFileTransformer,而只會走專門為retransform而准備的TransformerManager的ClassFileTransformer,不然想象一下字節碼又被無聲無息改成某個中間態了。
-
private:
-
void post_all_envs() {
-
if (_load_kind != jvmti_class_load_kind_retransform) {
-
// for class load and redefine,
-
// call the non-retransformable agents
-
JvmtiEnvIterator it;
-
for (JvmtiEnv* env = it.first(); env != NULL; env = it.next( env)) {
-
if (! env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
-
// non-retransformable agents cannot retransform back,
-
// so no need to cache the original class file bytes
-
post_to_env( env, false);
-
}
-
}
-
}
-
JvmtiEnvIterator it;
-
for (JvmtiEnv* env = it.first(); env != NULL; env = it.next( env)) {
-
// retransformable agents get all events
-
if ( env->is_retransformable() && env->is_enabled(JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
-
// retransformable agents need to cache the original class file
-
// bytes if changes are made via the ClassFileLoadHook
-
post_to_env( env, true);
-
}
-
}
-
}
附錄
// Jagent.cpp : 定義 DLL 應用程序的導出函數。
//
//Alt+F8
#include "stdafx.h"
#define MAX_TOKEN_LENGTH 16
#define MAX_THREAD_NAME_LENGTH 512
#define MAX_METHOD_NAME_LENGTH 1024
static jvmtiEnv *jvmti = NULL;
static jvmtiCapabilities capa;
/* Global agent data structure */
typedef struct {
/* JVMTI Environment */
jvmtiEnv *jvmti;
jboolean vm_is_started;
/* Data access Lock */
jrawMonitorID lock;
} GlobalAgentData;
static GlobalAgentData *gdata;
static jlong combined_size;
static int num_class_refs;
static int num_field_refs;
static int num_array_refs;
static int num_classloader_refs;
static int num_signer_refs;
static int num_protection_domain_refs;
static int num_interface_refs;
static int num_static_field_refs;
static int num_constant_pool_refs;
/* Every JVMTI interface returns an error code, which should be checked
* to avoid any cascading errors down the line.
* The interface GetErrorName() returns the actual enumeration constant
* name, making the error messages much easier to understand.
*/
static void check_jvmti_error(jvmtiEnv *jvmti, jvmtiError errnum, const char *str)
{
if ( errnum != JVMTI_ERROR_NONE ) {
char *errnum_str;
errnum_str = NULL;
(void)(*jvmti).GetErrorName(errnum, &errnum_str);
printf("ERROR: JVMTI: %d(%s): %s\n", errnum, (errnum_str==NULL?"Unknown":errnum_str), (str==NULL?"":str));
}
}
/* Enter a critical section by doing a JVMTI Raw Monitor Enter */
static void enter_critical_section(jvmtiEnv *jvmti)
{
jvmtiError error;
error = (*jvmti).RawMonitorEnter(gdata->lock);
check_jvmti_error(jvmti, error, "Cannot enter with raw monitor");
}
/* Exit a critical section by doing a JVMTI Raw Monitor Exit */
static void exit_critical_section(jvmtiEnv *jvmti)
{
jvmtiError error;
error = (*jvmti).RawMonitorExit(gdata->lock);
check_jvmti_error(jvmti, error, "Cannot exit with raw monitor");
}
void describe(jvmtiError err) {
jvmtiError err0;
char *descr;
err0 = (*jvmti).GetErrorName( err, &descr);
if (err0 == JVMTI_ERROR_NONE) {
printf(descr);
} else {
printf("error [%d]", err);
}
}
// Exception callback
static void JNICALL callbackException(jvmtiEnv *jvmti_env, JNIEnv* env, jthread thr, jmethodID method,
jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location)
{
enter_critical_section(jvmti); {
jvmtiError err, err1, err2, error;
jvmtiThreadInfo info, info1;
jvmtiThreadGroupInfo groupInfo;
jint num_monitors;
jobject *arr_monitors;
jvmtiFrameInfo frames[5];
jint count;
jint flag = 0;
jint thr_st_ptr;
jint thr_count;
jthread *thr_ptr;
jvmtiError err3;
char *name;
char *sig;
char *gsig;
err3 = (*jvmti).GetMethodName(method, &name, &sig, &gsig);
if (err3 != JVMTI_ERROR_NONE) {
printf("GetMethodName:%d\n", err);
return;
}
printf("Got Exception from Method:%s%s\n", name, sig);
err = (*jvmti).GetThreadInfo( thr, &info);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
jvmtiPhase phase;
jvmtiError phaseStat;
phaseStat = (*jvmti).GetPhase(&phase);
printf(" current phase is %d\n", phase);
printf("\n");
}
if (err == JVMTI_ERROR_NONE) {
err1 = (*jvmti).GetThreadGroupInfo(info.thread_group, &groupInfo);
if (err1 != JVMTI_ERROR_NONE)
{
printf("(GetThreadGroupInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
}
if ((err == JVMTI_ERROR_NONE ) && (err1 == JVMTI_ERROR_NONE ) )
{
printf("Current Thread is : %s and it belongs to Thread Group : %s\n", info.name, groupInfo.name);
}
err = (*jvmti).GetOwnedMonitorInfo(thr, &num_monitors, &arr_monitors);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
printf("Number of Monitors Owned by this thread : %d\n", num_monitors);
/* Get Thread Status */
err = (*jvmti).GetThreadState( thr, &thr_st_ptr);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
if (err == JVMTI_ERROR_NONE) {
printf("Thread Status\n");
printf("==============\n");
if ( thr_st_ptr & JVMTI_THREAD_STATE_ALIVE) {
printf("Thread %s is Alive\n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_TERMINATED) {
printf("Thread %s has been Terminated\n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_RUNNABLE ) {
printf("Thread %s is Runnable\n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING ) {
printf("Thread %s waiting\n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING_INDEFINITELY ) {
printf("Thread %s waiting indefinitely\n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT ) {
printf("Thread %s waiting with Timeout\n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_SLEEPING ) {
printf("Thread %s Sleeping \n", info.name);
flag = 1;
}
/**
if ( thr_st_ptr & JVMTI_THREAD_STATE_WAITING_FOR_NOTIFICATION ) {
printf("Thread %s Waiting for Notification \n", info.name);
flag = 1;
}
**/
if ( thr_st_ptr & JVMTI_THREAD_STATE_IN_OBJECT_WAIT ) {
printf("Thread %s is in Object Wait \n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_PARKED ) {
printf("Thread %s is Parked \n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER ) {
printf("Thread %s is blocked on monitor enter \n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_SUSPENDED ) {
printf("Thread %s is Suspended \n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_INTERRUPTED ) {
printf("Thread %s is Interrupted \n", info.name);
flag = 1;
}
if ( thr_st_ptr & JVMTI_THREAD_STATE_IN_NATIVE ) {
printf("Thread %s is in Native \n", info.name);
flag = 1;
}
if ( flag != 1 ) {
printf("Illegal value %d for Thread State\n", thr_st_ptr);
}
}
/* Get All Threads */
err = (*jvmti).GetAllThreads( &thr_count, &thr_ptr);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
if (err == JVMTI_ERROR_NONE && thr_count >= 1) {
int i = 0;
printf("Thread Count: %d\n", thr_count);
for ( i=0; i < thr_count; i++) {
/* Make sure the stack variables are garbage free */
(void)memset(&info1,0, sizeof(info1));
err1 = (*jvmti).GetThreadInfo(thr_ptr[i], &info1);
if (err1 != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err1);
describe(err1);
printf("\n");
}
printf("Running Thread#%d: %s, Priority: %d, context class loader:%s\n",
i+1,info1.name, info1.priority,(info1.context_class_loader == NULL ? ": NULL" : "Not Null"));
/* Every string allocated by JVMTI needs to be freed */
err2 = (*jvmti).Deallocate((unsigned char *)info1.name);
if (err2 != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err2);
describe(err2);
printf("\n");
}
}
}
/* Get Stack Trace */
err = (*jvmti).GetStackTrace( thr, 0, 5, frames, &count);
if (err != JVMTI_ERROR_NONE) {
printf("(GetThreadInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
describe(err);
printf("\n");
}
printf("Number of records filled: %d\n", count);
if (err == JVMTI_ERROR_NONE && count >=1) {
char *methodName;
methodName = "yet_to_call()";
char *declaringClassName;
jclass declaring_class;
int i=0;
printf("Exception Stack Trace\n");
printf("=====================\n");
printf("Stack Trace Depth: %d\n", count);
for ( i=0; i < count; i++) {
err = (*jvmti).GetMethodName( frames[i].method, &methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
err = (*jvmti).GetMethodDeclaringClass( frames[i].method, &declaring_class);
err = (*jvmti).GetClassSignature( declaring_class, &declaringClassName, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("at method %s() in class %s\n", methodName, declaringClassName);
}
}
}
printf("\n");
err = (*jvmti).Deallocate((unsigned char *)methodName);
err = (*jvmti).Deallocate((unsigned char *)declaringClassName);
}
} exit_critical_section(jvmti);
}
// VM Death callback
static void JNICALL callbackVMDeath(jvmtiEnv *jvmti_env, JNIEnv* jni_env)
{
enter_critical_section(jvmti); {
printf("Got VM Death event\n");
} exit_critical_section(jvmti);
}
/* Get a name for a jthread */
static void get_thread_name(jvmtiEnv *jvmti, jthread thread, char *tname, int maxlen)
{
jvmtiThreadInfo info;
jvmtiError error;
/* Make sure the stack variables are garbage free */
(void)memset(&info,0, sizeof(info));
/* Assume the name is unknown for now */
(void)strcpy(tname, "Unknown");
/* Get the thread information, which includes the name */
error = (*jvmti).GetThreadInfo( thread, &info);
check_jvmti_error(jvmti, error, "Cannot get thread info");
/* The thread might not have a name, be careful here. */
if ( info.name != NULL ) {
int len;
/* Copy the thread name into tname if it will fit */
len = (int)strlen(info.name);
if ( len < maxlen ) {
(void)strcpy(tname, info.name);
}
/* Every string allocated by JVMTI needs to be freed */
error = (*jvmti).Deallocate( (unsigned char *)info.name);
if (error != JVMTI_ERROR_NONE) {
printf("(get_thread_name) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, error);
describe(error);
printf("\n");
}
}
}
// VM init callback
static void JNICALL callbackVMInit(jvmtiEnv *jvmti_env, JNIEnv* jni_env, jthread thread)
{
enter_critical_section(jvmti); {
char tname[MAX_THREAD_NAME_LENGTH];
static jvmtiEvent events[] = { JVMTI_EVENT_THREAD_START, JVMTI_EVENT_THREAD_END };
int i;
jvmtiFrameInfo frames[5];
jvmtiError err, err1;
jvmtiError error;
jint count;
/* The VM has started. */
printf("Got VM init event\n");
get_thread_name(jvmti_env , thread, tname, sizeof(tname));
printf("callbackVMInit: %s thread\n", tname);
error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, (jthread)NULL);
check_jvmti_error(jvmti_env, error, "Cannot set event notification");
} exit_critical_section(jvmti);
}
/* JVMTI callback function. */
static jvmtiIterationControl JNICALL
reference_object(jvmtiObjectReferenceKind reference_kind,
jlong class_tag, jlong size, jlong* tag_ptr,
jlong referrer_tag, jint referrer_index, void *user_data)
{
combined_size = combined_size + size;
switch (reference_kind) {
case JVMTI_REFERENCE_CLASS:
num_class_refs = num_class_refs + 1;
break;
case JVMTI_REFERENCE_FIELD:
num_field_refs = num_field_refs + 1;
break;
case JVMTI_REFERENCE_ARRAY_ELEMENT:
num_array_refs = num_array_refs + 1;
break;
case JVMTI_REFERENCE_CLASS_LOADER:
num_classloader_refs = num_classloader_refs + 1;
break;
case JVMTI_REFERENCE_SIGNERS:
num_signer_refs = num_signer_refs + 1;
break;
case JVMTI_REFERENCE_PROTECTION_DOMAIN:
num_protection_domain_refs = num_protection_domain_refs + 1;
break;
case JVMTI_REFERENCE_INTERFACE:
num_interface_refs = num_interface_refs + 1;
break;
case JVMTI_REFERENCE_STATIC_FIELD:
num_static_field_refs = num_static_field_refs + 1;
break;
case JVMTI_REFERENCE_CONSTANT_POOL:
num_constant_pool_refs = num_constant_pool_refs + 1;
break;
default:
break;
}
return JVMTI_ITERATION_CONTINUE;
}
static void JNICALL callbackVMObjectAlloc(jvmtiEnv *jvmti_env, JNIEnv* jni_env,
jthread thread, jobject object, jclass object_klass, jlong size) {
char *methodName;
char *className;
char *declaringClassName;
jclass declaring_class;
jvmtiError err;
if (size > 50) {
err = (*jvmti).GetClassSignature(object_klass, &className, NULL);
if (className != NULL) {
printf("\ntype %s object allocated with size %d\n", className, (jint)size);
}
//print stack trace
jvmtiFrameInfo frames[5];
jint count;
int i;
err = (*jvmti).GetStackTrace(NULL, 0, 5, (jvmtiFrameInfo*)&frames, &count);
if (err == JVMTI_ERROR_NONE && count >= 1) {
for (i = 0; i < count; i++) {
err = (*jvmti).GetMethodName(frames[i].method, &methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE) {
err = (*jvmti).GetMethodDeclaringClass( frames[i].method, &declaring_class);
err = (*jvmti).GetClassSignature( declaring_class, &declaringClassName, NULL);
if (err == JVMTI_ERROR_NONE) {
printf("at method %s in class %s\n", methodName, declaringClassName);
}
}
}
}
//reset counters
combined_size = 0;
num_class_refs = 0;
num_field_refs = 0;
num_array_refs = 0;
num_classloader_refs = 0;
num_signer_refs = 0;
num_protection_domain_refs = 0;
num_interface_refs = 0;
num_static_field_refs = 0;
num_constant_pool_refs = 0;
err = (*jvmti).IterateOverObjectsReachableFromObject(object, &reference_object, NULL);
if ( err != JVMTI_ERROR_NONE ) {
printf("Cannot iterate over reachable objects\n");
}
printf("\nThis object has references to objects of combined size %d\n", (jint)combined_size);
printf("This includes %d classes, %d fields, %d arrays, %d classloaders, %d signers arrays,\n", num_class_refs, num_field_refs, num_array_refs, num_classloader_refs, num_signer_refs);
printf("%d protection domains, %d interfaces, %d static fields, and %d constant pools.\n\n", num_protection_domain_refs, num_interface_refs, num_static_field_refs, num_constant_pool_refs);
err = (*jvmti).Deallocate((unsigned char *)className);
err = (*jvmti).Deallocate((unsigned char *)methodName);
err = (*jvmti).Deallocate( (unsigned char *)declaringClassName);
}
}
jobject getLocalValue(JNIEnv *env,jvmtiEnv *jvmti_env, jthread thread, jint depth,
jvmtiLocalVariableEntry *table, int index) {
jobject result;
jint iVal;
jfloat fVal;
jdouble dVal;
jlong jVal;
jvmtiError tiErr;
jclass reflectClass;
jmethodID valueOf;
// Retreive
switch (table[index].signature[0]) {
case '[': // Array
case 'L': // Object
tiErr = (*jvmti_env).GetLocalObject( thread,depth, table[index].slot,&result);
break;
case 'J': // long
tiErr = (*jvmti_env).GetLocalLong( thread,depth, table[index].slot,&jVal);
break;
case 'F': // float
tiErr = (*jvmti_env).GetLocalFloat( thread,depth, table[index].slot,&fVal);
break;
case 'D': // double
tiErr = (*jvmti_env).GetLocalDouble(thread,depth, table[index].slot,&dVal);
break;
// Integer types
case 'I': // int
case 'S': // short
case 'C': // char
case 'B': // byte
case 'Z': // boolean
tiErr = (*jvmti_env).GetLocalInt(thread,depth, table[index].slot,&iVal);
break;
// error type
default:
return NULL;
}
if (tiErr != JVMTI_ERROR_NONE) {
return NULL;
}
// Box primitives
switch (table[index].signature[0]) {
case 'J': // long
reflectClass = (*env).FindClass("java/lang/Long");
valueOf = (*env).GetStaticMethodID(reflectClass, "valueOf","(J)Ljava/lang/Long;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, jVal);
break;
case 'F': // float
reflectClass = (*env).FindClass( "java/lang/Float");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf",
"(F)Ljava/lang/Float;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, fVal);
break;
case 'D': // double
reflectClass = (*env).FindClass("java/lang/Double");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(D)Ljava/lang/Double;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, dVal);
break;
// INTEGER TYPES
case 'I': // int
reflectClass = (*env).FindClass( "java/lang/Integer");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf",
"(I)Ljava/lang/Integer;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal);
break;
case 'S': // short
reflectClass = (*env).FindClass( "java/lang/Short");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(S)Ljava/lang/Short;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal);
break;
case 'C': // char
reflectClass = (*env).FindClass( "java/lang/Character");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(C)Ljava/lang/Character;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal);
break;
case 'B': // byte
reflectClass = (*env).FindClass( "java/lang/Byte");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(B)Ljava/lang/Byte;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal);
break;
case 'Z': // boolean
reflectClass = (*env).FindClass( "java/lang/Boolean");
valueOf = (*env).GetStaticMethodID( reflectClass, "valueOf","(Z)Ljava/lang/Boolean;");
result = (*env).CallStaticObjectMethod( reflectClass, valueOf, iVal);
break;
default: // jobject
break;
}
return result;
}
static void JNICALL callbackMethodEntry(jvmtiEnv *jvmti_env,JNIEnv* jni_env,jthread thread,jmethodID method)
{
unsigned char *methodName;
unsigned char *className;
unsigned char *declaringClassName;
jclass declaring_class;
jvmtiError err;
jvmtiThreadInfo info_ptr;
jint entry_count_ptr;
jvmtiLocalVariableEntry* table_ptr;
jint max_ptr;
jvmtiFrameInfo frames[2];
jint count;
int i;
//for get local variable value;
jobject objV =NULL;
jobject local;
jclass localClass;
jstring name;
jstring sig;
jstring gensig;
err = (*jvmti).GetStackTrace(NULL, 0, 1, (jvmtiFrameInfo*)&frames, &count);
if (err == JVMTI_ERROR_NONE && count >= 1)
{
for (i = 0; i < count; i++)
{
//err = (*jvmti).GetMethodName(frames.method, (char **)&methodName, NULL, NULL);
if (err == JVMTI_ERROR_NONE)
{
//err = (*jvmti).GetMethodDeclaringClass(frames[i].method, &declaring_class);
//err = (*jvmti).GetClassSignature(declaring_class, (char **)&declaringClassName, NULL);
err = (*jvmti).GetMaxLocals(frames[i].method,&max_ptr);
if(max_ptr >= 1)
{
//printf("max_ptr: %d\n",max_ptr);
//err = (*jvmti).GetMethodLocation(method,&start_location_ptr,&end_location_ptr);
//printf("strat_location_ptr:%s+++end_location_ptr:%s",start_location_ptr,end_location_ptr);
err = jvmti->GetLocalVariableTable(frames[i].method,&entry_count_ptr,&table_ptr);
//err = (*jvmti).GetLocalVariableTable(frames[i].method,&entry_count_ptr,&table_ptr);
if(err != JVMTI_ERROR_NONE)
{
//printf("(GetThreadGroupInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
//describe(err);
//printf("\n");
if(err == JVMTI_ERROR_ABSENT_INFORMATION)
{
//printf("No local variable information\n");
}else
{
// remove
//printf("(GetThreadGroupInfo) Error expected: %d, got: %d\n", JVMTI_ERROR_NONE, err);
}
}
else if(err == JVMTI_ERROR_NONE)
{
printf("variable count :%d\n",entry_count_ptr);
//printf("the first variable %s",variable_array_ptr[0]->name);
if(entry_count_ptr>0){
//jvmti->GetLocalObject(thread,0,1,objV);
localClass = (*jni_env).FindClass("com/Frame$LocalVariable");
if(err ==JVMTI_ERROR_INVALID_CLASS){
return;
}
name = (*jni_env).NewStringUTF(table_ptr[0].name);
sig = (*jni_env).NewStringUTF(table_ptr[0].signature);
if (table_ptr[0].generic_signature) {
objV =getLocalValue(jni_env,jvmti_env, thread, 0, table_ptr, 0);
gensig = (*jni_env).NewStringUTF(table_ptr[0].generic_signature);
local = (*jni_env).NewObject(localClass, method, name, sig, gensig, objV);
printf("local variable :%s\n",local);
} else {
gensig = NULL;
}
printf("Not found print variable.\n");
}
}
}
}
}
}
}
JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved){
printf("~~~~start onLoad function.~~~~\n");
static GlobalAgentData data;
jvmtiError error;
jint res;
jvmtiEventCallbacks callbacks;
(void)memset((void*)&data, 0, sizeof(data));
gdata = &data;
// We need to first get the jvmtiEnv* or JVMTI environment
res = (*jvm).GetEnv((void **) &jvmti, JVMTI_VERSION_1_0);
if (res != JNI_OK || jvmti == NULL)
{
// This means that the VM was unable to obtain this version of the
// JVMTI interface, this is a fatal error.
printf("ERROR: Unable to access JVMTI Version 1 (0x%x),"
" is your J2SE a 1.5 or newer version?"
" JNIEnv's GetEnv() returned %d\n",
JVMTI_VERSION_1, res);
}
// Here we save the jvmtiEnv* for Agent_OnUnload().
gdata->jvmti = jvmti;
(void)memset(&capa, 0, sizeof(jvmtiCapabilities));
capa.can_signal_thread = 1;
capa.can_get_owned_monitor_info = 1;
capa.can_generate_method_entry_events = 1;
capa.can_generate_exception_events = 1;
capa.can_generate_vm_object_alloc_events = 1;
capa.can_tag_objects = 1;
capa.can_access_local_variables = 1;//
capa.can_generate_method_entry_events = 1;//
capa.can_signal_thread = 1;//interrupt or stop thread
capa.can_suspend = 1;
error = (*jvmti).AddCapabilities( &capa);
if(error == JVMTI_ERROR_NOT_AVAILABLE)
{
printf("error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
check_jvmti_error(jvmti, error, "Unable to get necessary JVMTI capabilities.");
(void)memset(&callbacks, 0, sizeof(callbacks));
callbacks.VMInit = &callbackVMInit; // JVMTI_EVENT_VM_INIT
callbacks.VMDeath = &callbackVMDeath; // JVMTI_EVENT_VM_DEATH
callbacks.Exception = &callbackException;// JVMTI_EVENT_EXCEPTION
callbacks.VMObjectAlloc = &callbackVMObjectAlloc;// JVMTI_EVENT_VM_OBJECT_ALLOC
callbacks.MethodEntry = &callbackMethodEntry;//JVMTI_EVENT_METHOD_ENTRY
error = (*jvmti).SetEventCallbacks( &callbacks, (jint)sizeof(callbacks));
check_jvmti_error(jvmti, error, "Cannot set jvmti callbacks");
error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_VM_INIT, (jthread)NULL);
error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE,JVMTI_EVENT_VM_DEATH, (jthread)NULL);
//error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_OBJECT_ALLOC, (jthread)NULL);
error = (*jvmti).SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, (jthread)NULL);
check_jvmti_error(jvmti, error, "Cannot set event notification");
error = (*jvmti).CreateRawMonitor("agent data", &(gdata->lock));
check_jvmti_error(jvmti, error, "Cannot create raw monitor");
return JNI_OK;
}
JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm){
printf("this on Unload.. something todo.\n");
printf("release memery after running...");
}
