如何在 MinGW 下使用 SysV 调用约定 运行 编译我的 C 程序

How to make my C program compiled with SysV calling convention run under MinGW

我的平台是x86_64+Windows10+Cygwin.我的编译器是 x86_64-w64-mingw32-gcc.

出于某种原因,我不得不使用 -mabi=sysv 选项编译我的程序,如果可能的话,我想避免使用默认的 -mabi=ms 选项。

程序编译成功。但是当它调用像 printf 这样的库函数时,它 segfaults。原因是库函数驻留在 msvcrt.dll 中,它可能是使用 -mabi=sysv.

以外的调用约定预构建的

那么,有没有办法在 Cygwin 中安装使用 -mabi=sysv 编译的库?

根据您对手写汇编的评论,您似乎亲身体验了汇编实际上不是那么可移植以及为什么历史上需要像 C 这样的更可移植的高级语言。

您发生崩溃的原因可能是由于两个 ABI 之间的寄存器使用不同。调用函数时,ABI 定义了哪些寄存器与每个参数相关联。因此,当您使用一个 ABI 调用一个库函数时,该库之前是为另一个 ABI 编译的,该库代码将查看其指定的寄存器并可能访问内存,从而导致未定义的行为。

我认为获取库可以解决你的问题是有误解的。想一想你得到了你想要的,针对 System V ABI 编译的 windows C 运行时库。现在,当您的程序调用 printf(...) 时,您至少会将参数保存在正确的寄存器中。但是文本必须打印到控制台,因此必须要求内核介入并实现它。为了进行该系统调用,将使用 windows API 库,并且会发生另一个 ABI 不匹配。所以你可以看到这个困境是如何上升到顶端的。你基本上需要一个完整的 windows OS 针对 System V ABI 编译,或一些其他形式的兼容层,如 WINE 的工作方式。

这里您可能有两种选择。重写程序集以匹配本机 windows ABI,或研究使用提供完整(但虚拟化)Linux 环境的 WSL。

您通常希望避免这种情况,例如通过在您的 asm 中使用宏来在调用约定之间调整它。 Agner Fog's calling convention guide 有一些建议,例如#ifdef%if 一些额外的 mov 指令在函数之前将 args 放入寄存器中,而函数的其余部分是为其编写的。 (然后不要使用红色区域或阴影 space,最小公分母,如果你的函数仍然可以那样有效地工作。)

IDK 如果相同的 .cfi 指令可以为 MS 和 SysV 版本创建正确的 stack-unwind 元数据,但如果您只使用 SysV 程序集,则不太可能 SEH-safe完全符合 Windows ABI 的代码。


Per-function 属性指定调用约定

如果您使用 __attribute__((sysv_abi)) 声明每个 hand-written asm 函数的原型,GCC 将生成正确调用它的代码。

The GCC manual 还说:

Note, the ms_abi attribute for Microsoft Windows 64-bit targets currently requires the -maccumulate-outgoing-args option.

当然,你传入asm函数的任何回调function-pointers也需要是__attribute__((ms_abi)),所以如果你想传递任何Windows库函数的地址,你'将需要为其编写自己的包装器,并在定义中使用它。


这会带来一些性能开销,因为 SysV ABI 允许破坏所有 XMM regs,但 MS-ABI 不允许,所以每个函数调用任何 SysV-ABI 函数需要 save/restore xmm6-15。这也会使那些 call-sites 膨胀,这可以通过让 GCC 使用辅助函数来保存/恢复那些不匹配的寄存器来缓解:

-mcall-ms2sysv-xlogues
Due to differences in 64-bit ABIs, any Microsoft ABI function that calls a System V ABI function must consider RSI, RDI and XMM6-15 as clobbered. By default, the code for saving and restoring these registers is emitted inline, resulting in fairly lengthy prologues and epilogues. Using -mcall-ms2sysv-xlogues emits prologues and epilogues that use stubs in the static portion of libgcc to perform these saves and restores, thus reducing function size at the cost of a few extra instructions.


示例(在 Godbolt 上)

