文章目錄
本文基於win32下彙編。
1. 函數參數
函數棧增長方向與地址方向相反,從高地址向低地址增長。esp指向函數棧頂,ebp指向函數棧底。
sub esp XXX 將XXX長度的記憶體塊壓入棧
add esp XXX將XXX長度的記憶體塊彈出棧
參數壓棧順序樣本
int param1 = 1;00C939DF mov dword ptr [param1],1 int param2 = 3;00C939E6 mov dword ptr [param2],3 int sum = pushParametersOrderApp(param1, param2); #壓棧順序跟參數申明順序相反!00C939F0 mov eax,dword ptr [param2] # 將param2值放入eax00C939F6 push eax # 壓棧eax00C939F7 mov ecx,dword ptr [param1] # 將param1值放入ecx00C939FA push ecx # 壓棧ecx00C939FB call pushParametersOrderApp (0C911F9h) # 函數調用00C93A00 add esp,8 # 將之前壓棧的eax和ecx兩個值移除。pop操作是將esp加8,說明棧增長方向是從高地址到低地址。00C93A03 mov dword ptr [sum],eax # 函數傳回值在eax中,存入sum變數中# 函數調用現場int pushParametersOrderApp(int param1, int param2){00E41F80 push ebp # 儲存之前ebp指標,esp += 400E41F81 mov ebp,esp # 將新的esp賦給ebp指向當前函數棧底00E41F83 sub esp,0D8h # 當前函數棧幀 預留216(0D8)的位元組,存放自動變數00E41F89 push ebx 00E41F8A push esi 00E41F8B push edi 00E41F8C lea edi,[ebp-0D8h] # 棧頂指標地址存入edi00E41F92 mov ecx,36h # 棧上54(36h)個整數,即216(0D8)個位元組00E41F97 mov eax,0CCCCCCCCh # 初始化的數值00E41F9C rep stos dword ptr es:[edi] # 初始化棧空間int temp1 = param1;00E41F9E mov eax,dword ptr [param1] # param1的值存入eax00E41FA1 mov dword ptr [temp1],eax # eax值存入temp1中int temp2 = param2;00E41FA4 mov eax,dword ptr [param2] # 同理00E41FA7 mov dword ptr [temp2],eax return temp1 + temp2;00E41FAA mov eax,dword ptr [temp1] # temp1值存入eax00E41FAD add eax,dword ptr [temp2] # 相加結果存入eax,與函數返回後00C93A03行操作對應,函數結果在eax中}00FE1FB0 pop edi 00FE1FB1 pop esi 00FE1FB2 pop ebx 00FE1FB3 mov esp,ebp # 還原esp00FE1FB5 pop ebp # 還原ebp,同時esp-=400FE1FB6 ret
pushParametersOrderApp函數調用前後堆棧:
代碼比較簡單,對兩個變數進行求和,結果返回。通過彙編源碼說明幾點:
1)C/C++ 中函數參數壓棧順序:與申明順序相反
2)函數棧幀的定義:esp和ebp之間的記憶體塊
3)棧上自動變數的初始化,使用0CCCCCCCC
4)函數參數在函數棧幀的外面,見00C939F6、00C939FA行;函數局部變數參數定義在棧上。
傳值拷貝
struct TestItem{int a;int b;int arr[10];};int passValueApp(TestItem item){int value = item.a;int value1 = item.b;return value;}int passReferenceApp(TestItem& item){int value = item.a;int valu1 = item.b;return value;}int passPointerApp(TestItem* item){int value = item->a;int value1 = item->b;return value;}TestItem item = {1, 4, {1,2,3}};00FE3948 mov dword ptr [item],1 # &item.a==item,a置為100FE394F mov dword ptr [ebp-30h],4 # ebp-48 通過ebp指標訪問b00FE3956 mov dword ptr [ebp-2Ch],1 # ebp-4400FE395D mov dword ptr [ebp-28h],2 # ebp-4000FE3964 mov dword ptr [ebp-24h],3 # ebp-3600FE396B xor eax,eax # 異或操作將eax置零00FE396D mov dword ptr [ebp-20h],eax # ebp-3200FE3970 mov dword ptr [ebp-1Ch],eax # ebp-2800FE3973 mov dword ptr [ebp-18h],eax # ebp-2400FE3976 mov dword ptr [ebp-14h],eax # ebp-2000FE3979 mov dword ptr [ebp-10h],eax # ebp-1600FE397C mov dword ptr [ebp-0Ch],eax # ebp-1200FE397F mov dword ptr [ebp-8],eax # ebp-8passValueApp(item);###### 傳結構體函數 ######00FE3982 sub esp,30h # 參數值拷貝,esp減去48(30h)正好sizeof TestItem大小00FE3985 mov ecx,0Ch # 指定要拷貝次數,0Ch即12個整數,對用48byte00FE398A lea esi,[item] # 擷取item地址00FE398D mov edi,esp # 拷貝基地址指定00FE398F rep movs dword ptr es:[edi],dword ptr [esi] # rep迴圈12次拷貝00FE3991 call passValue (0FE11E5h) # 00FE3996 add esp,30h # 將參數拷貝彈出棧空間,esp+=48passPointerApp(&item);###### 傳指標函數 ######00FE3999 lea eax,[item] # 擷取item地址存入eax00FE399C push eax # 參數值拷貝,壓棧eax esp-=400FE399D call passPointerApp (0FE11EFh) # 00FE39A2 add esp,4 # 將參數拷貝彈出棧空間,esp+=4passReferenceApp(item);00FE39A5 lea eax,[item] # 彙編層面引用版本和指標一樣00FE39A8 push eax 00FE39A9 call passReferenceApp (0FE11EAh) 00FE39AE add esp,4
該樣本說明了函數傳值拷貝問題:
1)passValueApp函數調用需要在棧幀上預留48位元組的空間,00FE398D、00FE398F拷貝item結構體,函數執行完成以後將esp加上48位元組將參數拷貝出棧。
2)passPointerApp函數傳指標,00FE399C行將指標變數拷貝入棧,函數調用完成將esp+4完成參數拷貝出棧操作。
3)passReferenceApp 傳引用於傳指標原理一樣。
由此可見,大資料結構一定要傳指標或者引用。
2. 函數傳回值
# 函數內部堆棧現場TestItem returnStructApp(){00EC3850 push ebp 00EC3851 mov ebp,esp 00EC3853 sub esp,0F8h 00EC3859 push ebx 00EC385A push esi 00EC385B push edi 00EC385C lea edi,[ebp-0F8h] 00EC3862 mov ecx,3Eh 00EC3867 mov eax,0CCCCCCCCh 00EC386C rep stos dword ptr es:[edi] TestItem item = { 1,2, {3,4,5} };00EC386E mov dword ptr [item],1 00EC3875 mov dword ptr [ebp-30h],2 00EC387C mov dword ptr [ebp-2Ch],3 00EC3883 mov dword ptr [ebp-28h],4 00EC388A mov dword ptr [ebp-24h],5 00EC3891 xor eax,eax 00EC3893 mov dword ptr [ebp-20h],eax 00EC3896 mov dword ptr [ebp-1Ch],eax 00EC3899 mov dword ptr [ebp-18h],eax 00EC389C mov dword ptr [ebp-14h],eax 00EC389F mov dword ptr [ebp-10h],eax 00EC38A2 mov dword ptr [ebp-0Ch],eax 00EC38A5 mov dword ptr [ebp-8],eax return item;00EC38A8 mov ecx,0Ch # 拷貝長度12個整數00EC38AD lea esi,[item] # 擷取item地址00EC38B0 mov edi,dword ptr [ebp+8] # 臨時對象1地址00EC38B3 rep movs dword ptr es:[edi],dword ptr [esi] # 將item內容拷貝到edi指向記憶體區,edi被改變00EC38B5 mov eax,dword ptr [ebp+8] # 將臨時對象1地址存入eax寄存器,傳出函數!}00EC38B8 push edx 00EC38B9 mov ecx,ebp 00EC38BB push eax 00EC38BC lea edx,[ (0EC38D0h)] 00EC38C2 call @ILT+135(@_RTC_CheckStackVars@8) (0EC108Ch) 00EC38C7 pop eax 00EC38C8 pop edx 00EC38C9 pop edi 00EC38CA pop esi 00EC38CB pop ebx 00EC38CC mov esp,ebp 00EC38CE pop ebp 00EC38CF ret # 函數呼叫堆疊現場TestItem item1 = returnStructApp();00EC39B1 lea eax,[ebp-188h] 00EC39B7 push eax # returnStructApp是個無參函數,但是也會壓eax入棧00EC39B8 call returnStructApp (0EC11F4h) 00EC39BD add esp,4 # 彈出eax00EC39C0 mov ecx,0Ch # 拷貝長度12整數00EC39C5 mov esi,eax # eax指向函數返回的臨時對象資料區,在returnStructApp函數內部被修改00EC39C7 lea edi,[ebp-1C0h] # 臨時對象2記憶體塊地址00EC39CD rep movs dword ptr es:[edi],dword ptr [esi] # 迴圈執行資料拷貝,從函數內部臨時對象1到臨時對象200EC39CF mov ecx,0Ch # 拷貝長度12整數00EC39D4 lea esi,[ebp-1C0h] # 臨時對象2記憶體塊地址00EC39DA lea edi,[item1] # 目標item1資料區00EC39DD rep movs dword ptr es:[edi],dword ptr [esi] # 迴圈執行資料拷貝,從臨時對象2到item1
上面代碼中,函數中返回一個結構體對象,然後函數傳回值賦給item1,函數返回過程中構造了兩個臨時對象,一共執行了3次資料拷貝。所以效率比較低。盡量避免直接返回結構對象。
3. 參考
1. http://blog.csdn.net/ENO_REZ/article/details/2158682
2. http://blog.csdn.net/vagrxie/article/details/2501238