glibc scanf 从未对齐 RSP 的函数调用时出现分段错误

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

编译以下代码时:

global main
extern printf, scanf

section .data
   msg: db "Enter a number: ",10,0
   format:db "%d",0

section .bss
   number resb 4

section .text
main:
   mov rdi, msg
   mov al, 0
   call printf

   mov rsi, number
   mov rdi, format
   mov al, 0
   call scanf

   mov rdi,format
   mov rsi,[number]
   inc rsi
   mov rax,0
   call printf 

   ret

使用:

nasm -f elf64 example.asm -o example.o
gcc -no-pie -m64 example.o -o example

然后运行

./example

运行,打印:输入数字: 但随后崩溃并打印: 分段错误(核心转储)

所以 printf 工作正常,但 scanf 不行。 我对 scanf 做错了什么?

linux assembly nasm x86-64 calling-convention
2个回答
17
投票

在函数开始/结束时使用

sub rsp, 8
/
add rsp, 8
在函数执行
call
之前将堆栈重新对齐为 16 个字节。

或者更好地压入/弹出一个虚拟寄存器,例如

push rdx
/
pop rcx
,或者您实际上想要保存的调用保留寄存器,例如 RBP。 您需要将 RSP 的总变化量设为 8 的奇数倍,计算所有推送以及从函数入口到任何
sub rsp
call
,

即整数
8 + 16*n
n
字节。

在函数入口处,RSP 距离 16 字节对齐有 8 个字节,因为

call
推送了 8 字节返回地址。请参阅从 x86-64 打印浮点数似乎需要保存 %rbp主和堆栈对齐,以及使用 GNU 汇编器在 x86_64 中调用 printf 。这是一项 ABI 要求,过去当 printf 没有任何 FP 参数时,您可以违反该要求。但现在不再了。

另请参阅 为什么 x86-64 / AMD64 System V ABI 强制要求 16 字节堆栈对齐?

换句话说,

RSP % 16 == 8
在函数入口处,并且您需要在
RSP % 16 == 0
函数之前确保
call
。你如何做到这一点并不重要。 (如果不这样做,并非所有功能实际上都会崩溃,但 ABI 确实要求/保证它。)


gcc 的 glibc scanf 代码生成现在依赖于 16 字节堆栈对齐
即使

AL == 0

它似乎在

__GI__IO_vfscanf
中的某处自动矢量化复制了 16 个字节,常规
scanf
在将其寄存器参数溢出到堆栈1后调用它。 (许多类似的调用 scanf 的方法共享一个大的实现作为各种 libc 入口点的后端,如
scanf
fscanf
等)

我下载了Ubuntu 18.04的libc6二进制包:https://packages.ubuntu.com/bionic/amd64/libc6/download并提取文件(带有

7z x blah.deb
tar xf data.tar
,因为7z知道如何提取很多文件格式)。

我可以用

LD_LIBRARY_PATH=/tmp/bionic-libc/lib/x86_64-linux-gnu ./bad-printf
重现你的 bug,而且我的 Arch Linux 桌面上的系统 glibc 2.27-3 也能证明这一点。

使用 GDB,我在你的程序上运行它并执行了

set env LD_LIBRARY_PATH /tmp/bionic-libc/lib/x86_64-linux-gnu
然后
run
。使用
layout reg
,反汇编窗口在收到 SIGSEGV 时看起来像这样:

   │0x7ffff786b49a <_IO_vfscanf+602>        cmp    r12b,0x25                                                                                             │
   │0x7ffff786b49e <_IO_vfscanf+606>        jne    0x7ffff786b3ff <_IO_vfscanf+447>                                                                      │
   │0x7ffff786b4a4 <_IO_vfscanf+612>        mov    rax,QWORD PTR [rbp-0x460]                                                                             │
   │0x7ffff786b4ab <_IO_vfscanf+619>        add    rax,QWORD PTR [rbp-0x458]                                                                             │
   │0x7ffff786b4b2 <_IO_vfscanf+626>        movq   xmm0,QWORD PTR [rbp-0x460]                                                                            │
   │0x7ffff786b4ba <_IO_vfscanf+634>        mov    DWORD PTR [rbp-0x678],0x0                                                                             │
   │0x7ffff786b4c4 <_IO_vfscanf+644>        mov    QWORD PTR [rbp-0x608],rax                                                                             │
   │0x7ffff786b4cb <_IO_vfscanf+651>        movzx  eax,BYTE PTR [rbx+0x1]                                                                                │
   │0x7ffff786b4cf <_IO_vfscanf+655>        movhps xmm0,QWORD PTR [rbp-0x608]                                                                            │
  >│0x7ffff786b4d6 <_IO_vfscanf+662>        movaps XMMWORD PTR [rbp-0x470],xmm0                                                                          │

因此,它将两个 8 字节对象复制到堆栈中,使用

movq
+
movhps
进行加载,使用
movaps
进行存储。但由于堆栈未对齐,
movaps [rbp-0x470],xmm0
会出现故障。

我没有获取调试版本来准确找出 C 源代码的哪一部分变成了这个,但该函数是用 C 编写的,并由 GCC 编译并启用了优化。 GCC 一直被允许这样做,但直到最近它才变得足够聪明,可以通过这种方式更好地利用 SSE2。


脚注 1:带有

AL != 0
的 printf / scanf 始终需要 16 字节对齐,因为 gcc 的可变参数函数代码生成使用 test al,al / je 来溢出完整的 16 字节 XMM 寄存器 xmm0..7 并在其中对齐存储那种情况。
__m128i
可以是可变参数函数的参数,而不仅仅是
double
,并且 gcc 不会检查该函数是否实际读取过任何 16 字节 FP 参数。


0
投票

如上所述,我追踪到有问题的指令为:

0x7ffff7e4adcd <_IO_str_init_static_internal+61>:    movaps %xmm0,(%rsp)

glibc 内部,strops.c 中。它向堆栈执行 128 位移动。

我查看了“System V 应用程序二进制接口 AMD64 架构处理器补充”,我没有看到任何关于始终将堆栈对齐到 128 位的要求。有一个注释是需要对齐内存中的 128 个值。

这似乎是一个事实上的要求,据我所知,仅适用于 glibc,影响到每个人。否则,您将必须避免使用 glibc 或检查您是否正在调用 glibc 例程(或调用那里的例程等)。

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