上一节 《浅谈JPDA中JVMTI模块》 讲解了JVMTI功能作用,本节我们将通过一个具体的例子,来阐述如何开发一个简单的 Agent 。 Agent主要功能描述 :
通过 C++ 编写,监听 JVMTI_EVENT_METHOD_ENTRY 事件,注册对应的回调函数来响应这个事件,来输出所有被调用函数名;
具体实现都在 MethodTraceAgent 这个类里提供。按照顺序, 它会处理环境初始化、参数解析、注册功能、注册事件响应 ,每个功能都被抽象在一个具体的函数里。
#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; };
#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 编译非常简单,和编译普通的动态链接库没有本质区别, 只是需要将 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()"); } }
默认运行后输出,结果如下:
现在,运行程序前告诉 Java 先加载编译出来的 Agent:
java -agentlib:Agent=first MethodTraceTest
带有Agent运行后输出,结果如下:
当程序运行到到 MethodTraceTest 的 first 方法时,Agent 会输出这个事件。“ first ”是 Agent 运行的参数,如果不指定话,所有的进入方法的触发的事件都会被输出, 如果把这个参数去掉再运行的话,会发现在运行 main 函数前,已经有非常基本的类库函数被调用了 。