如何在MethodEntry回调中获取参数值 [英] How to get parameter values in a MethodEntry callback

查看:888
本文介绍了如何在MethodEntry回调中获取参数值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下java代码

public class Test {
    public void sayHello(String msg) {
         System.out.println(msg);
    }
}

new Test().sayHello("Bonjour");

我有一个附加到java的jvmti代理,我捕获函数调用。我想获取传递给我的方法的参数值(例如Bonjour)

I have a jvmti agent attached to java where I catch function calls. I want to get parameter value which was passed to my method (e.g. "Bonjour")

  static void JNICALL cbMethodEntry(jvmtiEnv *jvmti, 
                         JNIEnv* jni_env, jthread thread, jmethodID method) {
        // here I want to get a parameter value "Bonjour"
        // which was passed to my method sayHello("Bonjour")
  }

  jvmtiEventCallbacks    callbacks;
  callbacks.MethodEntry = &cbMethodEntry;

在回调本身中,我有一个线程和方法ID。

In the callback itself I have a thread and method ID.

查看jvmti.h标头我发现只有这个结构处理参数但没有值。

Looking into a jvmti.h header I found only this structure dealing with parameters but there are no values.

typedef struct {
    char* name;
    jvmtiParamKind kind;
    jvmtiParamTypes base_type;
    jboolean null_ok;
} jvmtiParamInfo;

如何从回调中获取参数值?

How can I get parameter values from my callback?

推荐答案

我正在从事类似的工作。以下是用C ++编写的两个代码示例。例1展示了如何使用 GetLocalVariableTable获得在MethodEntry回调局部变量 GetLocalObject 。示例2显示了如何使用BCI(字节码仪表)进行预处理。

I'm working on similar tasks. Here are two code examples written in C++. Example 1 shows how to get local variables in the MethodEntry callback using GetLocalVariableTable and GetLocalObject. Example 2 shows how to preform this using BCI (Bytecode Instrumentation).

示例1:

HandleMethodEntry是回调MethodEntry事件的方法。它记录有关方法参数的一些信息。 GetLocalVariableTable检索GetLocalObject使用的局部变量信息。深度为零的帧是当前帧,第一个参数位于插槽0.对于非静态帧,插槽0包含this对象。要从本机方法框架中检索this对象,您应该使用 GetLocalInstance 而不是 GetLocalObject

HandleMethodEntry is the call back method for MethodEntry event. It logs some information about the method's parameters. GetLocalVariableTable retrieves local variable information, which is used by GetLocalObject. Frame at depth zero is the current frame, the first parameter is at slot 0. For non-static frames, slot 0 contains the "this" object. To retrieve "this" object from native method frames, you should use GetLocalInstance instead of GetLocalObject.

签名的第一个字符是值类型。此示例仅检查作业的标记。对于字符串值,您可以使用 GetStringUTFChars 。可以在此处找到一个示例。

The first char of the signature is the value type. This example simply checks the tag of a jobject. For String values, you can use GetStringUTFChars. An example can be found here.

