Chapter 4. Memory Management Debugging

In this chapter

Dynamic Memory Functions动态内存函数 page 82

MEMWATCHMEMWATCH工具              page 84

YAMDYAMD工具                      page 86

Electric FenceElectirc Fence工具  page 93

ValgrindValgrind工具              page 97

Summary总结                       page 109

Web Resources for Memory Checkers page 109

 

Dynamic memory allocation seems straightforward enough: Memory is allocated on demandusing malloc() or one of its variants and memory is freed when it's no longer needed. Memory management would be that easy if programmers never made mistakes. Alas, we do make mistakes (from time to time), so memory management problems do occur.

动态内存分配看上去非常直接:内存通过malloc()或者变体函数被按需分配,当不再使用的时候,内存被释放掉。如果程序员从不犯错误的话,那么内存管理将是非常容易的。天呀,我们确实犯错(一直都是),所以内存管理问题出现了。

For example, a memory leak occurs when memory is allocated but never freed. Leaks can obviously be caused by a malloc() without a corresponding free(), but leaks can also be inadvertently caused if a pointer to dynamically allocated memory is deleted, lost, or overwritten. Memory corruption can occur when allocated (and in use) memory is overwritten accidentally or when using statically allocated memory and stack variables (especially if a pointer to stack-allocated memory is returned to a calling method). Buffer overruns caused by writing past the end of a block of allocated memory frequently corrupt memory.

举例来说,当分配的内存永远不被释放的时候,内存泄漏出现了。内存泄漏可以由一个没有相应的free()malloc()导致,但泄漏也能够由一个动态分配内存的指针漫不经心的被删除、丢失或者改写而导致。当被分配的内存被意外的改写或者当使用静态分配的内存和栈变量(尤其在指针指向栈上分配的内存从被调用函数返回的时候),内存冲突就会出现。缓冲区越界(通过写过分配内存的最后一块)也常常导致内存冲突。

Regardless of the root cause, memory management errors can have unexpected, even devastating effects on application and system behavior. With dwindling available memory, processes and entire systems can grind to a halt, while corrupted memory often leads to spurious crashes. System security is also susceptible to buffer overruns. Worse, it might take days before evidence of a real problem appears. Today, it's common for Linux systems to have a gigabyte of main memory. If a program leaks a small amount of memory, it takes some time before the application and system show symptoms of a problem. Memory management errors can be quite insidious and very difficult to find and fix.

不论由何种原因导致,内存管理错误能给应用程序和系统带来不可预料的,甚至是破坏性极大的行为。由于可用内存的减少,进程和整个系统可能会慢慢停下来,而冲突的内存会导致假崩溃。系统安全也对缓冲区溢出很敏感。更糟糕的是,在真正的问题出现前,可能要耗费好几天的时间。对于今天的linux系统来说,有 1G 的主存是很普通的,如果程序泄漏了一点点内存,在应用程序和系统显示出迹象之前会有一段时间。内存管理错误非常阴险并且很难发现和修复。

This chapter covers four memory-management checkers: MEMWATCH, YAMD, Electric Fence, and Valgrind. All these tools can help detect common memory management errors. We'll review the basics, write some "buggy" code, and then use each of these tools to find the mistakes.

本章主要覆盖四个内存管理检测工具:MEMWATCH,YAMD,Electirc FenceValgrind.这些工具都可以检测常见内存错误。我们将要回顾基础的,写一些带臭虫的代码,然后使用每一个工具来发现错误。

Dynamic Memory Functions

Of all the library calls (libc) in Linux, only four manage memory: malloc(), calloc(), realloc(), and free(). All these functions have prototypes in the stdlib.h include file.

linux下的所有库调用(libc)中,仅有四个管理内存的函数:malloc(),calloc(),reallocfree()。这些函数原型都在stdlib.h头文件中

malloc() allocates a memory block that is uninitialized. Its prototype is

void* malloc(size_t size)

The single argument is the number of bytes of memory to allocate.

malloc()分配未初始化的内存块,它的原型是void* malloc(size_t size),参数是要求分配的内存字节数量。

If the allocation is successful, malloc() returns a pointer to the memory. If memory allocation fails for some reason (for example, if the system is out of memory), malloc() returns a NULL pointer.

如果分配成功,malloc()返回指向内存的指针。如果由于某些原因内存分配失败(比如系统没有内存了),malloc()返回NULL指针

calloc() allocates an array in memory and initializes all the memory to 0 (with malloc(), the allocated memory is uninitialized). Here's the prototype:

void* calloc(size_t nmemb, size_t size)

The first argument is the number of elements in the array, and the second argument is the size (in bytes) of each element. Like malloc(), calloc() returns a pointer if the memory allocation was successful, and NULL otherwise.

Calloc在内存中分配一个数组并且初始化所有元素为0(对于malloc(),分配的内存是未初始化的)。Calloc()原型如下:

void* calloc(size_t nmemb, size_t size)

第一个参数是数组元素数目,第二个参数是每个元素的大小(字节单位)。如同malloc(),calloc()在内存分配成功的话返回指针,否则返回NULL

