0 foreword

In the previous section, JVMTI module in JPDA explained the functions of JVMTI. In this section, we will elaborate how to develop a simple Agent through a specific example. Main Function description of Agent:

Written in C++, listen for the JVMTI_EVENT_METHOD_ENTRY event, register the corresponding callback function to respond to this event, to output all called function names;

1 Agent design and implementation

The implementation is provided in the MethodTraceAgent class. In order, it handles environment initialization, parameter resolution, registration functions, and registration event responses, each abstracted into a concrete function.

The code for methodTraceAgent.h is as follows:

#include "jvmti.h"

class AgentException 
{
 public:
	AgentException(jvmtiError err) {
		m_error = err;
	}

	char* what() const throw() { 
		return "AgentException"; 
	}

	jvmtiError ErrCode() const throw() {
		returnm_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) {// You can extend the corresponding exception according to the error typeif(error ! = JVMTI_ERROR_NONE) { throw AgentException(error); } } static jvmtiEnv * m_jvmti; static char* m_filter; };Copy the code

The code for methodTraceAgent.cpp is as follows:

#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) {// Memory must be freed, 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); // Can be used to parse the parameters //... } void MethodTraceAgent: : AddCapability (const) throw (AgentException) {/ / create a new environment 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) {/ / create a new callback function jvmtiEventCallbacks callbacks. memset(&callbacks, 0, sizeof(callbacks)); callbacks.MethodEntry = &MethodTraceAgent::HandleMethodEntry; // Set the callback function 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); // Filter unnecessary methods based on parametersif(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() << "]"; }}Copy the code

The Agent_OnLoad function creates this class when the Agent is loaded and calls each of the above methods in turn to implement the Agent’s functionality. Agent. CPP code is as follows:

#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;
}
Copy the code

Time sequence diagram of Agent operation process, as shown in the figure:

2 The Agent is compiled and running

Agent compilation is very simple, similar to building a normal dynamic link library, except that you include some header files provided by the JDK.

Windows:

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

Linux:

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

Provides a runnable Java class, MethodTraceTest. Java, with the following code:

public class MethodTraceTest {

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

The output is as follows:

Now tell Java to load the compiled Agent before running the program:

java -agentlib:Agent=first MethodTraceTest
Copy the code

After the Agent runs, the output is as follows:

The Agent prints this event when the program runs to the first method on MethodTraceTest. “First” is the parameter used by the Agent to run. If not specified, all events triggered by the entry method will be output. If this parameter is removed and run again, you will find that very basic library functions have been called before the main function is run.