最近在看PG代码时,看到许多VALGRIND相关的宏,例如 VALGRIND_MAKE_MEM_DEFINED、 VALGRIND_MAKE_MEM_NOACCESS,特别是移植PG的某模块到OG时,由于OG中没有定义这些宏,遇到这些宏,一开始我也不知道该怎么办了。
后来了解了下,valgrind是一种可以探测你的C/C++程序是否有内存泄漏,或踩内存的程序,当然它还有其它功能,但是应用最多的就是memcheck功能了,对于valgrind的使用有两种情况:
1、C/C++程序用 -g 编译,然后用valgrind执行:
valgrind --leak-check=yes --log-file=1_g myprog arg1 arg2
如果有内存泄漏之类的问题,在执行过程或执行结束后会有相关打印。
2、还有一种情况是在客户程序(myprog)的代码中加入memcheck相关宏(#include valgrind.h或valgrind目录下的头文件),memcheck的仿真cpu在执行客户程序期间,会执行这些宏所代表的动作,或者这些宏会向memcheck仿真cpu传递一些信息,指导它检测内存错误。这样会让memcheck检测到之前检测不到的内存问题。
为了能够对自定义的内存分配系统进行跟踪,就需要在自定义的内存分配系统代码里加入种种valgrind宏,可以参考:
Use Valgrind Memcheck with a custom memory manager | Red Hat Developer
例如:
struct message*
next_message (struct message *m)
{
int next_seq_nr = m->seq_nr + 1;/* clean up the old message... */
memset (m, 0, sizeof (struct message));
m->seq_nr = next_seq_nr;/* Except for the sequence number, everything else is undefined till
frob () is used on the message. */
VALGRIND_MAKE_MEM_UNDEFINED (&m->msg, sizeof (struct rawmsg));
return m;
}
VALGRIND_MAKE_MEM_UNDEFINED 告诉 memcheck仿真cpu,从&m->msg开始sizeof (struct rawmsg)大小的内存,标记为未定义(初始化)的内存,如果没有这个宏,memcheck仿真cpu会认为这块内存是已经被初始化的,因为前面调用了memset(memcheck仿真cpu会监视memset),如果后面的代码没有给msg赋值就直接读取,valgrind就会报错,注意,valgrind只会在日志或终端输出错误信息,并不会让客户程序退出(其它宏也是这样的),这个宏,一般在需要重用一块内存时使用。
类似测试程序还有:
#include <string>
#include <iostream>
#include <assert.h>
#include <cmath>
#include <cstring>
#include <valgrind/memcheck.h>
#include <unistd.h>
int main() {
while(1) {
void* p1 = malloc(229);
int mod = ((long)p1) % 16;
cout << p1 << " " << mod << endl;
VALGRIND_MAKE_MEM_NOACCESS(p1, sizeof(int));
*(int*)p1 = 33;
//VALGRIND_MAKE_MEM_DEFINED(p1, 16);
//VALGRIND_MAKE_MEM_UNDEFINED(p1, sizeof(int));
int i = *(int*)p1;
cout << i << endl;
sleep(1);
}return 0;
}
这个程序分配程序但是不释放,会导致valgrind报错,用VALGRIND_MAKE_MEM_NOACCESS标记p1开始的4字节int的内存时不可访问的(不可读写),后面有对这块内存的读写,都会报错。
还可尝试 VALGRIND_MAKE_MEM_UNDEFINED 设置一块内存是未定义(无论之前是否初始化),如何后面访问这块内存,valgrind就会报访问未定义内存的错误。
了解memcheck实现原理,有利于更好的使用valgrind,了解它的优缺点:
memcheck实现了一个仿真的cpu,C/C++二进制程序是被memcheck的仿真cpu解释执行的,仿真cpu可以在所有内存读写指令发出时 ,检测读写的合法性和地址的合法性。
虚拟cpu为进程地址空间的每个字节分配了8bit的bitmap,记录了这个字节的属性,例如是否被初始化过,是否被读写过,是否是有效值。
为每个cpu寄存器分配到了若干bit(具体几个我也不知道)的bitmap,记录了寄存器的属性。
还为每个字节分配了1bit的bitmap,记录这个字节的地址是否能被读写。
当然,不可能真的为进程地址空间的每个字节,分配记录其属性的bitmap,因为这些bitmap本身也占用地址空间的字节,memcheck只记录被客户程序触及到的进程地址空间字节的属性,未触及到的字节的属性,应该有办法压缩表示。
可见,以valgrind运行C/C++程序是会减慢速度,而且增加内存消耗的。
有了上面的认知,再来看PG代码中的VALGRIND_XXX宏,如果要把PG代码移植到OG,遇到这些宏,可以直接删掉。
参考:
valgrind基本功能介绍、基础使用方法说明_valgrind教程-CSDN博客
https://valgrind.org/docs/manual/mc-manual.html/mc-manual.html#mc-manual.machine
https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
Use Valgrind Memcheck with a custom memory manager | Red Hat Developer