首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

有关问题:调用函数时,相关参数在堆栈中是怎么存放的

2012-02-11 
问题:调用函数时,相关参数在堆栈中是如何存放的?举例如下:voidFun(){......charchData[40]{0}memset(chD

问题:调用函数时,相关参数在堆栈中是如何存放的?
举例如下:

void   Fun()
{
......
char   chData[40]={0};
memset(chData, 'a ',50);//这句越界了
}

int   main()
{
......
        Fun();

}

main中调用Fun()时,怎么样将Fun()函数的相关信息入栈的;我试过,如果memset()这句中越界超过10个字节的话,函数返回main时就会出错(如果越界不超过10个,一般就不会出错。改变数组chData的大小也是一样的结果),不知道是不是这个memset把系统保留的一些函数的返回信息给改写了,导致返回出错。


[解决办法]
有参数和平衡堆栈的东西,还有回调地址,触及到回调地址就会core错误了~
看看“缓存区溢出”相关的文章还有微软的《write secure code》里面都介绍了
这个和编译器也有关系,不同的编译器维护堆栈也是稍微不同~
[解决办法]
不要数组越界是正事。
[解决办法]
函数的参数放在程序堆栈中,这点是必定无疑了。
比如在main中在调用fun,没有参数入栈,执行到fun体时, 首先在栈上创建一个chData临时对象,调用memset。由于menset是cdecl方式调用,因而,参数从右往左入栈,一般x86机器的的栈低在高地址,倒着长,直到这一点,就可以反汇编以下,到底参数在哪里了
[解决办法]
void Fun()
{
char chData[40]={0};
memset(chData, 'a ',50);//这句越界了
}

int main()
{

Fun();

}

确实越界了(是个人估计都看出来了)
这个和编译器有关了,有10个字节覆盖了不该覆盖的区域了。。

这个要说到call a function 's details,

首先堆(动词)栈是从高往低的堆的,)(Linux 和Windows, MAC都是这么做的)
那么EIP的下一个指令指针在高地址 被push ed.
然后是EBP在高地址,
然后是你func上面的栈变量。其实前面还会一些其他的寄存器值。

而memset(chData, 'a ',50)是从低到高写。
所以你的chData被刷的同时, 可能其他变量也被搞了,然后就是栈上的EBP, EIP.


[解决办法]
假设有这样一个函数
int f(int x,int y,int z)
{
int a;
int b;
int c;

a=x;
b=x+y;
c=x+y+z;
return c;
}

int main()
{
int m;
m=f(1,2,3);

return 0;
}


**********************************************************************************
int m;
int m=f(1,2,3);

004010C6 push 3 ;/Arg3 = 00000003 //从有往左压入参数
004010C8 push 2 ;|Arg2 = 00000002
004010CA push 1 ;|Arg1 = 00000001
004010CC call 00401080 ;-------> 跳到下面00401080处开始执行函数
;00401080为函数f的地址
;函数返回值保存在eax中

004010D1 add esp, 0x0C ;恢复到函数调用前的堆栈,即push 3,2,1前
004010D4 mov dword ptr [ebp-4], eax ;ebp-4即为m,为什么?
;看了下面的分析就明白了,相当于m=返回值

**********************************************************************************


::::::::::::::::::::::::调用开始::::::::::::::::::::::::::::::::::::::::::::::::::::

//call 00401080调用后到了这里,烦人的编译器优化,害我写了N久

00401080 push ebp
00401081 mov ebp, esp //保存esp的值到ebp,这句执行后,堆栈情况如下
------------------------------------------------
------------------------------------------------
地址 值
0013FF60 /0013FF80 ; <-----------------esp=ebp=0013FF60,都指向栈顶 <-----栈顶
;对局部变量和参数的操纵都通过ebp来完成
0013FF64 |004010D1 ;004010D1为函数的返回地址

0013FF68 |00000001 ;三个参数,ebp-0x8为第一个参数 <------ebp+0x8
0013FF6C |00000002 ;ebp-0xc为第二个参数 <------ebp+0xc
0013FF70 |00000003 ;ebp-0x10为第三个参数 <------ebp+0x10
------------------------------------------------
------------------------------------------------



00401083 sub esp, 0xC ;给局部变量在堆栈分配空间 int a,b,c;需要12个字节
00401086 push ebx ;下面用到了ebx,edi两个寄存器,先在堆栈上保存起来
00401087 push edi ;堆栈情况如下
-----------------------------------------------
-----------------------------------------------
地址 值
0013FF4C 7C910738 ;保存ecx(7C910738)的值 <------esp现在指向这里了 <-----栈顶
0013FF50 7FFD9000 ;保存ebx(7FFD9000)的值
0013FF54 00405D83 ;变量c <------ebp-0xc
0013FF58 00420790 ;变量b <------ebp-0x8
0013FF5C 00000098 ;变量a <------ebp-0x4
0013FF60 /0013FF80 <------ebp指向这里
0013FF64 |004010D1 ;004010D1为返回地址,即函数调用call的下一行的地址
0013FF68 |00000001 <------ebp+0x8
0013FF6C |00000002 <------ebp+0xc
0013FF70 |00000003 <------ebp+0x10
-----------------------------------------------
-----------------------------------------------


00401088 lea edi, dword ptr [ebp-0x4] ;ebp-4为变量a的地址
0040108E mov ebx, dword ptr [ebp+0x8] ;ebp+8为参数x,ebx = x
00401091 mov dword ptr [edi], ebx ;相当于a=x

00401093 mov ebx, dword ptr [ebp+0xC] ;ebp+c为参数y的地址
00401096 add ebx, dword ptr [edi] ;ebx=ebx+y 即ebx=x+y

00401098 lea edi, dword ptr [ebp-0x8] ;edi指向变量b
0040109E mov dword ptr [edi], ebx ;b=x+y

004010A0 mov ebx, dword ptr [ebp+0x10] ;ebp+0x10为参数z,即ebx = z
004010A3 add ebx, dword ptr [edi] ;ebx加上b的值(此时edi仍然指向b)
;即ebx=x+b=x+y+z
004010A5 lea edi, dword ptr [ebp-0xC] ;edi指向变量c
004010AB mov dword ptr [edi], ebx ;c=ebx,即c=x+y+z

004010AD mov eax, ebx ;对于Win32程序来说,很多使用eax保存返回值

004010AF pop edi
004010B0 pop ebx ;恢复ebx,edi的值
004010B1 mov esp, ebp ;恢复堆栈
004010B3 pop ebp ;堆栈情况如下
-----------------------------------------------
-----------------------------------------------
地址 值
0013FF54 00000006 ;局部变量c
0013FF58 00000003 ;局部变量b
0013FF5C 00000001 ;局部变量在esp上面,不受到保护,几个push,pop操作就可能将这个
;改变,所以返回局部变量的地址是危险的
0013FF60 /0013FF80 ;利用这个值语句 pop ebp 将ebp寄存器恢复

******上面的值都不在有效的堆栈范围内,不受保护了************************************
0013FF64 |004010D1 ; <------esp现在指向这里了,和调用前的堆栈完全相同 <----栈顶
0013FF68 |00000001 ; <------ebp+0x8
0013FF6C |00000002 ; <------ebp+0xc
0013FF70 |00000003 ; <------ebp+0x10 三个参数是在函数返回后被释放
;call后面的这条语句 004010D1 add esp, 0x0C
-----------------------------------------------
-----------------------------------------------

004010B4 retn ;返回地址004010D1继续执行程序
::::::::::::::::::::::::调用结束::::::::::::::::::::::::::::::::::::::::::::::::::::


通过上面的分析,我们也就明白了为什么函数不能返回局部变量的地址了,因为局部变量的分配,
只不过是通过(esp-**)来完成。调用结束后,通过mov esp,ebp来销毁局部变量,那个地址在esp上面
也就不受到保护,可能随时会被修改掉

总结:
局部变量和参数都是通过堆栈来传递的,在函数中通过ebp寄存器来对他们进行操作,
其中(ebp-**)为局部变量,而(ebp+**)为函数参数,但是有一点要注意,ebp+4保存的是函数的返回
地址,从ebp+8开始才是第一个参数的地址。

对于C语言的函数调用方式,局部变量分配的空间,由函数内部维持堆栈平衡,而参数则在调用
处维持平衡,如
004010C6 push 3 ;/Arg3 = 00000003 //从右往左压入参数
004010C8 push 2 ;|Arg2 = 00000002
004010CA push 1 ;|Arg1 = 00000001


004010CC call 00401080 ;00401080为函数f的地址
004010D1 add esp, 0x0C ;三个参数是在call调用返回后销毁维持堆栈平衡
[解决办法]
好像cdecl当中参数是右向左压入栈的
[解决办法]
只有delphi的默认传参是左到右的。
而且不是我们C++程序员看到的 PASCAL

反正我所知道的,除此之外都是right-> left的。



[解决办法]
这个和编译器有关的
[解决办法]
我感觉楼主已经在发问前就已经判断出原因了,只是不确定才又问了一下

热点排行