官术网_书友最值得收藏!

2.3.5 棧管理

棧通過維護自動的進程狀態數據來支持程序的執行。例如,如果一個程序的主例程(main routine)調用了a()函數,a()又調用了b()函數,則b()函數最終會將控制權返回給a(),a()則會接著將控制權返回給main()函數,如圖2.6所示。

圖2.6 棧管理

要做到將程序控制返回到正確的位置,就需要將返回地址的序列存儲起來。棧很適合做這項工作,因為動態的LIFO數據結構在內存限制允許的情況下可以支持任意層數的嵌套。當調用一個子例程時,調用例程中的下一條將要執行的指令地址被壓入棧中。當被調用的子例程返回時,預先存儲的返回地址從棧中彈出,程序的執行點就跳到該指定位置上,如圖2.7所示。棧維護的這些信息反映了任何時刻進程的執行狀態。

圖2.7 調用一個子例程

除了返回地址以外,棧還被用來保存子例程的參數以及局部(或自動)變量。幀(frame)指由函數調用引發的壓入棧的數據。當前幀的地址被存儲到幀或者基址寄存器中。在x86-32架構上,擴展基址指針(extended base pointer,ebp)寄存器就是用作此目的。幀指針在棧中是一個定點的引用。當調用一個子例程時,調用端函數的幀指針同樣被壓入棧,這樣當被調用子例程退出時,幀指針能被重新恢復。

Intel指令有兩種符號,微軟使用Intel符號。


mov eax, 4 # Intel Notation

GCC使用AT&T符號


mov $4, %eax # AT&T Notation

這兩種指令都把直接數4移動到eax寄存器。例2.4展示了調用foo(MyInt.MyStrPtr)所得的使用Intel符號表示的x86-32反匯編形式。

例2.4 使用Intel符號表示的反匯編


01  void foo(int, char *); // 
函數原形 
02 
03  int main(void) { 
04    int MyInt=1; // 
棧變量位于 ebp-8 
05    char *MyStrPtr="MyString"; //
棧變量位于ebp-4 
06    /* ... */ 
07    foo(MyInt, MyStrPtr); // 
調用 foo 
函數 
08      mov  eax, [ebp-4] 
09      push eax            # 
把第2
個參數壓入棧 
10      mov  ecx, [ebp-8] 
11      push ecx            # 
把第1
個參數壓入棧 
12      call foo            # 
把返回地址壓入棧 
13                          # 
并跳到那個地址 
14      add  esp, 8 
15    /* ... */ 
16  } 

調用由三個步驟組成,如下所示。

1.第二個參數被移到eax寄存器中,接著被壓入棧(第8行和第9行)。注意mov指令是如何利用ebp寄存器來引用參數以及棧中的局部變量的。

2.第一個參數被移到ecx寄存器中,接著被壓入棧(第10行和第11行)。

3.call指令將一個返回地址(call指令下一條指令的地址)壓入棧,然后將控制轉移到foo()函數(第12行)。

指令指針(eip)指向將要執行的下一條指令。當執行連續指令時,它會按照每個指令的大小自動遞增,從而使CPU按順序執行序列中的下一條指令。通常情況下,不能直接修改eip,相反,它必須通過諸如跳轉(jump),調用(call)和返回(return)指令間接修改。

當控制返回到返回地址時,棧指針(SP)被遞增8個字節(第14行)。(在x86-32中,棧指針被命名為esp,e前綴代表“擴展”,用來區分32位棧指針與16位棧指針)。棧指針指向棧的頂端。棧增長的方向取決于具體架構上的pop和push指令的實現(換言之,取決于對棧指針是遞增還是遞減操作)。對于很多流行的架構,包括x86、SPARC以及MIPS處理器在內,棧向低地址方向增長。在具有這些架構的機器上,遞增棧指針就意味著從棧中彈出數據。

foo()函數開頭。函數開頭中包含一個函數調用后所執行的指令。foo()函數的函數開頭如下所示:


1  void foo(int i, char *name) {
2    char LocalChar[24];
3    int LocalInt;
4      push ebp       # 
保存幀指針
5      mov ebp, esp   # 
子例程的幀指針被設置為
6                     # 
當前棧指針.
7      sub esp, 28    # 
為局部變量分配空間.
8    /* ... */ 

push指令將保存有指向調用者棧幀指針的ebp寄存器壓入棧。mov指令將函數的幀指針(ebp寄存器)指向當前棧指針。最后,函數在棧上為局部變量分配了總共28個字節的空間(Local Char占24字節,Local Int占4字節)。

函數開頭部分執行之后,foo()的棧幀如表2.2所示。在x86上,棧向內存低地址增長。

foo()的函數結尾。函數結尾包含將一個函數返回給調用者所執行的指令。下面是從foo()函數返回的函數結尾:


1  /* ... */
2   return;
3     mov  esp, ebp   # 
恢復棧指針
4     pop  ebp        # 
恢復幀指針  
5     ret             # 
將返回地址從棧彈出
6                     # 
并把控制移交給那個位置
7  }

表2.2 函數開頭部分執行之后,foo()的棧幀

這些返回序列可以看成是前面的函數開頭的逆序執行形式。mov指令將棧指針(esp)從幀指針(ebp)中恢復。pop指令將調用者的幀指針從棧中恢復。ret指令從棧中彈出調用函數中的返回地址,并且將控制轉移到該位置。

主站蜘蛛池模板: 锡林郭勒盟| 扬中市| 阿鲁科尔沁旗| 乌鲁木齐县| 新兴县| 泾川县| 乌兰察布市| 灯塔市| 霸州市| 唐山市| 罗甸县| 揭阳市| 义乌市| 马公市| 会东县| 定南县| 吴堡县| 古丈县| 永泰县| 丽江市| 临安市| 建平县| 高碑店市| 乐至县| 蕲春县| 彭水| 双桥区| 宣化县| 商河县| 贵南县| 张北县| 新乐市| 苏州市| 荥经县| 临洮县| 保定市| 固镇县| 赤水市| 张家川| 湛江市| 信宜市|