在 Windows x86-64 ABI 中将 args 索引为数组

Indexing args as an array in the Windows x86-64 ABI

我正在尝试将包装函数从 32 位移植到 Windows ABI 的 x86-64 asm。该函数依赖于将其参数作为数组进行索引。

我知道 MSVC 不能在 X64 项目中进行内联汇编,但我有兴趣将等效功能构建到 X64 .asm 文件中。

该函数为要调用的 api 设置堆栈帧。

__declspec( naked ) PVOID WINAPIV CGX86( FARPROC lpPtr, UINT lpSize, ... )
{
    __asm {
        push ebp;
        mov ebp, esp;
        lea eax, [ ebp + 0x04 ];
        mov [ ebp - 0x04 ], eax;
        mov eax, [ ebp - 0x04 ];
        mov ecx, [ ebp + 0x0C ];
        add ecx, 2;
ParseArgs:
        cmp ecx, 2;
        jz short MoveFinal;
        push dword ptr [ eax + ecx * 0x04 ];
        sub ecx, 1;
        jmp short ParseArgs;
MoveFinal:
        call [ ebp + 0x08 ];
        mov esp, ebp;
        pop ebp;
        retn;
    }
}

使用示例:

CGX86( ( FARPROC )MessageBoxA, 4, GetForegroundWindow( ), "BODY", "TITLE", MB_OK );

Jester 建议用 C 语言编写它可能是个好建议,尤其是。如果它可以内联到一些参数是编译时常量的调用中。您的示例用例主要传递编译时常量参数,包括函数指针。 任何体面的优化编译器都会将其内联并将间接优化为具有正确参数的普通函数调用。确保将定义放在可以内联的位置。


但是,如果您无法让编译器生成漂亮的代码:

将参数索引为数组是唯一在 64 位 ABI 中如何实现的功能,其中一些参数位于 regs 中。

Windows 64 位调用约定提供 space 用于存储堆栈参数正下方的 4 个寄存器参数(影子 space),因此您实际上 可以 创建一个 args 数组,您最多可以使用 4 条指令进行索引(将 args 存储到这些槽中)。您不需要对前 4 个参数进行特殊处理。

不要忘记将传出的 args 放入 regs 而不是堆栈,并为您调用的函数留下 shadow-space。

正如 Ross Ridge 指出的那样,确保在独立的 asm 中包含创建 SEH 展开信息的指令。这是支持纯 C++ 解决方案的另一个很好的理由,尤其是。如果 args 的数量被限制为一个小数字。

请参阅 标记 wiki 以获取调用约定等链接。

通常 Windows 调用约定的

I'm not a fan,但它确实使实现 var-args 函数变得简单。我很确定这就是 ABI 中存在 "shadow space" 的原因。