四、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

相關文章
相關標籤/搜索