GLIBC SCANF分割故障从不对RSP的函数调用

发布于 2025-01-27 23:52:08 字数 685 浏览 1 评论 0原文

编译下面的代码:

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上做错了什么?

When compiling below code:

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

using:

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

and then run

./example

it runs, print: enter a number:
but then crashes and prints:
Segmentation fault (core dumped)

So printf works fine but scanf not.
What am I doing wrong with scanf so?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夜无邪 2025-02-03 23:52:08

使用sub rsp,8/在功能的开始/结束处添加RSP,8在您的启动/结束时将堆栈重新列入16个字节之前函数做呼叫

或者更好地按/弹出虚拟寄存器,例如按下RDX/pop rcx或像RBP一样,您实际上想保存的呼叫寄存器。 您需要总的更改为RSP是8计数的奇数倍数,并且sub rsp从函数输入到任何call>呼叫。<<。 br>
IE 8 + 16*n整数的字节n

在功能输入时,RSP距离16字节对齐为8个字节,因为呼叫按下了8字节返回地址。 See Printing floating point x86-64的数字似乎需要保存%rbp
main and stack Alignment 使用GNU汇编器中的x86_64中调用printf 。这是一个ABI要求,您过去可以在没有任何FP ARG的printf时逃脱违规。但不再是。

另请参见为什么X86-64/AMD64系统v ABI授权16字节堆栈对齐方式?

换句话说, rsp%16 == 8在功能输入中,您需要在调用函数之前确保RSP%16 == 0。您如何做这没关系。 (如果您不这样做,并非所有功能实际上都会崩溃,但是ABI确实需要/保证。)


GCC的glibc scanf (以及最近的printf ) Code>)现在,即使al == 0,也取决于16字节堆栈对齐。

它似乎已自动归纳化复制16个字节在__ GI__IO_VFSCANF中的某个地方,该字节在将其寄存器ARG溢出到stack 1 后,常规scanf调用。 (呼叫SCANF的许多类似方法共享一个大实施,作为各种LIBC入口点的后端,例如scanffscanf等)

我下载了Ubuntu 18.04's libc6二进制软件包: https://packages.ubuntu.com/bionic.com/bionic/bionic/amd64/libc6/下载并提取文件(使用7z x blah.debtar xf data.tar,因为7z知道如何提取大量文件格式)。

我可以用ld_library_path =/tmp/bionic-libc/lib/x86_64-linux-gnu ./bad-printf ld_library_path =/tmp/bionic-libc/x86_64-linux-gnu。 Linux桌面。

使用GDB,我将其运行在您的程序上,然后设置ENV LD_LIBRARY_PATH/TMP/BIONIC-LIBC/LIB/lib/X86_64-Linux-GNU然后Run。使用布局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的堆栈中加载和移动存储。但是,随着堆栈未对准,移动[RBP-0x470],XMM0故障。

我没有抓住调试构建以确切找出C源的哪一部分将其转化为此,但是该功能是用C编写的,并由GCC编写并启用了优化。 GCC一直被允许这样做,但直到最近才变得足够聪明,可以更好地利用SSE2。


脚注1:带有al!= 0 < / code>的printf / scanf始终需要16字节对齐,因为GCC的variadic函数代码 - 基因使用test Al,al / je来溢出完整的16字节XMM XMM XMM0 ..7在这种情况下,有对齐的商店。 __ M128i可以是变异函数的参数,而不仅仅是double,并且GCC不会检查该函数是否实际读取任何16字节FP ARGS。

Use sub rsp, 8 / add rsp, 8 at the start/end of your function to re-align the stack to 16 bytes before your function does a call.

Or better push/pop a dummy register, e.g. push rdx / pop rcx, or a call-preserved register like RBP you actually wanted to save anyway. You need the total change to RSP to be an odd multiple of 8 counting all pushes and sub rsp, from function entry to any call.
i.e. 8 + 16*n bytes for whole number n.

On function entry, RSP is 8 bytes away from 16-byte alignment because the call pushed an 8-byte return address. See Printing floating point numbers from x86-64 seems to require %rbp to be saved,
main and stack alignment, and Calling printf in x86_64 using GNU assembler. This is an ABI requirement which you used to be able to get away with violating when there weren't any FP args for printf. But not any more.

See also Why does the x86-64 / AMD64 System V ABI mandate a 16 byte stack alignment?

To put it another way, RSP % 16 == 8 on function entry, and you need to ensure RSP % 16 == 0 before you call a function. How you do this doesn't matter. (Not all functions will actually crash if you don't, but the ABI does require/guarantee it.)


