C/C++記憶體問題檢查利器——Purify
C/C++記憶體問題檢查利器——Purify
一、 引言
我們都知道軟體的測試(在以產品為主的軟體公司中叫做QA—Quality Assessment)佔了整個軟體工程的30% -50%,但有這麼一種說法,即使是最優秀測試專家設計出來的測試案例,也無法徹底地檢測到記憶體上的問題。
使用C/C++開發的團隊一定有被其記憶體問題折磨過的經歷,記憶體問題一直是C/C++開發人員的心頭之痛。特別當程式越來越多時,類的繼承和關聯越來越多時,記憶體問題也就越來越多,很多時候,開發人員在不經意的時候就帶入了記憶體問題。這是C/C++世界中很難避免的東西,哪怕是有10年以上開發經驗的老手,也難以避免或是杜絕記憶體問題。
而且,記憶體的問題是讓人很難察覺的,特別是對於記憶體問題排名第一的Memory Leak來說,在幾萬行程式碼中出現Memory Leak的機率幾乎是100%,而且要靠一般的手段是很難檢測出這種程式錯誤的。它並不像“野指標”或是“陣列越界”那麼容易暴露出來(這些會讓程式異常退出,而Memory Leak則不會)。當你發現你的伺服器端的程式,每隔一個月(或是更長的時間)就把伺服器上的記憶體全部耗盡時,讓伺服器有規律地每過幾個月就當機一次,那麼你的程式可能是中了Memory Leak了,同時,你會發現在數十萬行程式碼中尋找這種Memory Leak無異於大海撈針。
於是,正如《黑客帝國II》中描述的那樣,當你的程式越來越大,越來越複雜的時候,你會發現程式就越來越不受你的控制,有一些讓你記憶體出現問題乃至讓你應用程式崩潰的變數,他們生存在系統的邊緣,你怎麼找也找不到,這種情況下,除了用程式測試程式,別無其它的方法。對於C/C++記憶體問題中的Memory Leak這種頂級殺手,那怕最牛的程式設計師再加上最牛的系統架構師也很難把其找出來,對此,我們只有依靠程式,用程式去尋找這種系統的BUG。這麼讓我們事半功倍。
在我們尋求解決記憶體問題的同時,讓我們所感到幸運的時,目前,已經有許多小的軟體可供我們選擇,如MallocDebug,Valgrind
Purify 所支援的作業系統有Windows 2000/XP Professional/NT、Sun Solaris、HP-UX、SGI-IRIX。我不知道其支不支援Linux,但在其網站上,我並沒有看到這樣的資訊,但又聽別人說他支援,所以在這裡我不敢斷言它不支援,想想要做UNIX下的軟體能有不支援Linux的嗎?可能很少吧。
下面,是我所使用的Purify的版本和執行Purify的作業系統:
> purify -version
Version 2003.06.00 Solaris 2
> uname -a
SunOS hostname 5.8 Generic_108528-11 sun4u sparc SUNW,Ultra-60
我會基於這個版本向你介紹Purify的強大功能。
二、 Purify簡介
在C/C++的軟體開發中,沒有任何一種工具可以讓你的應用程式避免引入記憶體問題,但是我們可以使用諸如Purify這樣的工具對已經做好了的程式進行記憶體問題的檢查。Purify的強大之處是可以找到應用程式中全面的記憶體問題,並可以和GDB/DBX等偵錯程式以配合使用,讓你對你的記憶體錯誤一目瞭然。
Purify是一個Run-Time的工具,也就是說只有在程式執行過程中,根據程式的執行情況來檢視在某種執行條件下程式是否有記憶體上的問題,它可以在一個非常複雜的程式中查詢記憶體錯誤,包括那種多程序或多執行緒的程式,它也可以進行測試。
Purify對程式中的每一個記憶體操作都進行檢測,並對精確報告記憶體出現錯誤的變數和語句,以提供出現錯誤原因的分析。Purify主要檢測的是下面這些記憶體錯誤:
l 陣列記憶體是否越界讀/寫。
l 是否使用了未初始化的記憶體。
l 是否對已釋放的記憶體進行讀/寫。
l 是否對空指標進行讀/寫。
l 記憶體漏洞。
在軟體工程中,以我的經驗而言,最好是在編碼階段時就使用Purify檢測記憶體存問題,一直到交給測試人員測試。請相信我,在一個大型的C/C++軟體產品中,即使檢測出了記憶體問題,離真正地解決它還有一定的距離,所以為了讓這個“距離”不算太遠,最好還是在功能模組完成時就進行Purify的記憶體檢測。
一般而言,在軟體測試中,首要的是軟體的功能測試,然後是反面案例測試,再而是壓力測試。就我個人經驗而言,使用記憶體檢測工具的階段應該是編碼階段、模組合併後、以及程式邏輯測試完成以後,直到產品釋出前,也要做一個記憶體測試。
要使用Purify這個工具很簡單,首先在你安裝好了的Purify的目錄上你會看到兩個Shell指令碼:purifyplus_setup.csh(對應於C-Shell) purifyplus_setup.sh(對應於標準Shell)。你先執行一下這兩個指令碼,以便讓Purify設定好一些環境引數,如:
> source purifyplus_setup.csh
而對於你的程式而言,你需要這樣使用:
> purify cc –g –o myProgram myProgram.c
> purify cc –g –c –o myLib.o myLib.c
就這麼簡單,然後你只要執行你的程式就行了,Purify會向你提交一份記憶體問題的報告清單,以供你分析。Purify使用的是OCI(Object Code Insertion)技術,它會在你的目標程式中插入一些它自己的函式,這些函式主要都是記憶體檢測的語句,這些語句將會放置在程式中所有,記憶體操作之前,一旦在程式執行時發現記憶體問題,Purify所插入的這些語句就會向你報告。一般來說,所有的記憶體檢測工具都是這樣工作的。
當被Purify編譯過的程式執行時,Purify會彈出一個圖形介面的視窗,來向你報告記憶體問題。如下所示:
點選三角符號後將出現更為詳細的資訊:
Purify在報告記憶體問題的時候,可以指出源程式中哪個地方出現記憶體問題,但對於記憶體洩漏而言,它只能指出出現問題的記憶體是哪一塊,也就是指出記憶體是在哪裡被分配的,而不是指出記憶體洩露是如何發生的。這是比較合乎情理的,所以,即使你知道那塊記憶體發生了洩漏,你也不一定能找到究竟在什麼時候發生的。當然,如果你讓Purify配合GDB一起使用,那麼要找到這種問題的根本原因,也不是什麼困難的事情。
C/C++記憶體問題檢查利器—Purify (二)
分類: 程式設計工具2003-12-11 11:37 8803人閱讀 評論(2) 收藏 舉報
windowsleaksolarissignalmafwpf
三、 示例
假設我們現在有這樣一段程式:hello.c
#include <stdio.h> #include <malloc.h>
static char *helloWorld = "Hello, World";
main() { char *mystr = malloc(strlen(helloWorld));
strncpy(mystr, helloWorld, 12); printf("%s/n", mystr); } |
很明顯,這段程式中有記憶體上的錯誤,假設我們疏忽了這些錯誤,當我們使用Purify進行測試時,我們會發現這段程式中的記憶體錯誤在Purify下很容易就被找出來了。首先,我們要做的是使用Purify編譯這段程式:
> purify gcc -g -o hello hello.c
Purify 2003.06.00 Solaris 2 (32-bit) Copyright (C) 1992-2002 Rational Software Corp.
All rights reserved.
Instrumenting: cckc6pUD.o Linking
記得加上“-g”的選項,不然,purify不能顯示源程式。好了,現在在當前目錄下產生了可執行檔案——hello,在執行hello之前,我們還得注意,執行被Purify編譯過的程式,有可能會出現X-Windows,所以請注意設定DISPLAY環境,如果你是在Windows的Telnet客戶端下執行,那麼你可以在Windows下裝一個叫做Exceed的工具軟體,它可以方便地把X-Window顯示在你的Windows桌面上)
好了,我們現在執行hello程式,就可以看到下面的一個視窗:
我們可以看到Purify的報告中有兩個記憶體錯誤,一個是ABR(Array Bounds Read)——陣列越界讀,一個是12個位元組的Memory Leaked,展開小三角符號,我們可以看到更為詳細報告:
展開ABR錯誤後,我們可以看到,ABR錯誤的產生是由printf產生的,而產生錯誤的記憶體是mystr。通過觀察,我們馬上可以發現為會什麼會出現ABR錯誤,原因是C/C++中的字串都是以“/0”結尾的,而我們分配字串時,應該要比實際長度多一,以存放結束符,而我們以後面呼叫的strncpy只拷貝了字串的有效內容,並沒有在字串最後加上一個結束符。而系統呼叫printf輸出字串內容直至遇到結束符,所以當其訪問到12個長度時,還沒有發現結束,於是繼續向下訪問,於是就出現了ABR錯誤。
好了,讓我們再來看看Memory Leaked的報告資訊:
我們可以看到,Purify指出了那塊記憶體出現了記憶體洩露,洩露了多少個位元組。通過Purify的報告,再加上我們對C/C++基礎的瞭解,我們立馬知道mystr是在堆上分配的記憶體,所以必須要我們自己手動釋放,檢視程式,我們發現我們忘了free ( mystr )。
好了,現在我們可以根據Purify報告修改我們的程式了:
#include <stdio.h> #include <malloc.h>
static char *helloWorld = "Hello, World";
main() { char *mystr = malloc(strlen(helloWorld)+1);
strncpy(mystr, helloWorld, 12); mystr[12]=”/0”;
printf("%s/n", mystr); free(mystr); } |
現在,我們再用Purify重新編譯我們的程式,然後執行,我們可以看到Purify會報告沒有任何的記憶體問題。其實,使用Purify很簡單,在後面,我將對Purify的各種常用特性做比較全面的闡述。
四、 記憶體問題一覽
下面是Purify所能檢測到的記憶體資訊表:
記憶體資訊 |
描述 |
錯誤等級 |
ABR |
Array Bounds Read 陣列越界讀 |
3級 |
ABW |
Array Bounds Write 陣列越界寫 |
2級 |
BSR |
Beyond Stack Read 越棧讀 |
3級 |
BSW |
Beyond Stack Write 越棧寫 |
3級 |
COR |
Core Dump Imminent 非法操作 |
1級 |
FIU |
File Descriptors In Use 檔案描述符被使用 |
4級 |
FMM |
Freeing Mismatched Memory 釋放錯誤記憶體 |
2級 |
FMR |
Free Memory Read 對已釋放記憶體讀 |
3級 |
FMW |
Free Memory Write 對已釋放記憶體寫 |
2級 |
FNH |
Freeing Non Heap Memory 釋放非堆記憶體 |
2級 |
FUM |
Freeing Unallocated Memory 釋放了沒有分配的記憶體 |
2級 |
IPR |
Invalid Pointer Read 非法指標讀 |
1級 |
IPW |
Invalid Pointer Write 非法指標寫 |
1級 |
MAF |
Malloc Failure 分配記憶體失敗 |
4級 |
MIU |
Memory In-Use 記憶體正在使用 |
4級 |
MLK |
Memory Leak 記憶體洩露 |
3級 |
MRE |
Malloc Reentrancy Error remalloc錯 |
2級 |
MSE |
Memory Segment Error 記憶體段錯 |
3級 |
NPR |
Null Pointer Read 空指標讀 |
1級 |
NPW |
Null Pointer Write 空指標寫 |
1級 |
PAR |
Bad Parameter 錯誤的引數 |
3級 |
PLK |
Potential Leak 潛在的記憶體洩露 |
3級 |
SBR |
Stack Array Bounds Read 棧陣列越界讀 |
3級 |
SBW |
Stack Array Bounds Write 棧數級越界寫 |
2級 |
SIG |
Signal 訊號 |
4級 |
SOF |
Stack Overflow 棧溢位 |
3級 |
UMC |
Uninitialized Memory Copy 對未初始化的記憶體進行拷貝 |
3級 |
UMR |
Uninitialized Memory Read 對未初始化的記憶體讀 |
3級 |
WPF |
Watchpoint Free 釋放被監控的記憶體 |
4級 |
WPM |
Watchpoint Malloc 被監控的記憶體分配 |
4級 |
WPN |
Watchpoint Entry 被監控的記憶體 |
4級 |
WPR |
Watchpoint Read 被監控的記憶體讀 |
4級 |
WPW |
Watchpoint Write 被監控的記憶體寫 |
4級 |
WPX |
Watchpoint Exit 退出被監控的記憶體 |
4級 |
ZPR |
Zero Page Read 零頁面讀 |
1級 |
ZPW |
Zero Page Write 零頁面寫 |
1級 |
1級:致命錯誤。 2級:危險錯誤。 3級:警告資訊 4級:提示資訊(非錯誤)
C/C++記憶體問題檢查利器—Purify (三)
分類: 程式設計工具2003-12-15 01:26 13581人閱讀 評論(1) 收藏 舉報
descriptorsolaris圖形filegccwindows
五、 檔案描述符問題
在上面的記憶體問題表中,對於大多數的記憶體問題來說,相信對於熟悉C/C++的程式設計師,並不陌生。有一些關於Watchpoint和檔案描述符的內容,可能會讓你看得比較模糊,對於Watchpoint,我會在後面講述。這一節,我就一個示例說一說檔案描述述問題是如何產生的,並由此介紹一下Purify的一些特性。
先檢視下面這段程式:
#include <stdio.h>
main() { FILE* fp; int num;
fp = fopen("./test.txt", "r"); if ( fp == NULL){ perror("Error:"); exit(-1); }
fscanf(fp, "%d", &num); if ( num < 0 ){ printf("Error: the num should be greater than 0!/n"); exit(-1); }
fclose(fp); } |
在當前目錄下建一個test.txt的檔案,並設定其內容為-20。使用Purify編譯並執行程式:
> purify gcc -g -o testfd testfd.c
Purify 2003.06.00 Solaris 2 (32-bit) Copyright (C) 1992-2002 Rational Software Corp. All rights reserved.
Instrumenting: ccqqF6pY.o Linking
>./testfd
出現以下畫面:
由圖中,我們可以看到,Purify報告有FIU錯誤,意思是,我們在程式退出時,沒有關閉檔案描述符。還有一些算是安全的檔案描述符資訊,那就是關於0,1,2這三個標準檔案描述符的FIU,這些資訊是正常的,所以在其前面也就沒有小三角符號了。
通過這個例子,我們可以看到,Purify不但可以找到記憶體的操作錯誤,還可以找到檔案描述符的錯誤。
如果你不想讓Purify顯示FIU資訊,你可以設定Purify的 -fds-inuse-at-exit=no 選項,如:
> purify –fds-inuse-at-exit gcc -g -o testfd testfd.c
或者使用Purify的API函式 purify_clear_fds_inuse 來阻止顯示,你可以在你的程式中呼叫Purify的API函式。有關Purify的API函式的細節,我會在後面給你講述。
六、 控制Purify的輸出
1、產生ASCII文字檔案
在預設情況下,Purify會顯示出一個圖形視窗來報告資訊。當然,如果你的環境所限,你不想Purify出現圖形介面,只是生成文字檔案來報告,能過設定Purify的引數,你可以很容易做到這一點。
在程式編譯時,你只需簡單的調置Purify的編譯引數 –windows=no 即可做到,如:
> purify –windows=no gcc –g –o hello hello.c
Purify會把其報告資訊寫到標準錯誤裝置上,在文字方式下,Purify就不報告同種錯誤出現在個數,而只是報告的資訊了。
我們可以使用兩種方式讓Purify的資訊輸出到文字檔案中。
第一種是使用作業系統的重定向功能,如:
在csh下: % a.out.pure >& a.out.messages
在sh和ksh下: $ a.out.pure 2> a.out.messages
第二種是指定Purify的日誌檔案引數,如:
-log-file=<filename>.plog
下面,是一個Purify生成的ASCII文字檔案的樣子:
> ./hello **** Purify instrumented hello (pid 25698 at Wed Dec 10 22:29:33 2003) * Purify 2003.06.00 Solaris 2 (32-bit) Copyright (C) 1992-2002 Rational Software Corp. All rights reserved. * For contact information type: "purify -help" * Options settings: -follow-child-processes=yes -purify -windows=no / -purify-home=/usr/rational/releases/purify.sol.2003.06.00 / -gcc3_path=/usr/local/bin/gcc / -cache-dir=/usr/rational/releases/purify.sol.2003.06.00/cache / -demangle_program=/usr/local/bin/c++filt * License successfully checked out. * Command-line: ./hello
**** Purify instrumented hello (pid 25698) **** ABR: Array bounds read: * This is occurring while in: strlen [rtlib.o] _doprnt [libc.so.1] printf [libc.so.1] main [hello.c:11] _start [crt1.o] * Reading 13 bytes from 0x8ea08 in the heap (1 byte at 0x8ea14 illegal). * Address 0x8ea08 is at the beginning of a malloc'd block of 12 bytes. * This block was allocated from: malloc [rtlib.o] main [hello.c:8] _start [crt1.o] Hello, World
**** Purify instrumented hello (pid 25698) **** Current file descriptors in use: 5 FIU: file descriptor 0: <stdin> FIU: file descriptor 1: <stdout> FIU: file descriptor 2: <stderr> FIU: file descriptor 26: <reserved for Purify internal use> FIU: file descriptor 27: <reserved for Purify internal use>
**** Purify instrumented hello (pid 25698) **** Purify: Searching for all memory leaks...
Memory leaked: 12 bytes (100%); potentially leaked: 0 bytes (0%)
MLK: 12 bytes leaked at 0x8ea08 * This memory was allocated from: malloc [rtlib.o] main [hello.c:8] _start [crt1.o]
Purify Heap Analysis (combining suppressed and unsuppressed blocks) Blocks Bytes Leaked 1 12 Potentially Leaked 0 0 In-Use 0 0 ---------------------------------------- Total Allocated 1 12
**** Purify instrumented hello (pid 25698) **** * Program exited with status code 13. * 1 access error, 1 total occurrence. * 12 bytes leaked. * 0 bytes potentially leaked. * Basic memory usage (including Purify overhead): 351348 code 101724 data/bss 8192 heap (peak use) 1272 stack * Shared library memory usage (including Purify overhead): 992 libpure_solaris2_init.so.1 (shared code) 280 libpure_solaris2_init.so.1 (private data) 1079516 libc.so.1_pure_p3_c0_111202132_58_32_1158500S (shared code) 31404 libc.so.1_pure_p3_c0_111202132_58_32_1158500S (private data) 2324 libdl.so.1_pure_p3_c0_111202132_58_32_4624S (shared code) 4 libdl.so.1_pure_p3_c0_111202132_58_32_4624S (private data) 14048 libinternal_stubs.so.1 (shared code) 940 libinternal_stubs.so.1 (private data)
|
2、產生Purify自己的檔案
通過檢視ASCII文字檔案,我們發現其很不容易檢視,特別是當錯誤很多時,而用在檔案中沒有原始碼,看起來就是不如圖形介面的好。但是我們為了把Purify的報告資訊通過電子郵件傳送給別人檢視時,檔案和圖形介面兼得,我們可以使用Purify自己的檔案,叫Purify View檔案。我們可以使用Purify的圖形介面開啟這個檔案,而來在圖形化的視窗下檢視。
我們可以有兩種方式得到這個檔案。一種是在Purify的圖形介面的選單中點選“File -> Save as”來生成。第二種方法是使用Purify的 -view-file=<filename>.pv 引數來設定Purify View檔案。
而要開啟這個檔案時,要麼簡單地在Purify的選單中選取“Open”選單,要麼使用這樣的命令:
% purify –view <filename>.pv
3、自動傳送郵件
使用Purify的-mail-to-user引數可以方便地讓Purify自動傳送報告郵件。如:
% purify -mail-to-user=chris gcc ...
% purify -mail-to-user=chris,pat gcc ...
% purify -mail-to-user=devgrp gcc ...
在預設情況下,只要你設定了這個引數,Purify是不會開啟圖形介面視窗的,如果你要Purify開啟圖形視窗,那麼你就一同使用 –windows=yes 引數。
4、輸出自己的資訊
如果你想在Purify中輸出自己的資訊,你可以在你的程式中使用Purify的API函式:
l purify_printf(const char *fmt, ...) 使用這個函式可以在Purify的圖形介面,檔案檔案中輸出你的自己的資訊。
l purify_logfile_printf(const char *fmt, ...) 使用這個函式可以在Purify的ASCII文字檔案中輸出你自己的資訊。
l purify_printf_with_call_chain(const char *fmt, ...) 使用這個函式可以在Purify的輸出的同時,打印出函式呼叫棧的資訊。這個函式和purify_printf很類似。
注意,以上三個函式和標準C中的printf函式幾乎是一樣的,不過,這幾個函式並不支援像printf函式中的所有%的格式,它僅支援:%d, %u, %n,%s, %c, %e, %f, 和 %g 這幾種格式,並且就 %e %f %g 而且,並不支援其精度定義。
C/C++記憶體問題檢查利器—Purify (四)
分類: 程式設計工具2003-12-16 12:58 5024人閱讀 評論(0) 收藏 舉報
七、 Purify的退出碼
像UNIX下的軟體,一般都會提供和別的應用程式的介面,像上面的生成文字檔案,也是給別的應用程式提供介面的一種方式。這裡,我們所要講述的是Purify的退出碼,我們知道程式都有退出碼,以提供給別的程式或作業系統自己執行的資訊。被Purify編譯過的程式,你可以通過指定-exit-status引數來告訴Purify是否用Purify的退出碼,如果這個引數值為yes,那麼表示使用Purify的退出碼,如果值為no則表示使用程式內的退出碼。
如果我們這樣設定:-exit-status=yes,那麼Purify的退出碼是這樣定義的:
記憶體錯誤種類 |
退出碼(按位或) |
記憶體存取錯誤 |
0x40 |
記憶體洩露 |
0x20 |
潛在記憶體洩露 |
0x10 |
通過上表,我們可以知道,當-exit-status引數被開啟後,程式的退出碼被Purify完全接管,如果程式中有記憶體錯誤,那麼退出碼所對應的位就會被置為1,這樣,我們可以用別的程式來呼叫Purify所編譯出來的程式,並根據其退出碼作相應的處理。
八、 Purify和Shell的整合
你可以在UNIX的Shell環境中使用Purify的一些引數和資訊,Purify為Shell提供了一些萬用字元之類的東西,只要你使用 –run-at-exit引數。例如你有一個Shell程式想把Purify生的檔案拷貝到別的目錄中,或是你想根據Purify的報告中是否有記憶體錯誤進行下一步的行動。
下面有兩個表格,說明了一些Purify和Shell互動的引數:
有關記憶體出錯的資訊:
通配字串 |
含義 |
%z |
指明是否有記憶體錯誤或記憶體洩露。其值是“true”或“false” |
%x |
程式的退出狀態(如果是0,表示程式沒有呼叫exit函式) |
%e |
程式中記憶體訪問錯誤的個數。 |
%E |
程式中錯誤總數。 |
%l |
記憶體洩露的位元組數。 |
%L |
潛在記憶體洩露的位元組數。 |
有關程式執行的資訊:
通配字串 |
含義 |
%V |
執行程式的全路徑(“/”被替換成了“_”) |
%v |
程式的名稱 |
%p |
程式的進行ID |
在使用Purify過程中,有兩種方法可以傳遞Purify的引數,一種就是在命令列上指明。另外一種是設定一個和Purify相關的環境變數:PURIFYOPTIONS。現在,我通過這個環境變數要舉一個例子,以說明上面表格中的引數在使用中的情況:
例如,如果我們這樣這置環境變數:(在C-Shell中)
setenv PURIFYOPTIONS '-run-at-exit="if %z ; then /
echo /"%v: %e errors, %l+%L bytes leaked./" ; fi"'
當我們執行被Purify編譯過的程式後,會出現以下結果:
hello: 2 errors, 1+10 bytes leaked.
我們可以看到,由於hello程式出錯了,所以%z為“true”,所以Purify執行echo命令,其中,%v表示了程式名(hello),%e表示了錯誤的個數(2),%l表示了記憶體洩露的位元組數(1),%L表示了程式中有潛在可能的記憶體洩露位元組數(12)。
讓我們再來看兩個例子:
示例一:
指定Purify的引數為: -log-file=./%v.plog
示例二:
指定Purify的引數為: -view-file=/home/hchen/%V.pv
總這,這些有“%”的變數,都是Purify提供給作業系統Shell的,以供Shell程式設計使用的。
九、 過濾Purify的報告資訊
如果你的程式比較大,模組也比較多,有時候出現的資訊非常的多,你程式中很可能有某段程式碼產生了若干個記憶體錯誤,所以,我們可以使用Purify的過濾器來讓Purify只顯示某一種類的資訊,這樣方便我們進行問題的查詢和排錯。
1、 在Purify的X-Window中設定資訊過濾,點選圖形介面中的選單“Options” -–>“Suppressions”,將出現“Suppressions”對話方塊,如下所示:
我們可以看到在上面的對話方塊中,如果過濾Purify的報告資訊。當我們點選“Where to suppress”只要,我們會看到有如下的五個選項:
l In Call Chain:表示在某個函式呼叫鏈中資訊。
l In File:表示只報告在某個檔案中的資訊。
l In Library:表示只報告在某個LIB檔案中的資訊。
l In Class:這是C++的,表示報告某個類的資訊。
l Everywhere:表示全部範圍內的資訊。
但是圖形介面中,Purify並沒有給我們提供一個選取檔案或LIB或類的對話方塊,我們只能通過其文字語法來描述,接下來就讓我們來看一看,過濾Purify報告資訊的文字語法。
2、 我們可以使用Purify的過濾語法來要求Purify的過濾資訊。並把其存於.purify檔案中,這樣當我們的Purify起動後載入這個檔案,就可以達到過濾資訊的目的了。通過文字語法來設定過濾資訊比圖形界有更為強大的地方。下面還是來看看suppress的語法:
語法:
suppress <message-type> <function-call-chain>
unsuppress <message-type> <function-call-chain>
其中,suppress和unsuppress中關鍵字,分別表示過濾或不過濾。<message-type>指明要操作的訊息,可以使用“*”做萬用字元,<function-call-chain>表示函式的呼叫鏈,呼叫的函式鏈用分號分隔,其同樣可以使用“*”做萬用字元,還可以使用“…”來表示無論中間是什麼。
還是來看幾個示例吧:
1) suppress AB*
表示過濾ABR和ABW錯誤。
2) suppress *W
表示過濾ABW、FMW、IPW、NPW、SBW、WPW和ZPW錯誤。
3) suppress ABR “libc*”
表示在所有以libc打頭的LIB檔案中過濾ABR資訊。
4) suppress ABR sortFunction; sort*; qsort; “libc*”
其表示,過濾ABR錯誤。過濾範圍是在sortFunction中,並且是在以libc開頭的函式庫檔案中,其呼叫鏈是qsort -> sort* -> sortFunction。換言之,只要有“libc*”檔案中的函式呼叫了qsort,並且qsort呼叫了開頭為sort*的函式,並且這些函式呼叫了sortFunction,那麼,在這一個函式鏈中,不顯示ABR錯誤資訊。
5) suppress UMR tzsetWall;…; main
其表示,在tzsetWall函式中過濾URM資訊,只要tzsetWall函式是被main函式間接呼叫的,無論有多遠,都不顯示UMR資訊。
6) suppress FNH Test: :Test
這是C++中使用的語法,表示在類Test所有的建構函式中過濾FNH資訊。如果要指明特定的函式,請加上其引數型別,如:suppress FNH Test::Test(const char*)。
注意,“…”語法表示呼叫鏈無論有多遠。當然,如果你設定了引數“-chain-length=6”,那麼,“…”只能到6層函式呼叫,7層的就不管了。
在啟動Purify時,我們可以這樣來讀取.purify檔案:
% purify -suppression-file-names=".purify,.purify.sunos4,/
$HOME/purify_suppressions"
Purify會在下面的目錄中尋找這個檔案:
<purifyhome>/.purify
<purifyhome>/.purify.sunos4
$HOME/.purify
$HOME/.purify.sunos4
<progdir>/.purify
<progdir>/.purify.sunos4
$HOME/purify_suppressions
C/C++記憶體問題檢查利器—Purify (五)
分類: 程式設計工具2003-12-16 13:01 6236人閱讀 評論(1) 收藏 舉報
十、 設定WatchPoint
你可以在你的程式中,對你所想監控的程式設定一些WatchPoint,以方便於你對程式進行除錯,或更容易找出問題的原因。就像我前面說,Purify可以找到你的記憶體洩露,但其不能找到記憶體洩露的原因,你可以通過設定WatchPoint來跟蹤一塊記憶體,以找到在程式執行過程中該記憶體的訪問情況。
Purify的WatchPoint可以產生下例訊息:
l WPR(被WatchPoint的記憶體讀)
l WPW(被WatchPoint的記憶體寫)
l WPM(被WatchPoint的記憶體分配)
l WPF(被WatchPoint的記憶體釋放)
l WPN(來到被WatchPoint的記憶體的Scope)
l WPX(離開被WatchPoint的記憶體的Scope)
一旦你設定好了一個WatchPoint,Purify會自動報告上述這些資訊,以告訴你記憶體的存取情況。很方便你除錯程式。
WatchPoint一般是在除錯程式時跟蹤一塊記憶體時候使用的,你也可以用其跟蹤一些系統級的全域性變數,如:errno。一旦errno被寫了,馬上會報告一個WPW訊息,展開後,你能看到函式的堆疊情況,以及是在哪個系統呼叫後出現了錯誤。這個使用很方便我們找到一些非記憶體方面的問題。
大家可能會有一種感覺,那就是在一般的偵錯程式中,如GDB中也有WatchPoint的設定(對GDB的使用請參考我的文章《用GDB除錯程式》),那麼,在偵錯程式中的WatchPoint和Purify的有什麼不同?下面是一些GDB中的WatchPoint不足的地方:
1) GDB中的WatchPoint用於單步跟蹤中。
2) GDB中的WatchPoint只能在其記憶體的Scope中,離開了Scope,WatchPoint會被刪除。
3) 在GDB中設定一個4位元組的記憶體WatchPoint,會讓程式的執行效率下降1000個數量級。
Purify中的WatchPoint有效地克服了這些問題,它在全域性範圍內監控所有記憶體的使用,並且,其速度上大大地快於GDB等一系列的偵錯程式。
有兩種方式可以讓我們設定Purify的WatchPoint,一種是在程式中使用WatchPoint的API函式,一種是直接在偵錯程式中使用(如:GDB),下面我介紹一下這兩種用法:
1、 在程式中使用。
寫下這段程式:
#include <errno.h>
main() { int i; printf("Note: errno=0x%x/n", errno); purify_watch(&errno);
errno = 0;
close(1000);
exit(0); } |
用Purify編譯: >sudo purify gcc -g -o watch watch.c
執行後,我們可以看到以下畫面:
我們可以看到,Purify成功地監控了errno變數。我們還可以看到被監控的變數改變前和改變後的值。
2、 在GDB中使用。
在GDB中,我們可以簡單地使用GDB的print命令來達到設定Purify的WatchPoint目的。這正是Purify的強大之處,其對這種技術稱為JIT(Just-In-Time)。
示例:
gdb) print purify_watch(&my_str)
(gdb) print purify_watch_1(&my_char)
(gdb) print purify_watch_n(buf, sizeof(buf), "rw")
(dbx) print purify_watch_n(write_only_buf,100,"w")
下面來讓我們看一看Purify的WatchPoint的API函式,其分成三類:
• 設定類
int purify_watch(char *addr)
對所指定的記憶體進行監視,char* 表示以單位元組為單位。
int purify_watch_<num> (char *addr) <num>=1,2,4,8
其中的<num>是一個數字,可以是1,2,4,8表示,監控單位元組,雙位元組,四位元組,八位元組。函式名為:purify_watch_1(),purify_watch_2(),purify_watch_4(),purify_watch_8。
int purify_watch_n(char *addr, unsigned int size, char *type)
(type = “r”, “w” or “rw”)
監控特定長度的記憶體,type取值為“r”,“w”“rw”,意為監控記憶體的讀還是寫。
• 查詢類
int purify_watch_info().
列印目前設定的WatchPoint的情況(一般在GDB類的偵錯程式中使用)。有點像GDB的info watch命令。
• 刪除類
Int purify_watch_remove(int watchno)
刪除指定的WatchPoint,其watchno為設定WatchPoint的函式的返回值。
int purify_watch_remove_all()
刪除所有的WatchPoint。
十一、 使用Purify的引數
Purify的引數很多,具體的引數我就不多說了,還請你參考其使用手冊。在這裡,我簡單地講一講其引數的使用規則和方式。
Purify的引數使用的規則如下:
1、 必須以連字元開始,也就是減號。
2、 在等號(=)的兩端不能有空格。
3、 忽略引數名和變數的大小寫。
4、 其引數中的連線符可以是減號,下劃線,或是乾脆就沒有。如:
-leaks-at-exit,-LEAKS_AT_EXIT和 –LeaksAtExit是一回事。
5、 在引數中,如果你要指多個路徑,可以用冒號或空格分開。使用空格時請使用引號。如:
% purify -user-path=’/usr/home/program /usr/home/program1’
% purify -user-path=/usr/home/program:/usr/home/program1
6、 指寫多個郵件使用者時,用逗號分隔。千萬不要回空格。如:
% purify -mail-to-user=chris,pat,kam
7、 可以使用萬用字元或轉義字串。如:
program* 和 -log-file=./%v.plog
Purify引數的型別有三種——布林、字串和整數,如:
-leaks-at-exit=yes 布林型
-log-file=./pureout 字串型
-chain-length=10 整數型
設定引數的方法有三種:
1、 在圖形視窗中,通過點選“Options -> Runtime”選單,在對話方塊中設定。
2、 通過兩個環境變數設定——PURIFYOPTIONS 或 PUREOPTIONS,如:
在csh下:
% setenv PURIFYOPTIONS "-log-file=new $PURIFYOPTIONS“
在sh或ksh下:
$ PURIFYOPTIONS="-log-file=new $PURIFYOPTIONS"; export/
PURIFYOPTIONS
3、 在Link程式的命令列中。如:
$ purify -cache-dir=$HOME/pcache -always-use-cache-dir $CC ...
十二、 使用Purify的API函式
Purify的函式有許多,我也不在這裡一一講解了,其具體細節還請參考使用手冊。我這裡只講一下Purify的API函式的使用方法。總的說來,有以下兩種方式我們可以使用Purify的API函式。
1. 在我們的偵錯程式中呼叫,如:
gdb) print purify_describe(addr)
(dbx) call purify_what_colors(buf, sizeof(buf))
(xdb) p purify_describe(addr)
注:對於purify_stop_here這個函式,我們可以這樣使用。
(gdb) break purify_stop_here
(dbx) stop in purify_stop_here
(xdb) b purify_stop_here
2. 在自己的程式中呼叫。要在程式中呼叫Purify的API函式,我們需要下面兩步:
1)加上標頭檔案:#include <purify.h>
2)把LIB檔案放到可被搜尋到的路徑中。主要是一個動態連結庫檔案libpurify_stubs.so和一個靜態連結庫檔案libpurify_stubs.a
十三、 結束語
Purify是一個很強大的工具,但可惜的是其只能是某幾個平臺中使用。好像其Windows版中的功能要差很多,我用的Sun的Solaris的版本,很不錯,因為我們的程式要在所有的UNIX下跑,用C跨平臺,所以,使用Solaris來做測試機。對於其它平臺的Purify,我沒有用過,不知道和Solaris下的是否一樣,還希望有經驗的同行給我指點。不管怎麼樣,我想信IBM的Rational部會把它做得越來越好的。
對於這篇文章,本來打算在9月或者10月寫這篇文章的。不過實在沒有辦法,前些時候太忙了,忙得腦子一堆漿糊,只要拖到現在,現在較好一點,不過腦子也不好用,寫作過程中發現腦袋很拙笨。所以寫出來的東西一點有錯誤,特別是我用的是五筆輸入法,所以有錯字錯詞會有可能讓你看不懂,還請各位見諒。
好了,不多說了,好累了。還是留上我的聯絡方式,歡迎和我討論交流。本人目前主要在UNIX下做產品軟體設計和管理工作,所以,對UNIX下的軟體開發比較熟悉,當然,不單單是技術,對軟體工程實施,軟體設計,系統分析,專案管理我也略有心得。歡迎大家找我交流。(MSN是:[email protected](常用) QQ是:753640(基本不用,因為不安全))