valgrind memcheck 检测机制学习总结
1、Valid-value (V) bits
作用:建立和维护值的有效性。
Memcheck实现了一个与真实CPU相同的合成CPU。 真实CPU处理,存储和处理的每一位(字面)数据在合成CPU中都有一个相关的“有效值”位,表示附带的位是否具有合法值。 该位被称为V(valid-value)位。
因此,系统中的每个字节都有一个8 V位,随后随之而来。 例如,当CPU从存储器加载字大小的项目(4个字节)时,它还从位图加载相应的32位,该位图存储进程的整个地址空间的V位。 如果CPU稍后应将该值的全部或部分写入不同地址的存储器,则相关的V位将存储回V位位图。
简而言之,系统中的每个位(概念上)都有一个相关的V位,即使在CPU内部,也会跟随它。 所有CPU的寄存器(整数,浮点,向量和条件寄存器)都有自己的V位向量。 为此,Memcheck使用大量压缩来紧凑地表示V位。
复制值不会导致Memcheck检查或报告错误。 但是,如果以可能会影响程序外部可见行为的方式使用某个值,则会立即检查相关的V位。 如果其中任何一个指示值未定义(甚至部分),则报告错误。
举例说明 :
int i, j;
int a[10], b[10];
for ( i = 0; i < 10; i++ ) {
j = a[i];
b[i] = j;
}
Memcheck没有对此发表任何抱怨,因为它只是将未初始化的值从[]复制到b []中,并且不会以可能影响程序行为的方式使用它们。 但是,如果循环更改为:
for ( i = 0; i < 10; i++ ) {
j += a[i];
}
if ( j == 77 )
printf("hello there\n");
然后Memcheck会抱怨if,条件取决于未初始化的值。 请注意,它不会在j + = a [i];处抱怨,因为此时未定义不是“可观察的”。 只有当必须决定是否要执行printf时 - 这是一个可观察到的程序动作 - Memcheck抱怨说。
大多数低级操作(例如add)会导致Memcheck使用操作数的V位来计算结果的V位。 即使结果部分或完全未定义,也不会抱怨。
定义检查仅在三个位置进行:当使用值生成内存地址,需要进行控制流决策时,以及检测到系统调用时,Memcheck会根据需要检查参数的定义。
如果检查应检测到undefinedness,则会发出错误消息。 随后将得到的值视为明确定义的。 否则会产生长链错误消息。 换句话说,一旦Memcheck报告未定义的值错误,它会尝试避免报告从该相同的未定义值派生的更多错误。
这听起来太复杂了。 为什么不检查内存中的所有读取,并抱怨是否将未定义的值加载到CPU寄存器中? 嗯,这不能很好地工作,因为完全合法的C程序经常在内存中复制未初始化的值,我们不希望对此有无尽的抱怨。 这是典型的例子。 考虑这样的结构:
struct S { int x; char c; };
struct S s1, s2;
s1.x = 42;
s1.c = 'z';
s2 = s1;
要问的问题是:struct S有多大,以字节为单位? 根据结构体对齐原则可知Struct S为8个字节。
因此s1占用8个字节,但只有5个将被初始化。 对于赋值s2 = s1,GCC生成代码以将所有8个字节批量复制到s2而不考虑它们的含义。 如果Memcheck只是检查了内存不足时的值,那么每次发生这样的结构分配时都会大喊大叫。 因此,上述更复杂的行为是必要的。 这允许GCC以任何方式将s1复制到s2中,并且只有在以后使用未初始化的值时才会发出警告。
2、 Valid-address (A) bits
作用:判断程序是否具有访问任何特定内存位置的权限。
如上所述,存储器或CPU中的每个位都具有相关的有效值(V)位。此外,内存中但不在CPU中的所有字节都具有关联的有效地址(A)位。这表明程序是否可以合法地读取或写入该位置,只是是否可以访问该位置。
每次程序读取或写入存储器时,Memcheck都会检查与地址相关的A位。如果其中任何一个指示无效地址,则会发出错误。读取和写入本身不会更改A位,只能查阅它们。
那么如何设置/清除A位?
程序启动时,所有全局数据区域都标记为可访问。
当程序执行malloc / new时,准确分配的区域的A位,而不是更多的字节,被标记为可访问。在释放该区域时,A位被改变以指示不可访问性。
当堆栈指针寄存器(SP)向上或向下移动时,设置A位。规则是从SP到堆栈底部的区域被标记为可访问,并且在SP下方不可访问。 (几乎所有Unix系统(包括GNU / Linux)上的堆栈都会向下增长,而不是上升。)像这样的跟踪SP具有有用的副作用,即函数使用的堆栈部分局部变量等在功能输入时自动标记为可访问,在退出时不可访问。
进行系统调用时,A位会相应更改。例如,mmap神奇地使文件出现在进程的地址空间中,因此如果mmap成功,则必须更新A位。
3、总结
Memcheck的检查机制可归纳如下:
存储器中的每个字节有8个相关的V(有效值)位,表示该字节是否具有定义的值,以及单个A(有效地址)位,表示该程序当前是否具有读取权限/写那个地址。如上所述,大量使用压缩意味着开销通常约为25%。
读取或写入存储器时,会查阅相关的A位。如果它们指示无效的地址,则Memcheck会发出无效读取或无效写入错误。
当存储器读入CPU寄存器时,相关的V位从存储器中取出并存储在模拟CPU中。他们没有被访问。
当寄存器写入存储器时,该寄存器的V位也会写回存储器。
当CPU寄存器中的值用于生成存储器地址或确定条件分支的结果时,将检查这些值的V位,如果未定义任何值,则发出错误。
当CPU寄存器中的值用于任何其他目的时,Memcheck计算结果的V位,但不检查它们。
一旦检查了CPU中的值的V位,就将它们设置为指示有效性。这避免了长链错误。
从内存加载值时,Memcheck会检查该位的A位,并在需要时发出非法地址警告。在这种情况下,尽管位置无效,但加载的V位被强制指示有效。
这种明显奇怪的选择减少了呈现给用户的混乱信息量。它避免了从一个既不可寻址又包含无效值的地方读取内存的令人不快的现象,结果,你不仅会得到一个无效地址(读/写)错误,而且还会得到一个潜在的大集合未初始化的值错误,每次使用该值时都会出错。
对于来自部分有效且部分无效的地址的多字节加载,存在模糊的边界情况。有关详细信息,请参阅选项--partial-loads-ok的详细信息。
Memcheck拦截对malloc,calloc,realloc,valloc,memalign,free,new,new [],delete和delete []的调用。你得到的行为是:
malloc / new / new []:返回的内存标记为可寻址但没有有效值。这意味着您必须先写入它才能阅读它。
calloc:返回的内存标记为可寻址和有效,因为calloc将区域清除为零。
realloc:如果新的大小比旧的大,新的部分是可寻址的但是无效,就像malloc一样。如果新尺寸较小,则掉落部分标记为不可寻址。您只能传递重新分配以前由malloc / calloc / realloc发给您的指针。
free / delete / delete []:您可能只会将相应分配函数先前发给您的指针传递给这些函数。否则,Memcheck抱怨道。如果指针确实有效,则Memcheck将其指向的整个区域标记为不可寻址,并将该块放置在freed-blocks-queue中。目的是尽可能延迟重新分配这个区块。在此之前,所有访问它的尝试都会引发无效地址错误,正如您所希望的那样。
4、参考资料
[1].http://valgrind.org/docs/manual/mc-manual.html#mc-manual.machine