32位x86汇编文件写入异常:年龄未存储+格式错乱求助
32位x86汇编写入文件的问题排查与修复
问题描述
在Kali Linux环境下测试32位x86汇编代码,编译命令为as --32 myapp.s -o myapp.o、ld -m elf_i386 myapp.o -o myapp。代码意图将3条包含姓名、地址、long类型年龄的记录写入test.dat文件,但运行后出现两个问题:
- 定义为long类型的年龄字段未被正确存储;
- 输出内容格式错乱,出现“-MarMarilyn”“DerrDerrick”这类异常拼接内容,预期每条记录的姓名、地址、年龄依次换行显示,实际不符合预期。
原汇编代码如下:
.section .data .equ RECORD_FIRSTNAME, 0 .equ RECORD_LASTNAME, 40 .equ RECORD_ADDRESS, 80 .equ RECORD_AGE, 320 .equ RECORD_SIZE, 324 .equ SYS_EXIT, 1 .equ SYS_READ, 3 .equ SYS_WRITE, 4 .equ SYS_OPEN, 5 .equ SYS_CLOSE, 6 .equ SYS_BRK, 45 .equ LINUX_SYSCALL, 0x80 .equ STDIN, 0 .equ STDOUT, 1 .equ STDERR, 2 .equ END_OF_FILE, 0 .equ ST_WRITE_BUFFER, 8 .equ ST_FILEDES, 12 record1: .ascii "Fredrick\0" .rept 31 #Padding to 40 bytes .byte 0 .endr .ascii "Bartlett\0" .rept 31 #Padding to 40 bytes .byte 0 .endr .ascii "4242 S Prairie\nTulsa, OK 55555\0" .rept 209 #Padding to 240 bytes .byte 0 .endr .long 45 record2: .ascii "Marilyn\0" .rept 32 #Padding to 40 bytes .byte 0 .endr .ascii "Taylor\0" .rept 33 #Padding to 40 bytes .byte 0 .endr .ascii "2224 S Johannan St\nChicago, IL 12345\0" .rept 203 #Padding to 240 bytes .byte 0 .endr .long 29 record3: .ascii "Derrick\0" .rept 32 #Padding to 40 bytes .byte 0 .endr .ascii "McIntire\0" .rept 31 #Padding to 40 bytes .byte 0 .endr .ascii "500 W Oakland\nSan Diego, CA 54321\0" .rept 206 #Padding to 240 bytes .byte 0 .endr .long 36 file_name: .ascii "test.dat\0" .equ ST_FILE_DESCRIPTOR, -4 .section .text .globl _start _start: #Copy the stack pointer to %ebp movl %esp, %ebp #Allocate space to hold the file descriptor subl $4, %esp #Open the file movl $SYS_OPEN, %eax movl $file_name, %ebx movl $0101, %ecx #This says to create if it #doesn’t exist, and open for #writing movl $0666, %edx int $LINUX_SYSCALL #Store the file descriptor away movl %eax, ST_FILE_DESCRIPTOR(%ebp) #Write the first record pushl ST_FILE_DESCRIPTOR(%ebp) pushl $record1 call write_record addl $8, %esp #Write the second record pushl ST_FILE_DESCRIPTOR(%ebp) pushl $record2 call write_record addl $8, %esp #Write the third record pushl ST_FILE_DESCRIPTOR(%ebp) pushl $record3 call write_record addl $8, %esp #Close the file descriptor movl $SYS_CLOSE, %eax movl ST_FILE_DESCRIPTOR(%ebp), %ebx int $LINUX_SYSCALL #Exit the program movl $SYS_EXIT, %eax movl $0, %ebx int $LINUX_SYSCALL .globl write_record .type write_record, @function write_record: pushl %ebp movl %esp, %ebp pushl %ebx movl $SYS_WRITE, %eax movl ST_FILEDES(%ebp), %ebx movl ST_WRITE_BUFFER(%ebp), %ecx movl $RECORD_SIZE, %edx int $LINUX_SYSCALL #NOTE - %eax has the return value, which we will # give back to our calling program popl %ebx movl %ebp, %esp popl %ebp ret
问题根源
1. 年龄字段存储错误
你定义的每条记录总长度是324字节(40+40+240+4),但各个字段的填充字节数计算错误,导致实际字段偏移和定义的RECORD_AGE(320)不匹配。比如:
- record1的firstname是"Fredrick\0"(8字节),你只填充了31个0,总共39字节,少1字节;
- lastname是"Bartlett\0"(8字节),同样填充31个0,又少1字节;
这就导致后面的address和age字段整体偏移2字节,最终年龄的long值没有写到正确的位置,存储自然出错。
2. 内容拼接错乱
因为每条记录的实际长度超过了定义的RECORD_SIZE(324),当write_record调用SYS_WRITE写入324字节时,会把下一条记录的开头部分也写入当前记录的末尾,比如record1的末尾会带上record2的firstname开头,就出现了“-MarMarilyn”这类拼接错误。
修复方案
步骤1:修正各字段的填充字节数
计算每个字符串的实际长度(包含末尾的\0),用目标字段长度减去该值得到需要填充的0的数量:
- record1:
- firstname:"Fredrick\0"共8字节,需填充
40-8=32个0(把.rept 31改成.rept 32); - lastname:"Bartlett\0"共8字节,需填充
40-8=32个0(把.rept 31改成.rept 32); - address:"4242 S Prairie\nTulsa, OK 55555\0"共32字节,需填充
240-32=208个0(把.rept 209改成.rept 208);
- firstname:"Fredrick\0"共8字节,需填充
- record2:
- firstname:"Marilyn\0"共7字节,需填充
40-7=33个0(把.rept 32改成.rept 33); - lastname:"Taylor\0"共6字节,需填充
40-6=34个0(把.rept 33改成.rept 34); - address:"2224 S Johannan St\nChicago, IL 12345\0"共38字节,需填充
240-38=202个0(把.rept 203改成.rept 202);
- firstname:"Marilyn\0"共7字节,需填充
- record3:
- firstname:"Derrick\0"共7字节,需填充
40-7=33个0(把.rept 32改成.rept 33); - address:"500 W Oakland\nSan Diego, CA 54321\0"共35字节,需填充
240-35=205个0(把.rept 206改成.rept 205);
- firstname:"Derrick\0"共7字节,需填充
步骤2:验证记录总长度
修正后每条记录的长度正好是40+40+240+4=324字节,和RECORD_SIZE一致,写入时就不会越界到下一条记录。
可选:年龄字段可读性优化
当前代码写入的是long类型的二进制值,文本编辑器查看会显示乱码。如果需要文件中显示可读的数字,需要把年龄数值转换成ASCII字符串再写入(比如把.long 45改成.ascii "45\0"并填充到4字节),但如果是存储二进制数据,原写法没问题,用xxd test.dat就能看到正确的十六进制值。
修正后的完整代码
.section .data .equ RECORD_FIRSTNAME, 0 .equ RECORD_LASTNAME, 40 .equ RECORD_ADDRESS, 80 .equ RECORD_AGE, 320 .equ RECORD_SIZE, 324 .equ SYS_EXIT, 1 .equ SYS_READ, 3 .equ SYS_WRITE, 4 .equ SYS_OPEN, 5 .equ SYS_CLOSE, 6 .equ SYS_BRK, 45 .equ LINUX_SYSCALL, 0x80 .equ STDIN, 0 .equ STDOUT, 1 .equ STDERR, 2 .equ END_OF_FILE, 0 .equ ST_WRITE_BUFFER, 8 .equ ST_FILEDES, 12 record1: .ascii "Fredrick\0" .rept 32 #Padding to 40 bytes (8+32=40) .byte 0 .endr .ascii "Bartlett\0" .rept 32 #Padding to 40 bytes (8+32=40) .byte 0 .endr .ascii "4242 S Prairie\nTulsa, OK 55555\0" .rept 208 #Padding to 240 bytes (32+208=240) .byte 0 .endr .long 45 record2: .ascii "Marilyn\0" .rept 33 #Padding to 40 bytes (7+33=40) .byte 0 .endr .ascii "Taylor\0" .rept 34 #Padding to 40 bytes (6+34=40) .byte 0 .endr .ascii "2224 S Johannan St\nChicago, IL 12345\0" .rept 202 #Padding to 240 bytes (38+202=240) .byte 0 .endr .long 29 record3: .ascii "Derrick\0" .rept 33 #Padding to 40 bytes (7+33=40) .byte 0 .endr .ascii "McIntire\0" .rept 31 #Padding to 40 bytes (9+31=40) .byte 0 .endr .ascii "500 W Oakland\nSan Diego, CA 54321\0" .rept 205 #Padding to 240 bytes (35+205=240) .byte 0 .endr .long 36 file_name: .ascii "test.dat\0" .equ ST_FILE_DESCRIPTOR, -4 .section .text .globl _start _start: #Copy the stack pointer to %ebp movl %esp, %ebp #Allocate space to hold the file descriptor subl $4, %esp #Open the file movl $SYS_OPEN, %eax movl $file_name, %ebx movl $0101, %ecx #Create if not exist, open for writing movl $0666, %edx int $LINUX_SYSCALL #Store the file descriptor away movl %eax, ST_FILE_DESCRIPTOR(%ebp) #Write the first record pushl ST_FILE_DESCRIPTOR(%ebp) pushl $record1 call write_record addl $8, %esp #Write the second record pushl ST_FILE_DESCRIPTOR(%ebp) pushl $record2 call write_record addl $8, %esp #Write the third record pushl ST_FILE_DESCRIPTOR(%ebp) pushl $record3 call write_record addl $8, %esp #Close the file descriptor movl $SYS_CLOSE, %eax movl ST_FILE_DESCRIPTOR(%ebp), %ebx int $LINUX_SYSCALL #Exit the program movl $SYS_EXIT, %eax movl $0, %ebx int $LINUX_SYSCALL .globl write_record .type write_record, @function write_record: pushl %ebp movl %esp, %ebp pushl %ebx movl $SYS_WRITE, %eax movl ST_FILEDES(%ebp), %ebx movl ST_WRITE_BUFFER(%ebp), %ecx movl $RECORD_SIZE, %edx int $LINUX_SYSCALL #Return the write result to caller popl %ebx movl %ebp, %esp popl %ebp ret
内容的提问来源于stack exchange,提问作者user25018149
相关产品推荐
相关产品推荐

