函数调用栈空间以及fp寄存器
Arm上函數調用的規則在ARM System Developer's Guide文檔中的ATPCS部分有詳細的定義,這里主要通過函數調用過程中函數棧的情況來說明fp和sp等寄存器的作用。有關ATPCS的詳細內容可以去文檔中看。
?
fp叫做frame pointer寄存器,即棧幀指針寄存器;sp叫做stack pointer寄存器,即棧指針寄存器。那么它們具體的作用是什么呢?
首先,大家知道每個進程都有自己獨立的棧空間,進程中有千千萬萬的函數調用,這些函數共享進程的這個棧空間,那么問題就來了,函數運行過程中會有非常多的入棧出棧的過程,當函數返回backtrace的時候怎樣能精確定位到返回地址呢?還有子函數所保存的一些寄存器的內容?這樣就有了棧幀的概念,即每個函數所使用的棧空間是一個棧幀,所有的棧幀就組成了這個進程完整的棧。而fp就是棧基址寄存器,指向當前函數棧幀的棧底,sp則指向當前函數棧幀的棧頂。通過sp和fp所指出的棧幀可以恢復出母函數的棧幀,以此類推就可以backtrace出所有函數的調用順序。
那一個函數的棧幀具體的范圍什么呢?fp和sp具體應該指向什么位置呢?請看下圖:
圖1
上圖描述的是ARM的棧幀布局方式,main stack frame為調用函數的棧幀,func1 stack frame為當前函數(被調用者)的棧幀,棧底在高地址,棧向下增長。此圖是網上的圖,理論上應該是上圖的格式,fp、sp、lr和pc這四個寄存器是非常特殊的寄存器,它們記錄了當前正在運行的函數一些重要信息,在剛進入一個新的函數開始執行的時候,它們保存的是上個函數的信息,需要將它們入棧保存起來,這很重要!這些并沒有定義在ATPCS中,ATPCS規定的是函數調用的時候參數如何傳遞,以及函數返回值的保存等。上面的這些個人覺得是一種默契,定義函數現場的保存及恢復,這些默契包括ATPCS都是人為的一種約束,目的是為了保證程序運行中不會出錯,具體怎樣實現應該是不同的編譯器不盡相同。
下面我為了驗證arm的gcc編譯器的實現,自己寫了個小實驗程序:
?
main.c:
#include <stdio.h>
int func(int i);
int main(void)
{
int i = 25;
func(i);
return 0;
}
?
func.c
int func(int i)
{
int a = 2;
return a * i;
}
main.c中調用了func函數,而func函數的實現在func.c文件中。下面是用arm-linux-androideabi-gcc編譯后的執行文件反匯編出來的代碼:
?
?
Disassembly of section .text:0000822c <func>:822c: e52db004 push {fp} ; (str fp, [sp, #-4]!)8230: e28db000 add fp, sp, #08234: e24dd014 sub sp, sp, #208238: e50b0010 str r0, [fp, #-16]823c: e3a03002 mov r3, #28240: e50b3008 str r3, [fp, #-8]8244: e51b3008 ldr r3, [fp, #-8]8248: e51b2010 ldr r2, [fp, #-16]824c: e0030392 mul r3, r2, r38250: e1a00003 mov r0, r38254: e24bd000 sub sp, fp, #08258: e49db004 pop {fp} ; (ldr fp, [sp], #4)825c: e12fff1e bx lr00008260 <main>:8260: e92d4800 push {fp, lr}8264: e28db004 add fp, sp, #48268: e24dd008 sub sp, sp, #8826c: e3a03019 mov r3, #258270: e50b3008 str r3, [fp, #-8]8274: e51b0008 ldr r0, [fp, #-8]8278: ebffffeb bl 822c <func>827c: e3a03000 mov r3, #08280: e1a00003 mov r0, r38284: e24bd004 sub sp, fp, #48288: e8bd8800 pop {fp, pc}
上面的匯編代碼可以看到,并沒有想上面圖中所畫的,將fp, sp, lr, pc全部都入棧,而是只入棧這四個寄存器中有改動的。fp是肯定要保存的,它指向的是每個函數棧幀的棧基址,而sp一般不用入棧,因為它的值一般保存在fp中,因為剛進入一個函數的時候,將上個函數的fp入棧保存以后,當前函數的棧空間應該是空的,fp應該指向與sp相同的位置,然后才會對sp做減法來分配棧空間保存臨時變量。而如果當前函數中沒有對其它函數的調用的時候,是不會對lr寄存器做修改的,所以也就不用保存了。
?
但是上面對main函數和func函數的fp指針所指向的位置也不完全相同,main函數中fp指向的是上個fp保存的內存地址,而func中的fp指向的是sp相同的位置。但是只要恢復的時候相對應不出錯就可以了,上面也說過ATPCS這些規定都是人為的一種約束,保證backtrace的時候可以把正確的內容恢復到寄存器中,具體怎么實現并沒有特別死板的定義。
?
另外一個比較重要的東西就是出入棧的順序,在ARM指令系統中是地址遞減棧,入棧操作的參數入棧順序是從右到左依次入棧,而參數的出棧順序則是從左到右的你操作。包括push/pop和LDMFD/STMFD等。
比如指令 push {fp, sp, lr, pc}執行的結果就是圖1中棧的樣子,pc被首先入棧存在高地址,從右到左依次入棧,fp存在低地址。
這些是比較細節和基礎的東西,同時也是需要搞清楚的。
?
?
參考鏈接:
1.?http://www.linuxidc.com/Linux/2013-03/81247.htm
2.?http://www.cnblogs.com/chyl411/p/4579053.html
3.?http://www.cnblogs.com/fanzhidongyzby/p/5250116.html
總結
以上是生活随笔為你收集整理的函数调用栈空间以及fp寄存器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux qt rsa加密解密,使用Q
- 下一篇: 【Kali渗透全方位实战】通过获取到的h