内存越界:

何谓内存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。

内存越界使用,这样的错误引起的问题存在极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响,正是这种不易重现的错误,才是最致命的,一旦出错破坏性极大。


什么原因会造成内存越界使用呢?有以下几种情况,可供参考:

 <textarea readonly="readonly" name="code" >
例1:

char buf[32] = {0};
        for(int i=0; i<n; i++)// n < 32 or n > 32
        {
            buf[i] = 'x';
        }
        ....        
例2:
        char buf[32] = {0};
        string str = "this is a test sting !!!!";
        sprintf(buf, "this is a test buf!string:%s", str.c_str()); //out of buffer space
        ....    
例3:
        string str = "this is a test string!!!!";
        char buf[16] = {0};
        strcpy(buf, str.c_str()); //out of buffer space


类似的还存在隐患的函数还有:strcat,vsprintf等
同样,memcpy, memset, memmove等一些内存操作函数在使用时也一定要注意。
        
当这样的代码一旦运行,错误就在所难免,会带来的后果也是不确定的,通常可能会造成如下后果:

1.破坏了堆中的内存分配信息数据,特别是动态分配的内存块的内存信息数据,因为操作系统在分配和释放内存块时需要访问该数据,一旦该数据被破坏,以下的几种情况都可能会出现。 
     

*** glibc detected *** free(): invalid pointer:
        *** glibc detected *** malloc(): memory corruption:
        *** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
        *** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***

     

2.破坏了程序自己的其他对象的内存空间,这种破坏会影响程序执行的不正确性,当然也会诱发coredump,如破坏了指针数据。
3.破坏了空闲内存块,很幸运,这样不会产生什么问题,但谁知道什么时候不幸会降临呢?
通常,代码错误被激发也是偶然的,也就是说之前你的程序一直正常,可能由于你为类增加了两个成员变量,或者改变了某一部分代码,coredump就频繁发生,而你增加的代码绝不会有任何问题,这时你就应该考虑是否是某些内存被破坏了。

排查的原则,首先是保证能重现错误,根据错误估计可能的环节,逐步裁减代码,缩小排查空间。
检查所有的内存操作函数,检查内存越界的可能。常用的内存操作函数:

sprintf snprintf 
vsprintf vsnprintf
strcpy strncpy strcat 
memcpy memmove memset bcopy


内存越界的常见原因:




 1 写越界: 向10个字节的数组写入了20个字节;内存操作越界,如char szText[10]; memset(szText,0,30);
 2 错误的函数调用:   sprintf等fmt中的预定义和实际输入的变量数不一致,如sprintf(szData,"Name:%d title:%s",1)
 3 错误的调用方式:  用stdcall 的函数指针 调用pascall的函数




如果有用到自己编写的动态库的情况,要确保动态库的编译与程序编译的环境一致。

一个同行对内存越界处理的过程:

最近做一个C++服务端程序,在使用多线程时,程序有时候会崩溃,从VC的错题提示看是内存访问错误导致程序崩溃,单步执行跟踪也无法定位错误所在。

  根据个人的经验分析,这种错误是内存越界导致其他对象或者堆(heap)被破坏而引起非法内存访问,结果出现不可debug跟踪的程序崩溃。

  这个问题困扰了我几周,我分析程序代码,搜索所有strcpy,memcpy,memset等内存操作相关的函数,确认所有使用这些函数的地方都没有内存越界的问题
  我又分析程序中一些字符数组char[]变量,发现也不存在数组空间不够大的问题。非常头疼,找不出原因,又替换了程序中一个觉得可疑的模块,问题还是存在。

  今天又再继续观察程序运行打印的LOG,发现似乎每次崩溃的时候,好像都是在处理某个客户端请求时发生。
  于是就单步debug该处理过程,这一次果然发现问题了,
  原来该处理过程有用到sprintf函数来把客户端上传的参数组合成一个sql语句,
  因为没有检测参数的合法性,导致参数非法的时候,可能造成内存越界的情况,
  但是不是每次内存越界的结果都会有一样的现象。

下面使用代码来说明这个问题。

int functiontest(char *pszParam, int nLen, int nID)
{
char szSQL[500];
sprintf(szSQL,
" Select * from tabble1 " 
" where cond1 = %s and id = %d ",
pszParam,    //当pszParam为未初始化的野指针,可能导致内存越界
nID
);
//以下代码省略
//......
}



