在介绍文件操作之前,我先介绍一下缓冲区。缓冲区是连续的字节块,用于批量的数据传输。一般缓冲区仅用于暂时存储数据,然后数据被缓冲区中读出并转换成便于程序处理的形式。注意,缓冲区的大小是固定的,由程序员设定的,例如:如果你想要一次读入500字节的数据,可以将500字节未使用的内存位置的地址发送给read系统调用,并将数字500发送给它,这样read调用的就知道数据的大小。在汇编中通过.bss来创建缓冲区,.bss段类似于数据段,不同的是它不占用可执行程序。.bss段可以保存存储位置,却不能对其进行初始化,e而在数据段中,你既可以保留存储位置,又可以对其进行初始化。例如如下指令:
.section .bss .lcomm my_buffer, 500.lcomm 指令将创建一个符号my_buffer ,指代我们用作缓冲区的500字节存储位置,my_buffer表示的数字本身就是缓冲区的起始地址。
下面介绍相关的文件操作,首先是打开文件:
打开文件是通过open系统调用来实现的,其所需要的参数是文件名,表示模式的数字以及权限集合。
1.将系统调用号5存放到%eax寄存器中
2.将文件名的第一个字符的地址应存放到%ebx寄存器中,该字符串必须以空字符串结束。
3.将用于表示打开用于读、写、读写、如果不存在则创建、如果存在则删除等的数字存入%ecx寄存器中
4.将表示打开文件的权限存入%edx寄存器中,当需要创建文件的时候会用到此项
最后在进行open系统调用的时候,新打开的文件描述符存储在%eax寄存器中。
通常代码如下:
#系统调用编号 .equ SYS_OPEN, 5 .equ ST_ARGV_1, 8 #输入文件的名字 .equ ST_ARGV_2, 12#输出文件的名字 <p class="p1"><span class="s1">.equ</span><span class="s2"> ST_FD_IN, </span><span class="s3">-4</span></p><p class="p1"><span class="s1">.equ</span><span class="s2"> ST_FD_OUT, </span><span class="s3">-8</span></p>#文件打开选项 .equ O_RDONLY, 0 .equ O_CREAT_WRONLY_TRUNC, 03101 open_files: open_fd_in: ###打开输入文件### #打开系统调用 movl $SYS_OPEN, %eax #将输入文件名字的指针放入%ebx movl ST_ARGV_1(%ebp), %ebx #只读标志 movl $O_RDONLY, %ecx #这实际上并不影响读操作 movl $0666, %edx #调用Linux int $LINUX_SYSCALL open_fd_out: ###打开输出文件### #打开文件 movl $SYS_OPEN, %eax #将输出文件名字的指针放在%ebx movl ST_ARGV_2(%ebp), %ebx #写入文件标志 movl $O_CREAT_WRONLY_TRUNC, %ecx #新文件模式(如果已经创建) movl $0666, %edx #调用Linux int $LINUX_SYSCALL
#这里存储文件描述符 store_fd_in: movl %eax, ST_FD_IN(%ebp) store_fd_out: movl %eax, ST_FD_OUT(%ebp)
下面就是读文件的操作:
读文件通过read体统调用来实现,这个调用的参数是一个用于读取的文件描述符、一个用于写入的缓冲区、以及缓冲区的大小。
1.将系统调用号3存入%eax寄存器中
2.将文件描述符存入%ebx寄存器中
3.将存储数据的缓冲区地址存入%ecx寄存器中
4.将缓冲区的大小存入%edx中
系统调用返回实际读取的字节数或者文件结束符(0),存放到%eax寄存器中。
通常代码如下:
<p class="p1"><span class="s1">.equ</span><span class="s2"> SYS_READ, </span><span class="s3">3</span></p><pre name="code" class="plain"><span style="font-family: Arial, Helvetica, sans-serif;">#####从文件中读取一个数据块#####</span>movl $SYS_READ, %eax#获取文件描述符movl ST_FD_IN(%ebp), %ebx#放置读取数据的存储位置movl $BUFFER_DATA, %ecx#放置缓冲区的大小movl $BUFFER_SIZE, %edx#读取缓冲区大小返回到%eaxint $LINUX_SYSCALL 接着是写文件操作:
写文件是通过write系统调用实现的,需要的参数与read系统调用的参数相同,唯一的区别是缓冲区应该填满了要写的数据。
1.将系统调用号4存入%eax寄存器中
2.将文件描述符存入%ebx寄存器中
3.将存储数据的缓冲区地址存入%ecx寄存器中
4.将缓冲区的大小存入%edx中
系统调用返回实际写入的字节数或者错误代码,存放到%eax寄存器中。
通常代码如下:
####将字符块写入文件#### #缓冲区大小 movl %eax, %edx movl $SYS_WRITE, %eax #要使用的文件 movl ST_FD_OUT(%ebp), %ebx #缓冲区位置 movl $BUFFER_DATA, %ecx int $LINUX_SYSCALL
最后是关闭文件操作:
关闭文件是通过close系统调用实现,其需要的参数是文件描述符。
1.将系统调用号6存入%eax寄存器中
2.将文件描述符存入%ebx寄存器中
通常代码如下:
##关闭文件## 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)如果未达到输入文件的尾部: # (a)将部分文件读入到内存缓冲区 # (b)读取内存缓冲区的每一个字节如果该字节# 为小写字母,就将转化为大写字母 # (c)将内存缓冲区写入输出文件 .section .data ######################## ########常数############ #系统调用编号 .equ SYS_OPEN, 5 .equ SYS_CLOSE, 6 .equ SYS_READ, 3 .equ SYS_WRITE, 4 .equ SYS_EXIT, 1 #文件打开选项 .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 #缓冲区--从文件中将数据加载到这里,也要从这里将数据写出到文件 # 由于种种原因,缓冲区大小不应该超过16000字节 .equ BUFFER_SIZE, 500 .lcomm BUFFER_DATA, BUFFER_SIZE .section .text #栈位置 .equ ST_SIZE_RESERVE, 8 .equ ST_FD_IN, -4 .equ ST_FD_OUT, -8 .equ ST_ARGC, 0 #参数数目 .equ ST_ARGV_0, 4 #程序名 .equ ST_ARGV_1, 8 #输入文件的名字 .equ ST_ARGV_2, 12#输出文件的名字 .globl _start _start: ###程序初始化#### #保存栈指针 movl %esp, %ebp #在栈上为文件描述符分配空间 subl $ST_SIZE_RESERVE, %esp #局部变量 open_files: open_fd_in: ###打开输入文件### #打开系统调用 movl $SYS_OPEN, %eax #将输入文件名字的指针放入%ebx movl ST_ARGC(%ebp), %ebx cmpl $1, %ebx #$1放在前面,%ebx放在后面 je input_STDIN movl ST_ARGV_1(%ebp), %ebx jmp input_assign_file input_STDIN: movl $STDIN, %ebx input_assign_file: #只读标志 movl $O_RDONLY, %ecx #这实际上并不影响读操作 movl $0666, %edx #调用Linux int $LINUX_SYSCALL store_fd_in: movl %eax, ST_FD_IN(%ebp) open_fd_out: ###打开输出文件### #打开文件 movl $SYS_OPEN, %eax #将输出文件名字的指针放在%ebx movl ST_ARGC(%ebp), %ebx cmpl $2, %ebx <span style="font-family: Arial, Helvetica, sans-serif;">#$2放在前面,%ebx放在后面</span> je output_STDOUT movl ST_ARGV_2(%ebp), %ebx jmp output_assign_file output_STDOUT: movl $STDOUT, %ebx output_assign_file: #写入文件标志 movl $O_CREAT_WRONLY_TRUNC, %ecx #新文件模式(如果已经创建) movl $0666, %edx #调用Linux int $LINUX_SYSCALL store_fd_out: #这里存储文件描述符 movl %eax, ST_FD_OUT(%ebp) ###主循环开始### read_loop_begin: #####从文件中读取一个数据块##### movl $SYS_READ, %eax #获取文件描述符 movl ST_FD_IN(%ebp), %ebx #放置读取数据的存储位置 movl $BUFFER_DATA, %ecx #放置缓冲区的大小 movl $BUFFER_SIZE, %edx #读取缓冲区大小返回到%eax int $LINUX_SYSCALL ###如果达到文件结束处就退出### #检查文件结束标志# cmpl $END_OF_FILE, %eax #如果发现文件结束符或出现错误,就跳转到程序结束处 jle end_loop continue_read_loop: ###将字符块的内容转换成大写形式#### pushl $BUFFER_DATA #缓冲区地址 pushl %eax #缓冲区大小 call conver_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 ###退出程序### movl $SYS_EXIT, %eax movl $0, %ebx int $LINUX_SYSCALL #目的; 这个函数实际上是将字符块的内容转化为大写的形式 #输入: 第一个参数是要转化的缓冲区的地址 # 第二个参数的是缓冲区的大小 #输出: 这个函数以大写字符覆盖当前的缓冲区 #变量: # %eax--缓冲区起始地址 # %ebx--缓冲区长度 # %edi--当前缓冲区偏移量 # %cl--当前正在检测的字节(%ecxde第一部分) ###常数### #搜索的下边界 .equ LOWERCASE_A, 'a' #搜索的上边界 .equ LOWERCASE_Z, 'z' #大小写转换 .equ UPPER_CONVERSION, 'A'-'a' ###栈相关的信息### .equ ST_BUFFER_LEN, 8 #缓冲区长度 .equ ST_BUFFER, 12 #实际缓冲区 conver_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 coop_end coop_start: #获取当前字节 movb (%eax, %edi, 1), %cl #除非该字节在'a'和'z'之间,否则读取下一个字节 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 coop_start #缓冲区结束,离开函数 coop_end: movl %ebp, %esp popl %ebp ret
as -32 toupper.s -o toupper.o ld -m elf_i386 toupper.o -o toupper ./toupper