函數和彙編

本文基於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值放入eax
00C939F6  push        eax  							# 壓棧eax
00C939F7  mov         ecx,dword ptr [param1] 		# 將param1值放入ecx
00C939FA  push        ecx  							# 壓棧ecx
00C939FB  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 += 4
00E41F81  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] 				# 棧頂指針地址存入edi
00E41F92  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的值存入eax
00E41FA1  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值存入eax
00E41FAD  add         eax,dword ptr [temp2] 		# 相加結果存入eax,與函數返回後00C93A03行操作對應,函數結果在eax中
}
00FE1FB0  pop         edi  							
00FE1FB1  pop         esi  
00FE1FB2  pop         ebx  
00FE1FB3  mov         esp,ebp 						# 還原esp
00FE1FB5  pop         ebp  							# 還原ebp,同時esp-=4
00FE1FB6  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置爲1
00FE394F  mov         dword ptr [ebp-30h],4 	# ebp-48 通過ebp指針訪問b
00FE3956  mov         dword ptr [ebp-2Ch],1 	# ebp-44	
00FE395D  mov         dword ptr [ebp-28h],2 	# ebp-40
00FE3964  mov         dword ptr [ebp-24h],3 	# ebp-36
00FE396B  xor         eax,eax 					# 異或操作將eax置零
00FE396D  mov         dword ptr [ebp-20h],eax 	# ebp-32	
00FE3970  mov         dword ptr [ebp-1Ch],eax 	# ebp-28
00FE3973  mov         dword ptr [ebp-18h],eax 	# ebp-24
00FE3976  mov         dword ptr [ebp-14h],eax 	# ebp-20
00FE3979  mov         dword ptr [ebp-10h],eax 	# ebp-16
00FE397C  mov         dword ptr [ebp-0Ch],eax 	# ebp-12
00FE397F  mov         dword ptr [ebp-8],eax 	# ebp-8
	passValueApp(item);							###### 傳結構體函數 ######
00FE3982  sub         esp,30h 					# 參數值拷貝,esp減去48(30h)正好sizeof TestItem大小
00FE3985  mov         ecx,0Ch 					# 指定要拷貝次數,0Ch即12個整數,對用48byte	
00FE398A  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+=48
	passPointerApp(&item);						###### 傳指針函數 ######	
00FE3999  lea         eax,[item] 				# 獲取item地址存入eax
00FE399C  push        eax  						# 參數值拷貝,壓棧eax 		esp-=4
00FE399D  call        passPointerApp (0FE11EFh) # 
00FE39A2  add         esp,4 					# 將參數拷貝彈出棧空間,esp+=4
	passReferenceApp(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 								# 彈出eax
00EC39C0  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到臨時對象2
00EC39CF  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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章