1

你们中的许多人都熟悉 ATL thunk,例如用于创建窗口。使这项工作 CStdCallThunk 的类以WindowProc调用为目标。从本质上讲,它将全局回调转换为 C++ 对象的成员函数。

这种类型的 thunk 不适用于需要完整的第一个参数的SetWindowsHookEx 回调。对于 32 位 Windows,我在ATL/AUX库的一部分 CAuxThunk 中找到了一个整洁的解决方案。不幸的是,这不适用于本机 64 位可执行文件

我想知道是否有任何 x64 汇编大师可以修补此 CauxThunk 以适用于 64 位窗口,或者提出任何等效的 thunk 将 this __stdcall 回调转换为成员函数?

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)

谢谢,
尼科斯

4

3 回答 3

4

我在这个答案中描述了一种生成您想要的任何thunk 代码的通用方法。让我们为您的案例重做它,作为练习。

假设您的类定义为:

struct YourClass {
    LRESULT YourMemberFunc(int nCode, WPARAM wParam, LPARAM lParam);
};

用 C++ 编写您的 thunk,并使用实际地址的占位符:

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
    YourClass *x = reinterpret_cast<YourClass*>(0x1122112211221122);
    __int64 im = 0x3344334433443344;
    LRESULT (YourClass::*m)(int,WPARAM,LPARAM) = *reinterpret_cast<LRESULT (YourClass::**)(int,WPARAM,LPARAM)>(&im);
    return (x->*m)(nCode, wParam, lParam);
}

并以防止编译器内联调用的方式调用它:

int main() {
    LRESULT (CALLBACK *volatile fp)(int, WPARAM, LPARAM) = CallWndProc;
    fp(0, 0, 0);
}

在 release 中编译并查看生成的程序集(在 Visual Studio 中,调试期间查看程序集窗口并打开“显示代码字节”):

4D 8B C8                       mov         r9,r8  
4C 8B C2                       mov         r8,rdx  
8B D1                          mov         edx,ecx  
48 B9 22 11 22 11 22 11 22 11  mov         rcx,1122112211221122h  
48 B8 44 33 44 33 44 33 44 33  mov         rax,3344334433443344h  
48 FF E0                       jmp         rax  

这将是您的 thunk,在运行时44 33 44 33 44 33 44 33替换为指向您的成员 ( &YourClass::YourMemberFunc) 的指针并22 11 22 11 22 11 22 11替换为指向实际对象实例的指针。

thunk 中发生的事情的解释

在 x64 调用约定中(在 Windows 上只有一个),前四个参数rcx, rdx, r8, r9按从左到右的顺序在寄存器中传递。所以当我们的 thunk 被调用时,我们有

rcx = nCode, rdx = wParam, r8 = lParam

对于成员函数,有一个隐式的第一个参数持有this指针,所以在进入时YourMemberFunc我们必须有

rcx = this, rdx = nCode, r8 = wParam, r9 = lParam

编译器生成的代码正是这样调整的:它移动r8 -> r9, rdx -> r8, ecx -> edx,然后将我们的占位符分配this = 1122112211221122rcx. 现在它已经设置了参数,它可以继续间接跳转到函数本身。rax用于保存返回值,因此不必在函数调用之间保留。这就是为什么这里使用它来临时保存目标地址,这为尾调用优化提供了机会(一个调用/返回对替换为单跳转)。

为什么我们必须进行间接调用?因为否则我们会得到一个相对的跳跃。但是我们不能使用硬编码的相对跳转,因为 thunk 将被复制到内存中的不同地址!因此,我们求助于在运行时设置绝对地址并进行间接跳转。

高温高压

于 2014-04-20T20:46:29.280 回答
0

更新见下文

看看 SetWindowLongPtr http://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspx

这允许您将指针与窗口相关联。鉴于 WindowProc 看起来像这样

LRESULT CALLBACK WindowProc( _In_ HWND hwnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam );

您可以使用带有 GetWindowLongPtr 的 hwnd 参数来检索回调中的 this 指针。

更新

看看How to thunk a function in x86 and x64? (就像 C++ 中的 std::bind 一样,但是是动态的)

这似乎是您正在寻找的

于 2013-10-28T13:50:57.887 回答
0

因为在 64 位模式下,所有类型(stdcall、cdecl 和 thiscall)的低级约定都是相同的,所以您实际上不需要汇编代码来实现这一点。只需创建一个调用适当成员函数的静态函数。您将需要找出this要使用的正确指针,例如通过将hwndinlparam与对象相关联。如果你只有一个回调,这当然没问题。就像是:

LRESULT CALLBACK static CallWndProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    Window w = GetWindowByHwnd(((CWPSTRUCT*)lParam)->hwnd);
    return w->CallWndProc(nCode, wParam, lParam);
}
于 2013-10-28T10:22:00.770 回答