在编译以下代码时:
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怎么了?
答案 0 :(得分:4)
在函数开始/结束时使用sub rsp, 8
/ add rsp, 8
,以便在函数执行call
之前将堆栈重新对齐为16个字节。
或者最好压入/弹出一个虚拟寄存器,例如push rdx
/ pop rcx
,或保存/恢复保留呼叫的寄存器,如RBP。
在函数输入中,RSP与16字节对齐方式相距8字节,因为call
推送了8字节的返回地址。参见Printing floating point numbers from x86-64 seems to require %rbp to be saved,
main and stack alignment和Calling printf in x86_64 using GNU assembler。这是一项ABI要求,您过去可以在没有printf的FP arg的情况下避免违反。但没有更多了。
gcc用于glibc scanf的代码生成现在依赖于16字节堆栈对齐,即使AL == 0
。
似乎在__GI__IO_vfscanf
中的某个地方自动向量化了16字节的复制,在将寄存器的args溢出到堆栈 1 之后,常规scanf
会调用它。 (许多类似的调用scanf的方法作为一个大的实现,作为scanf
,fscanf
等各种libc入口点的后端)
我下载了Ubuntu 18.04的libc6二进制软件包:https://packages.ubuntu.com/bionic/amd64/libc6/download并提取了文件(使用7z x blah.deb
和tar xf data.tar
,因为7z知道如何提取很多文件格式)。 / p>
我可以使用LD_LIBRARY_PATH=/tmp/bionic-libc/lib/x86_64-linux-gnu ./bad-printf
来复制您的错误,事实证明,我的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可变参数函数的代码源使用测试al,al / je溢出了完整的16字节XMM reg xmm0..7在那种情况下与对齐的商店。 __m128i
可以是可变参数函数的参数,而不仅仅是double
,并且gcc不会检查该函数是否实际读取了任何16字节FP args。