You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

执行cmpq指令触发SIGTTIN停止信号的汇编问题排查

问题背景

我正在阅读 Programming from the Ground Up 一书第五章《程序中的文件使用》第63页的示例代码,尝试将书中的32位x86汇编代码移植为amd64 64位版本,编写的代码如下:

# system call numbers
.equ SYS_OPEN, 2
.equ SYS_WRITE, 1
.equ SYS_READ, 0
.equ SYS_CLOSE, 3
.equ SYS_EXIT, 60

.equ O_RDONLY, 0
.equ O_CREAT_WRONLY_TRUNC, 03101

# standard file descriptors
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2

# system call interrupt
.equ LINUX_SYSCALL, 0x80
.equ END_OF_FILE, 0

.equ NUMBER_ARGUMENTS, 2

.section .bss
.equ BUFFER_SIZE, 500
.lcomm BUFFER_DATA, BUFFER_SIZE

.section .text

# STACK POSITIONS
.equ ST_SIZE_RESERVE, 16
.equ ST_FD_IN, -8
.equ ST_FD_OUT, -16
# main function arguments
.equ ST_ARGC, 0     # Number of arguments
.equ ST_ARGV_0, 8   # Name of program
.equ ST_ARGV_1, 16  # Input file name
.equ ST_ARGV_2, 24  # Output file name

.globl _start
_start:

    ### INITIALIZE PROGRAM ###
    # save the stack pointer
    movq %rsp, %rbp
    
    # Allocate space for our file descriptors on the stack
    subq $ST_SIZE_RESERVE, %rsp

open_files:
open_fd_in:
    ### OPEN INPUT FILE ###
    # open syscall
    movq $SYS_OPEN, %rax
    # input filename into %rbx
    movq ST_ARGV_1(%rbp), %rbx

    # read-only flag
    movq $O_RDONLY, %rcx
    # this doesn't really matter for reading
    movq $0666, %rdx
    # call Liunx
    int $LINUX_SYSCALL

store_fd_in:
    # save the given file descriptor
    movq %rax, ST_FD_IN(%rbp)

open_fd_out:
    ### OPEN OUTPUT FILE ###
    # open the file
    movq $SYS_OPEN, %rax
    # output filename into %rbx
    movq ST_ARGV_2(%rbp), %rbx
    # flags for writing to the file
    movq $O_CREAT_WRONLY_TRUNC, %rcx
    # mode for new file (if it's created)
    movq $0666, %rdx
    # call Linux
    int $LINUX_SYSCALL

store_fd_out:
    # store the file descriptor here
    movq %rax, ST_FD_OUT(%rbp)

    ### BEGIN MAIN LOOP ###
read_loop_begin:
    ### READ IN A BLOCK FROM THE INPUT FILE ###
    movq $SYS_READ, %rax
    # get the input descriptor
    movq ST_FD_IN(%rbp), %rbx
    # the location to read into
    movq $BUFFER_DATA, %rcx
    # the size of the buffer
    movq $BUFFER_SIZE, %rdx
    # Size of buffer read is returned in %rax
    int $LINUX_SYSCALL
    
    ### EXIT IF WE'RE REACHED THE END ###
    # check for end of file marker
    cmpq $END_OF_FILE, %rax
    # if found or on error, go to the end
    jle end_loop

continue_read_loop:
    ### CONVERT THE BLOCK TO UPPER CASE ###
    pushq $BUFFER_DATA  # loction of buffer
    pushq %rax      # size of the buffer
    callq convert_to_upper  
    popq %rax       # get the size back
    addq $8, %rsp       # restore %rsp

    ### WRITE THE BLOCK OUT TO THE OUTPUT FILE ###
    # size of the buffer
    movq %rax, %rdx
    movq $SYS_WRITE, %rax
    # file to use
    movq ST_FD_OUT(%rbp), %rbx
    # location of the buffer
    movq $BUFFER_DATA, %rcx
    int $LINUX_SYSCALL
    
    ### CONTINUE THE LOOP ###
    jmp read_loop_begin

end_loop:
    ### CLOSE THE FILES ###
    movq $SYS_CLOSE, %rax
    movq ST_FD_OUT(%rbp), %rbx
    int $LINUX_SYSCALL
    
    movq $SYS_CLOSE, %rax
    movq ST_FD_IN(%rbp), %rbx
    int $LINUX_SYSCALL

### EXIT ###
    movq $SYS_EXIT, %rax
    movq $0, %rbx
    int $LINUX_SYSCALL
    

### CONSTANTS ###
# The lower boundary of our search
.equ LOWERCASE_A, 'a'
# The lower boundary of our search
.equ LOWERCASE_Z, 'z'
# Conversion between upper and lower case
.equ UPPER_CONVERSION, 'A' - 'a'


### STACK STUFF ###
.equ ST_BUFFER_LEN, 24  # length of buffer
.equ ST_BUFFER, 32  # actual buffer

convert_to_upper:
    pushq %rbp
    movq %rsp, %rbp

### SET UP VARIABLES ###
    movq ST_BUFFER(%rbp), %rax
    movq ST_BUFFER_LEN(%rbp), %rbx
    movq $0, %rdi

    # if a buffer with zero length was given to us, just leave
    cmpq $0, %rbx
    je end_convert_loop
    
