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获取的是优化后的本地地址,而非调用者传入的原始参数地址。
解决方法
- 针对函数禁用优化
给目标函数添加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))禁用该函数的所有优化。
- 手动解析栈帧获取原始参数地址
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); ... }
- 全局调整编译参数(不推荐)
移除Release编译参数中的/O2和/Oi,但这会降低整个DLL的运行性能,仅适合临时调试场景。
内容的提问来源于stack exchange,提问作者Ofdrykkja
相关产品推荐
相关产品推荐

