美文网首页Java技术升华
☆基于JVMTI的Agent实现

☆基于JVMTI的Agent实现

作者: 七寸知架构 | 来源:发表于2018-05-21 18:48 被阅读36次

    0 前言

    上一节《浅谈JPDA中JVMTI模块》讲解了JVMTI功能作用,本节我们将通过一个具体的例子,来阐述如何开发一个简单的 Agent 。Agent主要功能描述

    通过 C++ 编写,监听 JVMTI_EVENT_METHOD_ENTRY 事件,注册对应的回调函数来响应这个事件,来输出所有被调用函数名;

    1 Agent 设计实现

    具体实现都在 MethodTraceAgent 这个类里提供。按照顺序,它会处理环境初始化、参数解析、注册功能、注册事件响应,每个功能都被抽象在一个具体的函数里。

    MethodTraceAgent.h 代码如下

    #include "jvmti.h"
    
    class AgentException 
    {
     public:
        AgentException(jvmtiError err) {
            m_error = err;
        }
    
        char* what() const throw() { 
            return "AgentException"; 
        }
    
        jvmtiError ErrCode() const throw() {
            return m_error;
        }
    
     private:
        jvmtiError m_error;
    };
    
    
    class MethodTraceAgent 
    {
     public:
    
        MethodTraceAgent() throw(AgentException){}
    
        ~MethodTraceAgent() throw(AgentException);
    
        void Init(JavaVM *vm) const throw(AgentException);
            
        void ParseOptions(const char* str) const throw(AgentException);
    
        void AddCapability() const throw(AgentException);
            
        void RegisterEvent() const throw(AgentException);
        
        static void JNICALL HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method);
    
     private:
        static void CheckException(jvmtiError error) throw(AgentException)
        {
            // 可以根据错误类型扩展对应的异常,这里只做简单处理
            if (error != JVMTI_ERROR_NONE) {
                throw AgentException(error);
            }
        }
        
        static jvmtiEnv * m_jvmti;
        static char* m_filter;
    };
    

    MethodTraceAgent.cpp 代码如下

    #include <iostream>
    
    #include "MethodTraceAgent.h"
    #include "jvmti.h"
    
    using namespace std;
    
    jvmtiEnv* MethodTraceAgent::m_jvmti = 0;
    char* MethodTraceAgent::m_filter = 0;
    
    MethodTraceAgent::~MethodTraceAgent() throw(AgentException)
    {
        // 必须释放内存,防止内存泄露
        m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(m_filter));
    }
    
    void MethodTraceAgent::Init(JavaVM *vm) const throw(AgentException){
        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) const throw(AgentException)
    {
        if (str == 0)
            return;
        const size_t len = strlen(str);
        if (len == 0) 
            return;
    
        // 必须做好内存复制工作
        jvmtiError error;
        error = m_jvmti->Allocate(len + 1,reinterpret_cast<unsigned char**>(&m_filter));
        CheckException(error);
        strcpy(m_filter, str);
    
        // 可以在这里进行参数解析的工作
        // ...
    }
    
    void MethodTraceAgent::AddCapability() const throw(AgentException)
    {
        // 创建一个新的环境
        jvmtiCapabilities caps;
        memset(&caps, 0, sizeof(caps));
        caps.can_generate_method_entry_events = 1;
        
        // 设置当前环境
        jvmtiError error = m_jvmti->AddCapabilities(&caps);
        CheckException(error);
    }
      
    void MethodTraceAgent::RegisterEvent() const throw(AgentException)
    {
        // 创建一个新的回调函数
        jvmtiEventCallbacks callbacks;
        memset(&callbacks, 0, sizeof(callbacks));
        callbacks.MethodEntry = &MethodTraceAgent::HandleMethodEntry;
        
        // 设置回调函数
        jvmtiError error;
        error = m_jvmti->SetEventCallbacks(&callbacks, static_cast<jint>(sizeof(callbacks)));
        CheckException(error);
    
        // 开启事件监听
        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;
            
            // 获得方法对应的类
            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);
            
            // 根据参数过滤不必要的方法
            if(m_filter != 0){
                if (strcmp(m_filter, name) != 0)
                    return;
            }           
            cout << signature<< " -> " << name << "(..)"<< endl;
    
            // 必须释放内存,避免内存泄露
            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() << "]";
        }
    }
    

    Agent_OnLoad 函数 会在 Agent 被加载的时候创建这个类,并依次调用上述各个方法,从而实现这个 Agent 的功能。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;
    }
    

    Agent运行过程时序图,如图所示:


    Agent 时序图

    2 Agent 编译运行

    Agent 编译非常简单,和编译普通的动态链接库没有本质区别,只是需要将 JDK 提供的一些头文件包含进来

    Windows:

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

    Linux:

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

    提供了一个可运行的 Java 类 MethodTraceTest.java,代码如下:

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

    默认运行后输出,结果如下:


    无Agent默认输出

    现在,运行程序前告诉 Java 先加载编译出来的 Agent:

    java -agentlib:Agent=first MethodTraceTest
    

    带有Agent运行后输出,结果如下:


    带有Agent后输出

    当程序运行到到 MethodTraceTest 的 first 方法时,Agent 会输出这个事件。“ first ”是 Agent 运行的参数,如果不指定话,所有的进入方法的触发的事件都会被输出,如果把这个参数去掉再运行的话,会发现在运行 main 函数前,已经有非常基本的类库函数被调用了

    相关文章

      网友评论

        本文标题:☆基于JVMTI的Agent实现

        本文链接:https://www.haomeiwen.com/subject/yieqjftx.html