convert_loop:
    # get the current byte
    movb (%rax, %rdi, 1), %cl
    
    # go to the next byte unless it is between  
    # 'a' and 'z'
    cmpb $LOWERCASE_A, %cl
    jl next_byte
    cmpb $LOWERCASE_Z, %cl
    jg next_byte

    # otherwise convert the byte to uppercase
    addb $UPPER_CONVERSION, %cl
    # and store it back
    movb %cl, (%rax, %rdi, 1)

next_byte:
    incq %rdi   # next byte
    cmpq %rdi, %rbx # continue unless we've reached the end
    jne convert_loop

end_convert_loop:
    # no return value, just leave
    movq %rbp, %rsp
    popq %rbp
    retq

运行上述代码时直接出现Segmentation fault (core dumped)错误,使用gdb进行调试排查过程中遇到以下问题:

  • 最初代码无法正常创建文件,排查后发现是32位系统调用号不适配Ubuntu amd64环境,参考公开的64位示例代码修改后仍想确认amd64架构最新系统调用号的正规查询渠道,原官方站点http://www.x86-64.org目前已无法访问;
  • 在代码第61行、78行设置断点调试时,执行int $LINUX_SYSCALL指令会出现[Detaching after fork from child process ...]提示,希望了解该现象产生的原因;
  • 在read_loop_begin代码段执行cmpq $END_OF_FILE, %rax指令时触发signal SIGTTIN, Stopped报错;清除原有断点后在第99行重新设置断点,使用r命令重新运行程序,发现程序执行流不符合预期:cmp指令执行后未按逻辑直接进入continue_read_loop段,反而直接跳转进入convert_to_upper段,同时gdb提示Cannot find bounds of current function。
代码错误与问题解答

核心代码逻辑错误

  • 系统调用方式完全不兼容64位环境:amd64架构下Linux原生系统调用使用syscall指令触发,传参规则、调用规范和32位int $0x80完全不同。在64位程序中混用int $0x80会导致寄存器高32位被截断、参数无法被内核正确识别、返回值异常,是段错误的核心诱因。64位系统调用传参规则为:系统调用号存rax,参数按顺序存入rdi(第1参数)、rsi(第2参数)、rdx(第3参数)、r10(第4参数)、r8(第5参数)、r9(第6参数),调用返回值存入rax。原代码全部使用32位规范的rbx/rcx/rdx传参,内核拿到的都是非法值,系统调用全部失败。
  • open调用flag值错误:原代码使用的03101是32位环境下的O_RDWR|O_CREAT|O_TRUNC值,64位环境下文件状态flag定义偏移不同,O_WRONLY|O_CREAT|O_TRUNC对应的八进制值为01101,原flag值会导致open调用行为异常,无法正确创建/打开目标文件。
  • convert_to_upper函数栈偏移计算错误:x86_64下call指令会压入8字节返回地址,进入函数后pushq %rbp再建立栈帧,栈上参数的正确偏移为:buffer长度(调用前最后压入的rax)在16(%rbp),buffer地址(调用前先压入的BUFFER_DATA)在24(%rbp)。原代码定义的ST_BUFFER_LEN=24、ST_BUFFER=32偏移多算了8字节,函数读取参数时拿到的是栈上的垃圾值,访问内存时直接触发段错误。
  • 错误判断逻辑放大异常影响:原代码用jle end_loop判断read返回值,当系统调用失败返回负数错误码时,本应跳转到退出逻辑,但因为前面寄存器、栈状态已经被错误的系统调用破坏,指令指针会跳转到非法地址,触发执行流混乱。

调试问题解答

  • amd64系统调用号查询渠道:最权威的查询渠道是当前系统自带的内核头文件,路径为/usr/include/asm/unistd_64.h,直接执行man 2 系统调用名也可以查到对应调用的参数规范、返回值和错误码说明,不需要访问已失效的第三方站点。
  • int $0x80触发fork detach提示的原因:64位程序执行int $0x80时,内核会切入32位兼容层处理请求,当传入的参数是被截断的非法64位地址时,内核会触发异常信号处理流程,gdb检测到进程状态异常、出现类似子进程创建的行为,就会输出detach提示,本质是错误的系统调用方式导致的异常。
  • SIGTTIN报错、执行流混乱、无函数边界提示的原因:
    • SIGTTIN是后台进程尝试读取控制终端时触发的信号:因为前面open调用失败,返回的文件描述符是负数错误值,后续read调用拿到非法fd后,默认将读取目标指向标准输入,gdb调试时进程处于后台状态读终端就会触发该信号。
    • 执行流混乱是因为系统调用返回负数错误码时,栈和寄存器状态已经被破坏,跳转指令没有正确跳到目标地址,刚好落在convert_to_upper函数的代码段中间。
    • Cannot find bounds of current function是gdb的常规提示:gdb依赖符号表标记的函数起止地址判断当前执行位置,当指令指针落在没有符号标记的地址、或者两个函数的中间位置时,就无法识别当前所属函数,是执行流被破坏的典型表现。

内容的提问来源于stack exchange,提问作者OnlyWick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 01:21:40