gcc's code-gen for glibc scanf (and more recently printf) now depends on 16-byte stack alignment even when AL == 0.

It seems to have auto-vectorized copying 16 bytes somewhere in __GI__IO_vfscanf, which regular scanf calls after spilling its register args to the stack1. (The many similar ways to call scanf share one big implementation as a back end to the various libc entry points like scanf, fscanf, etc.)

I downloaded Ubuntu 18.04's libc6 binary package: https://packages.ubuntu.com/bionic/amd64/libc6/download and extracted the files (with 7z x blah.deb and tar xf data.tar, because 7z knows how to extract a lot of file formats).

I can repro your bug with LD_LIBRARY_PATH=/tmp/bionic-libc/lib/x86_64-linux-gnu ./bad-printf, and also it turns out with the system glibc 2.27-3 on my Arch Linux desktop.

With GDB, I ran it on your program and did set env LD_LIBRARY_PATH /tmp/bionic-libc/lib/x86_64-linux-gnu then run. With layout reg, the disassembly window looks like this at the point where it received 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                                                                          │

So it copied two 8-byte objects to the stack with movq + movhps to load and movaps to store. But with the stack misaligned, movaps [rbp-0x470],xmm0 faults.

I didn't grab a debug build to find out exactly which part of the C source turned into this, but the function is written in C and compiled by GCC with optimization enabled. GCC has always been allowed to do this, but only recently did it get smart enough to take better advantage of SSE2 this way.


Footnote 1: printf / scanf with AL != 0 has always required 16-byte alignment because gcc's code-gen for variadic functions uses test al,al / je to spill the full 16-byte XMM regs xmm0..7 with aligned stores in that case. __m128i can be an argument to a variadic function, not just double, and gcc doesn't check whether the function ever actually reads any 16-byte FP args.

若相惜即相离 2025-02-03 23:52:08

如上所述,我将有问题的说明追溯到:

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

glibc内部,在strops.c中。它做了128位移至堆栈。

修复了我的堆栈对齐后,我发现进入MAIN:

(gdb) b main
Breakpoint 1 at 0x721a: file test.s, line 145.
(gdb) r
Starting program: /home/samiam/projects/pascal/pascal-p6/test

Breakpoint 1, main () at test.s:145
(gdb) i r
rax            0x55555555b21a      93824992260634
rbx            0x55555555b290      93824992260752
rcx            0x555555592860      93824992487520
rdx            0x7fffffffdf48      140737488346952
rsi            0x7fffffffdf38      140737488346936
rdi            0x1                 1
rbp            0x0                 0x0
rsp            0x7fffffffde48      0x7fffffffde48
...

GCC生成的启动将控制权传递到我的程序中,该堆栈没有对准16个字节!

我使用了对准器:

andq    $0xfffffffffffffff0,%rsp

这看起来很浪费,但是意识到堆栈必须按照定义对齐64位,因此这要么要在进入时将堆栈倒入0或64位。

呼叫指令将在堆栈上放置8个字节(RIP),这意味着呼叫将在16个字节的要求上固有地将堆栈不一致。该(在GCC中)由框架序列固定:

    pushq   %rbp
    movq    %rsp, %rbp

因为推动甚至将堆叠到16个字节。

As above, I traced down the offending instruction as:

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

Inside of glibc, in strops.c. Its doing a 128 bit move to the stack.

After fixing my stack alignments, I found that on entry to main:

(gdb) b main
Breakpoint 1 at 0x721a: file test.s, line 145.
(gdb) r
Starting program: /home/samiam/projects/pascal/pascal-p6/test

Breakpoint 1, main () at test.s:145
(gdb) i r
rax            0x55555555b21a      93824992260634
rbx            0x55555555b290      93824992260752
rcx            0x555555592860      93824992487520
rdx            0x7fffffffdf48      140737488346952
rsi            0x7fffffffdf38      140737488346936
rdi            0x1                 1
rbp            0x0                 0x0
rsp            0x7fffffffde48      0x7fffffffde48
...

The gcc generated startup passes control to my program with a stack that isn't aligned to 16 bytes!

I used the aligner:

andq    $0xfffffffffffffff0,%rsp

This appears wasteful, but realize that the stack must be 64 bit aligned by definition, so this is either going to dump 0 or 64 bits of stack on entry.

A call instruction is going to place 8 bytes on the stack (the rip), which means that a call is going to inherently UN-align the stack as far as the 16 byte requirement. This (in gcc) is fixed by the framing sequence:

    pushq   %rbp
    movq    %rsp, %rbp

Because the push is going to even up the stack to 16 bytes.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文