5、读取记录
功能:程序读取每条记录,并显示每条记录中的“名”
记录读取功能主要执行以下步骤:
1、打开文件
2、读取一条记录
3、若到文件末尾,则退出;否则计算“名”的字符数
4、将“名”写入STDOUT中
5、输出换行符到STDOUT中
6、返回,并读取另一条记录
(1)字数统计函数
由于每条记录中,“名”的长度不定,因此需要一个函数来统计写入的字符数。该程序存于count-chars.s
#目的:对记录中字符数进行统计,遇到空字符结束#输入:字符串地址#输出:计数值返回到%eax中#变量: %ecx——字符计数# %al——当前字符# %edx——当前字符地址.type count_chars, @function.globl count_chars.equ ST_STRING_START_ADDRESS, 8 #字符串开始地址count_chars: pushl %ebp movl %esp, %ebp movl $0, %ecx #计数器从0开始 movl ST_STRING_START_ADDRESS(%ebp), %edx #数据的起始地址 count_loop_begin: movb (%edx), %al #获取当前字符,采用间接寻址方式 cmpb $0, %al #判断是否为空字符 je count_loop_end incl %ecx incl %edx jmp count_loop_begincount_loop_end: movl %ecx, %eax #结束循环,将统计的字数存入%eax中 popl %ebp ret
(2)写一个换行符到STDOUT的函数
该程序存于文件write-newline.s文件中
.include "linux.s".globl write_newline.type write_newline, @function.section .data newline: .ascii "\n".section .text .equ ST_FILEDES, 8 write_newline: pushl %ebp movl %esp, %ebp movl $SYS_WRITE, %eax movl ST_FILEDES(%ebp), %ebx movl $newline, %ecx movl $1, %edx int $LINUX_SYSCALL movl %ebp, %esp popl %ebp ret
(3)主程序
该程序存放在文件read-records.s中。
.include "linux.s".include "record-def.s".section .data file_name: .ascii "test.dat\0".section .bss .lcomm record_buffer, RECORD_SIZE .section .text .globl _start #主程序_start: #定义存储输入输出描述符的栈位置 #也可用一个.data段中的内存地址代替 .equ ST_INPUT_DESCIPTOR, -4 .equ ST_OUTPUT_DESCRIPTOR, -8 movl %esp, %ebp subl $8, %esp #打开文件 movl $SYS_OPEN, %eax movl $file_name, %ebx movl $0, %ecx #表示只读打开 movl $0666, %edx int $LINUX_SYSCALL movl %eax, ST_INPUT_DESCRIPTOR(%ebp) #保存输入文件描述符 movl $STDOUT, ST_OUTPUT_DESCRIPTOR(%ebp) #保存输出文件描述符,此处为“标准输出” record_loop: pushl ST_INPUT_DESCRIPTOR(%ebp) #输入文件描述符 pushl $record_buffer #缓冲区地址指针 call read_record addl $8, %esp #比较读取的字节数和缓冲区大小 #如果不一致,说明达到文件尾部或出错 cmpl $RECORD_SIZE, %eax jne finished_reading #打印名,需先知道“名”的大小 pushl $RECORD_FIRSTNAME + record_buffer #该指令将两个常数相加,得到的结果为内存地址 #得到的结果将入栈。RECORD_FIRSTNAME记录从 #起始地址到名字字段之间的字节数 call count_chars addl $4, %esp movl %eax, %edx movl ST_OUTPUT_DESCRIPTOR(%ebp), %ebx movl $SYS_WRITE, %eax movl $RECORD_FIRSTNAME + record_buffer, %ecx int $LINUX_SYSCALL jmp recor_loopfinish_reading: movl $SYS_EXIT, %eax movl $0, %ebx int $LINUX_SYSCALL
编译、链接、执行上面程序
as read-record.s -o read-record.o
as count-chars.x -o count-chars.o
as write-newline.s -o write-newline.o
as read-records.s -o read-records.o
ld read-record.o count-chars.o write-newline.o read-records.o -o read-records
6、修改记录
修改程序主要有以下步骤:
1、打开输入文件和输出文件
2、从输入文件读取记录
3、递增年龄
4、新纪录写入文件
将下面程序存入文件add-year.s中
.include "linux.s".include "record-def.s".section .data input_file_name: .ascii "test.dat\0" output_file_name: .ascii "testout.dat\0".section .bss .lcomm record_buffer, RECORD_SIZE #局部变量的栈偏移量.equ ST_INPUT_DESCRIPTOR, -4.equ ST_OUTPUT_DESCRIPTOR, -8.section .text .globl _start _start: movl %esp, %ebp subl $8, %esp #打开输入文件 movl $SYS_OPEN, %eax movl $input_file_name, %ebx movl $0, %ecx movl $0666, %edx int $LINUX_SYSCALL movl %eax, ST_INPUT_DESCRIPTOR(%ebp) #输入文件的文件描述符 #打开输出文件 movl %SYS_OPEN, %eax movl $output_file_name, %ebx movl $0101, %ecx movl $0666, %edx int $LINUX_SYSCALL movl %eax,ST_OUTPUT_DESCRIPTOR(%ebp)loop_begin: pushl ST_INPUT_DESCRIPTOR(%ebp) pushl $record_buffer call read_record addl $8, %esp #判断是否到达文件尾部,或出现读错误 cmpl $RECORD_SIZE, %eax jne loop_end #递增年龄 incl record_buffer + RECORD_AGE #注意此处为直接寻址,和上面的“pushl $a+b”形式区别 #写记录 pushl ST_OUTPUT_DESCRIPTOR(%ebp) pushl $record_buffer call write_record addl $8, %esp jmp loop_begin loop_end: movl $SYS_EXIT, %eax movl $0, %ebx int $LINUX_SYSCALL
编译、链接、运行上面程序
as add-year.s -o add-years.o
ld add-year.o read-record.o write-record.o -o add-years
./add-years
7、让程序更加健壮
在上面add-years.s程序的基础上,我们添加下面这个程序。该程序打印错误消息并退出,其作为“被调函数”,由add-year.s程序调用。该函数存于文件:error-exit.s。为了使用该函数,需将错误信息的地址和错误代码入栈,然后进行调用。
.include "linux.s".equ ST_ERROR_CODE, 8.equ ST_ERROR-MSG, 12.globl .error_exit.type error_exit, @functionerror_exit: pushl %ebp movl %esp, %ebp #写错误代码 movl ST_ERROR_CODE(%ebp), %ecx pushl %ecx call count_chars #计算字符数 popl %ecx movl %eax, %edx #字符数量 movl $STDERR, %ebx movl $SYS_WRITE, %eax int $LINUX_SYSCALL #写错误信息 movl STD_ERROR_MSG(%ebp), %ecx pushl %ecx call count_chars popl %ecx movl %eax, %edx movl $SYS_WRITE, %eax movl $STDERR, %ebx int $LINUX_SYSCALL pushl $STDERR call write_newlines #退出 movl $SYS_EXIT, %eax movl $1, %ebx int $LINUX_SYSCALL
以上为处理出错时的函数。下面将给出add-years.s是如何使用该函数的。
前面的add-year.s在打开文件后并未进行检错——可能出现找不到文件等情况。所以下面程序对打开文件后的返回码进行检测,保证正确打开文件。
#打开供读取的文件movl $SYS_OPEN, %eaxmovl $input_file_name, $ebxmovl $0, %ecxmovl $0666, %edxint $LINUX_SYSCALLmovl %eax, INPUT_DESCRIPTOR(%ebp) #将返回码存入栈中,供检测#以下将对%eax存储的返回码进行检测,若为负值,则调用错误函数cmpl $0, %eaxjl continue_processing #0<%eax#发送错误消息.section .data no_open_file_code: .ascii "0001:\0" no_open_file_msg: .ascii "cant open input file\0".section .text pushl $no_open_file_msg pushl $no_open_file_code call error_exit continue_processing: #程序其余部分
编译、链接、运行程序:
as add-year.s -o add-year.o
as error-exit.s -o error-exit.o
ld add-year.o write-newline.o error-exit.o read-record.o write-record.o count-chars.o -o add-year