汇编学习——使用文件

      在很多场合中,应用程序需要存储数据以便以后使用,或者从配置文件读取配置信息。为了处

理这些功能,汇编语言必须能够和UNIX系统上的文件系统进行交流。本章介绍汇编语言程序中如何

处理文件,包括把数据写入文件和从文件读取数据。使用两种基本方法从汇编语言程序访问文件。

一种方法是使用标准C函数——fopen()、read()和write()。接下来C文件的I/O函数使用Linux系统调

用访问文件。在汇编语言程序设计中,可以绕过C函数调用,直接访问内核提供的Linux文件I/O系统

调用。

一、文件处理顺序

      和使用C和C++进行程序设计一样,在汇编语言程序设计中处理数据文件时必须使用特定的顺

序。

      这些动作的每一个——打开、读取、写入和关闭——都通过Linux系统调用执行。代表系统调

用的系统调用值被加载到EAX寄存器中,并且把所有参数值加载到其他通用寄存器中。

      下面列出文件处理系统调用的Linux系统调用值。

        每个文件系统调用都有其自己的一组输入值,在发出系统调用之前必须配置它们。

二、打开和关闭文件

        汇编语言中使用Linux系统调用打开和关闭文件和C或者C++语言中文件的打开和关闭是完全

相同的。C函数open()使用系统调用open,把必须的参数传递给系统调用。系统调用open返回一

个文件句柄,使用它标识打开的文件以便其他文件以便其他文件处理系统调用使用。完成文件处

理之后,使用系统调用close关闭文件。

         系统调用open的格式如下:

          pathname是包含所有子目录的完整的以空字符结尾的文件路径。如果只指定文件名,就假

设文件和运行的应用程序在相同的目录中。输入值flags确定对此文件允许的文件访问;如果是创建

文件,输入值mode确定UNIX权限设置。

           汇编语言系统调用open从下面的寄存器读取必须的参数:

  •  EAX:包含系统调用值5。
  •  EBX:包含以空字符结尾的文件名字符串的开始位置的内存地址。
  •  ECX:包含表示需要文件的访问类型的标志的整数值。
  •  EDX:如果创建新文件,则包含表示UNIX权限的整数值。

         为文件定义的访问类型和UNIX权限是至关重要的。

      1、访问类型

       对文件的所有open请求都必须声明用于打开文件的访问类型。汇编语言中必须使用这些常量

的数字值或者自己定义常量。这些常量的数字值通常表示为八进制。

      可以将文件访问类型组合起来以便启用多种访问特性。例如,如果希望创建文件并且打开它

用于读写访问,可以使用下面的指令:

movl $0102, %ecx

/*这把O_CREATE的值0100和O_RDWR的值02组合在一起。常量值中高位部分的0很重要。
  这表示这个值是八进制格式的。如果使用常量$102,就会得到十进制的错误结果*/

       如果希望把新的数据追加到已有的文件。可以使用这条指令:

movl $02002, %ecx

/*它把O_APPEND的值02000和O_RDWR的值02组合在一起,并且没有设置O_CREAT值。
  如果文件不存在,就不会创建它*/

      2、UNIX权限

      设置UNIX权限经常会导致复杂情况。操作时必须谨慎,以便确保为文件访问设置适当的权

限。标准UNIX权限针对3种类别的用户进行设置:

  • 文件的所有者
  • 文件的默认组
  • 系统上的其他所有用户

      这3种类别的每一种都被分配了文件的特定权限。使用3位表示每个类别的访问:

  • 读取位
  • 写入位
  • 执行位

       

        可以将值组合起来生成各种访问级别:

        将文件模式属性组合起来,形成单一的3位八进制数字,表示所有者。组和所有用户的权

限。这个3位的八进制数字调用open中定义,还有高位部分的0,使它成为八进制值。

movl $0644, %edx

          Linux系统为登录到系统上的每个用户分配一个umask值。umask值把默认的权

