1.概述
32位寄存器的堆栈和64位的寄存器的函数堆栈,还是有较大的差别的,这里准备花两篇文章好好学习温习一下,防止在不做技术的路上越忘越远,因此尽量写的详细简单,本文主要是64位寄存器的堆栈图,调试系统为ubuntu 16.04(64位)
具体堆栈图的画法,我是偶尔在网易云课堂学习的一节课(堆栈图),使用excel表格画图,也挺形象的
2.源代码及其汇编
源代码
#include<stdio.h>
int sum(int a,int b) {
return a+b;
}
int main(){
int i = 1;
int j = 2;
int k = 0;
k = sum(i,j);
printf("%d",k);
return 0;
}
汇编代码
0x000000000040053a <+0>: push rbp
0x000000000040053b <+1>: mov rbp,rsp
0x000000000040053e <+4>: sub rsp,0x10
0x0000000000400542 <+8>: mov DWORD PTR [rbp-0xc],0x1
0x0000000000400549 <+15>: mov DWORD PTR [rbp-0x8],0x2
0x0000000000400550 <+22>: mov DWORD PTR [rbp-0x4],0x0
0x0000000000400557 <+29>: mov edx,DWORD PTR [rbp-0x8]
0x000000000040055a <+32>: mov eax,DWORD PTR [rbp-0xc]
0x000000000040055d <+35>: mov esi,edx
0x000000000040055f <+37>: mov edi,eax
0x0000000000400561 <+39>: call 0x400526 <sum>// 将sum函数拆解
0x0000000000400526 <+0>: push rbp
0x0000000000400527 <+1>: mov rbp,rsp
0x000000000040052a <+4>: mov DWORD PTR [rbp-0x4],edi
0x000000000040052d <+7>: mov DWORD PTR [rbp-0x8],esi
0x0000000000400530 <+10>: mov edx,DWORD PTR [rbp-0x4]
0x0000000000400533 <+13>: mov eax,DWORD PTR [rbp-0x8]
0x0000000000400536 <+16>: add eax,edx
0x0000000000400538 <+18>: pop rbp
0x0000000000400539 <+19>: ret
0x0000000000400566 <+44>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400569 <+47>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040056c <+50>: mov esi,eax
0x000000000040056e <+52>: mov edi,0x400614
0x0000000000400573 <+57>: mov eax,0x0
0x0000000000400578 <+62>: call 0x400400 <[email protected]>
0x000000000040057d <+67>: mov eax,0x0
0x0000000000400582 <+72>: leave
0x0000000000400583 <+73>: ret
3.堆栈图(64位)
3.1开始执行main函数
gdb调试结果
堆栈图
其中:0x400590 (<__libc_csu_init>: push r15)这个值执行main函数之前的rp
3.2函数跳转之前的栈分布
参数传递之前,将参数保存在寄存器:RDI/RCI
gdb调试结果
堆栈图
3.3执行call指令之后
gdb调试结果
堆栈图
通过这个堆栈图可以看到,执行call指令的时候,将RIP压栈(main的下一条指令),同时保存RBP
3.4执行SUM函数的过程
gdb调试结果
堆栈图
通过查看堆栈情况,这里有个比较奇怪的地方,上面两个内存没有申请,竟然在使用:
3.5 执行完成子函数,跳转回main函数
gdb调试结果
堆栈图
4总结
1 注意点:
push操作会导致ESP-1(8个字节)
pop操作会导致ESP+1(8个字节)
64位下的指针是8个字节,但是int是4个字节,因此赋值采用的是edx,eax
有很多文章说说ebp不再作为栈帧指针,是存在一定的偏差的,实际linux-64中依然是使用rbp作为栈帧指针,待进一步研究调查
2 call指令:
push rip
函数跳转之后会导致
push rbp
mov rbp,rsp
3 Ret指令:
pop rip
4 函数传参和32位不同:
32位是通过堆栈传参
64位是通过寄存器+堆栈传参,顺序依次是:rdi、rsi、rdx、rcx、r8、r9(edi、esi、edx、ecx、r8、r9)
5奇怪的点:
跳转到新的函数之后,并没有分配新的内存,但是程序却临时借用了就近的几个字节
5参考
64位和32位的寄存器和汇编的比较
上面这篇文章实际操作过程中,会有不相符的情况,比如rbp栈帧指针
x86-64传参规则
x64函数调用过程分析