You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

32位旧应用DLL开发:Release编译获取调用参数地址异常

问题描述

我正在为一款固定不变的32位旧应用开发DLL,其中的钩子函数代码如下:

void PacketHandler::CMSG_Hook(CDataStore* data)
{
   CDataStore** pointerToPacket = &data;
   printf("%p\n", pointerToPacket);
   MessageBoxA(0,"debug pause", 0, 0);
   ...
}

Debug编译参数

使用以下Debug编译参数时,控制台输出符合预期(调用参数地址位于当前栈帧外):

/JMC /permissive- /GS /analyze- /W3 /Zc:wchar_t /I"include" /I"../common/include" /I"../common/include/json" /I"../common/include/json/nlohmann" /I"../lua/lua-5.4.6" /I"../Microsoft DirectX SDK2010/include" /ZI /Gm- /Od /Zc:inline /fp:precise /D "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" /D "LEGACY_FUNCTIONS_SUPPORTED" /D "WIN32" /D "INJECTED_EXPORTS" /D "_WINDOWS" /D "_USRDLL" /D "_WINDLL" /D "_MBCS" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /MT /std:c++20 /FC /EHsc /nologo /diagnostics:column

Release编译参数

但使用以下Release编译参数时,pointerToPacket指向的是当前栈帧内的内存地址,无法获取调用该方法时传入参数的真实地址:

/permissive- /GS /GL /analyze- /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Zc:inline /fp:precise /D "WIN32" /D "_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS" /D "NDEBUG" /D "INJECTED_EXPORTS" /D "_WINDOWS" /D "_USRDLL" /D "_WINDLL" /D "_MBCS" /errorReport:prompt /WX- /Zc:forScope /Gd /Oy- /Oi /MT /std:c++20 /FC /EHsc /nologo /diagnostics:column 

原因与解决方法

原因

Release模式下开启了/O2(最高等级优化)和/Oi(内建函数优化),编译器会对函数参数进行激进优化:比如将参数从调用者栈帧移到当前函数栈帧的临时位置,或直接放入寄存器,导致&data获取的是优化后的本地地址,而非调用者传入的原始参数地址。

解决方法

  1. 针对函数禁用优化
    给目标函数添加MSVC的优化禁用属性,强制编译器不对该函数的参数做栈帧优化:
__declspec(noinline) void __attribute__((optnone)) PacketHandler::CMSG_Hook(CDataStore* data)
{
   CDataStore** pointerToPacket = &data;
   printf("%p\n", pointerToPacket);
   MessageBoxA(0,"debug pause", 0, 0);
   ...
}

__declspec(noinline)防止函数被内联,__attribute__((optnone))禁用该函数的所有优化。

  1. 手动解析栈帧获取原始参数地址
    32位Windows下,编译参数/Gd对应cdecl调用约定,调用者会先将参数压入栈,再压入返回地址。可以通过内建函数获取返回地址,再偏移找到原始参数的地址:
#include <intrin.h>

void PacketHandler::CMSG_Hook(CDataStore* data)
{
    // 获取当前函数的返回地址(调用者压入栈的内容)
    void** returnAddr = (void**)_ReturnAddress();
    // cdecl约定下,返回地址的下一个栈位置就是传入的第一个参数地址
    CDataStore** pointerToPacket = (CDataStore**)(returnAddr + 1);
    printf("%p\n", pointerToPacket);
    MessageBoxA(0,"debug pause", 0, 0);
    ...
}
  1. 全局调整编译参数(不推荐)
    移除Release编译参数中的/O2和/Oi,但这会降低整个DLL的运行性能,仅适合临时调试场景。

内容的提问来源于stack exchange,提问作者Ofdrykkja

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:51:04