回复内容 | 【Kenmark】: 有参数和平衡堆栈的东西,还有回调地址,触及到回调地址就会core错误了~ 看看“缓存区溢出”相关的文章还有微软的《write secure code》里面都介绍了 这个和编译器也有关系,不同的编译器维护堆栈也是稍微不同~
【fishly_0】: 用vc6调试时,出错了就会跳到nt.dll里,但是有时候地址是0x000000,有时候又不是0x0000,这个又是怎么回事呢?我总觉得,函数调用时,相关的堆栈信息应该不会放在数组chData后面的空间中。
【taodm】: 不要数组越界是正事。
【fishly_0】: 呵呵,我只是觉得奇怪,函数运行的时候到底把那些参数放哪里了,怎么会跟其中定义的一个数组冲突呢。
【taodm】: 不同的操作系统和不同的编译器下,有不同的结果,讨论其实没有可推广性。 在PPC体系的CPU下,参数和返回地址一般不在栈里,都是放在寄存器里的。
【babala512】: 函数的参数放在程序堆栈中,这点是必定无疑了。 比如在main中在调用fun,没有参数入栈,执行到fun体时, 首先在栈上创建一个chData临时对象,调用memset。由于menset是cdecl方式调用,因而,参数从右往左入栈,一般x86机器的的栈低在高地址,倒着长,直到这一点,就可以反汇编以下,到底参数在哪里了
【fishly_0】: 那程序出错的原因是什么呢?是哪部分的数据被擦除或者改写了,导致程序出错呢?BTW:我想学一下怎么看反汇编,大家能不能帮我推荐一些书或者资料?我以前有学过x86的汇编。多谢了:)
【healer_kx】: 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.
【jxlczjp77】: 假设有这样一个函数 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调用返回后销毁维持堆栈平衡
【Nowish】: 楼上说的貌似正确,那怎么解决呢?
【jxlczjp77】: 对于你这个问题,越界过几个字节并没有出现异常,很明显数组越界后,将堆栈中保存的ebp的值冲掉了,但你以后的程序可能没有用到ebp,所以没有出现异常。
但是,如果你越界超过的位置大于4个字节的话,不但将ebp全部重写,而且还会将函数的返回地址重写,这时函数不能正确返回,所以出现异常。
至于你说的越界超过10个字节才出错,那是由于编译器在分配40个字节的 char chData[40数组时,可能为了内存对齐,实际多分配了一点,所以写到10个字节才会出错,但理论上来说应该是4个字节后就到了返回地址的位置了
举个例子: push ebp mov ebp,esp sub esp,40 . . ;<----esp指向栈顶,从这里到ebp都为局部变量char a[40]的空间 . . . . ;可能由于对齐,这上面的局部变量其实不只40个字节
0013FF60 /0013FF80 ;<---ebp(0013FF60)指向这里,而里面的值0013FF80为保存的原ebp值 0013FF64 |004010D1 ;<------函数的返回地址 0013FF68 |00000001 ;<------ebp+0x8 0013FF6C |00000002 ;<------ebp+0xc 0013FF70 |00000003 ;<------ebp+0x10 三个参数是在函数返回后被释放
如果没有其他局部变量的话,数组的首地址即为esp所指向的栈顶位置,向下走40个字节都是数组的空间,如果越界的话,就到了保存的ebp,再往下就是函数的返回地址
【ChrisK】: 好像cdecl当中参数是右向左压入栈的
【healer_kx】: 只有delphi的默认传参是左到右的。 而且不是我们C++程序员看到的 PASCAL
反正我所知道的,除此之外都是right->left的。
【healer_kx】: 即便是fastcall,除了两个借助寄存器的其他参数也是RL顺序的。
|
|