JVMTI 和 Agent 實現(xiàn)(visual studio)

簡介

VMTI(JVM Tool Interface)是 Java 虛擬機所提供的 native 編程接口擂达,是 JVMPI(Java Virtual Machine Profiler Interface)

Agent 的工作過程

Agent 是在 Java 虛擬機啟動之時加載的赠橙,這個加載處于虛擬機初始化的早期馍忽,在這個時間點上:

所有的 Java 類都未被初始化赐劣;
所有的對象實例都未被創(chuàng)建萝究;
因而,沒有任何 Java 代碼被執(zhí)行晒喷;

但在這個時候,我們已經(jīng)可以:

操作 JVMTI 的 Capability 參數(shù)凉敲;
使用系統(tǒng)參數(shù);

官方文檔(jdk111):

動態(tài)庫被加載之后果复,虛擬機會先尋找一個 Agent 入口函數(shù):

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

這個函數(shù)中独柑,虛擬機傳入了一個 JavaVM 指針车酣,以及命令行的參數(shù)者春。
通過 JavaVM,我們可以獲得 JVMTI 的指針,并獲得 JVMTI 函數(shù)的使用能力拥刻,所有的 JVMTI 函數(shù)都通過這個 jvmtiEnv 獲取,不同的虛擬機實現(xiàn)提供的函數(shù)細節(jié)可能不一樣,但是使用的方式是統(tǒng)一的霜运。

jvmtiEnv *jvmti;
...
(*jvm)->GetEnv(jvm, &jvmti, JVMTI_VERSION_1_0);

JVMTI的啟動方式

JVMTI有兩種啟動方式池摧,第一種是隨java進程啟動時,自動載入共享庫。另一種方式是蹈丸,java運行時荸百,通過attach api動態(tài)載入光绕。

方式1的實現(xiàn)方式是通過在java啟動時傳遞一個特殊的option:

    java -agentlib:<agent-lib-name>=<options> Sample
    注意停蕉,這里的共享庫路徑是環(huán)境變量路徑驯绎,例如 java -agentlib:foo=opt1,opt2册着,java啟動時會從linux的LD_LIBRARY_PATH或windows的PATH環(huán)境變量定義的路徑處裝載foo.so或foo.dll拴孤,找不到則拋異常
    java -agentpath:<path-to-agent>=<options> Sample
    這是以絕對路徑的方式裝載共享庫,例如 java -agentpath:/home/admin/agentlib/foo.so=opt1,opt2

    windows下:的動態(tài)鏈接生成為dll而不是.so文件,稍后主要使用windows下vs編譯共享庫文件

方式2的實現(xiàn)方式是通過attach api司顿,這是一套純java的api,它負責(zé)動態(tài)地將dynamic module attach到指定進程id的java進程內(nèi)并觸發(fā)回調(diào):

import java.io.IOException;
import com.sun.tools.attach.VirtualMachine;
public class VMAttacher {
   public static void main(String[] args) throws Exception {
    // args[0]為java進程id
        VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]);
        // args[1]為共享庫路徑朦拖,args[2]為傳遞給agent的參數(shù)
        virtualMachine.loadAgentPath(args[1], args[2]);
        virtualMachine.detach();
   }
}
  • Attach API位于$JAVA_HOME/lib/tools.jar,所以在編譯時作喘,需要將這個jar放入classpath
javac -cp $JAVA_HOME/lib/tools.jar VMAttacher.java

簡單開發(fā)JVMTI Agent

下面一個簡單的例子理疙,闡述如何開發(fā)一個簡單的 Agent 砖顷。這個 Agent 是通過 C++ 編寫的贰锁,通過監(jiān)聽 JVMTI_EVENT_METHOD_ENTRY 事件赃梧,
注冊對應(yīng)的回調(diào)函數(shù)來響應(yīng)這個事件,來輸出所有被調(diào)用函數(shù)名豌熄。

Agent.cpp

#include <iostream>

#include "MethodTraceAgent.h"
#include <jvmti.h>