限分配给这个用户创建的文件。创建的文件的最终权限如下:     

file privs = privs & ~umask

          umask的值被反转,并和系统调用open中请求的权限进行AND操作。计算如下:

final privileges = privs & ~umask
                 = 666 & ~022
                 = 666 & ~755
                 = 644

      3、打开文件代码

       系统调用的最后一部分是声明要打开的文件名。如果没有使用路径,就假设文件和运行的可

执行程序位于相同的目录中。  

        文件名必须声明为空字符结尾的字符串,可以使用.asciz声明完成这个工作:

.section .data
filename: 
  .asciz "output.txt"
.
.
.section .text
.
.
  movl $filename, %ebx  

          因为EBX寄存器包含字符串位置的内存地址,所以必须在变量名称前面使用美元符号以便

获得字符串位置的内存地址。

           经常使用的另一种方法是允许文件名被声明为程序的命令行参数。位置8(%ebp)包含指向

第一个命令行参数的内存位置的指针。这正是我们需要的文件名;因此,可以使用指针:

movl %esp, %ebp
.
.
movl 8(%ebp), %ebx

            所有部分组合到一起,可以使用下面代码片段:

movl %5, %eax
movl $filename, %ebx
movl $0102, %ecx  # 使用O_CREAT和O_WRONLY访问
movl $0644, %edx # UNIX权限为0644(所有者可以进行读/写访问,其他任何用户可以进行读取访问)
int $0x80
test %eax, %eax # 系统调用返回时,EAX寄存器包含一个带符号整数值。这个值要么是非负值,它是打开
                # 文件的文件句柄;要么是负值,它是表示为什么不能打开文件的错误代码。因为错误代码
                # 是负值,所以可以使用JS条件分支指令,通过检查符号位进行检查,
js badfile

         文件句柄存储在某个位置:

  • .data段中定义的内存位置
  • .bss段中定义的内存位置
  • 堆栈,在局部变量段中
  • 寄存器

      4、打开错误返回代码

      5、关闭文件

      完成对文件的使用之后,正确的操作是关闭它;否则,就有可能造成文件破坏。系统调用close

使用单一输入参数——要关闭的已打开的文件句柄。参数存在EBX寄存器中:

movl filehandle, %ebx
movl $6, %eax
int $0x80

三、写入文件

      打开文件之后,可以使用系统调用write写入它。

      1、简单的写入范例

.section .data
  filename:
    .asciz "cpuid.txt"
  output:
    .asciz "The processor Vender ID is 'xxxxxxxxxxxx'\n"

.section .bss
  .lcomm filehandle, 4

.section .text
  .globl _start

_start:
  movl $0, %eax
  cpuid 
  movl $output, %edi
  movl %ebx, 28(%edi)
  movl %edx, 32(%edi)
  movl %ecx, 36(%edi)

  movl $5, %eax
  movl $filename, %ebx
  movl $01101, %ecx # 使用O_TRUNC、O_CREAT和O_WRONLY文件访问模式打开(或者创建)文件cpuid.txt
                    # 如果文件已经存在,就把文件截断(清除其内容),因为没有包含O_APPEND访问模式
  movl $0644, %edx
  int $0x80
  test %eax, %eax
  js badfile
  movl %eax, filehandle
  
  movl $4, %eax
  movl filehandle, %ebx
  movl $output, %ecx
  movl $42, %edx
  int $0x80
  test %eax, %eax
  js badfile
  
  movl $6, %eax
  movl filehandle, %ebx
  int $0x80

badfile:
  movl %eax, %ebx
  movl $1, %eax
  int $0x80

    

      2、改变文件访问模式

       程序生成的新文件替换了已有文件中的数据。如果想把新的数据追加到文件中的

已有数据之后,那么可以改变文件访问模式值。使用下面代码替换系统调用open的代

码:

movl $5, %eax
movl $filename, %ebx
movl $02101, %ecx
movl $0644, %edx
int $0x80
test %eax, %eax
js badfile
movl %eax, filehandle

         值改变了ECX寄存器中设置的值,现在添加了O_APPEND访问模式值。

      3、处理文件错误

       在Linux环境中,有很多原因会造成访问文件的尝试失败。其他进程可能锁住文

件,用户可能意外地删除文件,甚至没有经验的系统管理员可能把错误的权限分配给

文件。必须确保汇编语言代码准备好处理失败的情况。

        测试错误代码处理的简单方式是强制产生错误的条件。对于这个例子来说,我把输出文件改

变为只读文件,然后再次运行程序:

         输出文件cpiid.txt的UNIX文件模式被设置为444,表示文件的所有者只能进行只读访问,系

统上的任何其他用户也一样。返回值是243,可以通过256减去这个值确定真正的错误码,其结果

是13。从文键errno.h可以确定错误代码13是EACCES错误,它表示不具有写入文件的权限。

四、读取文件

       要处理的下一个步骤是读取文件中包含的数据。使用系统调用read完成这个功能。系统调用

read的UNIX man页面如下:

      系统调用read使用3个输入值,生成单一输出值。3个输入值如下:

  • 从其读取数据的文件的文件句柄
  • 存放读出数据的缓冲区位置
  • 试图从文件读取的字节数

       返回值表示系统调用从文件实际读出的字节数量。数据类型ssize_t和数据类型size_t类似,

但是它是带符号整数值。这是因为如果发生错误,read函数可能返回负值。如果系统调用read返

回0,则表示已经到达了文件的末尾。

        能够读取的的字节可能少于输入值中指定的字节数量。这可能是由于到达了文件的末尾造成

的,也可能是发出调用read时数据不可用造成的。

         当然,在发出系统调用read之前要把输入值存放在寄存器中。系统调用read使用的寄存器

如下:

  • EAX:read的系统调用值(3)
  • EBX:打开文件的文件句柄
  • ECX:数据缓冲区的内存位置
  • EDX:要读取的字节数量的整数值

        可以看到,系统调用read需要打开文件的文件句柄,所以在能够使用系统调用read之前,必

须使用系统调用open打开文件。确保使用read访问模式设置(使用O_RDONLY或者O_RDWR模

式值)打开文件,这很重要。这有个例外:和STDOUT的情况类似,可以使用STDIN文件句柄

(值为0)从标准输入设备(通常是键盘)读取数据,而不必打开它。

       1、简单的读取范例

.section .bss
  .lcomm buffer, 42
  .lcomm filehandle, 4

.section .text
  .globl _start

_start:
  movl %esp, %ebp
  movl $5, %eax  # 使用open系统调用
  movl 8(%ebp), %ebx # 第一个命令行参数8(%ebp)中指定要打开的文件名
  movl $00, %ecx  # 因为这个程序仅仅读取数据,所以在O_RDONLY模式下打开文件
  movl $0444, %edx
  int $0x80
  test %eax, %eax
  js badfile
  movl %eax, filehandle

  movl $3, %eax  # 使用read系统调用
  movl filehandle, %ebx # 文件句柄
  movl $buffer, %ecx  # 标签buffer指定缓冲区
  movl $42, %edx  # 读取42字节的数据(包括新行字符)
  int $0x80
  test %eax, %eax
  js badfile
  
  movl $4, %eax # 使用write系统调用
  movl $1, %ebx # 文件句柄STDOUT
  movl $buffer, %ecx
  movl $42, $edx
  int $0x80
  test %eax, %eax
  js badfile

  movl $6, %eax # 使用close系统调用
  movl filehandle, %ebx
  int $0x80

badfile:
  movl %eax, %ebx
  movl $1, %eax
  int $0x80

     在真正的程序中,首先检查参数数量以便确保可以从命令行读取某些数据,这样的做法要好得

多,而不是让open函数发出错误消息。

      2、更加复杂的读取范例

      上面这个例子有些不切实际,它指定需从文件读取的数据的确切数量。更加常见的情况是,

