【问题标题】:Segmentation fault (core dumped) in x86 assemblyx86 程序集中的分段错误(核心已转储)
【发布时间】:2023-01-01 18:46:15
【问题描述】:

我编写了一个 x86 (IA-32) 汇编程序,它应该从标准输入中读取一个字符串,但无法理解为什么它会导致 SEGFAULT。

我用 GNU 汇编器组装了这个程序 使用以下标志:

$ gcc (flags used) (file_name)

下面是程序的代码:

.text

.globl _start

MAX_CHAR=30

_start:

    ## Start message ##
    movl $4, %eax
    movl $1, %ebx
    movl $msg, %ecx
    movl $len, %edx
    int $0x80


    ## READ ##
    movl $3, %eax       #sys_read (number 3)
    movl $0, %ebx       #stdin (number 0)
    movl %esp, %ecx     #starting point
    movl $MAX_CHAR, %edx    #max input
    int $0x80       #call


    ## Need the cycle to count input length ##  
    movl $1, %ecx       #counter
end_input:
    xor %ebx, %ebx
    mov (%esp), %ebx
    add $1, %esp        #get next char to compare 
    add $1, %ecx        #counter+=1
    cmp $0xa, %ebx      #compare with "\n" 
    jne end_input       #if not, continue 


    ## WRITE ##
    sub %ecx, %esp      #start from the first input char
    movl $4, %eax       #sys_write (number 4)
    movl $1, %ebx       #stdout (number 1)
    movl %ecx, %edx     #start pointer
    movl %esp, %ecx     #length
    int $0x80       #call
     

    ## EXIT ##
    movl $1, %eax
    int $0x80   

.data

msg: .ascii "Insert an input:\n"
len =.-msg

是什么导致了 SEGFAULT?

欢迎任何帮助。

【问题讨论】:

  • 请注意,将来有关汇编语言的问题应始终标记为assembly,以及您正在为其编码的体系结构(此处为x86)。 gnu-assembler 用于特定于 GNU 汇编器的东西,它可以针对许多不同的体系结构。
  • 另外请具体说明您面临的问题。仅仅说代码“错误”是非常无用的。你是怎么运行的?用什么输入?它做了什么?你期望它做什么?
  • 抱歉,这是 x86 Linux 计算机 5.15.0-52-generic #58-Ubuntu SMP Thu Oct 13 08:03:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
  • movl %esp, %ecx:这将覆盖堆栈的当前内容,这是不好的;堆栈甚至可能不足以容纳您的输入数据,这会更糟。您需要减少堆栈指针以为您的缓冲区腾出空间。类似于 subl $MAX_CHAR, %esp,除了堆栈应至少保持 4 个字节对齐,所以 subl $32, %esp。然后在完成缓冲区后添加 32。
  • mov (%esp), %ebx 加载一个 32 位字(4 个字节),而您实际上只想比较一个字节。所以使用 8 位寄存器,例如mov (%reg), %bl 然后是 cmp $0xa, %bl。或者只是将它们组合在一起并完全跳过寄存器加载:cmpb $0xa, (%reg)

标签: assembly x86 segmentation-fault


【解决方案1】:

我看到的错误:

  • 堆栈管理。您不能假设程序入口时堆栈中已有的数据,也不能假设有多少可用空间。并且你不能在%esp中的当前地址下面写;例如,信号处理程序可以随时意外地覆盖它。所以你需要从%esp中减去为你的缓冲区分配空间,然后在完成后加回去。

  • 此外,%esp 应始终保持 4 字节对齐。这不是严格的架构要求,但打破这个规则会导致执行效率低下和很多混乱。因此,要为 30 字节的缓冲区创建空间,请向上取整并从 %esp 中减去 32。

    当您想调用用 C 编写的函数时,还有其他对齐要求,请参阅gcc x86-32 stack alignment and calling printf

  • 由于上述两个原因,不要在循环中使用%esp 作为指针变量:不要管它,选择其他寄存器。

  • 操作数大小。 x86-32 指令通常可以在 8、16 或 32 位上运行。 l 后缀和/或 32 位寄存器(eax、ebx 等)的使用表示 32 位指令。所以 mov (%esp), %ebx 从内存中加载 4 个字节,cmp $0xa, %ebx 将它们与 32 位值 0x0000000a 进行比较。因此,比较将是错误的,除非内存中接下来的三个字节恰好全部为零。要获得 8 位操作,请使用 8 位寄存器(al、bl、ah、bh 等),但要注意它们与相应的 16 位和 32 位寄存器重叠;所以不要尝试同时使用 %ebx%bl 做不同的事情。试试movb (%reg), %bl(如上所述,%reg不应该是%esp,而是你使用的任何寄存器)和cmpb $0xa, %blb 后缀是可选的,因为大小是从 8 位 bl 寄存器中推断出来的,但是由于您在其余大部分鳕鱼中使用后缀,因此最好保持一致。)

  • 您在这里编写的是 32 位代码,所以一定要在 32 位模式下构建您的程序。例如,如果使用 gcc,则需要 -m32 标志。从长远来看,您可能更愿意学习 64 位 x86 汇编; 32 位 x86 代码几乎已经过时了。

  • 实际上,首先通过搜索换行符 (0xa) 来计算输入的长度并不合适。如果输入根本不包含换行符(如果该行的长度超过 30 个字节,则这是可能的),那么您的循环将从缓冲区的末尾运行。要找出读取了多少个字符,您应该使用 read 的返回值,它在 read 系统调用返回后留在 %eax 中。 (如果为零,则到达文件末尾;如果为负,则出现错误。)

    此外,如果您以默认模式从终端读取,通常您一次最多只能读取一行,因此如果有 它将对应于 @987654343 返回的输入的末尾@. (但如果标准输入是从文件重定向的,则这不适用。)

【讨论】:

  • _start 的实践中,您知道 argv[]envp[] 将高于初始 ESP,因此在任何普通系统中,仅需几个环境变量就足以容纳 30 字节的堆栈空间。实际上,args 和环境的指向字符串也在其上方的堆栈空间中,因此对于玩具黑客来说,覆盖该空间基本上没问题。只要您了解自己在做什么,并且这在需要返回的函数中是行不通的。所以我同意以这种方式使用 ESP 对初学者来说可能是一个错误,尤其是没有 cmets,但它并没有严格地破坏。
  • 不过,将 ESP 递增超过字符然后再减去是一个错误。实际上,在没有安装任何信号处理程序的情况下,没有什么会异步破坏 ESP 下面的空间。但 ABI 并不能保证这一点。如果您运行了print foo(),那么调试器可能会使用其当前的 ESP 在您的进程中发明对foo符号的调用。 (通常调试器是非侵入式的,但作为表达式的一部分评估函数调用会触发目标进程中代码及其堆栈的运行。)
猜你喜欢
  • 2019-01-14
  • 1970-01-01
  • 2020-04-21
  • 2017-07-24
  • 1970-01-01
  • 2021-12-27
  • 2015-06-25
  • 2021-06-03
相关资源
最近更新 更多