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:文件描述符字符串
上文已經指出,.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)打開輸入文件 (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中究竟如何進行的下面操做,待後面進一步探究。
#功能:將緩衝區中的字符轉爲大寫形式 #輸入:參數一:轉換的字符塊地址 # 參數二:緩衝區大小 #輸出:以大寫字符覆蓋當前緩衝區 #變量:%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
程序採用索引基址尋址方式,循環讀取緩衝區中的字符,並將字符作加法運算,將小寫字母變成大寫字母。