前一章介绍了如何用open系统调用来打开文件,一旦打开了文件,你就可以使用write系统调用向该文件里写入数据 在之前的 汇编开发示例 (一) 的文章里,介绍过一个cpuid.s的示例程式,该程式里就用到了write系统调用来向显示器输出字符串信息...

    本文由zengl.com站长对汇编教程英文版相应章节进行翻译得来。

    汇编教程英文版的下载地址:点此进入原百度盘   , 点此进入Dropbox网盘   , 点此进入Google Drive  (Dropbox与Google Drive里是Assembly_Language.pdf文档)

    另外再附加一个英特尔英文手册的共享链接地址:
    点此进入原百度盘点此进入Dropbox网盘点此进入Google Drive  (在某些例子中会用到,Dropbox与Google Drive里是intel_manual.pdf文档)

    本篇翻译对应汇编教程英文原著的第491页到第501页,对应原著第16章 (注意这里的页数不是页脚的页数,而是pdf电子档顶部,分页输入框中的页数,也就是包含了目录,前言部分的总页数,pdf电子档总页数是577,当前已翻译完的页数为501 / 577)。

Writing to Files 向文件中写入数据:

    前一章介绍了如何用open系统调用来打开文件,一旦打开了文件,你就可以使用write系统调用向该文件里写入数据。

    在之前的"汇编开发示例 (一)"的文章里,介绍过一个cpuid.s的示例程式,该程式里就用到了write系统调用来向显示器输出字符串信息,因为在UNIX和类UNIX系统中,所有的东东都是以文件的形式来进行操作的,当前会话的显示终端即标准输出设备(STDOUT),对应的文件描述符是1 ,通过向该文件描述符写入数据,就可以将信息显示到屏幕上,STDOUT的文件描述符1是由系统打开的,不需要我们手动去打开,还有一个标准错误输出设备(STDERR)的文件描述符默认是2,也是可以直接进行写入操作的。

    至于其他的非系统默认打开的文件,就必须先通过open系统调用打开文件,并获取到文件描述符,然后才能用write系统调用向该文件里写入数据。

A simple write example -- write系统调用的例子:

    下面的cpuidfile.s程式就演示了如何通过write系统调用向打开的文件中写入数据:

# cpuidfile.s - An example of writing data to a file
.section .data
filename:
	.asciz "cpuid.txt"
output:
	.asciz "The processor Vendor 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
	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


    上面的代码其实是修改自"汇编开发示例 (一)"的文章里的cpuid.s程式,通过修改将cpuid指令的返回信息写入到cpuid.txt的文件中。

    一开始,代码先通过cpuid指令将处理器的供应商ID字符串信息给写入到output标签对应的字符串里,有关cpuid指令的输出信息格式,请参考上面提到过的"汇编开发示例 (一)"的文章。

    得到了所需的供应商字符串信息后,只需再通过open,write和close系统调用将字符串写入到指定的文件中即可。

    上面的程式里,和open系统调用相关的代码如下:

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


    上面代码中,EAX里存储着open的系统调用号5,EBX里存储着文件名字符串的内存地址(这里filename标签指向的是"cpuid.txt"的文件名),ECX里存储的是flags(访问模式),从上一篇文章中我们可以知道ECX里的01101这个八进制数字表示的是,使用O_TRUNC,O_CREAT以及O_WRONLY这三个模式来打开文件,这三个访问模式结合在一起的含义是:以只写方式打开文件,如果文件不存在则创建该文件,如果文件存在,则将文件里的原有内容清空,从头开始写入数据。

    EDX寄存器里存储的0644的八进制数字表示,如果创建文件,则该文件的用户权限为:第一个6表示owner(文件的拥有者)对文件具有读和写的权限,中间的4表示Group(组用户)对文件只有读权限,最后一个4表示Everyone(系统中的其他用户)对文件也只有读的权限。

    在设置好参数后,就可以使用int $0x80中断来执行open系统调用,返回的文件描述符存储在EAX里,通过test %eax, %eax指令来检测返回的值是否是一个有效的文件描述符,如果不是有效的文件描述符,则通过js badfile指令跳转到badfile处来退出程序,如果是有效的文件描述符,则将该文件描述符通过movl %eax, filehandle指令保存到filehandle所在的内存里,接下来的write系统调用就可以根据该文件描述符来向文件中写入数据。

    我们先通过 man 2 write 命令来了解下write系统调用的具体格式:

$ man 2 write
........................................
ssize_t write(int fd, const void *buf, size_t count);
........................................
RETURN VALUE
       On  success,  the  number  of bytes written is returned (zero indicates
       nothing was written).  On error, -1  is  returned,  and  errno  is  set
       appropriately.
........................................
$


    可以看到,write有三个输入参数,第一个fd用于表示已经打开的文件描述符,第二个buf表示需要写入的数据的指针值,第三个count表示需要向文件中写入多少个字节的数据。简单来说就是:从buf缓冲区域向fd对应的文件里写入count个字节的数据。

    因此要在汇编中使用write系统调用的话,就必须先在寄存器里准备好这三个参数,第一个fd参数放置在EBX里,第二个buf参数放置在ECX里,第三个count参数则放置在EDX中,例如,上面的cpuidfile.s程式里和write系统调用相关的代码如下:

movl $4, %eax
movl filehandle, %ebx
movl $output, %ecx
movl $42, %edx
int $0x80
test %eax, %eax
js badfile


   上面代码先将write的系统调用号4设置到EAX中,再将filehandle(之前通过open系统调用打开的文件描述符)设置到EBX,接着将output标签的内存地址设置到ECX,表示需要将output所指向的字符串信息给写入到filehandle对应的文件里(output前面必须使用美元符来表示该标签所在的内存地址值),然后将output字符串的有效长度42设置到EDX里,最后通过执行int $0x80中断就可以将output字符串给写入到cpuid.txt的文件里了。

    从前面 man 2 write 命令的输出里可以看到,write系统调用的返回值表示成功写入文件的数据的字节数,如果返回0则表示没有写入任何数据,返回负数则表示写入失败,所以在int $0x80中断执行后,还需要通过test %eax, %eax指令来检测EAX里的返回值,如果是负数,则接下来的js badfile指令就会跳转到badfile位置处退出程序。

    在对文件操作完毕后,需要通过close系统调用来关闭文件,示例中的相关代码如下:

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


    上面EAX里存储的6是close的系统调用号(上一章讲解过),close系统调用只需要接收一个文件描述符作为输入参数。

    我们可以通过汇编链接来测试该程式:

$ as -o cpuidfile.o cpuidfile.s 
$ ld -o cpuidfile cpuidfile.o
$ ./cpuidfile 
$ ls -l cpuid.txt 
-rw-r--r-- 1 root root 42  6月 28 17:17 cpuid.txt
$ cat cpuid.txt 
The processor Vendor ID is 'GenuineIntel'
$


    从上面的输出可以看到,在执行完cpuidfile程式后,当前目录内就多了一个cpuid.txt的文件,该文件的用户权限为 -rw-r--r-- 即0644,与我们设置的一致,cpuid.txt文件的内容为 The processor Vendor ID is 'GenuineIntel' ,和预期的一样。

    在open系统调用打开文件时,设置了一个O_TRUNC的访问模式,即如果文件存在的话,就将原来文件里的内容给清空掉,我们可以先在之前生成的cpuid.txt文件里添加一些数据,然后再执行一次cpuidfile程式,然后查看cpuid.txt文件里的内容是否被覆盖掉了,如果被覆盖掉了,则表示O_TRUNC起作用了:

$ cat cpuid.txt 
The processor Vendor ID is 'GenuineIntel' xdfsd
$ ./cpuidfile 
$ cat cpuid.txt 
The processor Vendor ID is 'GenuineIntel'
$


    上面先在原来的cpuid.txt文件的末尾添加了 xdfsd 的字符串,当执行完cpuidfile程式后,cpuid.txt里的内容就又被覆盖掉了,说明O_TRUNC的访问模式确实起了作用。

Changing file access modes -- 修改文件的访问模式:

    上面的例子中使用的O_TRUNC访问模式会清除文件里的原有数据,如果想向文件末尾追加数据的话,只需对open系统调用的访问模式做些改动即可:

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


    上面的代码仅仅是将ECX里的值修改为02101而已,从上一篇文章中可以知道,02101这个八进制数是由02000(O_APPEND),0100(O_CREAT)以及01(O_WRONLY)这三个访问模式组合而成,表示以只写方式打开文件,如果文件不存在则创建文件,如果文件存在则向文件的末尾追加数据。

    我们将cpuidfile.s的代码拷贝到cpuidfile2.s里,然后按照上面的代码修改ECX的访问模式值,编译链接后的运行结果如下:

