执行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
相关产品推荐
相关产品推荐

