__cdecl 与 _stdcall 的栈平衡

各类关于VC的书中都多少写到:

1、_stdcall调用约定:函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈。

2、__cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。注意:对于可变参数的成员函数,始终使用__cdecl的转换方式。

__cdecl

说实在话,很多初学者对于这样的描述依然很不解,这两种调用方式究竟有什么区别呢?

我们先来看看以下代码:

void fun1(char *a, int n)
{
	for (int i = 0; i < n; i++)
	{
		std::cout << a;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	char *t = "abcdefg\n";
	int n = 3;
	__asm
	{
		push n
		push t
		call fun1
	}
	system("pause");
	return 0;
}

当然,fun1的调用方式为默认__cdecl,运行代码我们发现了在调用main返回时出现错误提示:

即,在RET前ESP与ESI并不相等,未成功回到函数调用前的堆栈状态。【主模块在调用Dll的导出函数时会保存返回地址在堆栈中(ESP - xxx)。函数调用返回时,会弹栈取得返回地址(ESP + xxx),从而返回到主模块。 】

现在明白了,我们以__cdecl方式调用函数,调用者负责对函数的清栈,保证栈平衡,所以我们需要在call fun1之后进行清栈操作,此处根据压入栈2个参数为例,我们只需要在call fun1后加入 add esp,8 实现栈平衡。

即代码改为:

__asm
	{
		push n
		push t
		call fun1
		add esp,8
	}

_stdcall

void _stdcall fun2(char *a, int n)
{
	for (int i = 0; i < n; i++)
	{
		std::cout << a;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	char *t = "abcdefg\n";
	int n = 3;
	__asm
	{
		push n
		push t
		call fun2
	}
	system("pause");
	return 0;
}

即:这里fun2调用方式为_stdcall ,从以上代码看来,调用函数并未对fun2进行任何清栈处理,fun2函数内部进行清栈处理。

那么问题来了

我们并不知道某一种函数的调用方式怎么办?很简单,我们在调用函数之前保存esp,调用完成之后恢复esp即可:

void __cdecl fun1(char *a, int n)
{
	for (int i = 0; i < n; i++)
	{
		std::cout << a;
	}
}

void __stdcall fun2(char *a, int n)
{
	for (int i = 0; i < n; i++)
	{
		std::cout << a;
	}
}

int _tmain(int argc, _TCHAR* argv[])
{
	char *t = "abcdefg\n";
	int n = 3;
	unsigned long dwEsp;
	__asm
	{
		mov dwEsp, esp
		push n
		push t
		call fun1
		call fun2
		mov esp, dwEsp
	}
	system("pause");
	return 0;
}

成功!

时间: 2024-08-01 19:13:05

__cdecl 与 _stdcall 的栈平衡的相关文章

__cdecl、_stdcall、_fastcall、_thiscall

1. 介绍 __cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈.被调用函数不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误. _stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针.这些堆栈中的参数由被调用的函数在返回后清除,

函数调用方法之__cdecl与_stdcall

在debug VS c工程文件时,碰到cannot convert from 'int (__cdecl *)(char *)' to 'xxx'这个问题,发现问题在于typedef函数指针类型时,将函数调用方法__cdecl写成了_stdcall. 后来百度了两者的区别,如下: __cdecl 是C Declaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈.被调用函数不会要求调用者传递多少参数,调用

函数栈平衡跟踪

例子: struct stu{ char name[20]; int age; char* addr; }; int inCall (char c,int n,char* hi, stu stu1){ int locInt = 0x1234; char* locStr = "here i am"; printf("%c\t0x%x\t%s\t0x%x\t%s\n",c,n,hi,locInt,locStr); printf("%s\t%d\t%s\n&qu

系统栈的工作原理(转)

1.开篇 本篇文章着重写的是系统中栈的工作原理,以及函数调用过程中栈帧的产生与释放的过程,有可能名字过大,如果不合适我可以换一个名字,希望大家能够指正,小丁虚心求教!如果有哪里写的不清楚的或者错误的地方请及时更正,小丁再次谢过了.文章里面有错别字,也可能会有好友说寄存器的32.16位的区别其实我感觉这里主要讲的还是些原理性的东西,后续会将文章图片错别字进行调整.(图片里面的posh改为push) 2.内存的不同用途 根据不同的操作系统,一个进程可能被分配到不同的内存区域去执行.但是不管什么样的操

_cdecl _stdcall

__cdecl程序的压栈方式为C风格__stdcall为PASCAL风格 举个例子:(1)   C函数  Fun1(a,b,c)   函数调用时,参数压栈顺序为 c , b , a(2)   PASCAL函数 Fun(a,b,c)  函数调用时,参数压栈顺序为 a, b , c ========================== STDCALL 告诉编译器参数的传递约定.参数的传递约定是指参数传达时的顺序(从左到右或从右到左)和由谁恢复堆栈指针(调用者或被调用者).在Win16下有两种约定:C

数据库缓冲区溢出漏洞原理(栈)

背景 在数据库系统中已经发现了许多安全漏洞,其中比较严重且危害性比较大的有:缓冲区溢出和SQL注入2种. SQL注入主要依赖于结构化查询语言,每种数据库略有出入:SQL注入漏洞的利用,最主要的威胁是提权:后台维护人员或黑客攻击,可以借此获得DBA权限.需要说明的是,这里所说的SQL注入并不是应用系统的SQL注入,而是数据库自身的注入漏洞,这种漏洞比应用系统的注入漏洞危险性更高:对于SQL注入漏洞的存在,主要是由于数据库中提供的系统或用户函数存在的参数检查不严和语句执行的缺陷.SQL注入漏洞不是本

NtCallbackReturn是否导致了用户态栈的不平衡

0:000> u ntdll!KiFastSystemCall ntdll!KiFastSystemCall: 7c92eb8b 8bd4 mov edx,esp 7c92eb8d 0f34 sysenter ntdll!KiFastSystemCallRet: 7c92eb8f 90 nop 7c92eb90 90 nop 7c92eb91 90 nop 7c92eb92 90 nop 7c92eb93 90 nop ntdll!KiFastSystemCallRet: 7c92eb94 c3

printf的那些坑

只要是玩过C或者C++的童鞋们,对printf肯定是再熟悉不过了.下面有几个方法,你知道每个方法输出是什么吗? void Test1() { printf("hello %d"); } void Test2() { printf("hello %s"); } void Test3() { int a = 0; printf("hello %s"); } 可以肯定的是,上面三个方法都是错误的写法,但我们在跑这三个方法的时候,程序一定会crash吗?

《C++反编译与逆向分析技术揭秘》之学习笔记03--函数的调用方式

※函数的调用方式 EBP:扩展基址指针寄存器(extended base pointer) 其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部. ESP:(Extended stack pointer)是指针寄存器的一种,用于指向栈的栈顶. _cdecl:C/C++默认的调用方式,调用方平衡栈,不定参数的函数可以试用. 调用方:1.参数压栈.esp-=42.调用函数.3.实现栈平衡.esp+=4 此处的printf也是同样道理0x004010CB.0x004010CC两处压入参数,共8个字节