Valgrind
Valgrind 已經在 Linux 應用程序開發社區中廣泛用來調試應用程序。它尤其擅長髮現內存管理的問題。它可以檢查程序運行時的內存泄漏問題。這個工具目前正由 Julian Seward 進行開發,並由 Paul Mackerras 移植到了 Power 架構上。
要安裝 Valgrind,請從 Valgrind 的 Web 站點上下載源代碼(參閱 參考資料)。切換到 Valgrind 目錄,並執行下面的命令:
# make
# make check
# make install
|
Valgrind 的錯誤報告
Valgrind 的輸出格式如下:
清單 1. Valgrind 的輸出消息
# valgrind du -x -s
.
.
==29404== Address 0x1189AD84 is 0 bytes after a block of size 12 alloc'd
==29404== at 0xFFB9964: malloc (vg_replace_malloc.c:130)
==29404== by 0xFEE1AD0: strdup (in /lib/tls/libc.so.6)
==29404== by 0xFE94D30: setlocale (in /lib/tls/libc.so.6)
==29404== by 0x10001414: main (in /usr/bin/du)
|
==29404==
是進程的 ID。消息 Address 0x1189AD84 is 0 bytes after a block of size 12 alloc'd
說明在這個 12 字節的數組後面沒有存儲空間了。第二行以及後續幾行說明內存是在 130 行(vg_replace_malloc.c)的 strdup()
程序中進行分配的。strdup()
是在 libc.so.6 庫的 setlocale()
中調用的;main()
調用了 setlocale()
。
未初始化的內存
最爲常見的一個 bug 是程序使用了未初始化的內存。未初始化的數據可能來源於:
- 未經初始化的變量
- malloc 函數所分配的數據,在寫入值之前使用了
下面這個例子使用了一個未初始化的數組:
清單 2. 使用未初始化的內存
2 {
3 int i[5];
4
5 if (i[0] == 0)
6 i[1]=1;
7 return 0;
8 }
|
在這個例子中,整數數組 i[5] 沒有進行初始化;因此,i[0] 包含的是一個隨機數。因此使用 i[0] 的值來判斷一個條件分支就會導致不可預期的問題。Valgrind 可以很容易捕獲這種錯誤條件。當您使用 Valgrind 運行這個程序時,就會接收到下面的消息:
清單 3. Valgrind 的輸出消息
# gcc -g -o test1 test1.c
# valgrind ./test1
.
.
==31363==
==31363== Conditional jump or move depends on uninitialised value(s)
==31363== at 0x1000041C: main (test1.c:5)
==31363==
==31363== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 7 from 1)
==31363== malloc/free: in use at exit: 0 bytes in 0 blocks.
==31363== malloc/free: 0 allocs, 0 frees, 0 bytes allocated.
==31363== For counts of detected errors, rerun with: -v
==31363== No malloc'd blocks -- no leaks are possible.
|
Valgrind 的輸出說明,有一個條件分支依賴於文件 test1.c 中第 5 行中的一個未初始化的變量。
內存泄漏
內存泄漏是另外一個常見的問題,也是很多程序中最難判斷的問題。內存泄漏的主要表現爲:當程序連續運行時,與程序相關的內存(或堆)變得越來越大。結果是,當這個程序所消耗的內存達到系統的上限時,就會自己崩潰;或者會出現更嚴重的情況:掛起或導致系統崩潰。下面是一個有內存泄漏 bug 的示例程序:
清單 4. 內存泄漏示例
1 int main(void)
2 {
3 char *p1;
4 char *p2;
5
6 p1 = (char *) malloc(512);
7 p2 = (char *) malloc(512);
8
9 p1=p2;
10
11 free(p1);
12 free(p2);
13 }
|
上面的代碼分別給字符指針 p1 和 p2 分配了兩個 512 字節的內存塊,然後將指向第一個內存塊的指針設置爲指向第二個內存塊。結果是,第二個內存塊的地址丟失了,並導致內存泄漏。在使用 Valgrind 運行這個程序時,會返回如下的消息:
清單 5. Valgrind 的輸出消息
# gcc -g -o test2 test2.c
# valgrind ./test2
.
.
==31468== Invalid free() / delete / delete[]
==31468== at 0xFFB9FF0: free (vg_replace_malloc.c:152)
==31468== by 0x100004B0: main (test2.c:12)
==31468== Address 0x11899258 is 0 bytes inside a block of size 512 free'd
==31468== at 0xFFB9FF0: free (vg_replace_malloc.c:152)
==31468== by 0x100004A4: main (test2.c:11)
==31468==
==31468== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 7 from 1)
==31468== malloc/free: in use at exit: 512 bytes in 1 blocks.
==31468== malloc/free: 2 allocs, 2 frees, 1024 bytes allocated.
==31468== For counts of detected errors, rerun with: -v
==31468== searching for pointers to 1 not-freed blocks.
==31468== checked 167936 bytes.
==31468==
==31468== LEAK SUMMARY:
==31468== definitely lost: 512 bytes in 1 blocks.
==31468== possibly lost: 0 bytes in 0 blocks.
==31468== still reachable: 0 bytes in 0 blocks.
==31468== suppressed: 0 bytes in 0 blocks.
==31468== Use --leak-check=full to see details of leaked memory.
|
正如您可以看到的一樣,Valgrind 報告說這個程序中有 512 字節的內存丟失了。
非法寫/讀
這種情況發生在程序試圖對一個不屬於程序本身的內存地址進行讀寫時。在有些系統上,在發生這種錯誤時,程序會異常結束,併產生一個段錯誤。下面這個例子就是一個常見的 bug,它試圖讀寫一個超出數組邊界的元素。
清單 6. 非法讀寫
1 int main() {
2 int i, *iw, *ir;
3
4 iw = (int *)malloc(10*sizeof(int));
5 ir = (int *)malloc(10*sizeof(int));
6
7
8 for (i=0; i<11; i++)
9 iw[i] = i;
10
11 for (i=0; i<11; i++)
12 ir[i] = iw[i];
13
14 free(iw);
15 free(ir);
16 }
|
從這個程序中我們可以看出,對於 iw[10]
和 ir[10]
的訪問都是非法的,因爲 iw
和 ir
都只有 10 個元素,分別是從 0 到 9。請注意 int iw[10 ]
和 iw = (int *)malloc(10*sizeof(int))
是等效的 -- 它們都是用來給一個整數數組 iw 分配 10 個元素。
當您使用 Valgrind 運行這個程序時,會返回如下的消息:
清單 7. Valgrind 的輸出消息
# gcc -g -o test3 test3.c
# valgrind ./test3
.
.
==31522== Invalid write of size 4
==31522== at 0x100004C0: main (test3.c:9)
==31522== Address 0x11899050 is 0 bytes after a block of size 40 alloc'd
==31522== at 0xFFB9964: malloc (vg_replace_malloc.c:130)
==31522== by 0x10000474: main (test10.c:4)
==31522==
==31522== Invalid read of size 4
==31522== at 0x1000050C: main (test3.c:12)
==31522== Address 0x11899050 is 0 bytes after a block of size 40 alloc'd
==31522== at 0xFFB9964: malloc (vg_replace_malloc.c:130)
==31522== by 0x10000474: main (test10.c:4)
==31522==
==31522== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 7 from 1)
==31522== malloc/free: in use at exit: 0 bytes in 0 blocks.
==31522== malloc/free: 2 allocs, 2 frees, 84 bytes allocated.
==31522== For counts of detected errors, rerun with: -v
==31522== No malloc'd blocks -- no leaks are possible.
|
在 test3.c 的第 9 行發現一個非法的 4 字節寫操作,在第 12 行發現一個非法的 4 字節讀操作。
Valgrind 也可以幫助判斷內存誤用的問題,例如:
- 讀/寫已經釋放的內存
- C++ 環境中錯誤地使用 malloc/new 與 free/delete 的配對
下面這個列表介紹了 POWER 架構上 Valgrind 的狀態:
- memcheck 和 addrcheck 工具都可以很好地工作。然而,其他工具還沒有進行大量的測試。另外,Helgrind (一個數據競爭的檢測程序)在 POWER 上尚不能使用。
- 所有的 32 位 PowerPC? 用戶模式的指令都可以支持,除了兩條非常少用的指令:lswx 和 stswx。具體來說,所有的浮點和 Altivec(VMX)指令都可以支持。
- Valgrind 可以在 32 位或 64 位 PowerPC/Linux 內核上工作,但是隻能用於 32 位的可執行程序。
有關 Valgrind 內存調試的更多信息,請訪問 Valgrind HOW TO 站點。還可以參閱 Steve Best 的"Debugging Memory Problems"(Linux Magazine,2003 年 5 月)。參考資料 中有它們的鏈接
除了 Valgrind 之外,還可以使用其他幾個內存調試工具;例如,Memwatch 和 Electric Fence。