ATL Windowing中的汇编:_stdcallthunk分析

文章探讨了stdcallthunk结构如何通过初始化成为一段代码,用于设置窗口过程函数。通过两个关键指令,实现将数据指针传入栈区并跳转至实际窗口过程函数。

1. 指令指针寄存器与正在执行指令指针关系:


I:                      CPU正在执行指令;

xIP:                  CPU指令指针寄存器内容;

xIP(I):              CPU正在执行的指令I的指针;

LEN(I):            指令I的长度。


1)顺序执行:xIP = xIP(I) + LEN(I);

2)相对跳转:xIP = xIP(I) + LEN(I) + REL_DISPLACEMENT_CONST;

3)绝对跳转:xIP = Target_CONST;

 

2. ATL:: _stdcallthunk 代码片断(节选自atlstdthunk.h)


struct _stdcallthunk

{

        DWORD   m_mov;          // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)

        DWORD   m_this;         //

        BYTE    m_jmp;          // jmp WndProc

        DWORD   m_relproc;      // relative jmp

        BOOL Init(DWORD_PTR proc, void* pThis)

        {

                m_mov = 0x042444C7;  //C7 44 24 0C

                m_this = PtrToUlong(pThis);

                m_jmp = 0xe9;

                m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));

                // write block from data cache and

                //  flush from instruction cache

                FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));

                return TRUE;

        }

        //some thunks will dynamically allocate the memory for the code

        void* GetCodeAddress()

        {

                return this;

        }

// code ignored ……

};

 

3. 数据作为代码运行


实际使用时,_stdcallthunk结构作为代码使用。经过Init()后,被解释为以下两条指令(X86机器):

            Mov dword ptr[esp+4], pThis_CONST;

            Jmp REL_DISPLACEMENT_CONST;

其中 pThis_CONST是CWindowXXX的指针;

REL_DISPLACEMENT_CONST是跳转到实际WndProc的相对偏移量。那为什么 REL_DISPLACEMENT_CONST是 “_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk))); ”呢?稍候解释。

           

下面是一个可能的伪码例子:

WNDPROC pWndProc = (WNDPROC)( _stdcallthunkOBJ. GetCodeAddress() );

SetWindowLongPtr( hwnd, GWLP_WNDPROC, pWndProc);


4. 两条指令的解释


当WndProc被调用时,程序堆栈如下:(地址约定:高地址在上,低地址在下)


LPARAM

WPARAM

UINT

HWND                                    ß esp+4

RetAddr( from WNdProc)       ß esp


所以当第一条指令Mov dword ptr[esp+4], pThis_CONST 执行后,程序堆栈如下:

LPARAM

WPARAM

UINT

pThis_CONST             ß esp+4

RetAddr( from WNdProc)       ß esp

 

当第二条指令Jmp REL_DISPLACEMENT_CONST 执行时,xIP和xIP(I)的关系如下(重复地址约定:高地址在上,低地址在下):


Others ….                                ß_stdcallthunk结构的基址+_stdcallthunk结构大小

Jmp ….. (第二条指令)           ß xIP(I)

Mov ….. (第一条指令)          ß_stdcallthunk结构的基址


由于xIP = xIP(I) + LEN(I) + REL_DISPLACEMENT_CONST;

显然 xIP(I) + LEN(I) = _stdcallthunk结构的基址+_stdcallthunk结构大小;

又因为 REL_DISPLACEMENT_CONST = _relproc = DWORD((INT_PTR)proc - (_stdcallthunk结构的基址+_stdcallthunk结构大小);


所以 xIP = xIP(I) + LEN(I) + REL_DISPLACEMENT_CONST

            =  (_stdcallthunk结构的基址+_stdcallthunk结构大小)+ (DWORD((INT_PTR)proc - (_stdcallthunk结构的基址+_stdcallthunk结构大小))

= proc;

既下一条将要执行的指令是实际WndProc的首条指令。


如果使用绝对寻址,要好理解的多,可能是基于性能考虑,使用相对寻址。X64使用寄存器绝对寻址(rcx rax),好理解一些,但显然须符合调用约定。

 

5.总结

1)      数据被解释为代码;

2)      相对跳转正好跳到实际WndProc。

 

本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/benny5609/archive/2008/06/30/2597419.aspx

在MFC程序中遇到 `ATL::_ATL_SAFE_ALLOCA_IMPL::_AtlVerifyStackAvailable` 返回 `E_OUTOFMEMORY` 错误,通常与内存分配问题相关,尤其是在使用 ATL(Active Template Library)进行 COM 编程时。此函数用于验证当前线程的堆栈是否有足够的空间进行安全的alloca操作[^1]。 ### 原因分析 - **堆栈空间不足**:`_AtlVerifyStackAvailable` 会检查当前堆栈是否足够大以执行alloca操作。如果堆栈空间不足,则返回 `E_OUTOFMEMORY`。 - **递归调用或局部变量过大**:在递归函数或局部变量占用大量堆栈空间的情况下,可能会耗尽线程堆栈,从而导致此错误。 - **线程堆栈大小设置过小**:默认情况下,Windows 线程堆栈大小为1MB(32位系统)或更大(64位系统),但如果显式设置了较小的堆栈大小,则可能导致此问题。 ### 解决方法 - **减少局部变量使用**: - 避免在函数内部声明大型局部数组或结构体。 - 使用动态内存分配(如 `new` 或 `std::vector`)代替栈上分配[^1]。 ```cpp // 推荐使用 std::vector 替代大型局部数组 std::vector<int> largeArray(10000); ``` - **调整线程堆栈大小**: - 如果是创建新线程时出现该问题,可以通过 `_beginthreadex` 或 `CreateThread` 设置更大的堆栈大小。 ```cpp #include <process.h> unsigned int __stdcall ThreadFunc(void* pParams) { // 线程逻辑 return 0; } uintptr_t hThread = _beginthreadex(nullptr, 0x40000, ThreadFunc, nullptr, 0, nullptr); // 设置堆栈大小为256KB ``` - **避免深层递归调用**: - 将递归算法转换为迭代方式,减少对堆栈的依赖。 - **检查第三方库行为**: - 如果使用了 ATL 或 MFC 的某些组件,确保没有在构造函数或析构函数中执行复杂的alloca操作。 - **启用 Safe Alloca 检查绕过(不推荐)**: - 如果确认环境安全,可以定义宏 `_ATL_DISABLE_VERIFY_STACK_AVAILABLE` 来禁用 `_AtlVerifyStackAvailable` 的检查。但应谨慎使用此选项,因为它可能带来稳定性风险。 ### 调试建议 - 使用调试器查看调用堆栈,确定是哪个模块或函数触发了 `_AtlVerifyStackAvailable` 的失败。 - 监控线程堆栈使用情况,可以借助工具如 Windows Performance Analyzer (WPA) 或 Visual Studio 的诊断工具。 - 启用 CRT 内存泄漏检测机制,排查是否有内存泄漏导致资源耗尽: ```cpp #define _CRTDBG_MAP_ALLOC #include <crtdbg.h> int main() { _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); // 主程序逻辑 } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值