无法确切地知道需要读取多少数据。替换的做法是,必须循环遍历整个文件,直到到达文件末

尾。

       可以使用这种方法是因为系统调用read的工作方式。每次使用系统调用read时,在打开的文

件中使用文件指针表示从文件读取的数据的最后一个字节。如果使用另一次系统调用read,它就

紧跟在文件指针位置后面的字节开始读取。读取完成时,把文件指针转移到读出的最后一个字

节。这样持续操作,直到到达文件的末尾。

       系统调用read到达文件的末尾的标志是读取操作返回0值。必须在每次迭代时检查返回值,

检查错误或者0值的情况。如果返回值为0,就说明已经到达了文件的末尾。

.section .bss
  .lcomm buffer, 10
  .lcomm filehandle, 4

.section .text
  .globl _start

_start:
  movl %esp, %ebp
  movl $5, %eax
  movl 8(%ebp), %ebx
  movl $00, %ecx
  movl $0444, %edx
  int $0x80
  test %eax, %eax
  js badfile
  movl %eax, filehandle

read_loop:
  movl $3, %eax
  movl filehandle, %ebx
  movl $buffer, %ecx
  movl $10, %edx  # 从文件读取10个字节的数据块
  int $0x80
  test %eax, %eax
  jz done # 当系统调用返回0值时,我们就知道已经到达了文件末尾,并且可以关闭文件句柄
  js done
  movl %eax, %edx
  movl $4, %eax
  movl $1, %ebx
  movl $buffer, %ecx
  int $0x80
  test %eax, %eax
  js badfile
  jmp read_loop

done:
  movl $6, %eax
  movl filehandle, %ebx
  int $0x80
  
badfile:
  movl %eax, %ebx
  movl %1, %eax
  int $0x80

     readtest2可以读取任何长度的文件,这里使用10字节是为了显示循环操作的效果。在产品型

的应用程序中,希望使用更大的块长度使执行系统调用read的次数更少,以便改进应用程序的性

能。

五、读取、处理和写入数据

     当处理文件读出的数据时,经常希望把数据写回到文件中。

     系统调用read使用文件指针跟踪已经从文件读出了什么数据,和它一样,系统调用

write保持指向已经写入到文件的最后数据的文件指针。可以把数据块写入输出文件,

然后使用另一次系统调用write把更多数据写到原始的数据块中。

.section .bss
  .lcomm buffer, 10
  .lcomm infilehandle, 4
  .lcomm outfilehandle, 4
  .lcomm size, 4

.section .text
  .globl _start

_start:
  # open input file, specified by the first command line param
  movl %esp, %ebp
  movl $5, %eax
  movl 8(%ebp), %ebx
  movl $00, %ecx
  movl $0444, %edx
  int $0x80
  test %eax, %eax
  js badfile
  movl %eax, infilehandle
  
  # open input file, specified by the second command line param
  movl $5, %eax
  movl 12(%ebp), %ebx
  movl $01101, %ecx
  movl $0644, %edx
  int $0x80
  test %eax, %eax
  js badfile
  movl %eax, outfilehandle

  # read one buffer's worth of data from input file
read_loop:
  movl $3, %eax
  movl infilehandle, %ebx
  movl $buffer, %ecx
  movl $10, %edx
  int $0x80
  test %eax, %eax
  jz done
  js badfile
  movl %eax, size

  # send the buffer data to the conversion function
  pushl $buffer
  pushl size
  call convert
  addl $8, %esp
  
  # write the converted data buffer to the output file
  movl $4, %eax
  movl outfilehandle, %ebx
  movl $buffer, %ecx
  movl size, %edx
  int $0x80
  test %eax, %eax
  js badfile
  jmp read_loop
  
done:
  # close the output file
  movl $6, %eax
  movl outfilehandle, %ebx
  int $0x80
  
  # close the input file
  movl $6, %eax
  movl infilehandle, %ebx
  int $0x80