void JNICALL MethodTraceAgent::HandleMethodEntry(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID method)
{
try {
    jvmtiError error;
    jclass clazz;
    char* name;
    char* signature;

    // get declaring class of the method
    error = m_jvmti->GetMethodDeclaringClass(method, &clazz); 
    Errors::Check(error);
    // get the signature of the class
    error = m_jvmti->GetClassSignature(clazz, &signature, 0);
    Errors::Check(error);
    // get method name
    error = m_jvmti->GetMethodName(method, &name, NULL, NULL);
    Errors::Check(error);

    char tmp[1024]; 
    sprintf(tmp, "%s%s", signature, name); 
    if(pFilter->Match("method", tmp)) { // intrested method?
        char out[1024];
        jint param_size = 0;

        error = m_jvmti->GetArgumentsSize(method, &param_size);
        int line_len = sprintf(out, "method_entry: %s%s%, param_size:%d", signature, name, param_size);             

        // visit local variable
        jint entry_count = 0;
        jvmtiLocalVariableEntry *table_ptr = NULL;      
        jlocation cur_loc;

        // this call may return JVMTI_ERROR_ABSENT_INFORMATION, this error is avoided by initialize entry_count to 0 to escape the following for loop
        error = m_jvmti->GetLocalVariableTable(method, &entry_count, &table_ptr);
        error = m_jvmti->GetFrameLocation(thread, 0, NULL, &cur_loc);
        for(int j=0; j<min(param_size, entry_count); j++) {
            if(table_ptr[j].start_location > cur_loc) break;
            if(table_ptr[j].signature[0] == 'L') { //   fully-qualified-class
                jobject param_obj;
                jlong param_obj_tag = 0; 

                error = m_jvmti->GetLocalObject(thread, 0, table_ptr[j].slot, &param_obj); // frame at depth zero is the current frame                  
                m_jvmti->GetTag(param_obj, &param_obj_tag);
                if(param_obj_tag == 0) {
                    m_jvmti->SetTag(param_obj, theTag);
                    param_obj_tag = theTag;
                    ++theTag;
                }
                line_len += sprintf(out + line_len, ", param_obj_tag: %ld", param_obj_tag);
                //line_len += sprintf(out+line_len, ", slot:%d, start:%ld, cur:%ld, param:%s%s", table_ptr[j].slot, table_ptr[j].start_location, cur_loc, table_ptr[j].signature, table_ptr[j].name);

                jni->DeleteLocalRef(param_obj);
                m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].signature));
                m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].name));
                m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].generic_signature));
            }

        } 

        error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr));
        Errors::Check(error);


        // put to log list
        logList.push_back(out);

        printf("\r%-10d", logList.size());
    }

    // release resources
    error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(name));
    Errors::Check(error);
    error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature));
    Errors::Check(error);

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

示例2:

一个类似的问题,在MethodEntry中处理它甚至回调都可能有性能问题。你可以考虑BCI方法。 MTRACE_native_entry是一个注入到每个方法调用的最开始的本机方法。它是从MTrace的method_entry方法中调用的。

As mentioned in the answer of a similar question, dealing with it in MethodEntry even callback may have preformance problem. You can consider the BCI approach. MTRACE_native_entry is a native method injected to very beginning of every method call. It's called from MTrace's method_entry method.

在MTRACE_native_entry中,您需要在第2帧追溯到感兴趣的方法(执行本机方法的当前帧在第0帧)。 param trace的类似示例可以在GitHub中的另一个项目stackparam中找到。但是,这两种方法的性能差异未经过测试。

In MTRACE_native_entry, you need to track back to the intrested method at frame 2 (current frame of the executing native method is at frame 0). Similar example of param trace can be found in another project stackparam in GitHub. However, performence differences of these two methods is not tested.

此示例的未示出代码可以在jdk文档dir demo / jvmti / mtrace中找到。核心步骤是使用java_crw_demo在ClassFileLoadHook事件回调中注入method_entry。

Unshown code of this example can be found in the jdk document dir demo/jvmti/mtrace. The core step is to inject method_entry in ClassFileLoadHook event callback using java_crw_demo.

此示例还说明了如何获取param对象的字段值。

This example also shows how to get an param object's field value.

