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

2.3.5 棧管理

棧通過維護(hù)自動(dòng)的進(jìn)程狀態(tài)數(shù)據(jù)來支持程序的執(zhí)行。例如,如果一個(gè)程序的主例程(main routine)調(diào)用了a()函數(shù),a()又調(diào)用了b()函數(shù),則b()函數(shù)最終會(huì)將控制權(quán)返回給a(),a()則會(huì)接著將控制權(quán)返回給main()函數(shù),如圖2.6所示。

圖2.6 棧管理

要做到將程序控制返回到正確的位置,就需要將返回地址的序列存儲起來。棧很適合做這項(xiàng)工作,因?yàn)閯?dòng)態(tài)的LIFO數(shù)據(jù)結(jié)構(gòu)在內(nèi)存限制允許的情況下可以支持任意層數(shù)的嵌套。當(dāng)調(diào)用一個(gè)子例程時(shí),調(diào)用例程中的下一條將要執(zhí)行的指令地址被壓入棧中。當(dāng)被調(diào)用的子例程返回時(shí),預(yù)先存儲的返回地址從棧中彈出,程序的執(zhí)行點(diǎn)就跳到該指定位置上,如圖2.7所示。棧維護(hù)的這些信息反映了任何時(shí)刻進(jìn)程的執(zhí)行狀態(tài)。

圖2.7 調(diào)用一個(gè)子例程

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

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


mov eax, 4 # Intel Notation

GCC使用AT&T符號


mov $4, %eax # AT&T Notation

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

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


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

調(diào)用由三個(gè)步驟組成,如下所示。

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

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

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

指令指針(eip)指向?qū)⒁獔?zhí)行的下一條指令。當(dāng)執(zhí)行連續(xù)指令時(shí),它會(huì)按照每個(gè)指令的大小自動(dòng)遞增,從而使CPU按順序執(zhí)行序列中的下一條指令。通常情況下,不能直接修改eip,相反,它必須通過諸如跳轉(zhuǎn)(jump),調(diào)用(call)和返回(return)指令間接修改。

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

foo()函數(shù)開頭。函數(shù)開頭中包含一個(gè)函數(shù)調(diào)用后所執(zhí)行的指令。foo()函數(shù)的函數(shù)開頭如下所示:


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

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

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

foo()的函數(shù)結(jié)尾。函數(shù)結(jié)尾包含將一個(gè)函數(shù)返回給調(diào)用者所執(zhí)行的指令。下面是從foo()函數(shù)返回的函數(shù)結(jié)尾:


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

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

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

主站蜘蛛池模板: 玉林市| 合作市| 新田县| 隆回县| 揭阳市| 奉新县| 慈利县| 锦州市| 石台县| 阿瓦提县| 庆城县| 乐东| 道孚县| 棋牌| 同江市| 丰都县| 湟源县| 高安市| 鹤庆县| 瓦房店市| 湛江市| 普洱| 漳浦县| 谢通门县| 仙游县| 偃师市| 绥芬河市| 安顺市| 金昌市| 隆化县| 余干县| 达拉特旗| 茶陵县| 巫溪县| 江北区| 武穴市| 叙永县| 儋州市| 麻栗坡县| 蕉岭县| 米易县|