realloc() is defined as

void* realloc (void *ptr, size_t size)

realloc() changes the size of the object referenced by the pointer to a new size specified by the second argument. realloc() returns a pointer to the moved block of memory.

Realloc()定义如下:void* realloc (void *ptr, size_t size)

Realloc()改变指针引用的对象的大小为第二个参数指定的大小,返回移动后内存块的指针

free() deallocates a memory block. It takes a pointer as an argument, as shown in its prototype, and releases that memory:

void free (void *ptr),

free()释放一个内存块。有一个指针参数,如原型中显示的那样,释放指针指向的内存。

Void free(void *ptr),

While the API for memory management is unusually small, the number and kind of memory errors that can occur are substantial. They include:

·         Reading and using uninitialized memory

·         Reading and writing memory after it has been freed

·         Reading and writing from memory after or in front of (underrun) the allocated size

·         Reading and writing inappropriate areas on the stack

·         Memory leaks

尽管内存管理API异常少,内存错误的数量和种类却是大量的,包括:

    读和使用未初始化的内存

       读写已经释放的内存

       读写内存越界

       读写栈中不合适的区域

       内存泄漏

The use of pointers can cause problems in all types of memory. In addition, indexes into statically allocated arrays can cause corruption. Stack issues can also cause problems with some compilers. Returning a pointer to a stack variable in a function is a big no-no.

指针使用能够在各种类型的内存中导致错误。此外,索引静态分配的数组也会导致冲突。栈也会在一些编译器上导致问题。函数内返回栈变量的指针绝对是禁止的。

MEMWATCH

MEMWATCH, written by Johan Lindh, is an open-source memory error-detection tool for C. It can be downloaded from www.linkdata.se/sourcecode.html. By simply adding a header file to your code and defining MEMWATCH in your gcc command, you can track memory leaks and corruptions in a program. MEMWATCH supports ANSI C; provides a log of the results; and detects double frees, erroneous frees, unfreed memory, overflow and underflow, and so on. To follow the example shown next, download a version of MEMWATCH. The example uses version 2.71. Figure 4.1 shows the unpacking of the tar file. Listing 4.1 is the sample source code that is used with MEMWATCH.

MEMWATCHjohan Lindh编写,是c语言的开源内存错误检测工具。可以从www.linkdata.se/sourcecode.html下载。简单的加一个头文件并且把MEMWATCH加入到gcc命令中,你就可以跟踪程序中的内存泄漏和冲突。MEMWATCH支持ANSI C,提供了一个结果日志;检测双重释放,错误释放,未释放的内存,上溢和下溢等等。为接下来的例子考虑,请下载一个版本的MEMWATCH。例子使用2.71版本,图4.1显示了拆包tar文件。列表4.1是配合MEMWATCH所使用的源代码

Figure 4.1. Unpacking the MEMWATCH tar file and building memory1.c.

 

Listing 4.1. Memory Sample (memory1.c)
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include "memwatch.h"
4
5 int main(void)
6 {
7   char *ptr1;
8   char *ptr2;
9
10  ptr1 = malloc(512);
11  ptr2 = malloc(512);
12
13  ptr2 = ptr1;
14  free(ptr2);
15  free(ptr1);
16 }

 

The only change to this sample code is to add the memwatch.h include on line 3 so that MEMWATCH can be enabled. Also, two compile-time flags-DMEMWATCH and -DMW_STDIOneed to be added to the compile statement for each source file in the program.

例子代码仅有的改变就是在第3行加入了memwatch.h头文件以使MEMWATCH可用。同时,两个编译时标志 –DMEMWATCH-DMW_STDIO也需要被加入到程序每一个源文件的编译语句中

The code shown in Listing 4.1 allocates two 512-byte blocks of memory (lines 10 and 11), and then the pointer to the first block is set to the second block (line 13). As a result, the address of the second block is lost, and a memory leak occurs.

显示在列表4.1中的代码分配了两个512字节块的内存(第10行和第11行),然后指向第一个块的指针被设置为指向第二个块(第13行)。结果,第二个块的地址丢失了,内存泄漏出现了。

Now compile the memwatch.c file, which is part of the MEMWATCH package with the sample source code (memory1.c) shown in Listing 4.1. The following is a sample makefile for building memory1.c. memory1 is the executable produced by this makefile:

现在编译MEMWATCH包的一部分memwatch.c文件以及例子源程序(memory1.c)。接下来的是构建memory1.c的例子makefile文件。Memory1是由makefile文件生成的可执行文件。

gcc -DMEMWATCH -DMW_STDIO memory1.c memwatch.c -o memory1

 

Figure 4.1 shows that running program memory1 captures two memory-management anomalies. The MEMWATCH package comes with a FAQ and a test.c sample program that shows another sample program where MEMWATCH catches memory management errors.

4.1显示了运行memory1而捕获到的两个内存管理问题。MEMWATCH附带了一个faq和一个test.c例子程序,展示了MEMWATCH可以捕获得内存管理错误。

