第五章 文件处理
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调用函数时,未将调用函数的当前执行地址压栈。区别如下图所示,图一为上一篇压栈情况,图二为本函数的压栈情况:
图一:
图二:
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
程序说明:
程序采用索引基址寻址方式,循环读取缓冲区中的字符,并将字符做加法运算,将小写字母变成大写字母。