函数和汇编

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dizuo/article/details/9280699

本文基于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



猜你喜欢

转载自blog.csdn.net/dizuo/article/details/9280699