在本文中将通过一段简短的c++代码,演示函数调用的汇编过程,主要是程序执行过程中函数调用过程的演示,基本寄存器、汇编命令说明,以及函数入栈出栈过程中寄存器的使用和使用gdb进行查看的方法。
首先回忆一些基础知识
x86-64汇编基础指令
| 指令 | 名称 | 描述 |
|---|---|---|
| mov | 片内RAM传送指令 | 把操作数从源位置传送到目的位置(数据复制)格式:MOV SRC,DST(从SRC复制数值,覆盖DST) |
| movl | 传送32位数据 | mov 源,目的 |
| callq | 函数调用 | 示例:callq sum;[rsp] = [rsp] - 8;[rsp] = 下一条指令的地址;[rip] = sum所在地址,跳转到sum |
| leaveq | 主要用于函数调用结束时清理栈帧mov %rbp, %rsp;pop %rbp | |
| retq | popq %rip: [%rip] = [%rsp];[%rsp] = [%rsp] + 8 | |
| sub | 算术减 | |
| add | 算术加 | |
| push %rbp | [%rsp] = [%rsp] - 8;[%rsp] = [%rbp]rsp存储的是rbp的栈地址?caller的栈地址 | |
| pop %rbp | [%rbp] = [%rsp]; [%rsp] = [%rsp] + 8 |
函数调用
caller-saved寄存器:rbx,rsp,rbp,r12-r14,r15,x87 cw
callee-saved寄存器:其他
如何传递参数
caller调用callee之前,会把参数放入寄存器(或栈)中,callee按上述规定从对应寄存器中取值即可
函数的前6个整型参数通过寄存器传递,分别存放在rdi,rsi,rdx,rcx,r8,r9寄存器;
(EAX 和 EDX 寄存器是 RAX 和 RDX 寄存器的低 32 位部分,esi和edi分别是 RSI 和 RDI 寄存器的低 32 位部分[extended])
第7个及以后的整型参数放在栈中,按从右到左顺序
如何传递返回值
整型返回值,放在rax寄存器中
栈的对齐问题
(todo)
基础寄存器
寄存器分类
通用寄存器(General-Purpose registers)
状态和控制寄存器(RFLAGS register,64位寄存器,每一个比特位有不同含义)
指令寄存器(RIP)
XMM寄存器(SSE2 Registers)
浮点控制和状态寄存器(MXCSR)
寄存器表示
几个跟本次示例相关的寄存器
| 寄存器分类 | 寄存器符号 | 寄存器名称 | 功能 |
|---|---|---|---|
| instruction pointer | RIP | 指令寄存器 | 下一条指令的逻辑地址。每取出一条指令,rip自增(偏移8字节)指向下一条指令 |
| General-Purpose registers | RSP | 栈顶指针高地址向低地址生长 | |
| RBP | 栈基址指针 |
示例
c++代码
test.cpp
#include <stdio.h>
int sub(int d, int e) {
return d - e;
}
int sum(int a, int b) {
int c = sub(100, 9);
return a + b + c;
}
int main() {
int a = 12;
int b = 98;
int sum_result = sum(a, b);
return 0;
}
编译下:g++ test.cpp -g -o test --std=c++11
汇编代码
这里只贴一下三个主体函数main、sum、sub的汇编代码
00000000004004e7 <_Z3subii>:
4004e7: 55 push %rbp
4004e8: 48 89 e5 mov %rsp,%rbp
4004eb: 89 7d fc mov %edi,-0x4(%rbp)
4004ee: 89 75 f8 mov %esi,-0x8(%rbp)
4004f1: 8b 45 fc mov -0x4(%rbp),%eax
4004f4: 2b 45 f8 sub -0x8(%rbp),%eax
4004f7: 5d pop %rbp
4004f8: c3 retq
00000000004004f9 <_Z3sumii>:
4004f9: 55 push %rbp
4004fa: 48 89 e5 mov %rsp,%rbp
4004fd: 48 83 ec 18 sub $0x18,%rsp
400501: 89 7d ec mov %edi,-0x14(%rbp)
400504: 89 75 e8 mov %esi,-0x18(%rbp)
400507: be 09 00 00 00 mov $0x9,%esi
40050c: bf 64 00 00 00 mov $0x64,%edi
400511: e8 d1 ff ff ff callq 4004e7 <_Z3subii>
400516: 89 45 fc mov %eax,-0x4(%rbp)
400519: 8b 55 ec mov -0x14(%rbp),%edx
40051c: 8b 45 e8 mov -0x18(%rbp),%eax
40051f: 01 c2 add %eax,%edx
400521: 8b 45 fc mov -0x4(%rbp),%eax
400524: 01 d0 add %edx,%eax
400526: c9 leaveq
400527: c3 retq
0000000000400528 <main>:
400528: 55 push %rbp
400529: 48 89 e5 mov %rsp,%rbp
40052c: 48 83 ec 10 sub $0x10,%rsp
400530: c7 45 fc 0c 00 00 00 movl $0xc,-0x4(%rbp)
400537: c7 45 f8 62 00 00 00 movl $0x62,-0x8(%rbp)
40053e: 8b 55 f8 mov -0x8(%rbp),%edx
400541: 8b 45 fc mov -0x4(%rbp),%eax
400544: 89 d6 mov %edx,%esi
400546: 89 c7 mov %eax,%edi
400548: e8 ac ff ff ff callq 4004f9 <_Z3sumii>
40054d: 89 45 f4 mov %eax,-0xc(%rbp)
400550: b8 00 00 00 00 mov $0x0,%eax
400555: c9 leaveq
400556: c3 retq
400557: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)
40055e: 00 00
单步看一下寄存器
gdb -tui test
首先在main函数中打一个端点,r使程序断到指定位置
layout asm 显示汇编状态
layout regs 显示寄存器状态
几个单步相关的命令
ni 单步执行
si 进入函数调用
main
断点位置前面的三行,是处理函数调用栈
// 将调用main函数的栈帧底存入栈中
0x400528 <main()> push %rbp
// 将当前函数栈帧的底地址赋给rbp
0x400529 <main()+1> mov %rsp,%rbp
// 给当前函数创建栈帧,rsp指向栈帧顶部
0x40052c <main()+4> sub $0x10,%rsp
断点位置,后面,是已经执行完压栈过程,rbp为0x7fffffffd6a0,rsp为0x7fffffffd690 ,差为16,即16个字节
然后将2个操作数入栈、如edx/eax寄存器,两个操作数作为sum的参数,存入esi及edi寄存器,然后进去sum函数的callq函数调用
sum
si进去,先看下寄存器内容
这里可以看到,在压栈之前rsp寄存器已经发生了变化,变化为rsp=rsp-8,那么rsp里面保存的内容呢
x/xg $rsp
打印出rsp中8个字节的内容,结果为0x000000000040054d,可以看到是main函数中callq后面的一行,即执行完sum之后的返回代码地址
前面三行,依然是压栈和创建栈帧过程
0x4004f9 <sum(int, int)> push %rbp 0x4004fa <sum(int, int)+1> mov %rsp,%rbp 0x4004fd <sum(int, int)+4> sub $0x18,%rsp
这里创建的栈帧大小是0x18字节
四五行,两个mov,是参数入栈,仍然是函数调用常规过程
0x400501 <sum(int, int)+8> mov %edi,-0x14(%rbp) 0x400504 <sum(int, int)+11> mov %esi,-0x18(%rbp)
六七行,仍然是函数使用esi和edi寄存器传递参数
0x400507 <sum(int, int)+14> mov $0x9,%esi 0x40050c <sum(int, int)+19> mov $0x64,%edi
第8行是一个函数调用
0x400511 <sum(int, int)+24> callq 0x4004e7 <sub(int, int)>
准备操作数,从寄存器eax中取出返回值,存到栈中-0x4(%rbp)位置
0x400516 <sum(int, int)+29> mov %eax,-0x4(%rbp) 0x400519 <sum(int, int)+32> mov -0x14(%rbp),%edx 0x40051c <sum(int, int)+35> mov -0x18(%rbp),%eax
加法的计算
0x40051f <sum(int, int)+38> add %eax,%edx 0x400521 <sum(int, int)+40> mov -0x4(%rbp),%eax 0x400524 <sum(int, int)+43> add %edx,%eax
最后两行,是返回值相关的
leaveq:
mov %rbp, %rsp //撤销栈空间,回滚%rsp。
pop %rbp //恢复上一个栈帧的%rbp。
400526: c9 leaveq
400527: c3 retq
可以看到,是进行的退栈操作,通过之前保存到栈中的返回代码地址0x000000000040054d,返回到main函数中
sub
同上
整体栈分布情况
参考
代码示例来自www.cnblogs.com/INnoVationv…
sourceware.org/gdb/current… gdb手册
www.intel.com/content/www… intel指令查询