内存改写及检测

  • Post author:
  • Post category:其他




本文所讨论的“内存”主要指(静态)数据区、堆区和栈区空间









数据区内存在程序编译时分配,该内存的生存期为程序的整个运行期间,如全局变量和static关键字所声明的静态变量




。函数执行时在栈上开辟局部自动变量的储存空间,执行结束时自动释放栈区内存。堆区内存亦称动态内存,由程序在运行时调用malloc/calloc/realloc等库函数申请,并由使用者显式地调用free库函数释放。




堆内存比栈内存分配容量更大,生存期由使用者决定,故非常灵活。然而,堆内存使用时很容易出现内存泄露、内存越界和重复释放等严重问题








数据区的内存访问越界可以分为




读越界









写越界




,数据区内存越界主要指读写某一数据区内存(如全局或静态变量、数组或结构体等)时,超出该内存区域的合法范围。




读越界表示读取不属于自己的数据




,如读取的字节数多于分配给目标变量的字节数。




若所读的内存地址无效,则程序立即崩溃;若所读的内存地址有效,则可读到随机的数据




,导致不可预料的后果。




写越界亦称“缓冲区溢出”,所写入的数据对目标地址而言也是随机的




,因此同样导致不可预料的后果。



内存越界访问会严重影响程序的稳定性,其危险在于后果和症状的随机性。这种随机性使得故障现象和本源看似无关,给排障带来极大的困难。你永远也不知道是不是有其他线程操作时候偷偷改动了你的数据。如果是一般的业务数据,唔,一个bug。但是是如果该内存块指向一个对象,然后就呵呵了——你持有了一个无效的内存地址,一般来说会crash,无止境的debug在等待你








写越界的主要原因有两种:1) memset/memcpy/memmove等内存覆写调用;2) 数组下标超出范围



内存覆盖例子1:


#include <string.h>



#include <stdio.h>


#define NAME_SIZE 8



#define NAME_LEN 9


char name1[NAME_SIZE] = “ABCDEFGH”;



char name2[NAME_LEN] = “123456789”;


int main() {




strncpy(name1, name2, NAME_LEN);



printf(“name2: %s\n”, name2);


return 0;



}



内存覆盖例子2:




内存覆盖是个很简单又很隐蔽的错误。如果在程序中发现某个数组a的一些结果被莫名奇妙改写了,但你可以很肯定你没有在代码中修改它们。很有可能内存被不小心覆盖了。例如:





int b[100];





int a[100];





int c=102;





b[c]=5;




与数组a相邻的数组b不够大,开了b[100],程序中不小心给越界的b[102]赋值,那么就有可能造成数组




a




中间一些数值被覆盖(当然也可能有其他问题,如程序崩溃)。



malloc()




函数也可能导致类似的错误。如果函数的实参为




sizeof(int)




,用于生成一个整型数据需要的大小。如果你写成:




int* p = (int *) malloc (1);




代码也能通过编译,但事实上只分配了




1




个字节大小的内存空间,当你往里头存入一个整数,就会有




3




个字节无家可归,而直接









住进邻居家









!造成的结果是后面的内存中原有数据内容被改写。



见的所谓数组越界方法实现起来比较繁琐。用工具(VALGRIND等)可以发现,但是对于生产系统(采用了全局数组+多线程之类的高级技巧……),一般来说是难以查找到的








对于这个问题,




gdb提供了一种可能的方法:观察点(watch命令)。用法如下:watch name2[0]




。这样当该变量被改写的时候进程将会停下来。当然你也可以watch某个地址:watch *(data type*)addr。如果你怀疑是特定线程改写了该变量的时候,可以使用watch expr thread threadnum,在某个线程改写的时候让进程停止








如果在调试状态下运行仍然没有发现问题或者是嵌入式环境根本无法调试。Linux还提供了一个杀手锏级的API:mprotect。




mprotect函数的原型如下:






int mprotect(const void *addr, size_t len, int prot);










其中addr是待保护的内存首地址,必须按页对齐;len是待保护内存的大小,必须是页的整数倍,prot代表模式,可能的取值有PROT_READ(表示可读)、PROT_WRITE(可写)等。