MEMWATCH creates a log called memwatch.log. Figure 4.2 displays this log file, which is created by running the memory1 program.

MEMWATCH创建了一个叫做memwatch.log的日志。图4.2显示了通过运行memory1而创建的日志文件。

Figure 4.2. Displaying the log file.

 

MEMWATCH tells you which line has the problem. For a free of an already freed pointer, it identifies that condition. The same goes for unfreed memory. The section at the end of the log displays statistics, including how much memory was leaked, how much was used, and the total amount allocated.

MEMWATCH指出那一行有问题。也可以识别对于一个已经释放指针的释放,对于未释放的内存也同样。日志文件的结尾部分显示了统计,包括多少内存被泄漏,多少被使用,以及整个分配的数量。

In Figure 4.2 you can see that the memory management errors occur on line 15, which shows that there is a double free of memory. The next error is a memory leak of 512 bytes, and that memory is allocated in line 11.

4.2中你可以看到内存管理错误出现在15行,在该处出现了内存的双重释放。下一个错误是一个512字节的内存泄漏,该内存在第11行分配。

YAMD

Written by Nate Eldredge, the YAMD package finds dynamic memory allocation-related problems in C and C++. The latest version of YAMD is 0.32. You can download yamd-0.32.tar.gz from www.cs.hmc.edu/~nate/yamd/. Execute a make command to build the program, and then execute a make install command to install the program and set up the tool.

Nate Eldredge编写的YAMD包可以发现cc++中动态内存分配相关的错误。最新版本的YAMD0.32。你可以从www.cs.hmc.edu/~nate/yamd/下载yamd-0.32.tar.gz。执行make以构建该程序,然后执行make install来安装和设置该工具。

Once YAMD has been downloaded, use it on yamd-memory1.c. Listing 4.2 is the same program as Listing 4.1, but without the memwatch.h include.

一旦YAMD已经下载,可以用来试验yamd-memory1.c。列表4.2程序如同列表4.1,但是没有包含memwatch.h

Listing 4.2. Memory Sample (yamd-memory1.c)

1 #include <stdlib.h>

2 #include <stdio.h>

3

4 int main(void)

5 {

6   char *ptr1;

7   char *ptr2;

8

9   ptr1 = malloc(512);

10  ptr2 = malloc(512);

11

12  ptr2 = ptr1;

13  free(ptr2);

14  free(ptr1);

15 }

Figure 4.3 shows the steps to unpack the yamd-0.32.tar.gz file and build YAMD.

4.3展示了解压缩yamd-0.32.tar.gz和构建YAMD的步骤

Figure 4.3. Unpacking the YAMD tar file and building YAMD.

Note注意:

Figures 4.3 and 4.4 have warnings. These warnings won't cause a problem with the building of YAMD.

4.34.4有一些警告。但是这些警告对构建YAMD不会有问题

Figure 4.4 continues to show YAMD building. The make install command is used to install YAMD.

4.4继续显示YAMD的构建,make install命令用于安装YAMD

Figure 4.4. Building and installing YAMD.

Figure 4.5 shows the gcc command used to build the program yamd-memory1. The program yamd-memory1 is started using ./run-yamd.

Figure 4.5. Building and running yamd-memory1.

 

Figure 4.6 shows the output from YAMD on program yamd-memory1. The first memory management error of multiple freeing is displayed.

4.6显示了YAMDyamd-memory1上的输出,第一个内存管理问题多重释放被显示出来。

Figure 4.6. YAMD output.

Figure 4.7 shows the continuation of the output from YAMD on program yamd-memory1. The second memory management error of a memory leak is displayed.

4.7显示了YAMD程序的剩余的输出,第二个内存管理错误内存泄漏被显示出来

Figure 4.7. YAMD output (continued).

 

Figure 4.8 shows the continuation of the output from YAMD on program yamd-memory1. A summary of the memory allocations is displayed.

4.8显示了YAMD的接下来的输出,内存分配的总结信息被显示出来

Figure 4.8. Summary of the YAMD output.

YAMD shows that the memory has already been freed and that a memory leak exists. Let's try YAMD on the sample program shown in Listing 4.3.

YAMD显示内存已经被释放,内存泄漏出现了。我们接下来在列表4.3的例子程序上试验一下YAMD

Listing 4.3. Memory Code (yamd-memory2.c)
1  #include <stdlib.h>
2  #include <stdio.h>
3
4 int main(void)
5 {
6    char *ptr1;
7    char *ptr2;
8    char *chptr;
9    int i = 1;
10   ptr1 = malloc(512);
11   ptr2 = malloc(512);
12   chptr = (char *)malloc(512);
13   for (i; i <= 512; i++) {
14    chptr[i] = 'S';
15   }
16   free(ptr2);
17   free(ptr1);
18   free(chptr);
19 }

 

Figure 4.9 shows the output.4.9显示了输出

Figure 4.9. Building yamd-memory2 and YAMD output.

 

The command run-yamd starts YAMD for the program yamd-memory2. Figure 4.10 shows the output from using YAMD on the sample program yamd-memory2. YAMD shows that there is an out-of-bounds condition in the for loop on line 14.

