Clang 使用 -nostdlib 生成崩溃代码

标签 c gcc assembly clang elf

我正在尝试为一个可执行文件设置我自己的运行时环境,但我无法让 clang(v3.4-1ubuntu1,目标:x86_64-pc-linux-gnu)生成一个不支持的可执行文件段错误。我已将问题简化为以下内容:

如果我有一个文件 crt1.c,它除了满足链接器对 _start 符号的要求外什么都不做:

void
_start(char *arguments, ...)
{
}

然后我用 clang -nostdlib crt1.c 编译它,它生成以下可执行文件(来自 objdump -d a.out):

a.out:     file format elf64-x86-64


Disassembly of section .text:

0000000000400150 <_start>:
  400150:   55                      push   %rbp
  400151:   48 89 e5                mov    %rsp,%rbp
  400154:   48 81 ec f0 00 00 00    sub    $0xf0,%rsp
  40015b:   84 c0                   test   %al,%al
  40015d:   0f 29 bd 30 ff ff ff    movaps %xmm7,-0xd0(%rbp)
  400164:   0f 29 b5 20 ff ff ff    movaps %xmm6,-0xe0(%rbp)
  40016b:   0f 29 ad 10 ff ff ff    movaps %xmm5,-0xf0(%rbp)
  400172:   0f 29 a5 00 ff ff ff    movaps %xmm4,-0x100(%rbp)
  400179:   0f 29 9d f0 fe ff ff    movaps %xmm3,-0x110(%rbp)
  400180:   0f 29 95 e0 fe ff ff    movaps %xmm2,-0x120(%rbp)
  400187:   0f 29 8d d0 fe ff ff    movaps %xmm1,-0x130(%rbp)
  40018e:   0f 29 85 c0 fe ff ff    movaps %xmm0,-0x140(%rbp)
  400195:   48 89 bd b8 fe ff ff    mov    %rdi,-0x148(%rbp)
  40019c:   4c 89 8d b0 fe ff ff    mov    %r9,-0x150(%rbp)
  4001a3:   4c 89 85 a8 fe ff ff    mov    %r8,-0x158(%rbp)
  4001aa:   48 89 8d a0 fe ff ff    mov    %rcx,-0x160(%rbp)
  4001b1:   48 89 95 98 fe ff ff    mov    %rdx,-0x168(%rbp)
  4001b8:   48 89 b5 90 fe ff ff    mov    %rsi,-0x170(%rbp)
  4001bf:   0f 84 5b 00 00 00       je     400220 <_start+0xd0>
  4001c5:   0f 28 85 c0 fe ff ff    movaps -0x140(%rbp),%xmm0
  4001cc:   0f 29 85 70 ff ff ff    movaps %xmm0,-0x90(%rbp)
  4001d3:   0f 28 8d d0 fe ff ff    movaps -0x130(%rbp),%xmm1
  4001da:   0f 29 4d 80             movaps %xmm1,-0x80(%rbp)
  4001de:   0f 28 95 e0 fe ff ff    movaps -0x120(%rbp),%xmm2
  4001e5:   0f 29 55 90             movaps %xmm2,-0x70(%rbp)
  4001e9:   0f 28 9d f0 fe ff ff    movaps -0x110(%rbp),%xmm3
  4001f0:   0f 29 5d a0             movaps %xmm3,-0x60(%rbp)
  4001f4:   0f 28 a5 00 ff ff ff    movaps -0x100(%rbp),%xmm4
  4001fb:   0f 29 65 b0             movaps %xmm4,-0x50(%rbp)
  4001ff:   0f 28 ad 10 ff ff ff    movaps -0xf0(%rbp),%xmm5
  400206:   0f 29 6d c0             movaps %xmm5,-0x40(%rbp)
  40020a:   0f 28 b5 20 ff ff ff    movaps -0xe0(%rbp),%xmm6
  400211:   0f 29 75 d0             movaps %xmm6,-0x30(%rbp)
  400215:   0f 28 bd 30 ff ff ff    movaps -0xd0(%rbp),%xmm7
  40021c:   0f 29 7d e0             movaps %xmm7,-0x20(%rbp)
  400220:   48 8b 85 b0 fe ff ff    mov    -0x150(%rbp),%rax
  400227:   48 89 85 68 ff ff ff    mov    %rax,-0x98(%rbp)
  40022e:   48 8b 8d a8 fe ff ff    mov    -0x158(%rbp),%rcx
  400235:   48 89 8d 60 ff ff ff    mov    %rcx,-0xa0(%rbp)
  40023c:   48 8b 95 a0 fe ff ff    mov    -0x160(%rbp),%rdx
  400243:   48 89 95 58 ff ff ff    mov    %rdx,-0xa8(%rbp)
  40024a:   48 8b b5 98 fe ff ff    mov    -0x168(%rbp),%rsi
  400251:   48 89 b5 50 ff ff ff    mov    %rsi,-0xb0(%rbp)
  400258:   48 8b bd 90 fe ff ff    mov    -0x170(%rbp),%rdi
  40025f:   48 89 bd 48 ff ff ff    mov    %rdi,-0xb8(%rbp)
  400266:   4c 8b 85 b8 fe ff ff    mov    -0x148(%rbp),%r8
  40026d:   4c 89 45 f8             mov    %r8,-0x8(%rbp)
  400271:   48 81 c4 f0 00 00 00    add    $0xf0,%rsp
  400278:   5d                      pop    %rbp
  400279:   c3                      retq   