$ as -o cpuidfile2.o cpuidfile2.s 
$ ld -o cpuidfile2 cpuidfile2.o 
$ ./cpuidfile2
$ cat cpuid.txt
The processor Vendor ID is 'GenuineIntel'
The processor Vendor ID is 'GenuineIntel'
$


    上面的 cat cpuid.txt 命令的输出中,第一行是之前cpuidfile程式运行时写入的字符串,第二行就是当前的cpuidfile2程式所追加的数据。

Handling file errors 处理文件错误:

    在Linux系统中,有很多的原因可以导致文件操作失败,例如,某个进程锁住了文件,或者访问的文件根本不存在等等,要知道确切的原因,最好的办法就是检测系统调用的返回值,在上一篇文章中,我们已经介绍过如何通过返回值与errno.h头文件来定位到具体的出错原因。

    我们可以将cpuid.txt文件删除,并将cpuidfile.s里的open系统调用的ecx值设置为01001,即将O_CREAT去掉,编译链接后,再执行一次cpuidfile程式:

$ rm cpuid.txt
$ as -o cpuidfile.o cpuidfile.s -gstabs
$ ld -o cpuidfile cpuidfile.o
$ ./cpuidfile
$ echo $?
254
$


    可以看到,返回值为254,本质上返回的是-2值(因为write和open之类的系统调用是以错误码的负数形式进行返回的),只不过,echo $? 命令将-2值以8位无符号数的形式进行的显示,我们也可以用256减去254,得到的2就是错误码,得到错误码后,就可以在errno.h头文件里定位到具体的出错原因:

$ cat /usr/include/asm/errno.h 
#include <asm-generic/errno.h>
$ cat /usr/include/asm-generic/errno.h 
#ifndef _ASM_GENERIC_ERRNO_H
#define _ASM_GENERIC_ERRNO_H

#include <asm-generic/errno-base.h>
.......................................
.......................................
$ cat /usr/include/asm-generic/errno-base.h 
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H

#define	EPERM		 1	/* Operation not permitted */
#define	ENOENT		 2	/* No such file or directory */
.......................................


    上面显示错误码2的出错原因是 No such file or directory 即找不到所需的文件。

Reading Files -- 从文件中读取内容:

    我们可以使用read系统调用来读取文件的内容,read系统调用的具体格式如下:

$ man 2 read
....................................
ssize_t read(int fd, void *buf, size_t count);
....................................
$


    read系统调用与write系统调用类似,也使用三个输入参数:第一个fd表示已经打开的有效的文件描述符,第二个buf表示要将fd对应文件里的内容读取到buf缓冲区域,第三个count参数表示需要从文件中读取count个字节的数据。

    该系统调用的返回值用于表示从文件中实际读取的数据的字节数,返回值是一个ssize_t类型,该类型与size_t类型相似,不过ssize_t是一个有符号的整数类型,因为,read系统调用除了可以返回正数以表示读取的字节数外,还可以在错误发生时返回负数。如果返回值为0,则表示已经读取到文件的结束位置了。

    另外,当文件里实际可供读取的数据小于count时,那么返回的字节数就会小于count参数所设置的值。

    同样的,在汇编里使用read系统调用之前,也必须先在寄存器里准备好所需的输入参数:
  • EAX: The read system call value (3)
    在EAX中设置好read系统调用号,read的系统调用号为3
  • EBX: The file handle of the open file
    将目标文件的文件描述符设置到EBX里
  • ECX: The memory location of a data buffer
    在ECX中设置buf读缓冲的内存地址
  • EDX: An integer value of the number of bytes to read
    在EDX里设置需要读取的字节数
    这里需要注意的是为了能让读取操作被正确执行,我们需要为read系统调用设置正确的访问模式,例如:O_RDONLY(只读模式)或O_RDWR(读写模式)。

    另外,类似于STDOUT,像键盘之类的标准输入设备(STDIN)的文件描述符(值为0)默认已经被打开了,所以read系统调用可以直接从标准输入设备里获取到所输入的数据。

A simple read example -- read系统调用的例子:

    下面的readtest1.s程式就演示了如何通过read系统调用来读取指定文件的内容:

# readtest1.s - An example of reading data from a file
.section .bss
	.lcomm buffer, 42
	.lcomm filehandle, 4
.section .text
.globl _start
_start:
	nop
	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
	movl $3, %eax
	movl filehandle, %ebx
	movl $buffer, %ecx
	movl $42, %edx
	int $0x80
	test %eax, %eax
	js badfile
	movl $4, %eax
	movl $1, %ebx
	movl $buffer, %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


    上面的readtest1.s程式会使用命令行里的参数1作为需要读取的文件名,在之前的"汇编函数的定义和使用 (三) 汇编函数结束篇"的文章里,介绍过程序启动时栈里的完整结构如下图所示:


图1

    由于程序一开始已经将ESP的值,赋值给了EBP,因此,EBP也指向了Number of Parameters(命令行的参数个数)的位置,EBP + 4则指向Program Name(程序名称),EBP + 8则指向了Pointer to Command Line Parameter 1(命令行参数1)的位置,因此,在代码中,就使用了movl 8(%ebp), %ebx指令将命令行参数1的字符串指针值设置到EBX里,这样该程式就可以用open系统调用来打开用户在命令行中所指定的文件了。

    在open系统调用打开文件后,我们就可以使用read系统调用从指定的文件里读取出所需的数据了,上面程式中和read系统调用相关的代码如下:

movl $3, %eax
movl filehandle, %ebx
movl $buffer, %ecx
movl $42, %edx
int $0x80
test %eax, %eax
js badfile


    上面的代码里,先将read系统调用号3设置到EAX,然后将filehandle里存储的之前open系统调用返回的文件描述符设置到EBX里,接着将buffer缓冲区域的指针设置到ECX,再将需要读取的字节数42设置到EDX里,最后执行int $0x80中断就可以将指定文件里的42个字节的数据给读取到buffer缓冲区域了,读操作执行完后,可以通过test指令来测试EAX里的返回值,如果是负数,则说明读操作失败,就通过js badfile指令跳转到badfile去退出程序。

    在将数据读取到buffer缓冲区后,还使用write系统调用将buffer里的数据写入到STDOUT(标准输出设备即显示器上,文件描述符为1),这样就知道read到底读出了哪些数据:

movl $4, %eax
movl $1, %ebx
movl $buffer, %ecx
movl $42, %edx
int $0x80


    所有文件相关的操作都执行完后,需要通过close系统调用来关闭掉打开的文件:

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


    上面的6是close的系统调用号。

    readtest1的编译运行结果如下:

$ as -o readtest1.o readtest1.s
$ ld -o readtest1 readtest1.o
$ ./readtest1 cpuid.txt 
The processor Vendor ID is 'GenuineIntel'
$


    上面通过提供cpuid.txt的命令行参数,让readtest1将cpuid.txt文件里的内容给读取和显示出来,如果不提供任何命令行参数的话,read系统调用就会执行失败,可以通过返回值来查看到具体的错误码:

$ ./readtest1 
$ echo $?
242
$


    从输出可以看到返回值是242,用256减去242,得到的14就是错误码,在errno.h头文件里可以查看到出错原因:

$ cat /usr/include/asm/errno.h 
#include <asm-generic/errno.h>
$ cat /usr/include/asm-generic/errno.h 
#ifndef _ASM_GENERIC_ERRNO_H
#define _ASM_GENERIC_ERRNO_H

#include <asm-generic/errno-base.h>
.......................................
.......................................
$ cat /usr/include/asm-generic/errno-base.h 
#ifndef _ASM_GENERIC_ERRNO_BASE_H
#define _ASM_GENERIC_ERRNO_BASE_H

.......................................
#define	EFAULT		14	/* Bad address */
.......................................

    原因是Bad address即错误的地址,因为没有提供命令行参数,因此之前代码里的8(%ebp)就指向了错误的文件名地址。

    当然,在实际开发时,最好是先检查一下命令行参数的个数,这样可以有效的防止系统调用直接执行出错。

A more complicated read example -- 一个更复杂点的读文件的例子:

    上面的readtest1.s程式只能读取出指定文件的头42个字节的数据,如果要将一个超过42个字节的文件的数据全部读取出来,可以利用到read系统调用的一个特性:每个打开的文件都有一个文件指针,该指针指示了需要从哪个位置开始读取数据,最开始这个文件指针指向了文件的第一个字节,每执行一次read系统调用,该文件指针就会根据读取的字节数进行移动,例如读取了5个字节,那么文件指针就会移动到5个字节之后的位置,这样下一次再执行read读操作时,就可以从之前读取的数据之后进行读操作,当文件指针移动到文件结束位置时,再执行read系统调用就会返回0值,这样我们就可以通过循环执行read系统调用把文件里的所有数据都读取出来,直到read返回0为止。

    下面的readtest2.s程式就演示了如何将文件里的所有内容给读取并显示出来:

# readtest2.s - A more complicated example of reading data from a file
.section .bss
	.lcomm buffer, 10
	.lcomm filehandle, 4
