1.概述
32位寄存器的堆棧和64位的寄存器的函數堆棧,還是有較大的差別的,這裏準備花兩篇文章好好學習溫習一下,防止在不做技術的路上越忘越遠,因此儘量寫的詳細簡單,本文主要是64位寄存器的堆棧圖,調試系統爲ubuntu 16.04(64位)
具體堆棧圖的畫法,我是偶爾在網易雲課堂學習的一節課(堆棧圖),使用excel表格畫圖,也挺形象的,
64位的作圖,原則上要採用8個字節的,因爲要與上一篇文章進行對比,所以依然採用的是4個字節(int型的數據)
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 <printf@plt>
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函數調用過程分析