可执行文件在地址 40015d 处的指令处因段错误而崩溃——保存 %xmm7 的指令。我不知道为什么 clang 将这些保存起来,gcc 没有产生这样的指令。

%rbp 中传递的值是 7fffffffe588,不是 16 字节对齐的,我猜这在某种意义上解释了段错误。但是我怎样才能让它发挥作用呢?让它抑制那些保存指令?让它以某种方式对齐 rbp 指针?

编辑:我想这个问题归结为 clang 生成的代码假设 %rsp 将按 16 字节对齐。这是一个有效的假设吗?为什么在这个例子中不是这样?

最佳答案

编辑:找到了一个解决方案 - 跳到下面看...


首先,确保堆栈 (%rsp) 是 16 字节对齐的:

pushq  %rbp
movq   %rsp, %rbp
andq   $-0x10, %rsp  ; rsp = rsp & 0xffffffffffffff0

这是有问题的,因为通常调用者有责任确保 %rsp 是 16 字节对齐的,因为 %rbp + 16.n 可能不在 16 字节上-字节边界。所以也许 movq %rsp, %rbp 应该出现在 %rsp 的对齐之后。

sub $0xf0, %rsp 分配0xf0字节的栈空间; 0xf0 是 (16) 的倍数。如果 %rsp 不是 16 字节对齐的,movaps %xmm7, -0xd0(%rbp) => movaps %xmm7, 0x20(%rsp)。换句话说,SSE 寄存器存储在 %rsp + 32。如果未对齐,则会引发“一般保护异常”,即段错误。

您可能遇到的另一个问题是对 -0x170(%rbp) 的读/写 => 那是 -0x80(%rsp),它要么在(或过去? 我可能不在我的偏移量中) red zone 的边界.由于这是叶函数,您可以自由使用它,但不能写过去。

注意:如果您的函数被调用,您应该从 %rsp 中减去另外 (8) 个字节以确保 16 字节对齐。这将反过来影响 (%ebp) 的偏移量。

我不是在这里用 ABI 标准核实事实,我可能犯了一些错误;所以最好检查一下 x86-64 SysV ABI (第 3.2 节)。


解决方案:使用 -mstackrealign 标志编译上述函数会将 %rsp 显式对齐到 16 字节边界。我在 OS X 上使用 clang,它在调用约定和对齐要求方面与 x86-64 SysV (x86-64 ELF/Linux) 基本相同:

clang -nostdlib -mstackrealign -c crt1.c

0000000000000000        pushq   %rbp
0000000000000001        movq    %rsp, %rbp
0000000000000004        andq    $0xfffffffffffffff0, %rsp
000000000000000b        subq    $0x170, %rsp
0000000000000012        testb   %al, %al
...

BTW - 这通过使所有加载/存储相对于 %rsp 来完全避免 %rbp 问题。因此,没有使用红色区域 - 至少使用 Apple 的基于 LLVM 3.3 的 clang。

关于Clang 使用 -nostdlib 生成崩溃代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21631579/

相关文章:

c - 有关字符串和 stdio.h 库的未知错误

c - Linux 上的 flock 与 lockf

c - 在Assembly中打印多个值

使用按位运算符比较两个字符

c - 为什么我们不需要取消引用命令行参数(简单)

debugging - 显示 "No function contains program counter for selected frame"时如何强制 GDB 反汇编代码?

c - Makefile 问题 - 阅读器中的 fatal error

python - 使用 MinGW+GCC 编译 SciPy 时编译器失败

gcc - 尝试使用GCC构建Octave时未定义对Magick的引用

c - 如何提供 memcpy 的实现