一个简单的入门实例
实现一个简单的exit(0);
exit.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 # 数据段的开始
.section .data
# 代码段开始(存放程序指令的部分)
.section .text
#注意这里的.globl 表示汇编程序不应在汇编之后废弃次符号(_start) ,因为连接
# 器需要它。_start 是个特殊的符号,总是用.global 来标记,因为它标记了该程序的开始位置。
.globl _start
#定义_start 标签(符号)的值,该标签后面的:告诉汇编程序,以该符号的只作为下一条指令或者下一个数据元素的位置
_start:
# 用于退出linux 内核命令号(系统调用)
movl $1,%eax
# 将0 作为退出码,echo $? 可以看到该值
movl $0,%ebx
#调用系统0x80号中断(唤醒内核运行退出命令)
int $0x80
```
编译运行方法:
```sh
as exit.s -o exit.o
ld exit.o -o exit
```
## 寄存器
通用寄存器:
```asm
%eax
%ebx
%ecx
%edx
%edi
%esi
专用寄存器:
1
2
3
4
5
6
7
8
9 %ebp(栈基址)
%esp(栈顶)
%eip(指令寄存器)
%eflags
```
## 寻址方式
内存地址引用的通用格式如下:
```asm
地址或者偏移(%基址寄存器,%索引寄存器,比例因子)
所有字段都是可选的,要计算地址可以按照下面公式进行计算:
结果地址 = 地址或者偏移 + %基址或者偏移量寄存器 + 比例因子 * %索引寄存器
其中地址或者偏移量 以及比例因子都必须是常量,其余的两个必须是寄存器。如果要省略任何一项,那么等式可以将0代替该项。
直接寻址方式
此模式可以通过使用地址或者偏移
部分实现:(即上面公式中%基址或者偏移量寄存器 和 %索引寄存器都是0)1
movl ADDRESS, %eax
以上指定将内存地址ADDRESS 加载到%eax
索引寻址方式
这种模式将通过使用·地址者偏移量以及%索引寄存器
部分实现(%索引寄存器可以是任何通用寄存器)。1
2
3
4
5
6
7
8movl string_start(,%ecx,1), %eax
```
该指令从string_start 处开始,将改地址与1*%ecx 相加,并将所得值加载到 %eax
0. 间接寻址方式
间接寻址方式从寄存器从寄存器指定的地址加载值例如:如果%eax 保存着一个地址,我们可以通通过下面的方式将该地址中的值移入到%ebx
```asm
movl (%eax), %ebx基址寻址方式
基址寻址方式与间接寻址方式类似,不同之处在于它将一个常量值与寄存器中的地址相加。例如:
如果已有一个记录,其中年龄值位于记录起始地址后4个字节处,该记录的起始地址在%eax中,那么
下面的指令将年龄提取到%ebx中:1
movl 4(%eax), %ebx
立即寻址方式
用于直接将值加载到寄存器或者存储位置.数字前面加$表示是立即数,否则表示的是将位于存储位置12中的值而不是12 加载到%eax1
movl $12, %eax
寄存器寻址方式
寄存器寻址方式仅仅是将数据移入或者移出寄存器
注意:除了立即寻址方式外的其他寻址方式都可用作源或者目的操作数。而立即寻址方式只能是源操作数
不同的指令操作的数据字节数不同例如movl 移动一个字,movb 移动一个字节大小
下面是%eax寄存器的布局:1
2
3|<--------%eax-------->|
[ ][ ][ %ah][%al ]
|<--%ax--->|
实例二
查找最大值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26.section .data
data_items: #数据项存放在数据段
.long
.section .text
.globl _start
_start:
movl $0,%edi $0移入索引寄存器
movl data_items(,%edi,4), %eax#加载第一个元素到%eax
movl %eax ,%ebx #%ebx 存放最大值,由于是第一项所以就是最大值
start_loop:
cmpl $0,$eax #数据中0 作为数据结束的标志
je loop_exit
incl %edi #索引指向下一个元素
movl data_items(,%edi,4) ,%eax
cmp %ebx,%eax #比较当前元素于原有的最大值
jle start_loop
movl %eax,%ebx #将新的最大值移入%ebx
jmp start_loop
loop_exit:
movl $1 ,%eax #1 是exit()系统调用
int $0x80汇编连接:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20as maximum.s -o maximum.o
ld maximum.o -o maximum
./maximum
$echo $? #注意这里会打印最大值,因为我们程序计算结果就是保存在%ebx中
```
***
## 关于函数
一个函数一般由一下几个部分:`函数名、函数参数、局部变量、静态变量、全局变量、返回地址、返回值`
约定函数参数从又到左入栈。
```asm
参数 #N <----- N*4 + 4(%ebp) [高地址]
...
参数 2 <----- 12(%ebp)
参数 1 <----- 8(%ebp)
返回地址 <----- 4(%ebp)
旧%ebp <----- (%esp) 和 (%ebp) [低地址]
局部变量 1 <---- -4(%ebp)
局部变量 2 <---- -8(%ebp) 和 (%esp)从上面可以看出 通过%ebp寄存器可以获取到参数、局部变量
如果需要从函数返回,那么需要执行一下指令1
2
3movl %ebp,%esp
popl %ebp
ret
函数示例
计算2^3 + 5^21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48#主程序中的所有内容都存储在寄存器中,
#因此数据段不含任何内容
.section .data
.section .text
.globl _start
_start:
pushl $3
pushl $2
call power
addl $8, %esp
pushl %eax
pushl $2
pushl $5
call power
addl $8, %esp
popl %ebx
addl %eax, %ebx
movl $1, %eax
int $0x80
.type power,@function #告诉连接器 factorial 是个函数,处罚法我们需要在别的程序中使用factorial函数,否则这条指令不是必须的。
power:
pushl %ebp
movl %esp,%ebp
subl $4,%esp
movl 8(%ebp),%ebx
movl 12(%ebp),%ecx
movl %ebx, -4(%ebp)
power_loop_start:
cmpl $1,%ecx
je end_power
movl -4(%ebp),%eax
imull %ebx,%eax
movl %eax, -4(%ebp)
decl %ecx
jmp power_loop_start
end_power:
movl -4(%ebp),%eax
movl %ebp,%esp
popl %ebp
ret递归调用函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34.section .data
#本程序无全局数据
.section .text
.globl _start
.globl factorial #除非我们希望与其他程序共享该函数,否则无需此项
_start:
pushl $4
call factorial
addl $4,%esp
movl %eax,%ebx
movl $1, %eax
int $0x80
# 这是实际函数的含义
.type factorial,@function
factorial:
pushl %ebp
movl %esp,%ebp
movl 8(%ebp),%eax
cmpl $1, %eax
je end_factorial
decl %eax #否则就递减
pushl %eax
call factorial
movl 8(%ebp),%ebx
imull %ebx,%eax
end_factorial:
movl %ebp, %esp
popl %ebp
ret