#define SYSV __attribute__((sysv_abi))

SYSV int foo(int x, double);

// no attribute so this function is the default MS-ABI
int bar(int *p, double d) {
    *p = 0;                    // incoming arg in RCX
    int tmp = foo(p[12], d);
    *p = tmp;
    return tmp;
}

使用 -O3 -mabi=ms -Wall -maccumulate-outgoing-args 和 Linux GCC11.2 构建。 -mabi=ms是用Cygwin或者MinGW搭建的粗略模拟

bar:
        push    rdi
        movapd  xmm0, xmm1                  # SysV wants first FP arg in XMM0
        push    rsi                         # save RDI/RSI, call-preserved in MS-ABI
        push    rbx
        mov     rbx, rcx
        sub     rsp, 160                     # shadow space + xmm spill space
        mov     DWORD PTR [rcx], 0          # deref pointer arg
        mov     edi, DWORD PTR [rcx+48]     # load a new 1st arg
        movaps  XMMWORD PTR [rsp], xmm6     # save the MS-abi call-preserved regs
        movaps  XMMWORD PTR [rsp+16], xmm7
        movaps  XMMWORD PTR [rsp+32], xmm8
        movaps  XMMWORD PTR [rsp+48], xmm9
        movaps  XMMWORD PTR [rsp+64], xmm10
        movaps  XMMWORD PTR [rsp+80], xmm11
        movaps  XMMWORD PTR [rsp+96], xmm12
        movaps  XMMWORD PTR [rsp+112], xmm13
        movaps  XMMWORD PTR [rsp+128], xmm14
        movaps  XMMWORD PTR [rsp+144], xmm15
        call    foo
        movaps  xmm6, XMMWORD PTR [rsp]
        movaps  xmm7, XMMWORD PTR [rsp+16]
        movaps  xmm8, XMMWORD PTR [rsp+32]
        movaps  xmm9, XMMWORD PTR [rsp+48]
        mov     DWORD PTR [rbx], eax
        movaps  xmm10, XMMWORD PTR [rsp+64]
        movaps  xmm11, XMMWORD PTR [rsp+80]
        movaps  xmm12, XMMWORD PTR [rsp+96]
        movaps  xmm13, XMMWORD PTR [rsp+112]
        movaps  xmm14, XMMWORD PTR [rsp+128]
        movaps  xmm15, XMMWORD PTR [rsp+144]
        add     rsp, 160
        pop     rbx
        pop     rsi
        pop     rdi
        ret

请注意,即使没有 doublefloat 参数,所有这些 XMM save/restore 仍然会发生;调用者必须假定被调用者可能在内部使用 SIMD 或 FP。幸运的是,即使启用了 AVX,只有高矢量 reg 的低 xmm 部分是 call-preserved,因此堆栈 space 的使用情况不会变得更糟。

如果我使用 __m128d arg,x64 fastcallms_abi 的默认值)将通过引用传递它,由 RDX 指向。 vectorcall 会像 double 一样在 XMM1 中传递它(不是 xmm0,因为 arg-register 编号在 MS-ABI 中的 int 和 FP 之间不是独立的)。我不知道如何在 GCC 中使用 x64 vectorcall。如果在 bar.

上使用,__attribute__((vectorcall)) 将被忽略

对比两部分都编译为 MS-ABI(在原型上注释掉 SYSV):

bar:
        push    rbx                        # save a call-preserved reg (and realign the stack)
        mov     rbx, rcx                     # save incoming arg around function call
        sub     rsp, 32                    # reserve shadow space
        mov     DWORD PTR [rcx], 0           # deref the arg
        mov     ecx, DWORD PTR [rcx+48]    # load a new 1st arg
                                           # 2nd arg still in XMM1
        call    foo
        mov     DWORD PTR [rbx], eax       # save return value
        add     rsp, 32
        pop     rbx
        ret                                # and return with it in EAX
Clang 忽略 -mabi=ms,但尊重 __attribute__((ms_abi))

因此默认为 MS-ABI 的 Windows clang 构建可能可以使用 __attribute__((sysv)).