汇编函数结构
- 函数外传递函数参数(push ss 或者 直接传寄存器)
- 保护bp:push bp
- 用bp记录原先sp的位置
- 在生成局部变量之间,应该先提升sp,用来开辟一段函数空间,然后把所有的局部变量入栈
- 保护寄存器:push通用寄存器
- --------- 业务逻辑代码 开始 ---------
- 定义局部变量:通过“bp-位数”入栈局部变量
- 获得局部变量:通过“bp-位数”来获得
- 获得参数:通过“bp+位数”来获得
- 将返回值赋给ax
- --------- 业务逻辑代码 结束 ---------
- 恢复通用寄存器:pop的顺序和push顺序相反,因为ax是存储返回值的,所以不用pop ax寄存器
- 恢复sp,达到销毁局部变量的效果
- 恢复bp:pop bp
- ret 内平栈
整个函数是栈中执行的 所以先push的会后pop,所以按照bp sp bx cx dx 的顺保护,也会按照倒序恢复。保护sp的之后立马提升栈空间,恢复sp的时候也顺便就销毁栈空间了。最后函数结束内平栈(也可以外平栈)。
代码实例
assume cs:code,ds:data,ss:stack
;栈段(存放数据,比如高级语言中的局部变量)
stack segment
db 20 dup(0)
stack ends
;数据段(存放数据,比如高级语言中的全局变量)
data segment
db 20 dup(0)
str db "Hello World!$"
data ends
;代码段
code segment
start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
;业务逻辑代码
push 3h ;传递参数
push 4h
call sum
;add sp,6
;退出程序
mov ah,4ch
int 21h
;参数:传递两个字型参数,参数分别用bx,dx存放
;返回值:返回值存放在ax中
sum:
;保护bp
push bp
mov bp,sp
sub sp,20 ;20字节留作局部变量
;保护寄存器
push bx
push cx
push dx
;*****************业务逻辑代码
;定义两个局部变量
mov ss:[bp - 2],1h
mov ss:[bp - 4],2h
;修改寄存器
mov bx,2h
mov cx,3h
mov dx,4h
mov ax,ss:[bp + 2]
add ax,ss:[bp + 4]
add ax,ss:[bp - 2]
add ax,ss:[bp - 4]
;*****************业务逻辑代码
;恢复寄存器
pop dx
pop cx
pop bx
;恢复sp
mov sp,bp
;恢复bp
pop bp
ret 4
code ends
end start
;int sum(int a, int b)
;{
; int c = 1;
; int d = 2;
; return a + b + c + d;
;}
;函数的调用流程
;1.push参数(64位cpu 任性使用寄存器)
;2.call指令调用(将下一条指令地址入栈)
;3.保护bp寄存器,将sp赋值给bp
;4.提升sp指针,作为局部变量空间(sp 减去值)
;5.保护寄存器
;6.业务逻辑
;7.恢复寄存器
;8.恢复sp(sp指向bp/sp 加上值)
;9.恢复bp(pop bp)
;10.返回(ret)
看看xcode的代码
OC
int sum(int a, int b) {
return a + b;
}
int main(int argc, char * argv[]) {
int a = sum(1, 2);
printf("%d", a);
return 0;
}
汇编 - main 函数
汇编`main:
0x100000f30 <+0>: pushq %rbp ;保护bp
0x100000f31 <+1>: movq %rsp, %rbp ;保护sp
0x100000f34 <+4>: subq $0x20, %rsp ;提升栈空间
0x100000f38 <+8>: movl $0x1, %eax ;传参1
0x100000f3d <+13>: movl $0x2, %ecx ;传参2
0x100000f42 <+18>: movl $0x0, -0x4(%rbp)
0x100000f49 <+25>: movl %edi, -0x8(%rbp) ;保护di
0x100000f4c <+28>: movq %rsi, -0x10(%rbp) ;保护si
-> 0x100000f50 <+32>: movl %eax, %edi ;调用函数
0x100000f52 <+34>: movl %ecx, %esi
0x100000f54 <+36>: callq 0x100000e90 ; sum at main.m:11
0x100000f59 <+41>: leaq 0x3a(%rip), %rdi ; "%d"
0x100000f60 <+48>: movl %eax, -0x14(%rbp)
0x100000f63 <+51>: movl -0x14(%rbp), %esi
0x100000f66 <+54>: movb $0x0, %al
0x100000f68 <+56>: callq 0x100000f7a ; symbol stub for: printf
0x100000f6d <+61>: xorl %ecx, %ecx
0x100000f6f <+63>: movl %eax, -0x18(%rbp)
0x100000f72 <+66>: movl %ecx, %eax
0x100000f74 <+68>: addq $0x20, %rsp ;恢复sp
0x100000f78 <+72>: popq %rbp ;恢复bp
0x100000f79 <+73>: retq ;函数结束
汇编 - sum 函数
汇编`sum:
0x100000e90 <+0>: pushq %rbp ;保护bp
0x100000e91 <+1>: movq %rsp, %rbp ;保护sp
0x100000e94 <+4>: movl %edi, -0x4(%rbp) ;保护 di
0x100000e97 <+7>: movl %esi, -0x8(%rbp) ;保护 si
-> 0x100000e9a <+10>: movl -0x4(%rbp), %esi ;取参
0x100000e9d <+13>: addl -0x8(%rbp), %esi ;相加
0x100000ea0 <+16>: movl %esi, %eax ;返回值
0x100000ea2 <+18>: popq %rbp ;恢复bp
0x100000ea3 <+19>: retq ;函数结束
应为xcode是64位机,所以很多操作和8086有差异,例如:
传参使用的寄存器,而不是ss。
因为函数很简单,没有提升栈空间,而是直接用的红色区域(红灯区)计算。
因为保护的通用寄存器本身没有发生变化,所以就优化掉了恢复通用寄存器的操作。
但是中心思想是不变的。
tips
不同的cpu对于函数的处理实现是不一样的,所以只要把中心函数基本思想记好才能以不变应万变。光死记硬背是行不通的。