目錄
前言
64位和32位彙編的差異
示例說明
前言
公司項目需要實現通過彙編來獲取調用棧的功能,自己寫了一個,直接崩潰,回頭學習一下,以此爲記;
64位和32位彙編的差異
這方面我現在涉及的不多,就不展開了,大家可以自己去查一下資料,或者直接參考一下下面的鏈接:
https://blog.csdn.net/qq_29343201/article/details/51278798
示例說明
重頭戲來了,先上C源碼:
#include <stdio.h>
#include <stdlib.h>
//gcc test.c -o test -g
int SumFunc(int a, int b)
{
int sum = 0;
sum = a + b;
return sum;
}
int main(int argc, char* argv[])
{
int a = 3;
int b = 4;
int sum = 0;
sum = SumFunc(a, b);
printf("sum = %d\n",sum);
return 0;
}
編譯後,通過objdump看一下彙編代碼:
注意AT&T和Intel的彙編的差異,後面不再說了;
開始GDB之:
前面幾步沒啥好說的,主要是關注RBP、RSP、RIP這3個,然後直接到關鍵點:
輸入si[注意一定是si,不能是ni],執行單條彙編命令跳入函數,也就是執行call 0x40052d,注意RSP、RIP以及stack的變化:
發生變化的寄存器:
1、RIP
變化是正常的,RIP每一步都會變換;但是注意SumFunc裏的push rbp還沒有執行——這是下一個要執行的指令——那麼變化了的RIP到底執行了什麼?
2、RSP
原來:
現在:
——這裏有點奇怪!
首先看一下RSP當前指向的是啥:
可以看到,RSP入棧了main函數裏call SumFunc後的下一條指令——這就很清楚了,這是保護現場,用於在SumFunc完成調用後繼續執行main流程的下一步;
所以,RSP減少了 0x7fffffffe170 - 0x7fffffffe168 = 8[這裏注意不是10-8=2,而是(F+1)-8 = 8],就是RIP寄存器的大小(8字節),等於是把一條彙編指令入棧;
同時可以得到這個結論:
在彙編代碼裏,call 一個函數的時候,其實是調用了 push RIP;
下面繼續:
從上面的截圖也可以看到,進入SumFunc後,前兩條彙編是:
一般進入每個函數的開始都是這兩條;下面來分析一下這兩條指令做了什麼;
1. push rbp
很明顯,就是把rbp入棧;rbp裏保存的是什麼呢?
rbp指向的是:
從開始到現在,rbp的值還沒有變化過,用下面的圖可以簡單表示:
——上面這個截圖有個錯誤,就是edi應該是佔4個字節,rdi纔是8字節,edi是指rdi的低32位;
在進入SumFunc時,執行第一句 push rbp前,當前的棧,以及各個寄存器的情況:
現在我們執行push rbp:
可以發現,入棧後,rbp的值沒有變化,還是190,而rsp的值又減小了8——符合預期,現在的棧的情況:
在GDB調試界面上輸入ni,執行mov rbp, rsp,就是把rsp的值賦給rbp,rsp的值不發生變化,而rbp從原來的190變爲160:
然後繼續執行,下面兩句彙編是從edi和esi中獲取入參,放入rbp相應的位置上,供後面相加使用:
——這裏有一點需要注意:在64位彙編中,當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。在本例中就是:
簡單來說就是把原來存儲入參的寄存器的值轉移到eax和edx中,然後進行add的操作;在執行完add操作後,會把sum寫入到rbp-0x4中;
再執行pop rbp的操作,在上述過程中,rsp和rbp一直沒有發生變化,都是160,並且rbp在rsp的棧頂:
rbp和rsp均發生了變化:
1.rsp:
pop是做出棧的動作,所以rsp應該是增加8,變爲168
2.rbp:
pop rbp,對於rbp而言,應該是rbp = pop(rsp),等於是把棧頂的值賦給rbp
這裏注意:0x7fffffffe160是棧頂的地址,其對應的值是0x7fffffffe190,所以pop後,rbp的值是0x7fffffffe190;
再執行ret,回到main函數內,注意rsp和rip的變化
可以看到rsp做了pop的動作,並且把pop的值賦給了rip,所以通過這裏可以得到結論:
ret ====> pop rip
接下來就是調用printf函數打印的過程了,中間涉及一點,就是調用SumFunc時,返回的返回值是保存在eax裏的——64位彙編裏,函數返回值結果一般保存在rax或者eax(rax的低32位)中;
另外需要注意,在3和4時,rbp已經發生了變化,但對整個分析沒有影響,因爲結果是放在eax中的,使用eax進行函數結果的返回;
而edi中存儲的則是printf函數的第一個參數:
至此,整個過程可以說告一段落;