如何在 MethodEntry 回调中获取参数值

2023-12-25

我有以下java代码

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

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

我有一个附加到 java 的 jvmti 代理,我可以在其中捕获函数调用。我想获取传递给我的方法的参数值(例如“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。

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

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

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


我正在从事类似的任务。下面是两个用 C++ 编写的代码示例。示例 1 显示如何使用 MethodEntry 回调获取局部变量获取本地变量表 https://docs.oracle.com/javase/9/docs/specs/jvmti.html#GetLocalVariableTable and 获取本地对象 https://docs.oracle.com/javase/9/docs/specs/jvmti.html#GetLocalObject。示例 2 显示如何使用 BCI(字节码检测)执行此操作。

示例1:

HandleMethodEntry是MethodEntry事件的回调方法。它记录有关方法参数的一些信息。 GetLocalVariableTable 检索局部变量信息,该信息由 GetLocalObject 使用。深度为零的帧是当前帧,第一个参数位于槽 0。对于非静态帧,槽 0 包含“this”对象。要从本机方法框架检索“this”对象,您应该使用获取本地实例 https://docs.oracle.com/javase/9/docs/specs/jvmti.html#GetLocalInstance代替获取本地对象 https://docs.oracle.com/javase/9/docs/specs/jvmti.html#GetLocalObject.

签名的第一个字符是值类型 https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#the_value_type。此示例仅检查作业对象的标签。对于字符串值,您可以使用获取字符串UTFChars https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#GetStringUTFChars。可以找到一个例子here https://github.com/neuroo/java-runtime-tracer/blob/master/src/agent.cpp.

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:

正如在回答中提到的类似的问题 https://stackoverflow.com/questions/7041177/jvmti-how-to-get-the-value-of-a-method-parameter-from-callback,在MethodEntry中处理它甚至回调也可能有性能问题。您可以考虑 BCI 方法。 MTRACE_native_entry 是注入到每个方法调用的最开始的本机方法。它是从 MTrace 的 method_entry 方法调用的。

在 MTRACE_native_entry 中,您需要回溯到第 2 帧处的感兴趣的方法(正在执行的本机方法的当前帧位于第 0 帧)。类似的参数跟踪示例可以在 GitHub 中的另一个项目 stackparam 中找到。然而,这两种方法的性能差异尚未经过测试。

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

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

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 函数的所有返回值。还可能存在其他一些问题。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 MethodEntry 回调中获取参数值 的相关文章

随机推荐

  • 在 scipy 中创建稀疏矩阵时,覆盖而不是添加重复的三元组

    在 scipy 中 要从三重格式数据 行 列和数据数组 创建稀疏矩阵 默认行为是对所有重复项的数据值求和 我可以更改此行为以覆盖 或不执行任何操作 吗 例如 import scipy sparse as sparse rows 0 0 co
  • React-native expo FileSystem.downloadAsync 损坏的图像

    我们有 RN 应用程序和服务器来存储图像 尝试根据用户从服务器下载一堆图像 并将它们显示在 RN 应用程序中 有时图像会被损坏 或者根本不显示 所以我必须重新加载应用程序才能使其工作 这甚至在很多时候都不起作用 或者清除存储并一次又一次下载
  • Jquery 和 JSF 找到 的特定 id [重复]

    这个问题在这里已经有答案了 我正在寻找匹配id for 输入文本在 JSF 中使用 Jquery 但它不起作用 我不知道为什么 JSF CODE
  • 无法更改 RVM 默认值

    我在更改默认 ruby 时遇到问题 运行 OS X Lion macbook work rvm list rvm rubies ruby 1 9 2 p290 x86 64 ruby 1 9 2 p318 x86 64 如您所见 1 9 2
  • 如何在单行上打印DataFrame

    With import pandas as pd df pd read csv pima data csv print df head 2 打印内容会自动格式化为多行 num preg glucose conc diastolic bp t
  • 是否可以知道用户是否正在文本字段中输入或删除字符?

    我正在使用文本字段委托方法 shouldChangeCharactersInRange 我想知道是否有任何方法可以判断用户是否正在删除字符或键入字符 有人知道吗 谢谢 BOOL textField UITextField textField
  • 如何在 macOS Mojave 上设置环境变量?

    我正在 macOS Mojave 上安装 THERMUS ROOT 热模型包 步骤之一 解压缩后 如下 设置环境变量 THERMUS 以指向包含 THERMUS 代码的顶级目录 我真的不知道该怎么办 我在 stackoverflow 上看过
  • 为什么 Javascript 中的 charCodeAt 的行为似乎与 PHP 的 chr 不同?我正在尝试实现base64

    我在 php 代码中使用了按位运算符 它返回 base64 中的解码字符串 我想实现与 javascript 相同的 php 代码 据我所知chr https www php net manual en function chr php相当
  • 单击后退按钮强制刷新页面

    我有 2 个 asp net 页面 页面 A 和页面 B 单击页面 A 上的链接时 用户将被重定向到页面 B 在页面 B 上时 如果用户单击浏览器的后退按钮 我需要强制调用页面 A 的页面刷新 我如何实现这个功能 注意 代码需要兼容不同浏览
  • 如何检查麦克风是否已插入 iPhone?

    我在 AVFoundation 框架中使用 AVAudioRecorder 我想知道如何检查麦克风设备是否已插入 在 iPod Touch 上 BOOL inputAvailable AVAudioSession sharedInstanc
  • 将 WCF 请求记录到数据库

    我想将 WCF 服务项目中的每个请求 xml 消息记录到数据库中 请建议我哪种方法是最好的和首选的方法 1 使用idispatchmessageinspector接口 http msdn microsoft com en us librar
  • DC.js 数据表的自定义文本过滤器

    我正在构建一个仪表板来显示一些数据 我有几个图表和一个列出所有数据的表格 我正在尝试添加搜索功能来过滤图表 我有很多公司以及每个公司的一些数据 因此 如果我搜索 Appl 则数据表中只会列出以 Appl 开头的公司 并且图表将反映这一点 当
  • 如何通过另一个 data.frame 中的某些列合并行名?

    我想知道是否有更好的方法将两个 data frames 放入一个中 将 rowname 视为一列 然后按此列与其他一些 data frame 合并 我知道我可以执行以下操作 df1 rn lt row df1 all lt merge df
  • 比较两个 pandas 数据框

    我有两个 pandas 数据框定义如下 data orig 1 Bob 3 0 2 Sam 2 0 3 Jane 4 0 columns ID Name GPA data new 1 Bob 3 2 3 Jane 3 9 4 John 1
  • 如何在意外挂断后恢复 git pull/clone?

    我对某个存储库 主要是 github 的 执行 git pull clone 有时几分钟后下载就会崩溃 这是一个日志 remote Counting objects 2284 done remote Compressing objects
  • 是否有最好的信用卡加密 .NET 算法?

    互联网System Security Cryptography命名空间有一个相当令人困惑的算法集合 我可以使用它们来加密信用卡详细信息 哪个是最好的 对于相对较短的字符串来说 它显然需要安全 编辑 我在英国 据我所知 只要不存储三位数的 C
  • 如何存储/加载大型 C++ 容器

    我想知道如何存储 C 容器以实现高效加载 例如如何存储非常大的整数向量 我知道我可以将它们保存在文件中 并从该数据中创建新的向量 include
  • IIS7,web.config 仅允许静态文件处理程序位于网站的 /uploads 目录中

    如果我认为可能的话 如何修改我的 web config 以使子目录静态 里面的文件将仅作为静态文件处理 即使其名称是 aspx 或其他名称 谢谢 将以下内容添加到web config文件位于包含您希望仅作为静态内容提供的文件的文件夹中
  • CMake 脚本的命名约定是什么?

    我知道 CMake 使用标准名称 CMakeLists txt 和add subdirectory用于在构建过程中直接调用脚本的函数 我有一些 CMake 代码 用于将文件转换为 C 字符串 然后可以使用这些代码将其烘焙到程序中 inclu
  • 如何在 MethodEntry 回调中获取参数值

    我有以下java代码 public class Test public void sayHello String msg System out println msg new Test sayHello Bonjour 我有一个附加到 ja