因memcpy引起的string变量cordump问题

因memcpy引起的string变量cordump问题

问题现象

最近项目中某个服务升级支持ipv6,该服务在测试过程中出现了coredump现象,并且每次调试core文件崩溃堆栈都是在不同的位置上。出现这种堆栈异常,想通过gdb调试出崩溃原因还是很困难。

pastedGraphic.png

原因分析

2.1 信号signal 6 产生原因

从上述堆栈信息可以看出,程序是因为收到一个signal 6 导致进程挂掉的。那么什么情况下出现程序被这个信号杀死呢?

1 进程内部初始化已申请内存时报错,但是程序没有做处理。

      malloc的申请会被OS尽可能延后的分配,所以很有可能已经申请的内存早就OOM了,但是程序还可以运行一段时间,这个时候再读写内存就会出发这个错误。

2 多次free导致的SIGABRT

      程序分配一块内存之后,经过使用将这块内存释放,但并没有将指向这块内存的所有指针抹零或回收,并在其他部分再次将指向同一块内存单元的指针交给内存分配器去进行释放操作,这个内存单元可能已经存储了其它数据,再次free操作系统就会认为程序出现致命错误不能再继续运行,就会出发SIGABRT信号。

3 执行abort函数

      abort()函数会导致进程的异常终止除非SIGABRT信号被捕捉并且信号处理句柄没有返回。

4 执行到assert函数

     在代码里面有assert失败了,这个时候进程也会挂掉。

2.2 string结构与崩溃分析

 从上面堆栈以及崩溃时间点程序日志来看,应该是在释放某个string对象时导致程序出现异常,那我们分析下string的内存结构。

pastedGraphic_1.png

      如上图所示,string对象的内存结构里包含有一个指向数据的指针,根据以往经验这种异常很有可能就是因为对string的指针出现了非法读写导致string的内存发生越界,在析构string对象时内存出现错误导致,后面经过仔细分析代码发现有结构体变量采用了memcpy复制数据,并且这个结构体里面新增了一个string结构。

2.3 memcpy函数与浅拷贝分析

memcpy函数的作用是根据数据在内存中的地址,将数据拷贝到目的内存地址上。

通过上述定义memcpy是从src中拷贝n个字节到dest,void*指针表明memcpy是不区分拷贝数据的内容是什么,假设拷贝的数据里面有一个指针,拷贝完成之后再操作这个数据会发生什么。

下面这个程序就是使用memcpy拷贝了一个含有指针的结构体变量ptr到另外一个变量dptr,改变变量ptr的data值,同时输出变量ptr和dptr的data的值,最后再把变量ptr的指针变量data进行释放,然后输出变量dptr的data的值。

pastedGraphic_2.png

程序运行结果如下,第一次输出ptr和dptr的data的值都是100,改变ptr的data的值之后,结果ptr和dptr的data的值同时变成了200,调用free释放ptr的data指向的内存空间之后,dptr的data输出的结果为0。

pastedGraphic_3.png

根据上述定义可知,调用memcpy之后ptr和dptr的内存中的数据是一样,那么ptr和dptr的指针data指向的内存空间是相同的地方。所以改变ptr的data的值,dptr的值也同步发生改变,这时就发生了浅拷贝。

pastedGraphic_4.png

根据上面介绍string结构的内部其实是通过指针_Mdata_plus指向数据在内存中的地址,如果对string结构采用memcpy,同样也会发生浅拷贝现象。按照理论来讲,string是一个类,在对象析构的时候会释放空间,这个时候再访问这个空间就可能会导致程序发生coredump。

string变量与memcpy函数使用分析

TestString内部包含一个string类型的data,对data进行初始化然后调用memcpy把str赋值在sstr,然后输出sstr的data值。

pastedGraphic_5.png

运行程序发现程序在输出data值之后出现coredump,这是因为在调用printf函数时,str还没有进行释放,程序结束时str会对内部指针进行释放、sstr也会对内部指针进行释放。因为memcpy之后str和sstr的指针指向了同一块内存空间,从而导致对这块内存空间产生2次释放,因此程序产生了coredump。

pastedGraphic_6.png

而我们的项目也是因为对结构体的赋值采用了memcpy函数,原本这个结构体内只有基本类型,支持ipv6时这个结构增加了一个string变量仍然继续使用memcpy函数,根据上述结论服务肯定会发生coredump,由于使用这种赋值方式的代码不止一处程序的内存被写乱,引起coredump堆栈异常。

C++memcpymemset函数使用总结

  1. 在C++里面浅拷贝可导致自定义类型变量发生2次释放相同内存空间的现象、进而导致程序出现异常、崩溃堆栈错乱问题。
  2. 在C++里面对于自定义类型(结构体、类)包含有string、vector、map等类型变量时,最好都能实现复制运算符重载函数,赋值时避免调用memcpy等直接操作变量内存函数。
  3. 在日常开发中使用memcpy与memset函数时要理清业务场景、完善注释避免后续开发同事新增自定义类型进而引起浅拷贝、指针重复释放、内存泄漏。

2 thoughts on “因memcpy引起的string变量cordump问题

发表评论

邮箱地址不会被公开。 必填项已用*标注