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

为何调用fputc持续触发段错误?汇编文件复制程序问题排查

汇编文件复制程序段错误问题排查

我写了一个实现文件复制的汇编程序,能成功打开源文件并读取内容,但写入目标文件时在fputc调用处触发段错误。调试显示调用fputc前,ecx持有正确的字符值,eax也正确加载了目标文件句柄,但调用fputc时直接崩溃,怀疑是文件句柄的传递出了问题。

汇编代码

section .data
openFile:    db 'file1.txt', 0       ; 源文件路径
fileModeR:   db 'r', 0               ; 只读模式
writeFile:   db 'file2.txt', 0       ; 目标文件路径
fileModeW:   db 'w', 0               ; 写入模式

section .bss
fileHandleR  resd 1                  ; 存储源文件句柄(FILE*)
fileHandleW  resd 1                  ; 存储目标文件句柄(FILE*)
buffer       resb 1                  ; 单字符缓冲区(未使用)

section .text
extern fopen, fgetc, fputc, fclose
global main

main:
    ; 打开源文件
    push fileModeR                    ; 压入打开模式
    push openFile                     ; 压入文件名
    call fopen
    add esp, 8                        ; 清理栈
    mov [fileHandleR], eax            ; 保存文件句柄
    test eax, eax                     ; 检查是否打开失败
    jz exit_program                  ; 失败则退出

    ; 打开目标文件
    push fileModeW                    ; 压入打开模式
    push writeFile                    ; 压入文件名
    call fopen
    add esp, 8                        ; 清理栈
    mov [fileHandleW], eax            ; 保存文件句柄
    test eax, eax                     ; 检查是否打开失败
    jz close_source                  ; 失败则关闭源文件后退出

    ; 循环复制字符
copy_loop:
    mov eax, [fileHandleR]            ; 加载源文件句柄
    push eax                          ; 压入参数调用fgetc
    call fgetc
    add esp, 4                        ; 清理栈
    test eax, eax                     ; 检查EOF或错误
    js close_files                    ; 符号位置位则退出循环

    mov ecx, eax                      ; 保存读取到的字符
    mov eax, [fileHandleW]            ; 加载目标文件句柄
    push ecx                          ; 压入要写入的字符
    push eax                          ; 压入目标文件句柄
    call fputc                        ; 写入字符(此处触发段错误)
    add esp, 8                        ; 清理栈

    test eax, eax                     ; 检查写入是否失败
    js close_files                    ; 失败则退出

    jmp copy_loop                     ; 继续循环

close_files:
    mov eax, [fileHandleW]            ; 关闭目标文件
    push eax
    call fclose
    add esp, 4

close_source:
    mov eax, [fileHandleR]            ; 关闭源文件
    push eax
    call fclose
    add esp, 4

exit_program:
    mov eax, 1                        ; exit系统调用号
    xor ebx, ebx                      ; 返回0状态
    int 0x80                          ; 调用内核

问题根源与修复方案

1. fputc参数入栈顺序错误

C语言的cdecl调用约定要求参数从右往左入栈,而fputc的原型是:

int fputc(int c, FILE *stream);

调用时需要先压入右侧的stream参数,再压入左侧的c参数。但代码中先压入了字符ecx,再压入文件句柄eax,导致fputc把字符值当成了FILE*指针去访问,直接触发非法内存访问(段错误)。

修复代码:
将copy_loop中的fputc调用部分修改为:

mov ecx, eax                      ; 保存读取到的字符
mov eax, [fileHandleW]            ; 加载目标文件句柄
push eax                          ; 先压入stream参数(右参数)
push ecx                          ; 再压入c参数(左参数)
call fputc                        ; 写入字符
add esp, 8                        ; 清理栈

2. fgetc的EOF判断逻辑错误

fgetc读取到文件末尾时返回EOF,其值为-1(二进制0xFFFFFFFF)。当前用test eax, eax后判断符号位(js),会把ASCII值在0x80-0xFF之间的扩展字符误判为EOF(因为这些值的最高位是1,符号位置位)。

修复代码:
将copy_loop中的EOF判断改为直接比较eax和-1:

call fgetc
add esp, 4
cmp eax, -1                       ; 直接判断是否为EOF
je close_files                    ; 是EOF则退出循环

3. 可选:使用libc的exit函数替代系统调用

当前用int 0x80调用exit系统调用虽然可行,但链接libc的情况下,直接调用exit函数更符合规范,避免可能的资源泄漏:

exit_program:
    push ebx                       ; 退出状态码
    call exit                      ; 调用libc的exit函数

修复后的完整代码

section .data
openFile:    db 'file1.txt', 0       ; 源文件路径
fileModeR:   db 'r', 0               ; 只读模式
writeFile:   db 'file2.txt', 0       ; 目标文件路径
fileModeW:   db 'w', 0               ; 写入模式

section .bss
fileHandleR  resd 1                  ; 存储源文件句柄(FILE*)
fileHandleW  resd 1                  ; 存储目标文件句柄(FILE*)

section .text
extern fopen, fgetc, fputc, fclose, exit
global main

main:
    ; 打开源文件
    push fileModeR
    push openFile
    call fopen
    add esp, 8
    mov [fileHandleR], eax
    test eax, eax
    jz exit_program

    ; 打开目标文件
    push fileModeW
    push writeFile
    call fopen
    add esp, 8
    mov [fileHandleW], eax
    test eax, eax
    jz close_source

copy_loop:
    mov eax, [fileHandleR]
    push eax
    call fgetc
    add esp, 4
    cmp eax, -1                     ; 正确判断EOF
    je close_files

    mov ecx, eax
    mov eax, [fileHandleW]
    push eax                        ; 先压入stream参数
    push ecx                        ; 再压入字符参数
    call fputc
    add esp, 8

    cmp eax, -1                     ; 判断写入是否失败
    je close_files

    jmp copy_loop

close_files:
    mov eax, [fileHandleW]
    push eax
    call fclose
    add esp, 4

close_source:
    mov eax, [fileHandleR]
    push eax
    call fclose
    add esp, 4

exit_program:
    xor ebx, ebx
    push ebx
    call exit

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:05:00