示例代码
#目的: 退出并向Linux内核返回一个状态码的简单程序。
#输入: 无
#输出: 返回一个状态码。运行程序后通过echo $?来读取状态码
#变量:
# %eax保存系统调用号
# %ebx保存返回状态
.section .data
.section .text
.globl _start
_start:
movl $1, %eax #用于退出程序的Linux内核命令号(系统调用)
movl $0, %ebx #返回给操作系统的状态码
#改变这个数字,echo $?的值会不同
int $0x80 #唤醒内核,以运行退出命令
代码解读
.secion
汇编中,任何以小数点.
开头的指令:- 叫做汇编指令或伪操作
- 都不会被直接翻译成机器指令
- 这些指令针对汇编程序本身,由汇编程序处理,不会由计算机运行。
本程序中,.section
指令将程序分为几个部分:
.section .data
是数据段的开始- 数据段中列出程序数据所需的所有内存存储空间。
- 由于该程序没有使用任何数据,所以不需要这个段,保留它只是为了保持程序完整性。
.section .text
表示文本段的开始- 文本段是存放程序指令的部分。
.globl _start
指示汇编程序- _start很重要,_start是一个符号,这就是说它将在汇编或链接过程中被其他内容替换。
- 符号一般用来标记程序或数据的位置,所以可以用名字而非内存位置编号来指代它们。
_start
定义 _start 标签的位置。- 标签是一个符号,后面跟一个冒号。
- 标签定义一个符号的值。
- 当汇编程序对程序进行汇编时,必须为每个数值和每条指令分配地址。
- 标签告诉汇编程序以该符号的值作为下一条指令或下一个数据元素的位置。
- 这样,如果数据或指令的实际物理地址更改,就无需重写其引用,因为符号会自动获得新值。
下来是真正的计算机指令:
movl $1, %eax
movl
有两个操作数:源操作数、目的操作数。- 这里源操作数是
$1
,目的操作数是%eax
。 - 操作数可以是数字、内存位置引用、寄存器。
大多数指令都有两个操作数,第一个是源操作数,第二个是目的操作数。(这里指AT&T语法)
类似有addl
、subl
、imull
,分别将源操作数相加、向减、相乘,并将结果村到目的操作数。
有的指令会硬编码操作数,比如:
idivl
要求被除数存放在%eax
,且%edx
的值为0。- 运算后,商存入
%eax
,余数存入%edx
。 - 但是除数可以是任何寄存器或内存位置的值。
知识点
硬编码是将数据直接嵌入到程序或其他可执行对象的源代码中的软件开发实践,与从外部获取数据或在运行时生成数据不同。 硬编码数据通常只能通过编辑源代码和重新编译可执行文件来修改,尽管可以使用调试器或十六进制编辑器在内存或磁盘上进行更改。 硬编码的数据通常表示不变的信息,例如物理常量,版本号和静态文本元素。 另一方面,软编码数据对用户输入,HTTP服务器响应或配置文件等任意信息进行编码,并在运行时确定。
x86处理器有如下几个通用寄存器(每个都可用于movl
指令操作):
%eax
%ebx
%ecx
%edx
%edi
%esi
还有几个专用寄存器:
%ebp
%esp
%eip
%eflags
- 其中一些寄存器,如
%eip
、%eflags
只能通过特殊指令访问 - 其他一些则能通过使用访问通用寄存器的指令来访问
- 但这些特殊指令会有特殊含义、用途,或者在以特定的方式使用时速度更快
movl $1, %eax
中:
- 数字1前的
$
表示要使用立即寻址方式。 - 如果没有
$
,会使用直接寻址,加载地址1中的数字。 - 我们希望家在实际数字1,所以必须使用立即寻址方式。
将数字1移入%eax
是因为:
- 准备调用Linux内核,数字1表示系统调用
exit
。 - 进行系统调用时必须将系统调用号加载到
%eax
movl $0, %ebx
:
- 系统调用所需的参数要存储在其他寄存器中
- 系统调用
exit
需要将状态码加载到%ebx
,稍后这个值会被返回给系统。
通常需要将系统调用号加载到%eax
,而对于其他寄存器,不同的系统调用有不同要求。
int 0x80
int
代表中断,0x80
是要用到的中断号。