x86_64汇编LUT转大写程序出现字符异常与截断问题求助
x86_64 Linux汇编大写转换程序的问题及修复
我正在编写一个x86_64 Linux汇编程序,通过预分配的LUT(查找表)将文本中的字母转换为大写。C语言版本逻辑正常,但汇编版遇到两个问题:
- 字符'A'在Emacs中被替换为奇怪符号
^@ - 文本末尾被截断
猜测是内存寻址错误或文件未正确关闭导致的。尝试调用syscall执行_close时程序有时崩溃,可能是寄存器被污染,但尚未定位到原因。目前使用GDB的r i命令查看寄存器,并用SASM辅助调试,仍未找到问题所在。
编译脚本Make
newupper: newupper.o gcc -o newupper newupper.o -no-pie newupper.o: newupper.asm nasm -f elf64 -g -F dwarf newupper.asm -l newupper.lst
汇编测试代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 读取文本文件,将所有小写字母转换为大写后写入新文件 ;; 使用LUT实现转换:['a']='A',['A']='A' ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; section .bss Buff resb 128 upperlut resb 128 section .data Bufflen dq 128 UPPERA dq 65 LOWERA dq 97 FnameR db "data.txt", 0 FnameW db "out.txt", 0 myFd dq 0 myFd2 dq 0 section .text LutInit: mov r10, 0 .L1: cmp r10, 128 jge .L3 mov [upperlut+r10], r10 inc r10 jmp .L1 .L3: xor r10, r10 mov r10, 0 mov r11, [UPPERA] mov r12, [LOWERA] .L2: cmp r10, 26 jge .END mov r9, r10 add r9, [UPPERA] mov [upperlut+r10+r11], r9 mov [upperlut+r10+r12], r9 inc r10 jmp .L2 .END: OpenFile: ;; sys_open 参数:const char *filename, int flags, int mode mov r10, rcx ; 保存指针,避免syscall破坏rcx mov rax, 2 ; sys_open的系统调用号 syscall cmp rax, 0 jle Exit mov [r10], rax ; 将文件描述符写入myFd或myFd2 ret global main main: mov rbp, rsp; 方便调试 ; 打开读取文件 call LutInit mov rdi, FnameR mov rsi, 0 mov rdx, 0 mov rcx, myFd call OpenFile mov rdi, FnameW mov rsi, 0102o mov rdx, 00600o mov rcx, myFd2 call OpenFile Read: mov rax, 0 mov rdi, [myFd] mov rsi, Buff mov rdx, [Bufflen] syscall cmp rax, 0 je Exit mov r12, 0 .L1: cmp r12, rax jge Write ;; 通过LUT转换字符 movzx r14, byte [Buff+r12] movzx r13, byte [upperlut+r14] mov byte [Buff+r12], r13b inc r12 jmp .L1 Write: xor r15, r15 mov r15, rax mov rax, 1 mov rdi, [myFd2] mov rsi, Buff mov rdx, r15 syscall jmp Read Exit: ;; mov rax, 3 ;; mov rdi, [myFd2] ;; syscall ;; mov rax, 3 ;; mov rdi, [myFd] ;; syscall ret
问题分析与修复
1. LUT初始化的内存写入错误
这是导致'A'变成^@的核心原因:
在LutInit的.L1循环中,mov [upperlut+r10], r10是将64位寄存器r10的值写入字节数组upperlut,会一次性覆盖8个字节的内存。比如当r10=65(对应字符'A')时,这条指令会把upperlut+65到upperlut+72的8个字节全部设为65的64位值(低字节65,高字节0),后续的LUT赋值会被覆盖,最终读取upperlut+65时得到空字符(0,显示为^@)。
修复:将64位寄存器改为8位寄存器,只写入单个字节:
; .L1循环中修改 mov [upperlut+r10], r10b ; .L2循环中修改 mov [upperlut+r10+r11], r9b mov [upperlut+r10+r12], r9b
2. 文件未关闭导致缓存未刷新
注释掉的sys_close调用会导致操作系统的文件缓存未完全写入磁盘,从而出现文本末尾截断的情况。直接ret返回main函数时,gcc运行时库会清理资源,但手动关闭文件更安全。
修复:取消注释Exit段的close调用,确保先关写入文件,再关读取文件:
Exit: mov rax, 3 ; sys_close的系统调用号 mov rdi, [myFd2] syscall ; 关闭写入文件 mov rax, 3 mov rdi, [myFd] syscall ; 关闭读取文件 ret
额外优化:OpenFile错误处理
当sys_open失败时(rax<=0),直接跳转到Exit会导致已打开的文件未被关闭。可以在OpenFile中添加局部错误处理,或者在Exit中检查文件描述符是否有效再关闭。
内容的提问来源于stack exchange,提问作者Ilian Zapryanov
相关产品推荐
相关产品推荐

