【发布时间】:2021-01-28 07:04:32
【问题描述】:
我正在尝试使用 mingw64 让现有的 JIT 在 Windows x86_64 上运行。
当 JIT 回调到预编译代码并且该代码调用 Windows API 时,我遇到了段错误,因为 Windows API 实现中的对齐移动指令(例如 movaps)是用 %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
在我期望的快速解决方法中,我想我会在进入由 JIT 代码调用的预编译函数并最终调用 Windows API 函数的途中让 gcc 强制重新对齐堆栈。
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
【问题讨论】:
-
最终,我将通过将 JIT 中的堆栈分配修复为对齐 16——通过手动修补机器代码块——来消除对这种对齐修复的需求——但是我很好奇为什么这些 gcc 选项不像它说的那样工作。
标签: gcc x86-64 mingw-w64 memory-alignment stack-pointer