雖然編了幾年程序,但是對于程序到底是什么規則變成匯編代碼的,在這里搞了一個小程序。用VC查看了一下匯編代碼。在此之前先介紹一下關于函數運行是堆棧變化的細節。
在高級語言編寫程序時,函數的調用是很常見的事情,但是在函數調用過程中堆棧的變化通常有幾個細節:
1.父函數將函數的實參按照從右至左的順序壓入堆棧;
2.CPU將父函數中函數調用指令Call的下一條指令地址EIP壓入堆棧;
3.父函數通過Push Ebp指令將基址指針EBP的值壓入堆棧,并通過Mov Ebp,Esp指令將當前堆棧指針Esp值傳給Ebp;
4.通過Sub Esp,m(m是字節數)指令可以為存放函數中的局部變量開辟內存。函數在執行的時候如果需要訪問實參或局部變量,都可以通過EBP指針來指引完成。
windows系統下常用的函數調用通常有種,__cdecl和__stdCall。
1.在VC、.net等開發環境中,編寫命令行程序時的Main或者_tmain函數,以及大家自己定義的很多函數都是默認采用__cdecl調用方式;
2.通過MFC編寫圖形界面程序的時候,其主函數聲明為extern "C" int WINAPI tWinMain(參數),該函數的調用約定是__stdCall。WINAPI和PASCAL等都是__stdCall的宏定義,是一個意思,此外,大家平時調用的API函數,絕大多數都是采用__staCall的調用方式;
3.__cdecl調用方式的函數,父函數在調用子函數的時候,先將子函數的實參按照從右至左的順序壓入堆棧中,子函數返回后,父函數通過Sub Esp,n(n=函數實參個數*4)指令來恢復堆棧;
4.__stdCall調用約定函數,子函數調用時實參入棧順序也是從左到右,但是堆棧恢復是子函數返回時自己通過Ret n指令來完成的。
下邊就是針對這些知識進行的部分實踐:
- #include<stdio.h>??
- #include<windows.h>??
- #include<stdlib.h>??
- int?fun(char?*szIn,?int?nTest)??
- {??
- ????char?szBuf[9];??
- ????printf("%d\n",nTest);??
- ????strcpy(szBuf,szIn);??
- ????return?0;??
- }??
- int?main(int?argc,?char?*argv[])??
- {??
- ????char?sz_In[]?=?"1234567";??
- ????fun(sz_In,888);??
- ????return?0;??
- }??
匯編代碼
- 00401003???int?????????3??
- 00401004???int?????????3??
- @ILT+0(?fun@@YAHPADH@Z):??
- 00401005???jmp?????????fun?(00401020)????//進入fun函數??
- @ILT+5(_main):??
- 0040100A???jmp?????????main?(00401080)????//進入main函數,該位置是整段代碼的入口??
- 0040100F???int?????????3??
- 00401010???int?????????3??
- 00401011???int?????????3??
- 00401012???int?????????3??
- 00401013???int?????????3??
- 00401014???int?????????3??
- 00401015???int?????????3??
- 00401016???int?????????3??
- 00401017???int?????????3??
- 00401018???int?????????3??
- 00401019???int?????????3??
- 0040101A???int?????????3??
- 0040101B???int?????????3??
- 0040101C???int?????????3??
- 0040101D???int?????????3??
- 0040101E???int?????????3??
- 0040101F???int?????????3??
- ---?c:\project\heap1\heap1.cpp??--------------------------------------------------------------------------------------------------------------------------------------??
- 1:????#include<stdio.h>??
- 2:????#include<windows.h>??
- 3:????#include<stdlib.h>??
- 4:????int?fun(char?*szIn,?int?nTest)??
- 5:????{??
- 00401020???push????????ebp??
- 00401021???mov?????????ebp,esp???????????????//保存基址指針,并將現在的棧頂保存為基址指針。??
- 00401023???sub?????????esp,4Ch???????????????//騰出一部分堆棧區用于存放局部變量。??
- 00401026???push????????ebx??
- 00401027???push????????esi??
- 00401028???push????????edi???????????????????//保存三個寄存器的值。??
- 00401029???lea?????????edi,[ebp-4Ch]??
- 0040102C???mov?????????ecx,13h??
- 00401031???mov?????????eax,0CCCCCCCCh??
- 00401036???rep?stos????dword?ptr?[edi]???????//將騰出的4Ch的空間初始化值為0xCC。???
- 6:????????char?szBuf[9];??
- 7:????????printf("%d\n",nTest);??
- 00401038???mov?????????eax,dword?ptr?[ebp+0Ch]??
- 0040103B???push????????eax??
- 0040103C???push????????offset?string?"%d\n"?(0042201c)????//先后壓入棧中兩個地址,nTest,一個是一個字符串指針。??
- 00401041???call????????printf?(004011d0)????????????????????????????//調用printf函數時,它會自動做到堆棧平衡。??
- 00401046???add?????????esp,8????????????????????????????????????????//由于剛才壓入和兩個參數,所以在這里手動將兩個參數彈出堆棧??
- 8:????????strcpy(szBuf,szIn);??
- 00401049???mov?????????ecx,dword?ptr?[ebp+8]?????????????????????????
- 0040104C???push????????ecx???????????????????????????//壓入szIn的指針。這個參數在高出基址的8位處,也就是調用該函數前壓入棧中的。??
- 0040104D???lea?????????edx,[ebp-0Ch]??
- 00401050???push????????edx?????????????????//壓入szBuf的指針,這個函數在低于基址的OCh位處,這是調用函數后分配的。局部變量的分配大??
- 00401051???call????????strcpy?(004010e0)???//小都是按4的倍數分配的,所以盡管szBuf[9]但是也分配在了0Ch處。??
- 00401056???add?????????esp,8??
- 9:????????return?0;??
- 00401059???xor?????????eax,eax?????????????//返回值在EAX中。??
- 10:???}??
- 0040105B???pop?????????edi??
- 0040105C???pop?????????esi??
- 0040105D???pop?????????ebx?????????????????//彈出保存的數據。??
- 0040105E???add?????????esp,4Ch?????????????//消除為局部變量騰出的空間。??
- 00401061???cmp?????????ebp,esp??
- 00401063???call????????__chkesp?(00401250)?//檢驗是否在用戶自定義匯編代碼中修改了ebp和esp的相對關系。一般情況下EBP>ESP??
- 00401068???mov?????????esp,ebp?????????????//將原基址恢復給棧頂寄存器。??
- 0040106A???pop?????????ebp?????????????????//彈出原調用函數的堆棧基址。??????????????
- 0040106B???ret?????????????????????????????//函數返回。???
- ---?No?source?file??--------------------------------------------------------------------------------------------------------------------------------------------------??
- 0040106C???int?????????3??
- 0040106D???int?????????3??
- 0040106E???int?????????3??
- 0040106F???int?????????3??
- 00401070???int?????????3??
- 00401071???int?????????3??
- 00401072???int?????????3??
- 00401073???int?????????3??
- 00401074???int?????????3??
- 00401075???int?????????3??
- 00401076???int?????????3??
- 00401077???int?????????3??
- 00401078???int?????????3??
- 00401079???int?????????3??
- 0040107A???int?????????3??
- 0040107B???int?????????3??
- 0040107C???int?????????3??
- 0040107D???int?????????3??
- 0040107E???int?????????3??
- 0040107F???int?????????3??
- ---?c:\project\heap1\heap1.cpp??--------------------------------------------------------------------------------------------------------------------------------------??
- 11:???int?main(int?argc,?char?*argv[])??
- 12:???{??
- 00401080???push????????ebp??
- 00401081???mov?????????ebp,esp???????????????????????//保存堆棧基址??
- 00401083???sub?????????esp,48h???????????????????????//騰出局部變量空間????
- 00401086???push????????ebx??
- 00401087???push????????esi??
- 00401088???push????????edi???????????????????????????//保存3個寄存器????
- 00401089???lea?????????edi,[ebp-48h]??
- 0040108C???mov?????????ecx,12h??
- 00401091???mov?????????eax,0CCCCCCCCh????????????????//初始化局部變量空間??
- 00401096???rep?stos????dword?ptr?[edi]??
- 13:???????char?sz_In[]?=?"1234567";??
- 00401098???mov?????????eax,[string?"1234567"?(00422020)]??
- 0040109D???mov?????????dword?ptr?[ebp-8],eax??
- 004010A0???mov?????????ecx,dword?ptr?[string?"1234567"+4?(00422024)]??
- 004010A6???mov?????????dword?ptr?[ebp-4],ecx?????????//將字符串通過寄存器將字符拷貝到分配的空間中。??
- 14:???????fun(sz_In,888);??
- 004010A9???push????????378h???????????????????????????
- 004010AE???lea?????????edx,[ebp-8]??
- 004010B1???push????????edx??????//從右至左將參數壓入堆棧中,數字直接壓入數值,字符串則壓入字符串指針??
- 004010B2???call????????@ILT+0(fun)?(00401005)??
- 004010B7???add?????????esp,8????????????????????????//恢復堆棧??
- 15:???????return?0;??
- 004010BA???xor?????????eax,eax??????????????????????//返回值在EAX中??
- 16:???}??
- 004010BC???pop?????????edi??
- 004010BD???pop?????????esi??
- 004010BE???pop?????????ebx??????????????????????????//恢復3個寄存器??
- 004010BF???add?????????esp,48h??????????????????????//清除局部變量空間??
- 004010C2???cmp?????????ebp,esp??
- 004010C4???call????????__chkesp?(00401250)??????????//檢測堆棧指針與堆棧基址?????
- 004010C9???mov?????????esp,ebp??????????????????????//恢復調用函數的棧頂??
- 004010CB???pop?????????ebp??????????????????????????//恢復調用函數的堆棧基址??
- 004010CC???ret??????????????????????????????????????//函數返回??
- ---?No?source?file??--------------------------------------------------------------------------------------------------------------------------------------------------??
- 004010CD???int?????????3??
- 004010CE???int?????????3??
關于這個程序的堆棧使用情況也做了一下分析,如圖:
?