学习一门语言,最好的方式就是在运用中学习,那么在这一章节中,我们开始编写我们的第一个汇编程序。当然作为第一个程序,其实十分的简单,但可以给大家一个基本的轮廓,了解汇编大概是这样的。
我们这个程序实际上没什么作用,只是简单的推出而已。下面就是程序的范例
# 目的: 退出程序并向Linux内核返回一个状态码
# 输入: 无
# 输出: 返回一个状态码。在程序运行结束后可以通过 echo $? 来读取状态码
# 变量: %eax保存系统调用号 %ebx保存返回状态
.section .data
.section .text
.globl _start
_start:
movl $1, %eax # 这是退出程序的Linux内核命令号
movl $0, %ebx # 返回的状态码
int $0x80 # 唤醒内核以执行退出命令
完成上面的源代码,进行汇编
32位机器:as exit.s -o exit.o
64位机器:as –32 exit.s -o exit.o
在命令中,as是汇编命令,exit.s 是源文件 -o 是重命名,让我们生成的文件叫exit.o,exit.o就是目标文件。在Linux为 .o 后缀,在Windows下为 .obj 后缀。
目标文件是用机器语言写的代码,接下来我们需要使用链接器进行链接。
32位机器:ld exit.o -o exit
64位机器:ld -melf_i386 exit.o -o exitls
ld就是链接器命令
要运行exit的话,可以使用 ./exit 命令。当你输入命令,点完回车之后,光标只是进入了下一行,毕竟我们这个程序只是退出而已。
不过但你运行完程序马上输入 echo $? 会发现屏幕上会出现一个0。这个0就是返回的状态码
接下来我们开始分析每个部分。
以#号开头的为注释
.section .data
在汇编中,任何以小数点开始的指令都不会被翻译为机器指令,这些针对汇编本身的指令有汇编程序处理,实际上并不会在计算机上运行,这些指令是汇编指令。.section 指令是把程序分成几个区块。
.section .data 命令是数据段的开始,数据段中要列出程序数据所需的内存空间。我们这个程序太过简单,所以没有数据,这个命令其实也可以不写。保留这个指令只是为了保留程序的结构完整,知道程序的基本框架。
.section .text 是文本段,也就是代码区啦,这里容纳程序指令
.globl _start
这一条指令中 _start 是一个符号,帮助人理解而已,在汇编和链接过程中会被替换。一般来说,符号用来标记程序或数据的位置,所以通过符号而不是内存中的位置编号来指代他们。
.globl 表示汇编程序不应该在汇编之后把这个符号舍弃,因为连接器还需要它。_start 是一个特殊符号,总是使用 .globl 来标记,应为这个标记是程序的入口。
_start:
定义_start标签的值。当汇编程序对程序进行汇编的时候,必须为每一个数值和指令分配地址。标签告诉汇编程序以符号的值作为下一条指令或下一个数据元素的位置。这样,如果数据或指令的实际物理地址更改了,也不需要重新引用,应为符号会自动获取新值。
下面开始就是真正的计算机指令了。
movl $1, %eax
当程序运行时该指令将数值1移入%eax寄存器中。 movl 指令有两个操作数,一个是源操作数,另一个是目的操作数。根据上一讲,$1 是立即寻址方式,在指令中就包含需要的数字。这里1是源操作数,%eax是目的操作数。操作数的类型一般是数字,内存位置引用或寄存器。而 movl 的作用是从内存位置复制一个字大小的数据到另一个位置。(PS:movb的话操作数是字节)。1移入%eax是让系统调用exit。进行系统调用时,必须把系统调用号加载到%eax中,有些系统调用还需要其他寄存器也包含有值。
x86处理器上的通用寄存器
- %eax
- %ebx
- %ecx
- %edx
- %edi
- %esi
除了通用寄存器,还有几个专用寄存器
- %ebp
- %esp
- %eip
- %eflags
引申一点,在x86处理器的通用寄存器都以e开头,e 是 extern 的意思,即扩展。主要是因为以前的x86处理器是16位的,后来才变为32位,为了向前兼容,保留了旧名,在前面加上了e作为32位的扩展寄存器。而在64位采用的是r前缀,比如说%rax就是64位的%eax寄存器。大家可以试着把前面的代码改一下,汇编,链接,运行。
在进行系统调用的情况下,操作系统还需要将状态吗加载到%ebx,这个值后背返回给系统
movl $0, %ebx
这句命令和上面类似,就不解释了。
int $0x80
int 表示中断,0x80是中断号。还有,0x80是16进制数,不是10进制的80。