在读《深入理解程序设计使用LInux汇编语言》[美]Jonathan Bartlett著 郭晴霞 译 人民邮电出版社这本书时,觉得对函数的调用有了一种醍醐灌顶的感觉,不是一般的爽。一路下来到第五章的在程序中使用文件时,按照书上的代码写完之后,一直不能编译,后来倒腾了下发现是由于“擅自更改了注释的位置”,废话不说,直接上代码:
1 #目的: 本程序将输入文件的所有字母转换为大写字母 2 # 最后将转换后的文件输出到新的文件 3 4 #处理过程 (1)、打开输入文件 5 # (2)、打开输出文件 6 # (3)、读取输入文件,如果未达到文件尾部: 7 # (a)、将部分文件读入内存缓冲区 8 # (b)、读取内存缓冲区的每个字节,若该字节为小写字符,将其转换为大写字符 9 # (c)、将内存缓冲区写入输出文件 10 11 .section .data 12 13 #######常数####### 14 15 #系统调用号 16 .equ SYS_CLOSE, 6 17 .equ SYS_OPEN, 5 18 .equ SYS_WRITE, 4 19 .equ SYS_READ, 3 20 .equ SYS_EXIT, 1 21 22 #文件打开选项(不同的值可参看/usr/include/asm/fcntl.h) 23 #可以将选项值相加或进行OR操作来组合使用各选项 24 .equ O_RDONLY, 0 25 .equ O_CREAT_WRONLY_TRUNC, 03101 26 27 #标准文件描述符 28 .equ STDIN, 0 29 .equ STDOUT, 1 30 .equ STDERR, 2 31 32 #系统调用中断 33 .equ LINUX_SYSCALL, 0x80 34 .equ END_OF_FILE, 0 #这是读文件操作的返回值,表明读取到文件结束处 35 .equ NUMBER_ARGUMENTS, 2 36 37 .section .bss 38 #缓冲区 将文件加载到这里,这里是存放未初始化的数据的地方 39 # 也是将这里的文件完成大小写的转换工作 40 # 将这里转换后的文件(缓冲区)输出到文件中 41 # 缓冲区的大小有严格规定,这里不超过16000字节 42 .equ BUFFER_SIZE, 600 43 .lcomm BUFFER_DATA, BUFFER_SIZE #通过指令.lcomm申请缓冲区,需要设置缓冲区的大小 44 45 .section .text 46 47 #栈位置 48 .equ ST_SIZE_RESERVE, 8 49 .equ ST_FD_IN, -4 50 .equ ST_FD_OUT, -8 51 .equ ST_ARGC, 0 #参数数目 52 .equ ST_ARGV_0, 4 #程序名 53 .equ ST_ARGV_1, 8 #输入文件名 54 .equ ST_ARGV_2, 12 #输出文件名 55 56 .globl _start 57 _start: 58 #######程序初始化####### 59 movl %esp, %ebp #保存栈指针 60 #sub $ST_SIZE_RESERVE,%esp #在栈上为文件描述符分配空间 61 sub $ST_SIZE_RESERVE, %esp 62 63 open_file: 64 open_fd_in: 65 #######打开输入文件###### 66 movl $SYS_OPEN, %eax #打开系统调用,将调用号放入到%eax中 67 movl ST_ARGV_1(%ebp), %ebx #将输入文件名放入%ebx中 68 movl $O_RDONLY, %ecx #将只读标志放入%ecx中 69 movl $0666, %edx #将权限集合放入%edx中 70 int $LINUX_SYSCALL #调用Linux内核完成系统调用 71 72 store_fd_in: 73 movl %eax, ST_FD_IN(%ebp) #保存给定的文件描述符 74 75 open_fd_out: 76 #######打开输出文件####### 77 movl $SYS_OPEN, %eax #打开系统调用,将调用号放入到%eax中 78 movl ST_ARGV_2(%ebp), %ebx #将输出文件名放入%ebx中 79 movl $O_CREAT_WRONLY_TRUNC, %ecx #将写入文件标志放入%ecx中 80 movl $0666, %edx #将权限集合放入%edx中 81 int $LINUX_SYSCALL #完成函数调用的一切准备工作 82 83 store_fd_out: 84 movl %eax, ST_FD_OUT(%ebp) #保存给定的文件描述符 85 86 #######主循环开始####### 87 read_loop_begin: 88 89 ###从输入文件中读取一个数据块### 90 movl $SYS_READ, %eax 91 movl ST_FD_IN(%ebp), %ebx #获取输入文件描述符 92 movl $BUFFER_DATA, %ecx #放置读取数据的存储地址或指针 93 movl $BUFFER_SIZE, %edx #设置缓冲区大小 94 int $LINUX_SYSCALL #通过系统调用,完成一次数据的读取操作,读取的结果就会保存在%eax中 95 96 ###若达到文件结束处就退出### 97 cmpl $END_OF_FILE, %eax #若文件读取结束或出现错误,就跳转到程序结束处 98 jle end_loop #跳转到文件结束的处理部分 99 100 continue_read_loop: 101 ###将字符块内容转换成大写形式### 102 pushl $BUFFER_DATA #缓冲区位置 103 pushl %eax #缓冲区大小 104 call convert_to_upper #调用转换函数 105 popl %eax #重新获取缓冲区的大小 106 addl $4, %esp #恢复%esp 107 108 ###将字符块写入输出文件### 109 movl %eax, %edx #获取缓冲区大小 110 movl $SYS_WRITE, %eax #将文件描述符放入%eax 111 movl ST_FD_OUT(%ebp), %ebx # 112 movl $BUFFER_DATA, %ecx # 113 int $LINUX_SYSCALL # 114 115 ###继续循环### 116 jmp read_loop_begin 117 118 end_loop: 119 ###关闭文件### 120 movl $SYS_CLOSE, %eax 121 movl ST_FD_OUT(%ebp), %ebx 122 int $LINUX_SYSCALL 123 124 movl $SYS_CLOSE, %eax 125 movl ST_FD_IN(%ebp), %ebx 126 int $LINUX_SYSCALL 127 128 ###退出### 129 movl $SYS_EXIT, %eax 130 movl $0, %ebx 131 int $LINUX_SYSCALL 132 133 #目的: 这个函数实际上将字符块内容转换为大写形式 134 # 135 #输入: 第一个参数:要转换的内存块的地址 136 # 第二个参数:缓冲区的长度 137 # 138 #输出: 这个函数以大小字符覆盖当前缓冲区 139 # 140 #变量: %eax --- 缓冲区起始地址 141 # %ebx --- 缓冲区长度 142 # %edi --- 当前缓冲区偏移量 143 # %cl --- 当前正在检测的字节(%ecx)的一个部分 144 145 ###常数### 146 .equ LOWERCASE_A, 'a' #搜索的下边界 147 .equ LOWERCASE_Z, 'z' #搜索的上边界 148 .equ UPPER_CONVERSION, 'A' - 'a' #转换常量 149 150 ###栈相关信息### 151 .equ ST_BUFFER_LEN, 8 #缓冲区长度 152 .equ ST_BUFFER, 12 #实际缓冲区大小 153 154 convert_to_upper: 155 pushl %ebp #保存现场 156 movl %esp, %ebp #开辟栈帧空间 157 158 ###设置变量### 159 movl ST_BUFFER(%ebp), %eax 160 movl ST_BUFFER_LEN(%ebp), %ebx 161 movl $0, %edi 162 163 cmpl $0, %ebx #若给定的缓冲区长度为0,表明不需要读取 164 je end_convert_loop #跳转到循环结束 165 166 convert_loop: 167 movb (%eax, %edi, 1), %cl #获取当前字节 168 cmpb $LOWERCASE_A, %cl 169 jl next_byte 170 cmpb $LOWERCASE_Z, %cl 171 jg next_byte 172 173 addb $UPPER_CONVERSION, %cl #将当前字节转换为大写 174 movb %cl, (%eax, %edi, 1) #并存放回原处 175 176 next_byte: 177 incl %edi #增加%edi的值,进入下一个字节 178 cmpl %edi, %ebx #继续比较直到文件结束 179 jne convert_loop 180 181 end_convert_loop: 182 movl %ebp, %esp 183 popl %ebp 184 ret
代码中都有注释,内容很简单,就不多说了。下面说下我碰到的奇怪的事情吧。
老规矩:
1、编译文件,使用命令as toUppers.s -o toUppers.o
提示有错误:toUppers.s: Assembler messages:
toUppers.s:60: 错误:number of operands mismatch for `sub' ,把所有可能的情况都考虑完之后,万般无奈之后把注释给删除了之后,代码可以编译了,也就是说使用61行的代码取代60行的代码就OK,虽然解决了一个问题,但是一个更大的问题出现了,不知道有人能解决吗
2、链接文件,使用命令ld toUppers.o -o toUppers
3、测试程序,使用命令./toUppers toUppers.s toUppersCase.s
经验证, toUppersCase.s中的内容与toUppers.s一样,只是将所有的小写字母变成了大写字符