C语言02_数据类型

裸函数

C源码(.c) →【预处理】→【编译】→【汇编】→【链接】→可执行程序

  1. 预处理 (cpp):展开头文件、宏替换、删掉注释,生成 .i 文件(还是 C 语言文本)
  2. 编译 (cc1)C → 汇编 (.s)
  3. 汇编 (as):汇编 (.s) → 机器码目标文件 (.o/.obj)
  4. 链接 (ld):多个.o + 库 → exe/elf 可执行文件

因此在我们的程序成为可执行文件之前,先转化为汇编语言,我们看到的函数里面的那些代码都死编译器自己生成的,push ebp等等,裸函数是编译器完全不生成函数 prologue (序言) /epilogue (尾声),你必须手写全部汇编。

需要注意的是 裸函数不能为空 而且不能出现C格式的代码

void __declspec(naked) Function()

{

_asm

}

{

汇编内容

}

列举三个常用的裸函数框架

无参数无返回值的框架

void __declspec(naked) Function()  				
{				
	__asm			
	{			
		push ebp		
		mov ebp,esp		
		sub esp,0x40		
		push ebx		
		push esi		
		push edi		
		lea edi,dword ptr ds:[ebp-0x40]		
		mov eax,0xCCCCCCCC		
		mov ecx,0x10		
		rep stosd		
				
		pop edi		
		pop esi		
		pop ebx		
		mov esp,ebp		
		pop ebp		
				
		ret		
	}			
}				

有参数有返回值的函数框架

int __declspec(naked) Function(int x,int y)  					
	{					
		__asm				
		{				
			push ebp			
			mov ebp,esp			
			sub esp,0x40			
			push ebx			
			push esi			
			push edi			
			lea edi,dword ptr ds:[ebp-0x40]			
			mov eax,0xCCCCCCCC			
			mov ecx,0x10			
			rep stosd			
						
			mov eax,dword ptr ds:[ebp+8]			
			add eax,dword ptr ds:[ebp+0xC]			
						
			pop edi			
			pop esi			
			pop ebx			
			mov esp,ebp			
			pop ebp			
						
			ret			
		}				
	}					
				

带局部变量的函数框架

int __declspec(naked) Function(int x,int y)  					
{					
	__asm				
	{				
		push ebp			
		mov ebp,esp			
		sub esp,0x40			
		push ebx			
		push esi			
		push edi			
		lea edi,dword ptr ds:[ebp-0x40]			
		mov eax,0xCCCCCCCC			
		mov ecx,0x10			
		rep stosd			
		mov dword ptr ds:[ebp-4],2			
		mov dword ptr ds:[ebp-8],3			
					
		mov eax,dword ptr ds:[ebp+8]			
		add eax,dword ptr ds:[ebp+0xC]			
					
		pop edi			
		pop esi			
		pop ebx			
		mov esp,ebp			
		pop ebp			
					
		ret			
	}				
}					

裸函数作业

练习:						
						
1、用__declspec(naked)裸函数实现下面的功能						
						
练习目的:						
						
(1) 熟悉堆栈结构						
						
(2) 参数、局部变量的位置						
						
(3) 返回值存储的位置						
						
int plus(int x,int y,int z)					010101010	
{						
	int a = 2;					
	int b = 3;					
	int c = 4;					
	return x+y+z+a+b+c;					
}						
下面是作业内容
#include<stdio.h>
int plus1(int x, int y, int z)
{
	int a = 2;
	int b = 3;
	int c = 4;
	return a + b + c + x + y + z;

}
int _declspec(naked) plus(int x, int y, int z)//转换的裸函数
{
	_asm
	{
		push ebp;
		mov ebp,esp
		sub esp,0x40
		push ebx
		push esi
		push edi
		mov eax,0xcccccccc
		mov ecx,0x10
		lea edi,dword ptr ds:[ebp-0x40]
		rep stosd
		mov dword ptr ds:[ebp-4],2
		mov dword ptr ds : [ebp - 8] , 3
		mov dword ptr ds : [ebp - 0Xc] , 4
		//	核心计算功能
		mov eax,dword ptr ds:[ebp-4]//第一个变量赋值给eax
		add eax,dword ptr ds:[ebp - 8]//第一个变量赋值给eax
		add eax,dword ptr ds:[ebp - 0XC]//第一个变量赋值给eax
		add eax,dword ptr ds:[ebp+8]//第一个参数赋值给eax
		add eax,dword ptr ds:[ebp+0xC]//第一个参数赋值给
		add eax,dword ptr ds:[ebp+0x10]//第一个参数赋值给
		pop edi
		pop esi
		pop ebx
		mov esp,ebp
		pop ebp
		ret
	}
}
extern "C" void test()
{

}
int main()
{
	int res = plus(1, 2, 3);
	printf("%d", res);
}

调用约定

常见的几种调用约定:

调用约定参数压栈顺序平衡堆栈
__cdecl从右至左入栈调用者清理栈
__stdcall从右至左入栈自身清理堆栈
__fastcall
ECX/EDX传送前两个
剩下:从右至左入栈
自身清理堆栈

简单来说是__cdecl是外平栈 __stdcall __fastcall是内平栈

举例:

__cdecl
1、int __cdecl Plus(int a, int b)				
{				
	return a+b;			
}				
				
push        2				
push        1				
call        @ILT+15(Plus) (00401014)				
add         esp,8
__stdcall
2、int __stdcall Plus(int a, int b)			
{			
	return a+b;		
}			
			
push        2			
push        1			
call        @ILT+10(Plus) (0040100f)			
			
函数内部:			
			
ret         8	
__fastcall
3、int __fastcall Plus(int a, int b)				
{				
	return a+b;			
}				
				
mov         edx,2				
mov         ecx,1				
call        @ILT+0(Plus) (00401005)				
				
函数内部:				
				
ret   
__fastcall多参数
4、int __fastcall Plus4(int a, int b,int c,int d)					
{					
	return a+b+c+d;				
}					
					
push        4					
push        3					
mov         edx,2					
mov         ecx,1					
call        @ILT+5(Plus) (0040100a)					
					
函数内部:					
					
ret         8

C语言数据类型

字符存储数据

以char类型举例

数据宽度 1个字节(8位)

存储格式分为有符号和无符号 有符号 -128 - 127 无符号255

需要注意的是有无符号是我们自己定义的 在内存中的存储的方式是完全一致的

作用域(变量属性,和数据类型无关)

变量可以被访问、使用的代码范围

局部变量、全局变量、静态局部变量

浮点类型

float i = 12.5f;
00401028 mov dword ptr [ebp-4],41480000h

float i = -8.25f;
00401028 mov dword ptr [ebp-4],0C1040000h

x86‑32 MSVC / MASM 汇编规则:

凡是以 A‑F 开头的十六进制常量,前面必须加一个前导数字 0

C1040000h 第一个字符是 C(字母),汇编器会分不清:

  • 它是数字 0C1040000h
  • 还是标识符 / 变量名 C1040000

单浮点在内存的存储

8.25

1.首先把整数部分转换成二进制(除2 取余运算)

8/2 = 4···0

4/2 = 2···0

2/2 = 1···0

1/2 = 0···1

2.小数部分转换为二进制(乘以2直到没有小数部分)

0.25 x 2 = 0.5 0

0.5 x 2 = 1.0 1

8.25的二进制表示10000.01->转换成科学计数法1.00001 x 23

3.然后就可以开始拼接二进制

符号位指数部分尾数
01000001000001000000000000000000

指数部分 = 科学技术法的指数部分-1 填入即可

01000001000001000000000000000000
41040000

验证:

float i = 8.25f;
00401028 mov dword ptr [ebp-4],41040000h 结果是正确的

调用约定作业

海哥用不同调用约定的函数嵌套构建了一个exe 作业内容需要根据汇编代码还原

如何找到程序入口(控制台程序)

1、程序的真正入口

(1) main 或WinMain 是“语法规定的用户入口”,而不是“应用程序入口”。应用程序入口通常是启动函数。

通过VC6堆栈调用查看

(2) mainCRTStartup 和 wmainCRTStartup 是控制台环境下多字节编码和Unicode 编码的启动函数.
而WinMainCRTStartup 和wWinMainCRTStartup 是windows 环境下多字节编码和Unicode 编码的启动函数.

设置完用户入口变成了test函数

main 函数的识别:

main 函数被调用前要先调用的函数如下:

GetVersion()
_heap_init()
GetCommandLineA()
_crtGetEnvironmentStringsA()
_setargv()
_setenvp()
_cinit()

这些函数调用结束后就会调用main 函数,根据main 函数调用的特征,将3 个参数压入栈内作为函数的参数。

启动函数

这里也可以看到GetVersion 是属于启动函数的 我们可以继续往下找一个三个参数的

三个参数

进去看一下发现就是我们要找的main函数

作业代码

#include<stdio.h>
int _stdcall Function2(int x,int y,int z)
{
	return x + y + z;
}
int _cdecl Function3(int x,int y)
{
	return x + y;
}
int _fastcall Function(int x, int y, int z, int c, int l)
{
	int a = x;
	int b = y;
	int q = Function2(a,b,z);//ebp-0xc
	int d = Function3(a, b);//ebp-0x10
   int res =  Function3(q, d);
   return res;

}
int main()
{
	int res = Function(1,3,4,6,7);
	printf("eax = 0x%x", res);
}

结果是0XC

上一篇 C语言01_后半节
下一篇 CFM挂机刷经验脚本[24小时不间断]
因特叁

因特叁管理员

动物世界

本月创作热力图

目录