void JNICALL MethodTraceAgent::MTRACE_native_entry(JNIEnv *jni, jclass klass, jthread thread, jint cnum, jint mnum)
{

/* It's possible we get here right after VmDeath event, be careful */
if ( !pTheAgent->vmInitialized || pTheAgent->vmDead || thread == NULL)
    return;


jvmtiError error;
char out[1024];
int line_len = 0;
jvmtiFrameInfo frames[3];
jint cframe;

error = m_jvmti->GetStackTrace(thread, 0, 3, frames, &cframe);
Errors::Check(error);

if(cframe < 3) 
    return;

jmethodID method = frames[2].method;
jclass dec_cls;
char *mtd_name, *dec_cls_sig;

m_jvmti->GetMethodDeclaringClass(method, &dec_cls);
m_jvmti->GetClassSignature(dec_cls, &dec_cls_sig, NULL);
m_jvmti->GetMethodName(method, &mtd_name, NULL, NULL);


jboolean isNative = false;
m_jvmti->IsMethodNative(method, &isNative);
if(isNative)
    return;

line_len += sprintf(out + line_len, "m_en: %s%s", dec_cls_sig, mtd_name);

// operate tags
jint param_size = 0;
jint entry_count = 0;
jvmtiLocalVariableEntry *table_ptr = NULL;      

error = m_jvmti->GetArgumentsSize(method, &param_size);
error = m_jvmti->GetLocalVariableTable(method, &entry_count, &table_ptr);
Errors::Check(error);

line_len += sprintf(out + line_len, ", %d, %d", param_size, entry_count);

for(int j=0; j<min(param_size, entry_count); j++) {
    jobject param_obj = 0;

    if(j==0 && strcmp(table_ptr[0].name, "this") == 0) { // this instance
        error = m_jvmti->GetLocalInstance(thread, 2, &param_obj);
        if(thiso == 0) thiso = param_obj;
        else {
            line_len += sprintf(out + line_len, ", same_this: %d", jni->IsSameObject(thiso, param_obj));
        }

        jfieldID field = jni->GetFieldID(dec_cls, "a", "I");
        jint a = jni->GetIntField(param_obj, field);
        line_len += sprintf(out + line_len, ", a: %d", a);

        Errors::Check(error);
    }
    else if(table_ptr[j].signature[0] == 'L') { // object
        error = m_jvmti->GetLocalObject(thread, 2, table_ptr[j].slot, &param_obj); // frame at depth zero is the current frame      
        Errors::Check(error);
    }

    if(param_obj != 0) {

        //line_len += sprintf(out + line_len, ", modi: %d, this: %d, same: %d", modied, param_obj, jni->IsSameObject(param_obj, modied));

        jlong param_obj_tag = 0; 
        m_jvmti->GetTag(param_obj, &param_obj_tag);
        if(param_obj_tag == 0) {
            error = m_jvmti->SetTag(param_obj, pTheAgent->ctag);
            Errors::Check(error);
            param_obj_tag = pTheAgent->ctag;
            ++pTheAgent->ctag;
        }
        line_len += sprintf(out + line_len, ", param_obj_tag: %ld", param_obj_tag);
        //line_len += sprintf(out+line_len, ", slot:%d, start:%ld, cur:%ld, param:%s%s", table_ptr[j].slot, table_ptr[j].start_location, cur_loc, table_ptr[j].signature, table_ptr[j].name);

        jni->DeleteLocalRef(param_obj);
        m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].signature));
        m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].name));
        m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr[j].generic_signature));
    }

}

error = m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(table_ptr));
Errors::Check(error);


m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(dec_cls_sig));
m_jvmti->Deallocate(reinterpret_cast<unsigned char*>(mtd_name));



logList.push_back(out);

}

用于注入的类方法:

public class MTrace {

private static int engaged = 0;

/* At the very beginning of every method, a call to method_entry()
 *     is injected.
 */

private static native void _method_entry(Object thread, int cnum, int mnum);
public static void method_entry(int cnum, int mnum)
{
    if ( engaged != 0 ) {
        _method_entry(Thread.currentThread(), cnum, mnum);
    }
}

/* Before any of the return bytecodes, a call to method_exit()
 *     is injected.
 */

private static native void _method_exit(Object thread, int cnum, int mnum);
public static void method_exit(int cnum, int mnum)
{
    if ( engaged != 0 ) {
       _method_exit(Thread.currentThread(), cnum, mnum);
    }
}
}

请注意,这两个例子是为测试目的而编写,并未检查jvmti函数的所有返回值。还可能存在其他一些问题。

Note that these two examples is written for test purpose, not all the return value of jvmti functions are checked. Some other problems may also exist.

这篇关于如何在MethodEntry回调中获取参数值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