badfile:
  movl %eax, %ebx
  movl $1, %eax
  int $0x80
  
  # convert lower case letters to upper case
  .type convert, @function
convert:
  pushl %ebp
  movl %esp, %ebp
  movl 12(%ebp), %esi
  movl %esi, %edi
  movl 8(%ebp), %ecx

convert_loop:
  lodsb   # 一次一字节地把字符串加载到EAX寄存器中。
  cmpb $0x61, %al
  jl skip
  cmpb $0x7a, %al
  jg skip
  subb $0x20, %al

skip:
  stosb # 把字符串存储回目标内存位置中
  loop convert_loop
  movl %ebp, %esp
  popl %ebp
  
  ret
  

六、内存映射文件

      如果错误地试图使用相同的文件名作为输入和输出文件名,就会发生这样的结果:

      程序运行了并且没有生成错误代码,但是输出文件是空的。文件存在,但是其中没有输出数

据。

      问题在于系统不能在读取文件的同时把数据写入同一个文件。很多应用程序都必须更新文

件。如果需要在应用程序中使用这个功能,有几种方式去解决它。一种方法称为内存映射文件

(memory-mapped files)。

     1、什么是内存映射文件

      内存映射文件使用系统调用mmap把部分文件映射到系统的内存中。文件(或者部分文件)被

存放到内存之后,程序可以使用标准内存访问指令访问内存位置,并且如果必须的话,可以修改

它们。可以在多个进程之间共享内存位置,这使多个程序可以同时更新同一个文件。

         把文件加载到内存中之后,操作系统控制哪写用户可以对内存区域进行什么类型的访问。内

存映射文件的内存可以写回原始文件。使用内存映射文件的内存替换原始文件的内容。这是更新

几乎任何长度的文件(最大到系统的虚拟内存限制)。

     2、mmap系统调用

     mmap系统调用用于创建内存映射文件。mmap系统调用的格式如下:

     下面是输入值:

  • addr:在内存中的什么位置映射文件
  • length:加载到内存中的字节数量
  • prot:内存保护设置
  • flags:要创建的映射对象的类型
  • fd:要映射到内存的文件的文件句柄
  • offset:文件中要复制到内存的数据

      addr值可以设置为0,这使系统可以选择在内存中的什么位置存放内存映射文件。如果offset

值被设置为0,length值被设置为文件长度,就把整个文件映射到内存。mmap函数在很大程度上

依赖于系统的内存页面长度。如果length值步恩嗯填充整个页面(或者填充多个页面,还剩下没

有填充满的页面),就使用0填充页面的剩余部分。如果使用offset值,它必须是系统页面长度的

倍数。

      prot值包含确定对内存映射文件允许的访问权限的设置(和系统调用open中使用的访问权限

类似)。可以使用的值如下表所示:

      flags值定义操作系统如何控制内存映射文件。可以使用很多不同的标志,但是最常用的值有

两个,如下表所示:

      这两种模式之间的区别很大。标志MAP_SHARE指示操作系统把对内存映射文件做出的任何

改动都写入原始文件。关闭内存映射文件时,标志MAP_PRIVATE忽略对内存映射文件作出的任

何改动。虽然这种方式看上去很奇怪,但是如果需要创建需要快速访问的临时文件,那么它实际

上是很方便的。

      对内存映射文件作出改动时,并没有同时把改动写入原始文件。这是要记住的很重要的一

点。使用两个系统调用确保把内存映射文件中的数据写入原始文件:

  •   mysnc:对原始文件和内存映射文件进行同步
  •   munmap:从内存中删除内存映射文件并且把所有改动写入原始文件

      如果打算在做出任何改动之后,在很长一段时间内把内存映射文件保存在内存中,那么使用

系统调用msync确保把改动写入文件是个好主意。如果在发出系统调用mysnc或者munmap之

前,程序或者操作系统崩溃,对内存映射文件做出的任何改动都不会写入原始文件。

      系统调用msync和munmap具有类似的格式:

        输入值addr是内存映射文件在内存中的开始位置。系统调用mmap返回这个值。输入值