Run-yamd启动了YAMD来运行yamd-memory2。图4.10显示了在例子程序yamd-memory2上使用YAMD的输出。YAMD显示在第14行的for循环中有一个越界情况出现。

Figure 4.10. YAMD output showing a memory management overrun.

 

MEMWATCH and YAMD are both useful debugging tools that require different approaches. With MEMWATCH, the include file memwatch.h and two compile-time flags are needed. YAMD requires only the -g option for the link statement.

MEMWATCHYAMD是两种要求不同方法的有用的调试工具。对于MEMWATCH,包含头文件memwatch.h以及两个编译选项就可以了。YAMD仅仅需要-g链接选项就可以了。

Electric Fence

Most Linux distributions include the Electric Fence package; it can also be downloaded from http://perens.com/FreeSoftware/. Electric Fence is a malloc() debugging library written by Bruce Perens. It allocates protected memory just after the memory the program allocates. If a fencepost error occurs (running off the end of an array), the program immediately exits with a protection error. By combining Electric Fence with gdb, you can track down exactly what line tried to access the protected memory. Electric Fence can also detect memory leaks.

绝大部分的linux发行版本都包含了Electric Fence包。也可以从http://perens.com/FreeSoftware/下载。Electric Fence是由Bruce Perens编写的malloc()调试库。它在程序分配的内存之后分配保护的内存。如果一个越界错误出现,程序立即以保护错误形式退出。通过组合Electric Fencegdb,你可以跟踪到是哪一行存取了保护内存。Electric Fence也可以检测内存泄漏。

To use Electric Fence, you must add the -lefence and -g options to the program's makefile. One side effect of Electric Fence is that the program runs more slowly and uses more memory. The main side effect is that if something is wrong with memory accesses (such as out-of-bounds array accesses), the program has a segmentation fault where the memory problem is. Therefore, gdb or ddd needs to be used to identify the memory problems.

为了使用Electric Fence,你必须在程序的makefile文件中加入-lefence-g选项。Electric Fence的一个副作用就是程序运行的更慢并且使用更多内存。如果有内存存取错误,它的主要的作用就是可以导致在内存出错的地方出现段错误。因此,gdb或者ddd需要配合来识别内存问题。

The main advantage of using Electric Fence is that it exposes "hidden" bugs that can silently wreak havoc in programs and that can later lead to inexplicable segmentation faults (these problems can be very hard to find).

使用Electric Fence的主要优点就是能够暴露出这些隐藏的bug,这些bug能够在程序中沉默的造成大灾难,能在后来导致无法解释的段错误(这种问题很难发现)

Listing 4.4 is a makefile that is set up to build two executables. The first executable, efence_test, is built with Electric Fence, and the second is built without Electric Fence.

列表4.4是构建两个执行文件的makefile文件。第一个文件efence_test使用了Electric Fence构建,第二个没有。

One reason to create two executables is that running Electric Fence results in overhead. It would be great to find and fix memory problems with a program and then, when the program is placed into production, to not use the version of the program with Electric Fence built in. In the case of the makefile shown in Listing 4.4, the executable without Electric Fence is called test.

创建两个可执行文件的原因是运行Electric Fence导致过头。发现和修复一个程序的内存问题是很棒的,然后,当程序产品化后,使用没有Electric Fence的版本。在列表4.4中,没有使用Electric Fence的版本的程序是test.

Listing 4.4. Makefile for malloc_test.c
1 CC =gcc
2
3 all: efence_test test
4
5 efence_test:
6     $(CC) -g -o efence_test malloc_test.c -lefence
7
8 test:
9     $(CC) -g -o test malloc_test.c
10
11 clean:
12    rm -rf efence_test
13    rm -rf test

 

The malloc_test.c code, shown in Listing 4.5, contains a memory error. Can you tell on which line the error occurs?

显示在列表4.5中的malloc_test.c的代码包含了内存错误。你能说出那一行出错了吗?

Listing 4.5. malloc_test.c
1  #include<stdlib.h>
2
3  int main (void)
4  {
5   const int SIZE = 20;
6   char *xx;
7   int i = 0;
8   xx  = (char *) malloc (SIZE);
9   for (i ; i <= SIZE; i++) {
10     xx[i] = 2;
11  }
12 }

 

Electric Fence can!

Electric Fence可以!

 

Electric Fence is a link-in replacement for malloc() that highlights bugs like the one shown here to be automatically caught at runtime. The problem with bad pointers is that they probably point to data near where they should point. If they point off into the blue, a core dump happens with or without Electric Fence. However, if they point nearer to your own data, the machine probably will keep running in an undefined state until the program accesses the incorrect memory.

Electric Fence替代了malloc(),比如上面的错误,可以在运行时被自动捕获。坏指针的问题就是他们可能指向了应该指向的附近。如果他们指到远处,不管有无Electric Fence,一个内核转储就会发生.然而,如果他们指向你的附近的数据,机器可能在未定义的状态下运行,直到程序存取了错误的内存。

 

