x86汇编程序segmentation fault问题排查求助
问题描述
我是汇编语言新手,掌握的相关知识有限。我编写了一段Linux环境下的x86汇编程序,预期实现:读取用户输入的message,先输出"Hello, World!\n",再输出用户输入的内容。但程序运行出现segmentation fault,我怀疑问题出在_get_buffer_size标签处。以下是程序代码及各标签说明:
# TODO: Read message from input, output "Hello, World!", and output message on newline .section .data # Data that will be used by program ## * CONSTANTS * ## .equ LINUX_SYSCALL, 0x80 # Interrupt ID to make syscall on kernel (GNU/Linux) ## * SYCALL IDs * ## .equ EXIT_SYS, 1 # SYSCALL Number for exiting .equ OPEN_SYS, 5 # SYSCALL Number for opening files .equ READ_SYS, 3 # SYSCALL Number for reading from files .equ WRITE_SYS, 4 # SYSCALL Number for writing to files .equ CLOSE, 6 # SYSCALL Number to close files ## * File modes * ## .equ READ_MODE, 0 # Reading mode for files .equ WRITE_MODE, 03101 # Writing mode for files ## * File permissions * ## .equ DEFAULT_PERMISSION, 0666 # Default permission to interact with files ## * Default file descriptors * ## .equ STDIN, 0 # Default GNU/Linux input file .equ STDOUT, 1 # Default GNU/Linux output file .equ STDERR, 2 # Default GNU/Linux error-log file ## * Constant variables * ## OUTPUT_MSG: # "Hello, World!" message .ascii "Hello, World! " SIZE_OF_MSG: # Size of "Hello, World!" .long 14 # remember to count '\n' as 1 byte .section .bss # Reserved memory area .equ BUFFER_SIZE, 500 # Size of buffer that will be read from STDIN .lcomm BUFFER, 500 # Buffer that will be read from STDIN .section .text # Code area .globl _start # Starting function definiton # @brief: Label to terminate program successfuly _exit_success: movl $EXIT_SYS, %eax movl $0, %ebx int $LINUX_SYSCALL # @brief: Label to output `message` that stored in `%ecx` # @param: `%ecx` must have `message` to output # @param: `%edx` must have size of `message` _output_msg: movl $WRITE_SYS, %eax movl $STDOUT, %ebx int $LINUX_SYSCALL ret # @brief: Label to get input `buffer` from STDIN # @param: `%ecx` must have `buffer` to store data # @param: `%edx` must have size of `buffer` which will be used for data _read_msg: movl $READ_SYS, %eax movl $STDIN, %ebx int $LINUX_SYSCALL ret # @brief: Label to get size of a `buffer` # @param: `%eax` must have pointer to `buffer`'s first char # @return: size of buffer will be stored in `%edx` _get_buffer_size: movl $0, %edx jmp .start_buffer_size ret .start_buffer_size: incl %eax incl %edx cmpl $0, (%eax) jnz .start_buffer_size ret # @brief: _start function to start program _start: ## * Get input message * ## movl $BUFFER, %ecx movl $BUFFER_SIZE, %edx call _read_msg ## * Output constant message * ## movl $OUTPUT_MSG, %ecx # Store message in %ecx parameter movl $SIZE_OF_MSG, %edx # Store size of message in %edx parameter call _output_msg # Output the constant message ## * Output message that was read * ## movl $BUFFER, %ecx call _get_buffer_size call _output_msg jmp _exit_success # Terminate program
标签说明
- _exit_success:用于正常终止程序,
EXIT_SYS值为1,LINUX_SYSCALL值为0x80。 - _output_msg:输出
%ecx中存储的消息,要求%edx存放消息长度,WRITE_SYS值为4,STDOUT值为1。 - _read_msg:从
STDIN(值为0)读取输入,要求%ecx存放BUFFER,%edx存放BUFFER的预留大小。 - _get_buffer_size:获取buffer长度,要求
%eax存放buffer首字符指针,返回值(buffer长度)存储在%edx中。
备注:我知道汇编里标准函数调用不是这么写的,只是栈操作对我来说太复杂,所以自己用寄存器传递参数和返回值。现在需要排查程序出现段错误的原因。
问题排查与修复
核心错误点
_get_buffer_size参数传递错误
调用该标签时,你把BUFFER地址放到了%ecx,但标签要求参数存在%eax。此时%eax里是read系统调用的返回值(实际读取字节数),不是buffer指针,导致后续访问(%eax)时触发非法内存访问,直接引发段错误。_get_buffer_size逻辑缺陷- 初始跳转后直接
incl %eax,跳过了buffer第一个字符,不仅长度计算少1,还会在buffer为空时直接越界。 - 循环依赖
0终止符判断结束,但read系统调用不会自动在输入末尾添加0。如果用户输入不含0,循环会持续读取非法内存,最终触发段错误。
- 初始跳转后直接
常量消息输出的地址/数值混淆
movl $SIZE_OF_MSG, %edx把SIZE_OF_MSG的标签地址存入%edx,而非它存储的数值14。正确写法是movl SIZE_OF_MSG, %edx(去掉$),用于加载内存中的数值。未利用
read的返回值read系统调用会把实际读取的字节数存在%eax中,这是最可靠的输入长度,完全可以直接用于后续输出,无需手动计算长度。
修复方案
方案1:简化逻辑,直接用read返回值(推荐)
去掉_get_buffer_size的调用,利用read的返回值作为输出长度,彻底避免手动计算的问题:
_start: ## * Get input message * ## movl $BUFFER, %ecx movl $BUFFER_SIZE, %edx call _read_msg movl %eax, %edx # 保存read返回的实际读取字节数到%edx ## * Output constant message * ## movl $OUTPUT_MSG, %ecx movl SIZE_OF_MSG, %edx # 加载SIZE_OF_MSG存储的数值14 call _output_msg ## * Output message that was read * ## movl $BUFFER, %ecx # 直接使用之前保存的%edx作为长度 call _output_msg jmp _exit_success
方案2:修复_get_buffer_size(不推荐,依赖输入含0终止符)
如果一定要保留手动计算长度的逻辑,需修复参数传递和循环逻辑:
# 修复后的_get_buffer_size _get_buffer_size: movl $0, %edx .start_buffer_size: cmpl $0, (%eax) # 先检查当前字符是否为0 je .end_buffer_size incl %eax incl %edx jmp .start_buffer_size .end_buffer_size: ret # 调用时的正确参数传递 _start: # ... 其他代码不变 ... ## * Output message that was read * ## movl $BUFFER, %eax # 把buffer地址放到%eax,符合标签要求 call _get_buffer_size movl $BUFFER, %ecx # 恢复%ecx为buffer地址(因为_get_buffer_size修改了%eax) call _output_msg jmp _exit_success
内容的提问来源于stack exchange,提问作者cMard
相关产品推荐
相关产品推荐

