clang 未对齐堆栈,然后在 _start 中尝试将 vmovaps 编写为 C 函数

问题描述 投票:0回答:1

我有一个简单的 c 函数

start.c

$ cat start.c
int main(int,char**);
void _start(){
 char*v[2]={"k",0};
 main(1,v);
}

当我编译为汇编程序时,使用

clang -O -march=cannonlake -S start.c
我得到start.s,其中包含以下代码:

_start:                                 # @_start
    .cfi_startproc
# %bb.0:
    subq    $24, %rsp
    .cfi_def_cfa_offset 32
    vmovaps .L__const._start.v(%rip), %xmm0
    vmovaps %xmm0, (%rsp)

当我运行此代码(在具有定制操作系统的 bochs 模拟器中)时,我收到一个异常,因为

vmovaps %xmm0, (%rsp)
发生在未 16 字节对齐的 rsp 上。当调用 _start 时,堆栈的对齐方式是 16 字节,并且
subq $24, %rsp
将更改该对齐方式。

我尝试过 clang 13 和 17,结果非常相似。 clang 是错误的,还是我看错了?

当我在 _start 的开头添加

asm volatile ("subq $8,%%rsp"::);
时,它解决了问题。

这与 主和堆栈对齐

有相似之处
c clang x86-64 memory-alignment abi
1个回答
0
投票

您将

_start()
定义为 C 函数,因此 Clang 会编译代码,以便在使用
RSP % 16 == 8
调用时工作,就像 ABI 保证一样。

但是随后您将其链接到可执行文件中,它实际上是进程入口点,而不是函数。 它没有返回地址。 没有

call
推送返回地址;正如你所说,进入
RSP % 16 == 0
_start
。 这本质上是未定义的行为,从某种意义上说,编译器可以假设正常函数不会在堆栈未对齐的情况下被调用(或以其他方式跳转到)。

至少对于 GCC,您可以使用

__attribute__((force_align_arg_pointer))
来告诉它传入的对齐方式低于正常值。

或者使用更改 ABI 的命令行选项,例如

gcc -mpreferred-stack-boundary=3
1<<3 == 8
而不是 16 字节对齐的默认
4
。)

参见 How to get argument value in _start and call main() using inline assembly in C, without Glibc or CRT start files? 对于用 GNU C 编写的 x86-64 System V ABI,这是一个非常 hacky 但有效的

_start
没有任何内联汇编,但仍然从堆栈中获取
argc
argv
(作为返回地址和第一个堆栈参数)。 并让 RSP 为未来的
call
正确调整。 我没有用clang测试过,不知道是否支持相同的属性。

© www.soinside.com 2019 - 2024. All rights reserved.