length是要写入原始文件的字节数量。msync的输入值flags可以定义如何更新原始文件:

  • MS_ASYNC:在下次可以写入文件时安排更新,并且系统调用返回
  • MS_SYNC:系统调用等待,直到做出更新,然后再返回调用程序

         要记住的重要一点是内存映射文件不能改动原始文件的长度。

     3、mmap汇编语言格式

      在执行Linux系统调用之前,必须把文件unistd.h中找到的系统调用值放到EAX寄存器中。系

统调用mmap的系统调用值显示在下表:

     

      但是,使用前面介绍的Linux系统调用标准格式和系统调用mmap时有一个问题,输入值超过5

个的系统调用不能使用这种方法(没有足够的寄存器可用)。 

       替换做法是具有超过5个输入值的系统调用(比如mmap)从内存中定义的结构读取输入值。

从指定的内存位置开始,依次把每个输入值存放到内存中。发出系统调用之前,将内存结构的开

始位置存放在EBX寄存器中。

       在函数调用之前,输入值要么存放在单独定义的内存中,要么压入一般的程序堆栈中。如果

把这些值压入堆栈,要记住在堆栈中按照相反的顺序存放它们(从右到左)。还必须记住当系统

调用返回时从堆栈中删除它们(把ESP寄存器移动回去)。

       系统调用mmap使用的模板如下:

pushl $0  # offset of 0
pushl filehandle  # the file handle of the open file
pushl $1  # MAP_SHARED flag set to write changed data back to file
pushl $3  # PROT_READ and PROT_WRITE permissions
pushl size  # the size of the entire file
pushl $0  # Allow the system to select the location in  memory to start
movl %esp, %ebx  # copy the parameters location to EBX
movl $90, %eax # set the system call value
int $0x80
addl  $24, %esp
movl %eax, mappedfile  # store the memory location of the memory mapped file

       这个例子创建包含整个文件的内存映射文件,允许对文件进行读取和写入处理,并且允许把

所有更新存储到原始文件中。系统调用的返回值存放在EAX寄存器中,它指向内存映射文件开始

的内存位置。可以储存这个值,然后可以像使用任何其他内存位置(比如程序readtest中使用的

缓冲区位置)一样使用它。

       为了对内存中的文件进行unmap操作,系统调用unmap的格式如下:

movl $91, %eax
movl mappedfile, %ebx
movl size, %ecx
int $0x80

        因为系统调用munmap只使用两个输入值,所以在发出系统调用之前,可以把它们存放在

EBX和ECX寄存器中。如果成功地把数据写回文件,系统调用munmap将返回0值。如果不成

功,就会返回-1值。

     4、mmap范例

     1)程序的组成部分

      在构建mmap主程序之前,需要构建其他几个程序片段。

      首先,要确定文件的长度。一种最简单的方法是使用Linux系统调用llseek(它是POSIX系统

调用lseek的扩展)。使用Linux系统调用llseek创建一个函数,给定打开文件的文件句柄,使用这

个函数计算文件长度。

       其次,必须有一个函数修改内存映射文件的数据,以便我们能知道完成操作时数据被正确地

存放回了文件中。程序readtest3.s中的函数convert可以很好地完成这个工作。对它做出一些修

改,使它成为正确的C样式汇编语言函数。

       系统调用llseek试图使文件指针前进到文件中的特定位置。可以指示系统调用llseek使指针向

文件末尾,并且查看从文件到这个位置有多少字节。

       系统调用llseek的格式如下:

       输入值fd是文件的文件句柄。输入值offest_high和offset_low定义从文件开头算起的偏移值的

高位和低位部分(long类型数据)。因为我们使用系统调用确定整个文件的长度,所以把这些值

设置为0。输入值result有些容易引起误解。它是将存储系统调用的结果的内存位置的地址。

      输入值whence用于确定系统调用llseek将到达文件中的什么位置。我们感兴趣的值是

