基于JVMTI的Agent实现

2,811 阅读3分钟

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 函数前,已经有非常基本的类库函数被调用了