In Figure 4.11, the make command is used to build malloc_test.c. Next, the test program, which is malloc_test.c without Electric Fence built in, is run, and no errors are displayed. ddd is used to start the efence_test program. Figure 4.12 shows an overrun error caught by Electric Fence. Line 10 is where the error occurs when the loop counter i equals 20.

4.11中,make命令用于构建malloc_test.c。接着,没有使用Electric Fencemalloc_test.c的可执行程序test运行,没有错误显示。Ddd被用来启动efence_test程序。图4.12显示了一个Electric Fence捕获的越界错误。错误出现在第10行循环计数i等于20的时候。

Figure 4.11. Building malloc_test and starting ddd.

 

Figure 4.12. ddd with efence_test.

 

Listing 4.6 contains the fix to the for loop to remove the overrun error found by Electric Fence on line 10. The loop has been changed from i <= SIZE to i < SIZE.

列表4.5显示了对Electric Fence在第10行发现的越界错误的for循环的修复。循环已经从i<=SIZE被改成i<SIZE

Listing 4.6. malloc_test-fixed.c
1   #include<stdlib.h>
2
3   int main (void)
4   {
5    const int SIZE = 20;
6    char *xx;
7    int i = 0;
8    xx  = (char *) malloc (SIZE);
9    for (i ; i < SIZE; i++) {
10      xx[i] = 2;
11   }
12 }

 

Chasing Memory Overruns追逐内存越界

It is not good to be in a situation where an allocation overrun happens after thousands of memory management calls. I've spent many long hours tracking down odd memory corruption problems. One problem application worked on our development workstation but would fail after 2,000 calls to malloc() on the new product workstation. The real problem was an overrun around call 1,000. The new system had the problem because the reserved malloc() area was laid out differently, so the offending memory was located at a different place and destroyed something different when it did the overrun.

在几千次内存管理调用后一个内存分配越界出现并非一个好的境况。我曾经花费了很多小时跟踪奇怪的内存冲突问题。一个问题是应用可以在我们的开发平台上工作,但是在新的产品平台上,2000次调用malloc()后就会失败。真正的问题是在1000次调用左右会越界。新系统的问题在于保留的malloc()区域的布局不同,所以讨厌的内存被分配在了不同的地方,当它越界的时候,摧毁了其他的东西。

I solved this problem using many different techniquesone using a debugger, and another adding tracing to the source code. I started to look at memory debugging tools around this time, looking to solve these types of problems faster and more efficiently. One of the first things I do when starting a new project is run the memory checkers in this chapter to see if they can pinpoint possible memory management problems. Memory leaks are a common problem in applications, but you can use the tools described in this chapter to find and solve them.

通过使用不同的技术,一个调试器,在源代码中添加跟踪,最终解决了这个问题。这次后,我开始看内存条是工具,注意解决这些类型问题更快和更有效的工具。开始一个新的工程后的第一件事就是使用本章提到内存检查工具来看是否他们能给出可能的内存管理问题。内存泄漏是应用中常见的问题,但你可以使用本章提到的工具发现和解决他们

 

 

Valgrind

Valgrind was written by Julian Seward; it's available under the GNU Public License. Valgrind is closely tied to the operating system architecture and currently is supported only on Linux x86 machines. There is a port of Valgrind to the PowerPC architecture, and a patch for this is available under the Related Projects link on the Valgrind web site. Valgrind works on machines with kernels from the 2.4.x and 2.6.x series and glibc 2.2.x and 2.3.x. When a program is run under Valgrind's control, all writes and reads of memory are looked at, and calls to malloc()/new()/free()/delete() are intercepted. As a result, Valgrind can detect problems such as:

Valgrindjulian Seward编写,在GNU公共许可下可用。Valgrind跟操作系统架构关联密切,当前只是在linux x86机器上可用。也有一个到ppc的移植,Valgrind网站的相关工程目录下有一个移植补丁。Valgrind在内核2.4.x2.6.x系列和glilbc 2.2.x2.3.x下工作。当一个程序在valgrind控制下运行的时候,所有的读写内存都被注意,对malloc()/new()/free()/delete()的调用也被拦截。结果是Valgrind能够检测如下问题:

·         Use of uninitialized memory

·         Reading and writing memory after it has been freed

·         Reading and writing from memory past the allocated size

·         Reading and writing inappropriate areas on the stack

·         Memory leaks

·         Passing of uninitialized and/or unaddressable memory

·         Mismatched use of malloc/new/new [] versus free/delete/delete []

使用未初始化的内存

读写已经释放的内存

读写超过分配尺寸的内存

读写栈上不正确的区域

内存泄漏

传递未初始化和/或者不可访问的地址的内存

Malloc/new/new[]free/delete/delete[]的使用不匹配

When a program is run under Valgrind, all memory reads and writes are inspected, and all calls to malloc()/new() and free()/delete() are intercepted.

当一个程序在valgrind的控制下运行的时候,所有的内存读写都被检查,所有对malloc()/new()free()/delete()的调用都被拦截。

