我正在尝试使用 mingw64 让现有的 JIT 在 Windows x86_64 上运行。
当 JIT 回调到预编译代码并且该代码调用 Windows API 时,我遇到了段错误,因为movaps
在调用 Windows API 实现中的对齐移动指令时,调用的%rsp
不是 16 的倍数,即堆栈未对齐一个 16 字节的边界。
Thread 1 hit Catchpoint 2 (signal SIGSEGV), 0x00007fff5865142d in KERNELBASE!FindFirstFileA () from C:\WINDOWS\System32\KernelBase.dll
1: x/i $pc
=> 0x7fff5865142d <KERNELBASE!FindFirstFileA+125>: movaps 0x60(%rsp),%xmm0
2: /x $rsp = 0xd8edd8
在我期望的快速解决方法中,我想我会让 gcc 在进入由 JIT 代码调用的预编译函数并最终调用 Windows API 函数的途中强制重新对齐堆栈。
该force_align_arg_pointer
属性的 gcc 文档:
在 x86 目标上,该
force_align_arg_pointer
属性可以应用于单个函数定义,生成备用序言和尾声,必要时重新对齐运行时堆栈。这支持将使用 4 字节对齐堆栈运行的旧代码与保留 16 字节堆栈以实现 SSE 兼容性的现代代码混合。
但是,添加__attribute__((force_align_arg_pointer))
到函数说明符对输出程序集没有影响。
我也试过-mpreferred-stack-boundary=4
,它明确要求2**4 == 16
所有功能对齐:
-mpreferred-stack-boundary=num
尝试保持堆栈边界与 2 提升到num字节边界对齐。
这也没有效果。
事实上,我发现影响输出程序集的第一件事是-mpreferred-stack-boundary=3
(它应该保持堆栈与 8 字节边界对齐)。
这导致了这种差异:
@@ -46,8 +59,15 @@
.def foo; .scl 2; .type 32; .endef
.seh_proc foo
foo:
+ pushq %rbp
+ .seh_pushreg %rbp
+ movq %rsp, %rbp
+ .seh_setframe %rbp, 0
+ andq $-16, %rsp
.seh_endprologue
leaq .LC0(%rip), %rcx
+ movq %rbp, %rsp
+ popq %rbp
jmp printf
.seh_endproc
.def __main; .scl 2; .type 32; .endef
奇怪的是,这实际上是放入andq $-16, %rsp
(将堆栈指针对齐为 16 的倍数),尽管我们说更喜欢 8 字节对齐。
我对这些选项或它们适用的案例有什么误解?
gcc的版本是MSYS2 mingw64的10.2.0:
$ gcc --version
gcc.exe (Rev4, Built by MSYS2 project) 10.2.0