.section .text
.globl _start
_start:
	nop
	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
	int $0x80
	test %eax, %eax
	jz done
	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


    上面在read_loop的循环里,每次read系统调用都会读取10个字节的数据到buffer缓冲里,然后通过test %eax, %eax指令来检测EAX里的返回值,如果大于0则将buffer里存储的读取出来的数据显示到屏幕上,这样循环下去,直到read系统调用的返回值为0(读到了文件结束位置)或者为负数(读操作出错)才跳转到done位置关闭掉打开的文件并退出程序。这样readtest2.s程式就可以将任意长度的文件的内容给读取并显示出来了,下面是编译运行的结果:

$ as -o readtest2.o readtest2.s 
$ ld -o readtest2 readtest2.o 
$ ./readtest2 readtest2.s 
# readtest2.s - A more complicated example of reading data from a file
.section .bss
	.lcomm buffer, 10
	.lcomm filehandle, 4
.section .text
.globl _start
_start:
.........................................
.........................................
done:
	movl $6, %eax
	movl filehandle, %ebx
	int $0x80
badfile:
	movl %eax, %ebx
	movl $1, %eax
	int $0x80

$ 


    从上面的输出可以看到,readtest2程式将指定的readtest2.s文件的内容都显示了出来,有点类似于cat命令。

    另外,你还可以将代码中read每次读取的字节数增加(上面的代码中每次只读取了10个字节),这样可以有效减少read系统调用的执行次数,从而提高程序的执行性能。

Reading, Processing, and Writing Data -- 从文件中读取数据,经过处理后,再将处理过的数据写入文件:

    我们通过read系统调用从文件中读取出数据后,还可以对这些数据做一些进一步的处理,例如,将小写字母转为大写字母之类的,然后再将处理的结果写入到其他文件里,下面的readtest3.s程式就演示了这种做法:

# readtest3.s - An example of modifying data read from a file and outputting it
.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 an output 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
	cmpb $0x61, %al
	jl skip
	cmpb $0x7a, %al
	jg skip
	subb $0x20, %al
skip:
	stosb
	loop convert_loop
	movl %ebp, %esp
	popl %ebp
	ret


    上面的代码中,一开始会依次执行open系统调用来打开命令行参数1与命令行参数2所指定的文件,并将命令行参数1对应的文件作为输入文件(文件描述符存储在infilehandle中),命令行参数2对应的文件作为输出文件(文件描述符存储在outfilehandle里),接着在read_loop循环里,每次read系统调用都会从输入文件中读取10个字节的数据到buffer缓冲区域,然后通过convert函数将buffer里所有的字符全部转为大写字母,最后通过write系统调用将处理后的buffer中的数据写入到输出文件里,这样循环下去,就可以把输入文件里所有的字符都转为大写字母,并存储到输出文件中。

    在convert函数中用到了lodsb与stosb的指令,在之前的"汇编字符串操作 (二) 字符串操作结束篇"的文章里,详细的介绍过这两个指令的用法,lodsb指令用于将ESI指向的内存里的一个字节的数据给加载到AL寄存器,这样就可以对AL里加载的数据进行处理了,例如,上面会先判断AL里的字符是否是小写字母,如果是小写字母,则通过subb $0x20, %al指令将AL里的值减去0x20,就可以得到对应的大写字母了,接着就可以通过stosb指令将AL里的大写字母写入到EDI指向的内存里了,这里,ESI与EDI都指向同一个内存位置(即convert函数的第二个参数)。

    下面是readtest3程式编译运行的结果:

$ as -o readtest3.o readtest3.s 
$ ld -o readtest3 readtest3.o 
$ ./readtest3 cpuid.txt output.txt 
$ cat cpuid.txt 
The processor Vendor ID is 'GenuineIntel'
$ cat output.txt 
THE PROCESSOR VENDOR ID IS 'GENUINEINTEL'
$


    可以看到,readtest3程式将cpuid.txt里的所有的字符都转为了大写字母,并存储到了output.txt文件中,和readtest2类似,readtest3一样可以用于转换任意尺寸大小的文件。

    限于篇幅,本章就到这里,下一篇介绍和内存映射文件相关的内容。

    OK,休息,休息一下 o(∩_∩)o~~
上下篇

下一篇: 汇编中使用文件 (三) 使用文件结束篇

上一篇: 汇编中使用文件 (一)

相关文章

汇编里使用Linux系统调用 (三) 系统调用结束篇

汇编里使用Linux系统调用 (一)

汇编流程控制 (一)

Moving Data 汇编数据移动 (一)

调用汇编模块里的函数 (三) 静态库、共享库、本章结束篇

使用内联汇编 (二)