C++调用jvm.dll未导出GetPhase函数访问异常排查
问题排查与修复方案
你的代码触发访问异常是三个核心问题叠加导致的:
1. 地址计算存在指针运算逻辑错误
当前地址计算代码:
func* f = (func*) (DWORD_PTR) hMod + 0x086059b8;
存在C指针运算的典型优先级错误:强转操作优先级高于加法,代码实际是先把模块基址hMod转为func*类型指针,再执行加法。C中对类型化指针加整数时,偏移量会自动乘以指针指向类型的大小:64位环境下函数指针占8字节,最终计算出的地址实际为hMod + 0x086059b8 * 8,完全跳离了目标函数所在的内存段,直接执行非法内存的指令触发异常。
正确的地址计算需要先按字节粒度计算偏移,再转为函数指针:
// 先转uint8_t*(单字节指针)做字节级偏移,再转对应函数类型 using GetPhaseFn = jvmtiError(*)(void*, jvmtiPhase*); GetPhaseFn get_phase = (GetPhaseFn)((uint8_t*)hMod + 0x086059b8);
2. 函数调用约定、参数传递完全不匹配
从OpenJDK源码可见,你要调用的GetPhase是JvmtiEnv类的成员函数,不是全局函数:
- Windows x64环境下C++类成员函数默认使用
__thiscall约定,第一个隐式参数为JvmtiEnv实例的this指针(存在RCX寄存器),第二个参数才是你要传入的jvmtiPhase*输出指针 - 你当前代码仅传入一个参数,且把空指针
phase_ptr错当this指针传入,函数内部访问实例成员时会直接访问非法地址 - 你定义的
phase_ptr初始值为nullptr,即使参数顺序正确,函数向空地址写入阶段值也会触发内存写违例
正确的调用逻辑需要先拿到当前环境对应的JvmtiEnv实例地址作为this参数传入,同时为phase值分配有效内存:
jvmtiPhase current_phase; // 第一个参数传JvmtiEnv实例指针,第二个参数传phase值的存储地址 jvmtiError err = get_phase(jvmti_env_instance, ¤t_phase);
3. 硬编码偏移存在有效性问题
你从Ghidra拿到的0x086059b8地址不能直接用:
- Ghidra加载PE文件时默认使用PE头记录的默认镜像基址计算绝对地址,你需要先减去jvm.dll的默认镜像基址,得到相对加载基址的RVA偏移,才能和运行时拿到的
hMod相加得到正确的函数地址 - 该偏移仅和你当前分析的特定JDK版本、特定构建(32/64位、release/debug)绑定,换JDK版本、甚至同版本不同更新包偏移都会发生变化,硬编码的兼容性极差。
内容的提问来源于stack exchange,提问作者Kaspek
相关产品推荐
相关产品推荐

