如下,一個簡單的程序
1 #include <stdio.h> 2 int add(int a, int b) 3 { 4 return a + b; 5 } 6 7 void main() 8 { 9 int a = 1, b = 2; 10 int result; 11 result = add(a, b); 12 printf("%d",result); 13 }
執行反匯編指令:gcc -g test.c
objdump -S
得到x86機器的匯編代碼(除去一些初始化的代碼)如下:
在分析上面的匯編程序之前,需要了解rbp、rsp為棧基址寄存器、棧頂寄存器,分別指向棧底和棧頂;edx、eax、esi、edi均為x86CPU上的通用寄存器,可以存放數據(雖然它們還有別的作用,但是本文章不涉及)
x86下棧生長是從高地址往低地址,即push操作一次,rsp減少4個字節,pop操作一次,rsp增加4個字節。
對上面匯編代碼的分析:
進入main函數,保護現場,將rbp壓入堆棧;
然后為main函數開拓新的堆棧框架,rbp與當前rsp相同,rsp再向上擴充16個字節(0x10);(以前的C程序只能在函數前面聲明變量,是因為編譯器還么有那么“智能”,它只能通過分析前部分的變量,一次性的為程序擴充堆棧)
然后向棧底上方的偏移地址為8和12的單元存入數據1和2;
把數據送入通用寄存器中,以供新的函數調用;
跳轉到add;
再次將main的rbp壓棧,保護;
新的rbp與當前rsp相同,把通用寄存器中的數據賦給棧底上方偏移地址為4和8的單元(此為函數參數傳遞的關鍵);
將傳入新棧的參數賦給通用寄存器,進行加法操作,結果存入eax;
pop出rbp,回到main函數;
將eax中的運算結果賦給棧底上方偏移地址為4的單元;
然后調用printf函數顯示結果。
使用arm-linux-gcc編譯並反匯編:arm-linux-objdump -D -m arm a.out
得到arm機器的匯編代碼(除去一些初始化的代碼)如下:
這段代碼的解析與x86類似,只不過需要了解幾個arm匯編指令和寄存器名稱。fp為幀寄存器,起“標簽”作用。lr是連接寄存器,在ARM體系結構中lr的用途有兩種:一是用來保存子程序返回地址;二是當異常發生時,lr保存的值等於異常發生時PC的值減4(或者減2),因此在各種異常模式下可以返回到異常發生前的相應位置繼續執行。bx lr即跳轉到lr存放的地址處。sp為棧頂指針。str 源寄存器 存儲地址,即將源存儲器數據送到存儲器中,ldr為其逆操作。
ARM為堆棧提供硬件支持,它有一個專門的寄存器sp指向棧頂,ARM支持四種堆棧工作方式,最常用的也是和x86類似,即從高地址向低地址生長。
參考資料:http://mooc.study.163.com/course/USTC-1000029000#/info