Installing Valgrind

You can download the source for Valgrind from http://valgrind.org/. Download the latest stable release (or the latest development release, depending on your sense of adventure) and build the software.

你可以从http://valgrind.org/下载Valgrind的源代码。下载最新的稳定版本(或者最新的开发版本,依赖于你的冒险感)并且构建该软件

Figure 4.13 shows two commands. bunzip2 unzips the valgrind- 2.0.0 .tar file. tar expands the Valgrind files.

4.13显示了两个命令:bunzip2 解压valgrind- 2.0.0 .tar文件和 tar展开Valgrind文件

Figure 4.13. Unpacking the Valgrind tar file.

 

Figure 4.14 shows three commands. ./configure sets up the build environment for the Valgrind package. make and make install build and install the Valgrind libraries and executables.

4.14显示了三个命令: ./configure设置Valgrind的构建环境,make make install构建和安装Valgrind的库和可执行文件

Figure 4.14. Building and installing Valgrind.

 

One great feature of Valgrind is that it doesn't require building (or rebuilding) the application in any special way. Simply place Valgrind in front of the program that needs to be inspected. For example, the command valgrind ls -all, shown in Figure 4.15, inspects and monitors the ls command. (Running this command on SuSE 9.0 showed no errors.)

Valgrind的一个最大的特点就是它不要求用特别的方式构建(或者重新构建)应用程序。简单的把Valgrind放在需要检查的程序前面。例如,显示在图4.15中的命令 valgrind ls –all 检查和监控ls命令(在SuSE9.0中运行这个命令显示没有错误)

Figure 4.15. valgrind ls -all.

 

When it finds a problem, the Valgrind output has the following format:

当发现问题的时候,Valgrind输出如下格式:

==20691== 8192 bytes in 1 blocks are definitely lost in loss record 1 of 1
==20691==    at 0x40048434: malloc (vg_clientfuncs.c:100)
==20691==    by 0x
   
   
    
    806910C
   
   : fscklog_init (fsckwsp.c:2491)
==20691==    by 0x806E7D0: initial_processing (xchkdsk.c:2101)
==20691==    by 0x
   
   
    
    806C
   
   70D: main (xchkdsk.c:289)

 

The ==xxxxx== string prefixes each line of Valgrind-specific output.

每行前面的==xxxxx==字符串前缀代表Valgrind特定的输出

(Application-specific output does not have this prefix.) In the sample output shown here, 20691 is the process ID. The message indicates that there is a memory leak of 8,192 bytes and provides diagnostics and a kind of trace to direct you to the error. The second and subsequent lines indicate that the memory was initially allocated on line 2491 in the routine fscklog_init() (in the file fsckwsp.c). fscklog_init() was called from initial_processing() at line 2101, and main() called initial_processing(). By the way, if fscklog_init() is called more than once in the initial_processing() routine, the line number clearly identifies which call caused the problem.

(应用特定的输出没有这个前缀)在例子输出中,20691是进程ID.该消息指示有8,192字节的内存泄漏,并提供了诊断和一定程度的跟踪以引导你到错误发生处。第二行和接下来的行指示内存初始在文件fsckwsp.cfscklog_init2491行。Fscklog_initinitial_processing2101行调用,主函数main调用了init_processing。顺便说一下,如果在initial_processingfscklog_init被调用多次,行数可以明显的指出那一行导致了该问题。

Losing Your Memory

Valgrind can be used to find some common memory errors. The first sample program, valgrind-1.c, shown in Listing 4.7, has more than one memory leak. The code shown in Listing 4.7 allocates two 512-byte blocks of memory and then sets the pointer to the second block to the first block. As a result, the address of the second block is lost, causing a memory leak. Also, 512 10-byte blocks of memory are never freed. This memory is allocated by the call to the get_mem routine.

Valgrind也可以用于发现一些常见的内存错误。在列表4.7中的第一个例子程序valgrind-1.c中有超过一个内存泄漏。代码分配了两个512字节块的内存,然后将第二个内存块的指针指向第一个内存块。结果,第二个内存块泄漏了。另外,51210字节的内存块从未被释放,这些内存通过调用get_mem函数分配的来。

Listing 4.7. valgrind-1.c, a Program with a Memory Leak
1  #include <stdlib.h>
2  #include <stdio.h>
3  void get_mem()
4  {
5    char *ptr;
6    ptr = (char *) malloc (10);   /* memory not freed */
7  }
8  int main(void)
9  {
10   char *ptr1, *ptr2;
11   int i;
12   ptr1 = (char *) malloc (512);
13   ptr2 = (char *) malloc (512);
14   ptr2 = ptr1;
15   free(ptr2);
16   free(ptr1);
17   for ( i = 0; i < 512; i++) {
18       get_mem();
19     }
20 }

 

Compile and analyze valgrind-1.c using the commands shown at the top of Figure 4.16.

4.16先是了编译和分析valgrind-1.c的命令

Figure 4.16. Building valgrind-1.c and memory leak output.

 

