Bootstrap

3、Linux汇编——文件处理

第五章 文件处理

1、Unix中的文件及操作

    Unix中的文件都可以作为连续的“字节流”进行访问。每个进程有若干“文件描述符”,这些“文件描述符”即为数字。如:标准输入、输出、错误输出的文件描述符为:0,1,2。每个进程都有以上三个标准文件描述符。当打开文件时,系统自动分配文件描述符,关闭文件后,文件描述符由系统回收。

    有了文件描述符后,还需要指出对该文件进行什么操作——打开模式,常见的四种打开模式:读、写、读写、创建。由于汇编语言不像C语言,可以用字符串来制定文件名和打开模式,所以用数字来表示系统调用和参数。以下为文件系统调用对应的寄存器使用说明:

(1)open调用——返回的文件描述符存入%eax中:功能:根据文件名打开文件。

%eax=5:系统调用号        %ebx:文件名起始地址            %ecx:以数字表示的读写模式            %edx:权限集合

(2)read调用——返回读取字符数或负值(错误码)存入%eax中:功能:根据文件描述符读取数据

%eax=3:系统调用号    %ebx:文件描述符    %ecx:缓冲区地址    %edx:缓冲区大小

(3)write调用——返回写字符数或负值(错误码)存入%eax中:功能:根据文件描述符读取数据

%eax=4:系统调用号    %ebx:文件描述符    %ecx:缓冲区地址    %edx:缓冲区大小

(4)close调用——    :功能:根据文件描述符关闭文件

%eax=6:系统调用号    %ebx:文件描述符

2、缓冲区和.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

3、程序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)关闭输入、输出文件

其中需要注意如下知识点:

1、.equ为“宏”,即将抽象的以数字形式的系统调用等,改成字符串,更加形象,在汇编过程中,字符串将被数字替换。

2、本函数与上一篇中关于函数调用压栈顺序有不一致,其不一致处为:上一篇中将当前“调用函数”的地址压栈,而本函数在bash调用函数时,未将调用函数的当前执行地址压栈。区别如下图所示,图一为上一篇压栈情况,图二为本函数的压栈情况:

图一:

192547_IRAK_438386.png

图二:

192547_WXTO_438386.png

Linux中究竟如何进行的下面操作,待后面进一步探究。

4、程序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

程序说明:

程序采用索引基址寻址方式,循环读取缓冲区中的字符,并将字符做加法运算,将小写字母变成大写字母。

转载于:https://my.oschina.net/u/438386/blog/543527

;