SEEK_END,它的值为2。

      函数sizefunc.c使用系统调用llseek确定文件长度:

.section .text
  .globl sizefunc
.type sizefunc, @function

sizefunc:
  pushl %ebp
  movl %esp, %ebp
  subl $8, %esp # 一般C样式函数开头,在堆栈中保留4个字节用作系统调用llseek的结果值
  pushl %edi
  pushl %esi
  pushl %esi
  movl $140, %eax # 系统调用llseek的系统调用值
  movl 8(%ebp), %ebx # 文件句柄作为第一个输入值被传递进来
  movl $0, %ecx 
  movl $0, %edx
  leal -8(%ebp), %esi # 把局部变量位置存放到ESI寄存器中(必须是8字节长,因为llseek系统
                      # 调用返回的文件长度为long类型值)
  movl $2, %edi 
  int $0x80
  movl -8(%ebp), %eax # 把结果存入EAX寄存器

  popl %ebx
  popl %esi
  popl %edi
  movl %ebp, %esp
  popl %ebp

  ret

        函数convert被修改为正确的C样式函数,程序convert.s中显示它:

.section .text
.type convert, @function
  .globl cnvert

convert:
  pushl %ebp
  movl %esp, %ebp
  pushl %esi
  pushl %edi
  
  movl 12(%ebp), %esi # 读取缓冲区的位置并且存放到ESI寄存器中
  movl %esi, %edi 
  movl 8(%ebp), %ecx # 读取文件长度并且存放在ECX寄存器中
  
convert_loop:
  lodsb
  cmpb $0x61, %al
  jl skip
  cmpb $0x7a, %al
  jg skip
  subb $0x22, %al

skip:
  stosb
  loop convert_loop
  pop %edi
  pop %esi
  movl %ebp, %esp
  popl %ebp

  ret
  

     2)主程序

      程序fileconvert.s把文件中的文本从小写转为大写:

.section .bss
  .lcomm filehandle, 4
  .lcomm size, 4
  .lcomm mappedfile, 4

.section .text
  .globl _start

_start:
  # get the file name and open it in read/write mode
  movl %esp, %ebp
  movl $5, %eax
  movl 8(%ebp), %ebx
  movl $0102, %ecx
  movl $0644, %edx
  in $0x80
  test %eax, %eax
  js badfile
  movl %eax, filehandle

  # find the size of the file
  pushl filehandle
  call sizefunc
  movl %eax, size
  addl $4, %esp
  
  # map file to memory
  pushl $0
  pushl filehandle
  pushl $1    # MAP_SHARED
  pushl $3    # PROT_READ | PROT_WRITE
  pushl size  # file size
  pushl $0    # NULL
  movl %esp, %ebx
  movl $90, %eax
  int $0x80
  test %eax, %eax
  js badfile
  movl %eax, mappedfile
  addl $24, %esp
  
  # convert the memory mapped file to all uppers
  pushl mappedfile
  pushl size
  call convert
  addl $8, %esp
  
  # use munmap to send the changes to the file
  movl $91, %eax
  movl mappedfile, %ebx
  movl size, %ecx
  int $0x80
  test %eax, %eax
  jnz badfile

  # close the open file handle
  movl $6, %eax
  movl filehandle, %ebx
  int $0x80

badfile:
  movl %eax, %ebx
  movl $1, %eax
  int $0x80
  

       应用程序代码中的每个指令段执行一个单一功能。执行步骤如下:

  • 使用读/写访问打开文件
  • 使用函数sizefunc确定文件长度
  • 使用系统调用mmap把文件映射到内存中
  • 把内存映射文件的全部内存转换为大写字母
  • 使用munmap把内存映射文件写入原始文件
  • 关闭原始文件并且退出

     3)监视程序

    

      可以用strace监视系统调用:

转载于:https://my.oschina.net/u/2537915/blog/700886

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值