Valgrind produces the output shown in Figures 4.16 and 4.17, correctly identifying the 512-byte and 10-byte memory leaks. The -v option provides verbose feedback, and the leak-check=yes option searches for memory leaks when the client program exits.

Valgrind的输出显示在图4.164.17中,512字节和10字节的内存泄漏已经被正确识别出来。-v选项提供了更细节的反馈。Leak-check=yes选项在用户程序退出后搜索内存泄漏。

Figure 4.17. More memory leak output from valgrind-1.

 

valgrind-2.c, shown in Listing 4.8, demonstrates another common memory error: reading beyond the end of an array of bytes. Again, build the sample code and run Valgrind to analyze it.

显示在列表4.8中的valgrind-2.c演示了另外一个常见的内存错误:读数组越界。在一次构建例子代码并且运行Valgrind分析它。

Listing 4.8. valgrind-2.c, a Program That Tries to Access Memory Beyond the End of an Array
1 #include <stdlib.h>
2 #include <stdio.h>
3
4 int main(void)
5 {
6  char *chptr;
7  char *chptr1;
8  int i  = 1;
9  chptr  = (char *) malloc(512);
10 chptr1 = (char *) malloc (512);
11      for (i; i <= 513; i++)  {
12       chptr[i] = '?';
13       chptr1[i] = chptr[i];
14    }
15
16  free(chptr1);
17  free(chptr);
18 }

 

As you can see from the output shown in Figure 4.18, references to element 513 in the two arrays cause a write error, a read error, and another write error. The message Address 0x411B6224 is 0 bytes after a block of size 512 alloc'd indicates that there is no storage beyond the end of the array of 512 bytes.

在图4.18中你可以看到Valgrind的输出,指出在两个数组内的元素513导致一个写错误,一个读错误和另外一个写错误。在分配的512字节的块后面的0字节的消息地址0x411B6224表明没有存储超过512字节的尾边界。

Figure 4.18. Building valgrind-2.c and Valgrind output.

 

Finally, to see how Valgrind finds invalid use of uninitialized memory, let's look at the results of analyzing the Journaled File System's (JFS) fsck utility. As before, you run fsck under the auspices of Valgrind.

最终,看看Valgrind如何发现未初始化内存的使用,让我们看看JFS文件系统fsck实用工具的分析结果,如前面一样,你可以看到fsckValgrind的照看下。

The command valgrind -vleak-check=yes fsck.jfs /dev/hdb2 is used to check for problems on the fsck.jfs utility, as shown in Figure 4.19.

命令 valgrind –vleak-check=yes fsck.jfc /dev/hdb2 用于检查实用工具 fsck.jfs的问题,结果显示在图4.19中。

Figure 4.19. A snippet of the output for the Journaled File System utility fsck.jfs.

 

The validate_super() routine can be found in the jfsutils package in jfsutils-1.x.x/fsck/fsckmeta.c. Listing 4.9 shows a portion of the code.

可以在jfsutils包的jfsutils-1.x.x/fsck/fsckmeta.c中找到Validate_super()例程。列表4.9给出了部分代码:

Listing 4.9. A Code Snippet from fsckmeta.c
int validate_super(int which_super)
{
  int64_t bytes_on_device;

  
  
   
    
  
  
    /* get physical device size */
                 vfs_rc = ujfs_get_dev_size(Dev_IOPort,
bytes_on_device);
 .
 .
 .
dev_blks_on_device = bytes_on_device / Dev_blksize; /* Line
    2331 */
if (sb_ptr->s_pbsize != Dev_blksize) {

 

The output from Valgrind indicates that an uninitialized variable is used on line 2331. That's the line that says dev_blks_on_device = bytes_ on_device / Dev_blksize. As you can see, bytes_on_device is not set before it is used. Using Valgrind, this memory management problem was identified and fixed before an end user ever came across it.

Valgrind的输出指示了在2331行使用了未初始化的变量。该行为dev_blks_on_device = bytes_ on_device / Dev_blksize,如你所见,bytes_ on_device在使用前没有赋值。使用Valgrind,这个内存管理问题在终端用户遇到之前就被识别出来并被修复了

Cache Profiling

Valgrind can also perform cache simulations and annotate your source line by line with the number of cache misses. In particular, it records the following:

Valgrind可以用来执行cache命中模拟和通过cache命中函数来注解你的源代码行。特别是它记录如下事件:

·         L1 instruction cache reads and misses

·         L1 data cache reads and read misses and writes and write misses

·         L2 unified cache reads and read misses and writes and write misses

L1 指令cache读和未命中

L1 数据读和读未命中和写和写未命中

L2 统一cache读和读未命中以及写和写未命中

L1 is a small amount of static RAM (SRAM) memory that's used as a cache. L1 temporarily stores instructions and data, ensuring that the processor has a steady supply of data to process while memory catches up on delivering new data. L1 is integrated or packaged within the same module as the processor. Level 2 caching is performed in L2.

L1是被用作cache的少量静态RAM内存。L1临时存储指令和数据,保证在内存传送新数据的时候处理器可以稳定的提供数据到进程。

L1是集成的或者和处理器是一个模块。第2层缓存在L2中执行。

Valgrind's cachegrind tool is used to do cache profiling you use it just like valgrind. For example, the following command can be used to look at the fsck.jfs program:

Valgrind skin=cachegrind fsck.jfs -n -v /dev/hdb2

  
  
   
    
  
  

Valgrindcachegrind工具用于cache剖析。你可以如同valgrind一样使用它。接下来的命令用于剖析fsck.jfs程序:

Valgrind skin=cachegrind fsck.jfs -n -v /dev/hdb2

 

The output of cachegrind is collected in the file cachegrind.out.pid. Sample output from analyzing fsck.jfs is shown in Figures 4.20 and 4.21.

Cachegrind的输出被收集到文件cachegrind.out.pid中。图4.20421显示了分析fsck.jfs的样例输出

Figure 4.20. ca chegrind's analysis of fsck.jfs.

 

The output uses the following abbreviations for recorded events:

输出使用了下列简写来记录事件。

Abbreviation

Description

Ir

I cache reads (instructions executed)指令cache

I1mr

I1 cache read misses I1 cache 未命中

I2mr

L2 cache instruction read misses L2 cache 指令读未命中

Dr

D cache reads (memory reads) 数据cache

D1mr

D1 cache read misses D1 cache读未命中

D2mr

L2 cache data read misses L2 cache数据读未命中

Dw

D cache writes (memory writes) 数据cache

D1mw

D1 cache write misses D1 cache写未命中

D2mw

L2 cache data write misses L2 cache 数据写未命中

 

Figure 4.21. ca chegrind's analysis of fsck.jfs (continued).

 

You can annotate the output from cachegrind using cg_annotate -pid 1915.

你能使用cg_annotate –pid 1915来注解cachegrind的输出

cg_annotate produces output like that shown in Listing 4.10. It shows one annotation for the routine dmap_pmap_verify(). The entry states that 88,405,584 instructions of 99,813,615 total instructions were spent in dmap_pmap_verify(). This information is invaluable for deciding where to tune the program. You can also further annotate dmap_pmap_verify() to find the actual instructions executed in that routine.

Cg_annotate生成了列表4.10类似的输出,显示了函数dmap_pmap_verify()的注解。条目表明总共99,813,615条指令中的88,405,584个条目被花费在了dmap_pmap_verify()上。该信息对于决定调整程序的何处是无价的。你也可以在后面注解dmap_pmap_verify(),看该函数中究竟多少指令被执行。

Listing 4.10. Annotation of One Entry of cachegrind for fsck.jfs
--------------
Ir         I1mr 
   
   
    
    
     
     I2mr
     
        Dr
    
    
   
       D1mr   D2mr      Dw   D1mw   D2mw
--------------
88,405,584 23  23  61,740,960 14,535  98 576,828      9    9
fsckbmap.c:dmap_pmap_verify

 

For a complete description of cachegrind, see the Valgrind user manual in docs/index.html in the Valgrind distribution.

请在Valgrind发行版本的doc/index.html用户手册文档中获得cachegrind的完整描述。

Some Limitations of Valgrind

You should be aware of two issues when analyzing an application with Valgrind. First, an application running under Valgrind consumes more memory. Second, your program will run slower. However, these two minor annoyances shouldn't stop you from using this powerful memory management debugging tool.

使用Valgrind分析应用程序的时候你应该注意到两个问题。首先,一个在Valgrind下运行的程序消耗了更多的内存,其次你的程序运行更慢。然而,这两个次要的小毛病不应当停止你使用如此有力的内存管理调试工具。

Several projects use or have used Valgrind, including OpenOffice, StarOffice, AbiWord, Koffice, Evolution, Mozilla, and Opera. For a complete list of projects, see the Valgrind web site.

几个工程使用或者已经使用了Valgrind,包括OpenOffice,StarOffice,AbiWord,Koffice,Evolution,Mozilla以及Opera。完整的列表可以参考Valgrind网站

Summary

This chapter looked at four different memory checkers: MEMWATCH, YAMD, Electric Fence, and Valgrind. All these memory checkers can be integrated into any small or large project. They are valuable tools that can uncover memory management problems without large integration effort. It would be easy to set up the project's makefile to include one of the memory checkers. An example of how to change a makefile to add Electric Fence was shown in Listing 4.4.

本章关注了四个不同的内存检查工具:MEMWATCH,YAMD,Electric FenceValgrind。所有这些内存检查工具可以被集成到小的或者大的项目中。不需要太大努力,这些有价值的工具就可以暴露内存管理问题。配置工程的makefile文件以包含这些工具中的一个也是容易的。列表4.4给出了一个如何改变makefile文件以加入Electric Fence支持的例子。

 

Web Resources for Memory Checkers

URL

Description

http://www.linkdata.se/sourcecode.html

MEMWATCH

http://www.cs.hmc.edu/~nate/yamd/

YAMD

http://perens.com/FreeSoftware/

Electric Fence

http://valgrind.org/

Valgrind web page

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值