using namespace std;

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
{
    cout << "Agent_OnLoad(" << vm << ")" << endl;
    try{
        
        MethodTraceAgent* agent = new MethodTraceAgent();
        agent->Init(vm);
        agent->ParseOptions(options);
        agent->AddCapability();
        agent->RegisterEvent();
        
    } catch (AgentException& e) {
        cout << "Error when enter HandleMethodEntry: " << e.what() << " [" << e.ErrCode() << "]";
        return JNI_ERR;
    }
    
    return JNI_OK;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)
{
    cout << "Agent_OnUnload(" << vm << ")" << endl;
}

MethodTraceAgent.h

#include "<jvmti.h>"
#include <string>
using namespace std;
class AgentException 
{
 public:
    AgentException(jvmtiError err) {
        m_error = err;
    }

    string what() const throw() { 
        return "AgentException"; 
    }

    jvmtiError ErrCode() const throw() {
        return m_error;
    }

 private:
    jvmtiError m_error;
};


class MethodTraceAgent 
{
 public:

    MethodTraceAgent() {}

    ~MethodTraceAgent() ;

    void Init(JavaVM *vm) ;
        
    void ParseOptions(const char* str) ;

    void AddCapability() ;
        
    void RegisterEvent() ;
    
    static void JNICALL HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method);

 private:
    static void CheckException(jvmtiError error) 
    {
        // 可以根據(jù)錯誤類型擴展對應(yīng)的異常授嘀,這里只做簡單處理
        if (error != JVMTI_ERROR_NONE) {
            throw AgentException(error);
        }
    }
    
    static jvmtiEnv * m_jvmti;
    static char* m_filter;
};

MethodTraceAgent.cpp

#include <iostream>
#include <string.h>
#include <stdio.h> 
#include "MethodTraceAgent.h"
#include "<vmti.h>"

using namespace std;

jvmtiEnv* MethodTraceAgent::m_jvmti = 0;
char* MethodTraceAgent::m_filter = 0;

MethodTraceAgent::~MethodTraceAgent()
{
    // 必須釋放內(nèi)存,防止內(nèi)存泄露
    m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(m_filter));
}

void MethodTraceAgent::Init(JavaVM *vm) {
    jvmtiEnv *jvmti = 0;
    jint ret = (vm)->GetEnv(reinterpret_cast<void**>(&jvmti), JVMTI_VERSION_1_0);
    if (ret != JNI_OK || jvmti == 0) {
        throw AgentException(JVMTI_ERROR_INTERNAL);
    }
    m_jvmti = jvmti;
}

void MethodTraceAgent::ParseOptions(const char* str)
{
    if (str == 0)
        return;
    const size_t len = strlen(str);
    if (len == 0) 
        return;

    // 必須做好內(nèi)存復(fù)制工作
    jvmtiError error;
    error = m_jvmti->Allocate(len + 1,reinterpret_cast<unsigned char**>(&m_filter));
    CheckException(error);
    strcpy(m_filter, str);

    // 可以在這里進行參數(shù)解析的工作
    // ...
}

void MethodTraceAgent::AddCapability()
{
    // 創(chuàng)建一個新的環(huán)境
    jvmtiCapabilities caps;
    memset(&caps, 0, sizeof(caps));
    caps.can_generate_method_entry_events = 1;
    
    // 設(shè)置當(dāng)前環(huán)境
    jvmtiError error = m_jvmti->AddCapabilities(&caps);
    CheckException(error);
}
  
void MethodTraceAgent::RegisterEvent()
{
    // 創(chuàng)建一個新的回調(diào)函數(shù)
    jvmtiEventCallbacks callbacks;
    memset(&callbacks, 0, sizeof(callbacks));
    callbacks.MethodEntry = &MethodTraceAgent::HandleMethodEntry;
    
    // 設(shè)置回調(diào)函數(shù)
    jvmtiError error;
    error = m_jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks)));
    CheckException(error);

    // 開啟事件監(jiān)聽
    error = m_jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, 0);
    CheckException(error);
}