没错,就是上述使用sprintf()函数时产生了内存越界的错误,但是该函数本身的执行并不一定马上报错或者引起程序崩溃,
因为pszParam为野指针, strlen(pszParam)则是未确定的长度,
所以在有些情况下,strlen(pszParam)的长度可能远大于szSQL数组的大小512,sprintf()就会有内存越界的操作,
结果就会导致未定义的异常,我的程序里就导致了后续其他函数内存访问错误,直接程序崩溃。

其实在该函数中我忽略了nLen参数的意义,该参数是指pszParam的长度,
但是nLen == 0 的情况我确没有判断并加以处理,所以导致内存越界程序崩溃。

内存越界的一种定位方法:

就是查看确定越界的那段内存,然后查看内存的实际使用情况,看看是否有异常!!

多是数组出现越界!!

或者是一些字符串的拷贝问题。


#include <stdlib h="">
#include <stdio h="">
#include <string h="">

void fn(char *str)
{
    memset(str, 0, 64);
    return;
}

int main(int argc, char **argv)
{
    char badstr[32] = "abc";
    int fd = 1;
    printf("badstr = %s\n", badstr);
    printf("fd = %d\n", fd);
    fd = 2;
    printf("fd = %d\n", fd);
    fn(badstr);
    printf("fd = %d\n", fd);
    printf("badstr = %s\n", badstr);
    return 0;
}

  </string></stdio></stdlib>


上述代码明显内存越界,一个watch搞定。


结果:


[root@localhost root]# ./he 
 
 badstr = abc 
 
 fd = 1 
 
 fd = 2 
 
 fd = 0 
 
 badstr =  
 
 Segmentation fault



如下操作:

设置断点:break 33

查看逆序的调用链:where

将调用栈上移一个函数调用:up

将调用栈下移一个函数调用:down

在使用watch时步骤如下:

1. 使用break在要观察的变量所在处设置断电;

2. 使用run执行,直到断点;

3. 使用watch设置观察点;

4. 使用continue观察设置的观察点是否有变化。




[root@localhost root]# ./gdb test 
 
GNU gdb Red Hat  
 Linux 
  (6.6-8.fc7rh) 
 
Copyright (C) 2006 Free Software Foundation, Inc. 
 
GDB is free software, covered by the GNU General Public License, 
 
welcome to change it and/or distribute copies of it under certain 
 
Type "show copying" to see the conditions. 
 
There is absolutely no warranty for GDB.  Type "show warranty" fo 
 
This GDB was configured as "i386-redhat-linux-gnu"... 
 
Using host libthread_db library "/lib/i686/nosegneg/libthread_db. 
 
(gdb) b main 
 
Breakpoint 1 at 0x80484cb: file test.cpp, line 13. 
 
(gdb) r 
 
Starting program: /home/qiyk/test 
 
Breakpoint 1, main () at test.cpp:13 
 
13          char badstr[32] = "abc"; 
 
(gdb) n 
 
14          int fd = 1; 
 
(gdb) watch fd 
 
Hardware watchpoint 2: fd 
 
(gdb) c 
 
Continuing. 
 
Hardware watchpoint 2: fd[第一次人为修改,此处中断] 
 
Old value = 6317008 
 
New value = 1 
 
main () at test.cpp:15 
 
15          printf("badstr = %s\n", badstr); 
 
(gdb) c 
 
Continuing. 
 
badstr = abc 
 
fd = 1 
 
Hardware watchpoint 2: fd[第二次人为修改,此处中断] 
 
Old value = 1 
 
New value = 2 
 
main () at test.cpp:18 
 
18          printf("fd = %d\n", fd); 
 
(gdb) c 
 
Continuing. 
 
fd = 2 
 
Hardware watchpoint 2: fd[第三次意外修改,此处中断] 
 
Old value = 2 
 
New value = 0 
 
0x004ea367 in memset () from /lib/i686/nosegneg/libc.so.6 
 
(gdb) bt[查看现场堆栈] 
 
#0  0x004ea367 in memset () from /lib/i686/nosegneg/libc.so.6 
 
#1  0x080484b8 in fn (str=0xbf92bd20 "") at test.cpp:7 
 
#2  0x0804854d in main () at test.cpp:19 
 
(gdb) up 
 
#1  0x080484b8 in fn (str=0xbf92bd20 "") at test.cpp:7 
 
7           memset(str, 0, 64);[问题点出现:str越界,导致fd值变为0] 
 
(gdb) q 
 
The program is running.  Exit anyway? (y or n) y