Visual c++ 在Windows x86-64 ABI中将参数索引为数组

Visual c++ 在Windows x86-64 ABI中将参数索引为数组,visual-c++,assembly,x86,calling-convention,Visual C++,Assembly,X86,Calling Convention,我正在尝试将Windows ABI的包装器函数从32位移植到x86-64 asm。函数依赖于将其参数作为数组索引 我知道MSVC不能在X64项目中进行内联汇编,但我有兴趣将等效函数构建到X64.asm文件中 该函数为要调用的api设置stackframe __declspec( naked ) PVOID WINAPIV CGX86( FARPROC lpPtr, UINT lpSize, ... ) { __asm { push ebp; mov ebp

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

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

该函数为要调用的api设置stackframe

__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位调用约定为堆栈arg(阴影空间)正下方的4个寄存器arg提供了存储空间,因此您实际上可以创建一个arg数组,最多只能使用4条指令进行索引(将arg存储到这些插槽中)。您不需要对前4个参数进行特殊处理

不要忘记将传出参数放入regs而不是堆栈,并为调用的函数留下阴影空间

正如Ross Ridge所指出的,确保在独立asm中包含创建SEH放松信息的指令。这是支持纯C++解决方案的另一个好理由,尤其是如果ARG的数量限制在一个小数目上。 有关调用约定等的链接,请参见标记wiki


一般来说,这是Windows调用约定的一部分,但它确实使实现var args函数变得简单。我很确定这就是为什么ABI中存在“影子空间”的原因。

你说的“帮助”是指你想让我们为你写吗?@MichaelPetch是的,它是有效的,因为eax如你所说指向返回地址,它使用基于1的参数索引,所以它复制的最后一个参数是
ecx=3
,它给出了
eax+3*4
,这就是
ebp+16
。我不明白推ecx的目的。当然,你也可以编写相应的代码。另外,如果你知道你最多需要6个参数,比如说,一个简单的C
开关
,就可以去掉汇编代码。@PeterCordes我认为你的编辑并不能改善这个问题。对于这个问题的一个好答案仍然需要详细说明使用X64调用约定的完整解决方案,包括如何处理用于参数的寄存器、正确对齐堆栈以及为SEH创建展开信息。因为代码现在需要在MASM或其他汇编程序中编写,而不是同样需要介绍的内联汇编程序。还有一个问题是,这个函数似乎是无用和不必要的,因此不清楚它的真正需求是什么。@PeterCords实际上,您需要将传入的args 5和6从堆栈中取出并放入寄存器。你不需要在阴影空间中放置任何东西。您可以通过从第三个阴影插槽开始复制MAX(2,lpSize-2)堆栈插槽来简化工作,但在此之前需要对齐堆栈,这意味着只复制实际使用的堆栈插槽可能更简单。使此函数看起来毫无用处的是,直接调用函数(例如,
MessageBoxA(getforegroundindow(),“BODY”,“TITLE”,MB_OK)
)是最佳选择。所以问题是这个函数真正解决的是什么问题?谢谢你的帮助,我现在明白了。我给你投了赞成票,还打了勾,不知道是谁投的票,为什么你会被否决。@RaulFernando看起来你按错了按钮,你投了反对票,但没有被否决(只有一票,结果被否决了)。@Jester:在Raul发表评论之前有一段时间被否决了。现在,在我编辑以澄清为什么这是一个答案后,否决票被删除了是的,我很困惑,因为劳尔说他“投了赞成票”。MSDN文档中的窗口中提到了阴影空间的两个原因。C Varargs和非类型化的C函数。