为何调用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
相关产品推荐
相关产品推荐

