【问题标题】:Segmentation fault error when using fgets in assembly?在汇编中使用 fgets 时出现分段错误错误?
【发布时间】:2020-03-10 18:54:07
【问题描述】:

我在汇编中使用了fgets() 函数,它应该可以工作,但是我的缓冲区有问题。有没有办法定义一个 char 指针?我问是因为该函数需要一个 char 指针作为第一个参数。

这里你可以看到我的代码:

; nasm fgets.asm -f elf64 -o fgets.o
; gcc -no-pie fgets.o
; ./a.out

; Define fgets as an external function
extern fgets

SECTION .DATA
buffer: db "0000000000", 0

SECTION .TEXT
    global main

main:
    push rbp ; Push stack

    ; Set up parameters and call the C function

    mov rdi, buffer
    mov rsi,10
    mov rdx, 1
    mov rax,0
    call fgets

    pop rbp     ; Pop stack

    mov rax,0   ; Exit code 0
    ret         ; Return

我想从标准输入中读取最多 10 个字符的内容。

【问题讨论】:

  • 你已经正确设置了缓冲区——你的问题是第三个参数(在 rdx 中),它需要是 FILE *,而不是文件描述符。
  • 你知道我如何从标准输入读取数据吗?
  • 是的,从全局变量[stdin]中加载FILE *stdin的值。查看编译器输出(但要注意 GNU .intel_syntax 与 NASM 语法差异)
  • 请注意,stdin 不一定是全局变量——在非 glibc 实现中,它可能是访问某个数组或其他名称不同的对象的宏。但只要你只关心 glibc,你应该没问题mov rdx, qword [stdin]
  • 您不需要先将 AL 归零; fgets 不采用可变数量的参数。它不会伤害,但它与mov ecx, 0 一样无关紧要。

标签: c assembly x86 glibc


【解决方案1】:

FILE * 不是文件描述符。不要像你那样传递1,而是传递[stdin](这是因为stdin是glibc中的全局指针,而NASM中的关键字stdin是指向它的指针):

    mov rdx, [stdin]

如果您使用 GAS,这将起作用:

    mov stdin, %rdx

但是,您可能应该使用 RIP 相对寻址;这允许您的可执行文件被重新定位,并且是 PIE(与位置无关的可执行文件)所必需的,这是现在的默认设置。在 NASM 中,只需将其放在文件顶部即可:

default rel

在 GAS 中,它有点复杂。您必须将(%rip) 添加到您使用的所有外部符号中,如下所示:

    mov stdin(%rip), %rdx

这会将位于stdin 的内存(即您要查找的8 字节FILE * 指针)加载到rdx

【讨论】:

  • 感谢它与 [stdin] 一起使用,但是当我将某些内容写入控制台时,我也遇到了分段错误:(
  • @JanWolfram 抱歉,这应该可以解决。
  • 为什么要删除 RIP 相关的寻址和解释?在 x86-64 GAS 代码中,您总是需要mov symbol(%rip), %reg,而不是mov symbol, %reg。在 NASM 中相同:default rel[rel symbol]。另外,stdin 不是关键字,只是符号名称。
  • @PeterCordes 因为它不再是 my 的答案了。如果您愿意,我会写更多内容并添加注释。
  • 我喜欢我对为什么它是负载的解释,而不仅仅是将符号stdin 的地址放入寄存器。我也不会以糟糕的 GAS 方式进行加载。我发布了一个答案
【解决方案2】:

fgetsFILE * pointer arg(不是整数文件描述符)作为第三个 arg。

另外,1 是标准输出文件描述符,而不是 stdin。但无论如何,当fgets 取消引用1 作为指针时,它会出现段错误。您可以使用调试器来查找出错的指令。

在 C 语言中,您可以调用 fgets(buf, len, stdin)。全局变量stdinFILE *stdin 类型的指针。该指针值(指向不透明的FILE 结构)本身存储在内存中的符号地址stdin 处。 (Glibc 的启动代码将这个指针初始化为指向它分配的 FILE 结构)。

因此,您希望从静态存储中加载一个 qword 指针作为fgets 的第三个参数。您可以通过查看 C 函数的编译器输出自己看到这一点。

default rel            ; Use RIP-relative addressing by default
extern  stdin
extern  fgets

...
main:
    push   rax             ; dummy push to realign the stack by 16
   ...
    lea    rdi, [buffer]       ; RIP-relative LEA, or mov edi, buffer in a non-PIE
    mov    esi, buffer.len
    mov    rdx, [stdin]
    call   fgets
   ...
    pop    rcx             ; dummy pop to readjust RSP
   ret

section .bss
 buffer: resb 11      ; reserve 11 bytes, zero-filled
 .len equ $ - buffer

请注意,将stdin 变量本身的地址 放入寄存器中不仅仅是lea rdx, [stdin]mov edx, stdinfgets 按值获取指针 arg,而不是作为指向指针的指针,也不是 FILE stdin,而是 FILE *stdin

我让汇编器为我计算buffer 的长度。我用零字节而不是 ASCII '0' 字符填充它。

fgets() 最多读取 len-1 个字节, 并在其后写入终止 \0,因此您需要通过 11(完整缓冲区的大小)来读取到 10 个字节。

fgets 不是可变参数,因此您无需在调用它之前将 AL 归零。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-01-15
    • 1970-01-01
    • 2022-01-14
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2020-05-30
    • 2015-08-27
    相关资源
    最近更新 更多