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
程序采用索引基址寻址方式,循环读取缓冲区中的字符,并将字符作加法运算,将小写字母变成大写字母。