三、Linux彙編——文件處理

第五章 文件處理

一、Unix中的文件及操做

    Unix中的文件均可以做爲連續的「字節流」進行訪問。每一個進程有若干「文件描述符」,這些「文件描述符」即爲數字。如:標準輸入、輸出、錯誤輸出的文件描述符爲:0,1,2。每一個進程都有以上三個標準文件描述符。當打開文件時,系統自動分配文件描述符,關閉文件後,文件描述符由系統回收。算法

    有了文件描述符後,還須要指出對該文件進行什麼操做——打開模式,常見的四種打開模式:讀、寫、讀寫、建立。因爲彙編語言不像C語言,能夠用字符串來制定文件名和打開模式,因此用數字來表示系統調用和參數。如下爲文件系統調用對應的寄存器使用說明:bash

(1)open調用——返回的文件描述符存入%eax中:功能:根據文件名打開文件。函數

%eax=5:系統調用號        %ebx:文件名起始地址            %ecx:以數字表示的讀寫模式            %edx:權限集合oop

(2)read調用——返回讀取字符數或負值(錯誤碼)存入%eax中:功能:根據文件描述符讀取數據spa

%eax=3:系統調用號    %ebx:文件描述符    %ecx:緩衝區地址    %edx:緩衝區大小code

(3)write調用——返回寫字符數或負值(錯誤碼)存入%eax中:功能:根據文件描述符讀取數據索引

%eax=4:系統調用號    %ebx:文件描述符    %ecx:緩衝區地址    %edx:緩衝區大小進程

(4)close調用——    :功能:根據文件描述符關閉文件內存

%eax=6:系統調用號    %ebx:文件描述符字符串

二、緩衝區和.bss段

    上文已經指出,.data段中的數據能夠開闢空間,同時也可賦初值;.text段中存放代碼。

    當須要申請一大塊內存區域時,則使用".bss"段聲明:其相似於數據段,可是它不一樣於.data段,它不佔用可執行程序空間,且數據不能進行初始化。以下,.lcomm命令建立一個符號my_buffer來標識500bytes的緩衝區。

.section .bss
.lcomm my_buffer, 500

現假定讀取一個打開的文件,%ebx存儲文件描述符

movl $my_buffer, %ecx    #將緩衝區地址存入%eax中,不加$符表示直接尋址
movl $500, %edx          #緩衝大小,以byte爲單位
movl $3, %eax            #調用號
int 0x80

三、程序1

#功能:該程序從輸入文件中讀取一些列字符,降字符串轉換成大寫後,寫入輸出文件中
#算法:(1)打開輸入文件
        (2)打開輸出文件
        (3)若未達到輸入文件尾部,則:
                (a)從輸入文件讀入緩衝區
                (b)轉換緩衝區字符爲大寫
                (c)將緩衝區文件寫入輸出文件中
.section .data
    #定義程序所需常量,方便表示。僞指令.equ爲「宏定義」
    .equ SYS_OPEN, 5  #用SYS_OPEN表示打開文件的系統調用號「5」,原理下同
    .equ SYS_CLOSE, 6 #關閉文件
    .equ SYS_EXIT, 1 #退出程序
    .equ SYS_WRITE, 4 #寫操做
    .equ SYS_READ, 3  #讀操做
    
    #文件打開選項,可用or進行鏈接,具體參見文件/usr/include/asm/fcntl.h
    .equ O_RDONLY, 0
    .equ O_CREAT_WRONLY_TRUNC, 03101
    
    #標準文件描述符
    .equ STDIN, 0
    .equ STDOUT, 1
    .equ STDERR, 2
    
    #系統調用中斷
    .equ LINUX_SYSCALL, 0x80
    
    .equ END_OF_FILE, 0    #文件尾標誌
    .equ NUMBER_ARGUMENTS, 2     #程序的參數個數

.section .bss
    #緩衝區(未初始化空間),緩衝區不大於16 000字節
    .equ BUFFER_SIZE,500
    .lcomm BUFFER_DATA, BUFFER_SIZE #以BUFFER_DATA標記的大小爲500字節的空間

