|
一.向函數(shù)傳遞參數(shù): 1.C函數(shù)調(diào)用慣例:參數(shù)是以反序壓入棧中的. (1)調(diào)用函數(shù)(caller)把函數(shù)的參數(shù)一個(gè)一個(gè)地按反序壓入棧(從右向左,所以第一個(gè)被指定的函數(shù)參數(shù)最后一個(gè)被壓進(jìn)去). (2)然后調(diào)用函數(shù)執(zhí)行一個(gè)caller指令把控制權(quán)交給被調(diào)函數(shù)(callee). (3) 被調(diào)函數(shù)收到控制權(quán),一般地(這并不是必要的,因?yàn)橛械暮瘮?shù)不必去訪問(wèn)它們的參數(shù)),先把ESP中的值放入EBP中,以使EBP成為一個(gè)基址指針,從而用 EBP去訪問(wèn)棧中的參數(shù).然而調(diào)用函數(shù)也可能做了這個(gè),所以慣例是EBP必須被任何C函數(shù)保存.因此被調(diào)用者函數(shù),如果它要把EBP作為一個(gè)幀指針,必須 先把以前的值壓入棧. (4)被調(diào)函數(shù)然后就可以用EBP去訪問(wèn)它的參數(shù)了.在[EBP]處的雙字(dword)存放了先前壓入的EBP的值.下一個(gè)雙字[EBP+4],存入了返回地址,它是由CALL隱式地壓入的. 真正的參數(shù)從[EBX+8]開(kāi)始.最左邊的參數(shù)由于是最后入棧的,可用這個(gè)偏移量進(jìn)行訪問(wèn);余下的參數(shù),在余下的更高的偏移量上.因此,在像printf這樣的函數(shù)里,它可以跟可變的函數(shù)參數(shù).但我們可以找到它的第一個(gè)參數(shù),從而知道余下的參數(shù)的個(gè)數(shù)和類型. (5)被調(diào)函數(shù)也可能希望減低ESP的大小,以給局部變量分配空間,這些局部變量將用EBP的負(fù)偏移量進(jìn)行訪問(wèn). (6)被調(diào)函數(shù)如果想返回一個(gè)值給調(diào)用函數(shù),應(yīng)當(dāng)把其值放到AL,AX或EAX中,這取決于返回值的大小.浮點(diǎn)數(shù)一般放在ST0中. (7)一旦被調(diào)函數(shù)完成了任務(wù),如果它已經(jīng)分配了本地棧空間,它把ESP的值從EBP中恢復(fù)然后彈出原來(lái)EBP的值,最后通過(guò)RET返回. (8) 當(dāng)調(diào)用函數(shù)從被調(diào)函數(shù)重新得到控制權(quán)的時(shí)候,函數(shù)的參數(shù)認(rèn)然在棧中,所以一般可以將ESP加上一個(gè)常數(shù)來(lái)移除它們(而不是選用一系列慢的POP指令).所 以,如果一個(gè)函數(shù)偶然地輸入與原形不一樣的錯(cuò)誤的參數(shù)個(gè)數(shù),棧仍然能回到一個(gè)智能的狀態(tài).因?yàn)檎{(diào)用函數(shù)知道壓入了多少個(gè)參數(shù),所以它也能正確地移掉它們. 2.例子: char res;/*global variable*/ char f(char a,char b);/* function prototype*/ int main(){/*entry point */ res=f(0x12,0x23); /*function call*/ } char f(char a,char b){ /*function definition*/ return a+b; /*return code*/ } 它將會(huì)產(chǎn)生如下的二進(jìn)制代碼: 00000000 55 push ebp 00000001 89E5 mov ebp,esp 00000003 6A23 push byte+0x23 00000005 6A12 push byte+0x12 00000007 E810000000 call 0x1c 0000000C 83c408 add esp,byte+0x8 0000000F 88c0 mov al,al 00000011 880534120000 mov [0x1234],al 00000017 C9 leave 00000018 C3 ret 00000019 8D7600 lea esi,[esi+0x0] 0000001C 55 push ebp 0000001D 89E5 mov ebp,esp 0000001F 83EC04 sub esp,byte+0x4 00000022 53 push ebx 00000023 8B5508 mov edx,[ebp+0x8] 00000026 8B4D0C mov ecx,[ebp+0xc] 00000029 8855FF mov [ebp-0x1],dl 0000002C 884DFE mov [ebp-0x2],cl 0000002F 8A45FF mov al,[ebp-0x1] 00000032 0245FE add al,[eb0-0x2] 00000035 0FBED8 movsx ebx,al 00000038 89D8 mov eax,ebx 0000003A EB00 jmp short 0x3c 0000003C 8B5DF8 mov ebx,[ebp-0xi] 0000003F C9 leave 00000040 C3 ret 3.剖析: 在壓入兩個(gè)字節(jié)的參數(shù)到棧中后,隨即有一個(gè)調(diào)用在0X1C處的函數(shù)的CALL.這個(gè)函數(shù)首先將esp減4個(gè)字節(jié),作為本地變量使用.然后這個(gè)函數(shù)把函數(shù)參數(shù)拷貝到本地.然后a+b被計(jì)算并存放在eax中返回. 二.賦值: main(){ unsigned int i=251; } 當(dāng)我們把這個(gè)編譯成平的二進(jìn)制文件時(shí),我們得到: 00000000 55 push ebp 00000001 89E5 mov ebp,esp 00000003 83EC04 sub esp,byte+0x4 00000006 C745FCFB000000 mov dword [ebp-0x4],0xfb 0000000D C9 leave 0000000E C3 ret 當(dāng)我們把賦值語(yǔ)句改成: unsigned int i=-5; 我們?cè)诘刂?x6處得到了下面的指令: 00000006 C745FCFBFFFFFF mov dword [ebp-0x4],0xfffffffb 現(xiàn)在讓我們看一看有符號(hào)整數(shù).語(yǔ)句:int i=251; 產(chǎn)生: 00000006 C745FCFB000000 mov dword [ebp-0x4],0xfb 一個(gè)使用負(fù)數(shù)的語(yǔ)句: int i=-5; 產(chǎn)生了: 00000006C745FCFBFFFFFF mov dword [ebp-0x4],0xfffffffb 看起來(lái)像是有符號(hào)和無(wú)符號(hào)的賦值被一樣對(duì)待了. 三.把signed char轉(zhuǎn)換成signed int: main(){ char c=-5; int i; i=c; } 產(chǎn)生了如下的二進(jìn)制文件: 00000000 55 push ebp 00000001 89E5 mov ebp,esp 00000003 83EC08 sub esp,byte+0x8 00000006 0645FFFB mov byte [ebp-0x1],0xfb 0000000A 0FBE45EF movsx eax,byte [ebp-0x1] 0000000E 8945F8 mov [ebp-0x8],eax 00000011 C9 leave 00000012 C3 ret 剖析: 首 先我們看到在地址0x3處在棧中為本地變量c和i保留了8個(gè)字節(jié).編譯器使用8個(gè)字節(jié)以使其對(duì)齊整數(shù)i.然后我們可以看到char c被0xfb填充,也就是-5(0xfb=251,251-256=-5).注意編譯器使用[ebp-0x1]而不是使用 [ebp-0x4].這是由于小端表示法的緣故.下一個(gè)movsx才真真正做了從signed char到 signed int的轉(zhuǎn)換.MOVSX指令對(duì)源操作數(shù)進(jìn)行符號(hào)擴(kuò)展然后拷貝到目標(biāo)操作數(shù).在leave前的最后一個(gè)指令把存儲(chǔ)在eax中的數(shù)據(jù)傳到int i中. 四.將signed int轉(zhuǎn)換成signed char main(){ char c; int i=-5; c=i; } 將產(chǎn)生如下的二進(jìn)制代碼: 00000000 55 push esb 00000001 89E5 mov ebp,esp 00000003 83EC08 sub esp,byte+0x8 00000006 C745F8FBFFFFFF mov dword [ebp-0x8],0xfffffffb 0000000D 8A45F8 mov al,[ebp-0x8] 00000010 8845FF mov [ebp-0x1],al 00000013 C9 leave 00000014 C3 ret 剖 析:c=i這句只有在值在-128(-2^7)到127(2^7-1)之間的時(shí)候才有意義。因?yàn)樗仨氁趕igned char的范圍內(nèi)。0xfffffffb實(shí)際上就是-5。當(dāng)我們只看那個(gè)不太重要的0xfb字節(jié)并把它變成一個(gè)signed char時(shí),我們同樣也可以得到-5.所以從一個(gè)signed int轉(zhuǎn)換成一個(gè)signed char我們也可以只用一個(gè)簡(jiǎn)單的mov指令. 五.把unsigned char轉(zhuǎn)換為unsigned int main(){ unsigned char c=5; unsigned int i; i=c; } 將產(chǎn)生如下的二進(jìn)制文件: 00000000 55 push ebp 00000001 89E5 mov ebp,esp 00000003 83EC08 sub esp,byte+0x8 00000006 C645FF05 mov byte [ebp-0x1],0x5 0000000A 0FB645FF movzx eax,byte [ebp-0x1] 0000000E 8945F8 mov [ebp-0x8],eax 00000011 C9 leave 00000012 C3 ret 剖析:除了在0xA處的指令外,我們得到了和從signed char到signed int一樣的代碼。這里我們的指令是movzx。MOVZX將源操作數(shù)零擴(kuò)展,然后拷貝到目標(biāo)操作數(shù)。 六.把unsigned int轉(zhuǎn)換為unsigned char main(){ unsigned char c; unsigned int i=251; c=i; } 將產(chǎn)生如下的二進(jìn)制代碼: 00000000 55 push ebp 00000001 89E5 mov ebp,esp 00000003 83EC08 sub esp,byte+0x8 00000006 C745F8FB000000 mov dword [ebp-0x8],0xfb 0000000D 8A45F8 mov al,[ebp-0x8] 00000010 8845FF mov [ebp-0x1],al 00000013 C9 leave 00000014 C3 ret 剖析:整數(shù)的值嚴(yán)格限制在0到255(2^8-1)之間。這是因?yàn)閡nsigned char不能處理更大的數(shù)了。在0xD處的mov是真正做轉(zhuǎn)換的指令,它和從signed int到signed char的轉(zhuǎn)換是一樣的。 |
|
|