void JNICALL MethodTraceAgent::HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method)
{
    try {
        jvmtiError error;
        jclass clazz;
        char* name;
        char* signature;
        
        // 獲得方法對應(yīng)的類
        error = m_jvmti->GetMethodDeclaringClass(method, &clazz);
        CheckException(error);
        // 獲得類的簽名
        error = m_jvmti->GetClassSignature(clazz, &signature, 0);
        CheckException(error);
        // 獲得方法名字
        error = m_jvmti->GetMethodName(method, &name, NULL, NULL);
        CheckException(error);
        
        // 根據(jù)參數(shù)過濾不必要的方法
        if(m_filter != 0){
            if (strcmp(m_filter, name) != 0)
                return;
        }           
        cout << signature<< " -> " << name << "(..)"<< endl;

        // 必須釋放內(nèi)存锣险,避免內(nèi)存泄露
        error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(name));
        CheckException(error);
        error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature));
        CheckException(error);

    } catch (AgentException& e) {
        cout << "Error when enter HandleMethodEntry: " << e.what() << " [" << e.ErrCode() << "]";
    }
}

創(chuàng)建一個java程序

public class MethodTraceTest {

    public static void main(String[] args){
        MethodTraceTest test = new MethodTraceTest();
        test.first();
        test.second();
    }
    
    public void first(){
        System.out.println("=> test first()");
    }
    
    public void second(){
        System.out.println("=> test second()");
    }
}

運行時序圖:
圖片.png

操作流程

  1. 編譯java程序生成class文件

javac MethodTraceTest.java

  1. 編譯Agent 動態(tài)鏈接庫蹄皱,需要將 JDK 提供的一些頭文件包含進來

Windows:

cl /EHsc -I${JAVA_HOME}/include/ -I${JAVA_HOME}/include/win32 -LD MethodTraceAgent.cpp Agent.cpp -FeAgent.dll

Linux:

g++ -I${JAVA_HOME}/include/ -I${JAVA_HOME}/include/linux  MethodTraceAgent.cpp Main.cpp -fPIC -shared -o libagent.so

注意需要有c++環(huán)境,如果是windows環(huán)境使用vs 可使用cl

Windows下vs2019 c++ toolset
注意編譯動態(tài)庫操作系統(tǒng)類型芯肤,64bit下使用工具"x64 Native Tools Command Prompt for VS 2019"(可在安裝vs后windows搜素框搜索)巷折,具體可參考上面官方文檔

執(zhí)行

上面編譯動態(tài)庫后,win下為dll文件崖咨、linux為so文件锻拘,將編譯后的class文件拷貝到動態(tài)文件下,執(zhí)行命令

java -agentlib:Agent=first -cp ./ MethodTraceTest
結(jié)果如下說明成功監(jiān)聽到虛擬機相應(yīng)事件

Agent_OnLoad(000000006990F4A0)
LMethodTraceTest; -> first(..)
=> Call first()
=> Call second()
Agent_OnUnload(000000006990F4A0)
  • 至此算是初步了解jvmti击蹲,后續(xù)再詳解另一種方式以及更多jvmti相關(guān)操作

第二種Agent實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末署拟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子歌豺,更是在濱河造成了極大的恐慌推穷,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件世曾,死亡現(xiàn)場離奇詭異缨恒,居然都是意外死亡,警方通過查閱死者的電腦和手機轮听,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門骗露,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人血巍,你說我怎么就攤上這事萧锉。” “怎么了述寡?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵柿隙,是天一觀的道長。 經(jīng)常有香客問我鲫凶,道長禀崖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任螟炫,我火速辦了婚禮波附,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己掸屡,他們只是感情好封寞,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著仅财,像睡著了一般狈究。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盏求,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天抖锥,我揣著相機與錄音,去河邊找鬼碎罚。 笑死宁改,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的魂莫。 我是一名探鬼主播还蹲,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼耙考!你這毒婦竟也來了谜喊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤倦始,失蹤者是張志新(化名)和其女友劉穎斗遏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鞋邑,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡诵次,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了枚碗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逾一。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖肮雨,靈堂內(nèi)的尸體忽然破棺而出遵堵,到底是詐尸還是另有隱情,我是刑警寧澤怨规,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布陌宿,位于F島的核電站,受9級特大地震影響波丰,放射性物質(zhì)發(fā)生泄漏壳坪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一掰烟、第九天 我趴在偏房一處隱蔽的房頂上張望爽蝴。 院中可真熱鬧扩灯,春花似錦、人聲如沸霜瘪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颖对。三九已至,卻和暖如春磨隘,著一層夾襖步出監(jiān)牢的瞬間缤底,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工番捂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留个唧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓设预,卻偏偏與公主長得像徙歼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鳖枕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容