.section .text
    #定義棧中各個數據的存放位置
    .equ ST_SIZE_RESERVE, 8 #該程序所需的局部變量空間
    .equ ST_FD_IN, -4        #輸入文件描述符
    .equ ST_FD_OUT, -8         #輸出文件描述符
    
    #當bash程序調用此程序時,因爲命令的形式爲:./function_name in_file out_file。
    #bash將參數按照以下順序順次壓棧:out_file, in_file, function_name, 參數數量(包括函數名)
    .equ ST_ARGV_2, 12  #參數2
    .equ ST_ARGV_1, 8    #參數1
    .equ ST_ARGV_0, 4    #函數名
    .equ ST_ARGC, 0        #參數個數
    
    .globl _start
    _start :    #初始化程序
                movl %esp, %ebp
                subl $ST_SIZE_RESERVER, %esp  #爲「被調函數」的局部變量開闢空間
                
     #打開文件
     open_files:
         #打開輸入文件
         open_fd_in:    movl $SYS_OPEN, %eax         #打開文件的系統調用
                        movl ST_ARGV_1(%ebp), %ebx   #輸入文件
                        movl $O_RDONLY, %ecx         #對文件的操做
                        movl $0666, %edx             #權限
                        int $LINUX_SYSCALL            #系統調用
         store_fd_in:   movl %eax, ST_FD_IN(%ebp)    #存儲輸入文件的文件描述符
         
         #打開輸出文件
         open_fd_out:    movl $SYS_OPEN, %eax
                         movl ST_ARGV_2(%ebp), %ebx
                         movl $O_CREAT_WRONLY_TRUNC, %ecx
                         movl $0666, %edx
                         int $LINUX_SYSCALL
         store_fd_out:    movl %eax, ST_FD_OUT(%ebp)
         
     #讀取文件,並轉成大寫字母
         read_loop:    movl $SYS_READ, %eax
                       movl $ST_FD_IN(%ebp), %ebx
                       movl $BUFFER_DATA, %ecx
                       movl $BUFFER_SIZE, %edx
                       int $LINUX_SYSCALL         #讀取的字符數存儲在%eax中
                       
                       cmpl $END_OF_FILE, %eax    #檢測是否到達文件尾部,或出現錯誤
                       jle end_loop                #文件尾部爲0,出錯爲負數
         
         continue_read_loop:    pushl $BUFFER_DATA    #緩衝區位置
                                pushl %eax            #緩衝區大小
                                call convert_to_upper    #調用轉換函數
                                popl %eax
                                addl $4, %esp        #恢復%esp,清除「被調函數」開闢的緩衝區
                                
                                #將字符塊寫入輸出文件中
                                movl %eax, %edx    #讀取的字符串大小
                                movl $SYS_WRITE, %eax
                                movl ST_FD_OUT(%ebp), %ebx
                                movl $BUFFER_DATA, %ecx
                                int $LINUX_SYSCALL
                                
                                jmp read_loop_begin  #循環讀取下一字符塊的內容
                                
           end_loop:    #關閉文件輸出文件
                        movl $SYS_CLOSE, %eax
                        movl ST_FD_OUT(%ebp), %ebx
                        int $LINUX_SYSCALL
                        
                        #關閉輸入文件
                        movl $SYS_CLOSE, %eax
                        movl ST_FD_in(%ebp), %ebx
                        int $LINUX_SYSCALL

程序說明:

如上爲主函數,該函數執行流程以下:

(1)打開輸入、輸出文件

(2)循環讀取固定大小字節塊,並將字節塊轉換成大寫字母

(3)將字節塊寫入輸出文件中

(4)關閉輸入、輸出文件

其中須要注意以下知識點:

一、.equ爲「宏」,即將抽象的以數字形式的系統調用等,改爲字符串,更加形象,在彙編過程當中,字符串將被數字替換。

二、本函數與上一篇中關於函數調用壓棧順序有不一致,其不一致處爲:上一篇中將當前「調用函數」的地址壓棧,而本函數在bash調用函數時,未將調用函數的當前執行地址壓棧。區別以下圖所示,圖一爲上一篇壓棧狀況,圖二爲本函數的壓棧狀況:

圖一:

圖二:

Linux中究竟如何進行的下面操做,待後面進一步探究。

四、程序2

#功能:將緩衝區中的字符轉爲大寫形式
#輸入:參數一:轉換的字符塊地址
#        參數二:緩衝區大小
#輸出:以大寫字符覆蓋當前緩衝區
#變量:%eax:存放緩衝區起始地址
        %ebx:緩衝區大小
        %edi:當前緩衝區偏移量
        %cl:當前字符
        
   #定義的常數
   .equ LOWERCASE_A, 'a' #下界
   .equ UPPERCASE_Z, 'z' #上界
   .equ UPPER_CONVERSION, 'A'-'a' #距離差
   
   #棧相關信息
   .equ ST_BUFFER_LEN, 8 #棧中,存放緩衝區大小的位置
   .equ ST_BUFFER, 12    #棧中,存放緩衝區的位置

convert_to_upper:    pushl %ebp
                     movl %esp, %ebp
                     
                     #設置變量
                     movl ST_BUFFER(%ebp), %eax
                     movl ST_BUFFER_LEN(%ebp), %ebx
                     movl $0, %edi
                     
                     #若緩衝區大小爲0,則退出程序
                     cmpl $0, %ebx
                     je end_convert_loop
convert_loop:    movb (%eax, %edi, 1), %cl
                
                 cmpb $LOWERCASE_A, %cl #判斷是否越界
                 jl next_byte
                 cmpb $LOWERCASE_Z, %cl
                 jg next_byte
                 
                 addb $UPPER_CONVERSION, %cl 
                 movb %cl, (%eax, %edi, 1)
             
next_byte:    incl %edi
              cmpl %edi, %ebx
              jne convert_loop
         
end_convert_loop:    movl %ebp, %esp
                     popl %ebp
                     ret

將程序1和程序2存於文件:toupper.s中,並編譯、連接執行以下:

編譯:as toupper.s -o toupper.o

連接:ld toupper.o -o toupper

執行:./toupper toupper.s toupper.uppercase

程序說明:

程序採用索引基址尋址方式,循環讀取緩衝區中的字符,並將字符作加法運算,將小寫字母變成大寫字母。

相關文章
相關標籤/搜索