四、Linux汇编——文件中结构化数据的操做(下)

五、读取记录

功能:程序读取每条记录,并显示每条记录中的“名”linux

记录读取功能主要执行如下步骤:函数

一、打开文件oop

二、读取一条记录spa

三、若到文件末尾,则退出;不然计算“名”的字符数指针

四、将“名”写入STDOUT中code

五、输出换行符到STDOUT中内存

六、返回,并读取另外一条记录ci

(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_begin

count_loop_end:    movl %ecx, %eax   #结束循环,将统计的字数存入%eax中
                   popl %ebp
                   ret

(2)写一个换行符到STDOUT的函数

该程序存于文件write-newline.s文件中input

.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_loop

finish_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

六、修改记录

修改程序主要有如下步骤:

一、打开输入文件和输出文件

二、从输入文件读取记录

三、递增年龄

四、新纪录写入文件

将下面程序存入文件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

七、让程序更加健壮

在上面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, @function

error_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, %eax
movl $input_file_name, $ebx
movl $0, %ecx
movl $0666, %edx
int $LINUX_SYSCALL

movl %eax, INPUT_DESCRIPTOR(%ebp)    #将返回码存入栈中,供检测

#如下将对%eax存储的返回码进行检测,若为负值,则调用错误函数
cmpl $0, %eax
jl 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

相关文章
相关标签/搜索