首发于 小耸写BUG的地方

Valgrind笔记(二):MemCheck基本原理

Valgrind的MemCheck工具,可以检测多种内存错误,它是如何做到的?

V bits

MemCheck构造出一个“虚拟CPU”。与真实CPU不同的是,虚拟CPU中的每一个bit,都有一个关联的“是否有效”bit( "valid-value" bit),用于表示它关联的bit是否有效(更确切地,应该是“是否初始化”)。以下称这个bit为V bit。

例如,当CPU从内存中加载一个4-byte int时,也同时从V-bit位图中加载32bits的V bits。当CPU把这个int写回某个地址时,这32bits的V bits也会被存回V-bit位图中。

也就是说,系统中的所有bits,都有对应的V bits。不仅仅是内存,甚至是CPU寄存器,也有它们的V bits。为了实现这样的功能,MemCheck需要为V bits提供强大的压缩能力。

仅仅是访问非法的数据,并不会直接触发MemCheck的报错。仅当这一错误影响到程序的执行结果时,才会报错。例如:

int i, j;
int a[10], b[10];
for ( i = 0; i < 10; i++ ) {
    j = a[i];
    b[i] = j;

虽然访问a[i]时,未初始化,但是它并不会报错。因为这段程序仅仅是把未初始化的a[10]复制到b[10]。并没有把这些未初始的值用于某种判断或输出。如果程序改为:

for ( i = 0; i < 10; i++ ) {
    j += a[i];
if ( j == 77 ) 
    printf("hello there\n");

MemCheck就会报错了。未初始化的值a[i]之和,被用于计算j并用于判断if (j == 77)。

在CPU做各种运算时(加,位运算等),操作数的V bits被引用,用于计算出运算结果的V bits。但是,即使这里包含非法的V bits,MemCheck仍然不会报错。仅当以下几种情况发生时,MemCheck才会去检查V bits的有效性:

  • 运算结果被用于生成地址
  • 影响程序控制流(如条件判断)
  • 有系统调用发生

这样的机制似乎看起来过度设计了,但一旦联想到C语言中有一个常见的机制叫“结构体自动填充”,你就不会这么觉得了。以下这个例子:

struct S { int x; char c; };
struct S s1, s2;