簡介前端
調試程序有不少方法,例如向屏幕上打印消息,使用調試器,或者只需仔細考慮程序如何運行,並對問題進行有根有據的猜想。java
在修復 bug 以前,首先要肯定在源程序中的位置。例如,當一個程序產生崩潰或生成核心轉儲(core dump)時,您就須要瞭解是哪行代碼發生了崩潰。在找到有問題的代碼行以後,就能夠肯定這個函數中變量的值,函數是如何調用的,更具體點說,爲何會發生這種錯誤。使用調試器查找這些信息很是簡單。linux
本文將簡要介紹幾種用於修復一些很難經過可視化地檢查代碼而發現的 bug 的技術,並闡述瞭如何使用在 Linux on Power 架構上可用的工具。算法
動態內存分配看起來彷佛很是簡單:您能夠根據須要分配內存 —— 使用 malloc() 或其變種 —— 並在不須要時釋放這些內存。實際上,內存管理的問題是軟件中最爲常見的 bug,由於一般在程序啓動時這些問題並不明顯。例如,程序中的內存泄漏可能開始並不爲人注意,直到通過多天甚至幾個月的運行纔會被發現。接下來的幾節將簡要介紹如何使用流行的調試器 Valgrind 來發現並調試這些最多見的內存 bug。數組
在開始使用任何調試工具以前,請考慮這個工具是否對從新編譯應用程序有益,是否能夠支持具備調試信息的庫(-g
選項)。若是沒有啓用調試信息,調試工具能夠作的最好的事情也不過是猜想一段特定的代碼是屬於哪一個函數的。這使得錯誤消息和概要分析輸出幾乎沒有什麼用處。使用 -g
選項,您就有可能得到一些信息來直接指出相關的代碼行。數據結構
Valgrind 已經在 Linux 應用程序開發社區中普遍用來調試應用程序。它尤爲擅長髮現內存管理的問題。它能夠檢查程序運行時的內存泄漏問題。這個工具目前正由 Julian Seward 進行開發,並由 Paul Mackerras 移植到了 Power 架構上。架構
要安裝 Valgrind,請從 Valgrind 的 Web 站點上下載源代碼(參閱 參考資料)。切換到 Valgrind 目錄,並執行下面的命令:
# make # make check # make install |
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。
除了內存 bug 以外,開發人員一般還會碰到程序雖然可以成功編譯,可是在運行時卻會產生內核轉儲或段錯誤的問題。有時在程序完成以後,程序的輸出可能與所指望或設計的不一樣。在這兩種狀況中,可能代碼中存在您認爲正確而實際上錯誤的狀況。接下來的幾節中介紹的調試器將幫助您找到這些狀況的緣由。
GDB(GNU 項目調試器)可讓您瞭解程序在執行時「內部」 究竟在幹些什麼,以及在程序發生崩潰的瞬間正在作什麼。
GDB 作如下 4 件主要的事情來幫助您捕獲程序中的 bug:
- 在程序啓動以前指定一些能夠影響程序行爲的變量或條件
- 在某個指定的地方或條件下暫停程序
- 在程序中止時檢查已經發生了什麼
- 在程序執行過程當中修改程序中的變量或條件,這樣就能夠體驗修復一個 bug 的成果,並繼續瞭解其餘 bug
要調試的程序能夠是使用 C、C++、Pascal、Objective-C 以及其餘不少語言編寫的。GDB 的二進制文件名是 gdb。
gdb 中有不少命令。使用 help
命令能夠列出全部的命令,以及關於如何使用這些命令的介紹。下表給出了最經常使用的 GDB 命令。
表 1. gdb 中最經常使用的命令
命令 | 說明 | 例子 |
---|---|---|
help |
顯示命令類別 | help - 顯示命令類別help breakpoints - 顯示屬於 breakpoints 類別的命令help break - 顯示 break 命令的解釋 |
run |
啓動所調試的程序 | ? |
kill |
終止正在調試的程序的執行 | 一般這會在要執行的代碼行已經超過了您想要調試的代碼時使用。執行 kill 會重置斷點,並從頭再次運行這個程序 |
cont |
所調試的程序運行到一個斷點、異常或單步以後,繼續執行 | ? |
info break |
顯示當前的斷點或觀察點 | ? |
break |
在指定的行或函數處設置斷點 | break 93 if i=8 - 當變量 i 等於 8 時,在第 93 行中止程序執行 |
Step |
單步執行程序,直到它到達一個不一樣的源代碼行。您可使用 s 來表明 step 命令 |
? |
Next |
與 step 命令相似,只是它不會「單步跟蹤到」子例程中 | ? |
print |
打印一個變量或表達式的值 | print pointer - 打印變量指針的內容print *pointer - 打印指針所指向的數據結構的內容 |
delete |
刪除某些斷點或自動顯示錶達式 | delete 1 - 刪除斷點 1。斷點能夠經過 info break 來顯示 |
watch |
爲一個表達式設置一個觀察點。當表達式的值發生變化時,這個觀察點就會暫停程序的執行 | ? |
where |
打印全部堆棧幀的棧信息 | where - 不使用參數,輸出當前線程的堆棧信息where all - 輸出當前線程組中全部線程的堆棧信息where threadindex - 輸出指定線程的堆棧信息 |
attach |
開始查看一個已經運行的進程 | attach <process_id> - 附加到進程 process_id 上。process_id 可使用 ps 命令找到 |
info thread |
顯示當前正在運行的線程 | ? |
thread apply threadno command |
對一個線程運行 gdb 命令 | thread apply 3 where - 對線程 3 運行 where 命令 |
Thread threadno |
選擇一個線程做爲當前線程 | ? |
若是一個程序崩潰了,並生成了一個 core 文件,您能夠查看 core 文件來判斷進程結束時的狀態。使用下面的命令啓動 gdb:
# gdb programname corefilename |
要調試一個 core 文件,您須要可執行程序、源代碼文件以及 core 文件。要對一個 core 文件啓動 gdb,請使用 -c 選項:
# gdb -c core programname |
gdb 會顯示是哪行代碼致使這個程序產生了核心轉儲。
默認狀況下,核心轉儲在 Novell 的 SUSE LINUX Enterprise Server 9(SLES 9)和 Red Hat? Enterprise Linux Advanced Server(RHEL AS 4)上都是禁用的。要啓用核心轉儲,請以 root 用戶的身份在命令行中執行 ulimit –c unlimited
。
清單 8 中的例子闡述瞭如何使用 gdb 來定位程序中的 bug。清單 8 是一段包含 bug 的 C++ 代碼。
清單 8 中的 C++ 程序試圖構建 10 個連接在一塊兒的數字框(number box),例如:
圖 1. 一個包含 10 個連接在一塊兒的數字框的列表
而後試圖從這個列表中逐個刪除數字框。
編譯並運行這個程序,以下所示:
清單 9. 編譯並運行這個程序
# g++ -g -o gdbtest1 gdbtest1.cpp # ./gdbtest1 Number Box "0" created Number Box "1" created Number Box "2" created Number Box "3" created Number Box "4" created Number Box "5" created Number Box "6" created Number Box "7" created Number Box "8" created Number Box "9" created list created Number Box "9" deleted Segmentation fault |
正如您能夠看到的同樣,這個程序會致使段錯誤。調用 gdb 來看一下這個問題,以下所示:
清單 10. 調用 gdb
# gdb ./gdbtest1 GNU gdb 6.2.1 Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "ppc-suse-linux"...Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) |
您知道段錯誤是在數字框 "9" 被刪除以後發生的。執行 run
和 where
命令來精肯定位段錯誤發生在程序中的什麼位置。
清單 11. 執行 run 和 where 命令
(gdb) run Starting program: /root/test/gdbtest1 Number Box "0" created Number Box "1" created Number Box "2" created Number Box "3" created Number Box "4" created Number Box "5" created Number Box "6" created Number Box "7" created Number Box "8" created Number Box "9" created list created Number Box "9" deleted Program received signal SIGSEGV, Segmentation fault. 0x10000f74 in NumBox<int>::GetNext (this=0x0) at gdbtest1.cpp:14 14 NumBox<T>*GetNext() const { return Next; } (gdb) where #0 0x10000f74 in NumBox<int>::GetNext (this=0x0) at gdbtest1.cpp:14 #1 0x10000d10 in NumChain<int>::RemoveBox (this=0x10012008, item_to_remove=@0xffffe200) at gdbtest1.cpp:63 #2 0x10000978 in main (argc=1, argv=0xffffe554) at gdbtest1.cpp:94 (gdb) |
跟蹤信息顯示這個程序在第 14 行 NumBox<int>::GetNext (this=0x0)
接收到一個段錯誤。這個數字框上 Next 指針的地址是 0x0,這對於一個數字框來講是一個無效的地址。從上面的跟蹤信息能夠看出,GetNext
函數是由 63 行調用的。看一下在 gdbtest1.cpp 的 63 行附近發生了什麼:
清單 12. gdbtest1.cpp
54 } else { 55 temp->SetNext (current->GetNext()); 56 delete temp; 57 temp = 0; 58 return 0; 59 } 60 } 61 current = 0; 62 temp = current; 63 current = current->GetNext(); 64 } 65 66 return -1; |
第 61 行 current=0
將這個指針設置爲一個無效的地址,這正是產生段錯誤的根源。註釋掉第 61 行,將其保存爲 gdbtest2.cpp,而後編譯並從新運行。
清單 13. 再次運行程序(gdbtest2.cpp)
# g++ -g -o gdbtest2 gdbtest2.cpp # ./gdbtest2 Number Box "0" created Number Box "1" created Number Box "2" created Number Box "3" created Number Box "4" created Number Box "5" created Number Box "6" created Number Box "7" created Number Box "8" created Number Box "9" created list created Number Box "9" deleted Number Box "0" deleted |
這個程序如今能夠成功完成而不會出現段錯誤了。然而,結果並不像咱們預期的同樣:程序在刪除 Number Box "9"以後刪除了 Number Box "0",而不像咱們指望的同樣刪除 Number Box "8,"。使用 gdb 再次來看一下。
清單 14. 再次使用 gdb 進行查看
# gdb ./gdbtest2 GNU gdb 6.2.1 Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "ppc-suse-linux"...Using host libthread_db library "/lib/tls/libthread_db.so.1". (gdb) break 94 if i==8 Breakpoint 1 at 0x10000968: file gdbtest2.cpp, line 94. (gdb) run Starting program: /root/test/gdbtest2 Number Box "0" created Number Box "1" created Number Box "2" created Number Box "3" created Number Box "4" created Number Box "5" created Number Box "6" created Number Box "7" created Number Box "8" created Number Box "9" created list created Number Box "9" deleted Breakpoint 1, main (argc=1, argv=0xffffe554) at gdbtest2.cpp:94 94 list ->RemoveBox(i); |
您可能但願找出爲何這個程序刪除的是 Number Box 0,而不是 Number Box 8,所以須要在您認爲程序會刪除 Number Box 8 的地方中止程序。設置這個斷點:break 94 if i==8
,能夠在 i 等於 8 時在第 94 行處中止程序。而後單步跟蹤
到 RemoveBox()
函數中。
清單 15. 單步跟蹤到 RemoveBox() 函數中
(gdb) s 38 NumBox<T> *temp = 0; (gdb) s 40 while (current != 0) { (gdb) print pointer $1 = (NumBox<int> *) 0x100120a8 (gdb) print *pointer $2 = {Num = 0, Next = 0x0} (gdb) |
指針早已指向了 Number Box "0",所以這個 bug 可能就存在於程序刪除 Number Box "9" 的地方。要在 gdb 中從新啓動這個程序,請使用 kill
刪除原來的斷點,而後添加一個 i 等於 9 時的新斷點,而後再次運行這個程序。
清單 16. 在 gdb 中從新啓動程序
(gdb) kill Kill the program being debugged? (y or n) y (gdb) info break Num Type Disp Enb Address What 1 breakpoint keep y 0x10000968 in main at gdbtest2.cpp:94 stop only if i == 8 breakpoint already hit 1 time (gdb) delete 1 (gdb) break 94 if i==9 Breakpoint 2 at 0x10000968: file gdbtest2.cpp, line 94. (gdb) run Starting program: /root/test/gdbtest2 Number Box "0" created Number Box "1" created Number Box "2" created Number Box "3" created Number Box "4" created Number Box "5" created Number Box "6" created Number Box "7" created Number Box "8" created Number Box "9" created list created Breakpoint 2, main (argc=1, argv=0xffffe554) at gdbtest2.cpp:94 94 list ->RemoveBox(i); (gdb) |
當這一次單步跟蹤 RemoveBox()
函數時,要特別注意 list->pointer
正在指向哪個數字框,由於 bug 可能就在於 list->pointer
開始指向 Number Box "0" 的地方。請使用 display *pointer
命令來查看,這會自動顯示這個函數。
清單 17. 使用 display *pointer 命令進行監視
Breakpoint 2, main (argc=1, argv=0xffffe554) at gdbtest2.cpp:94 94 list ->RemoveBox(i); (gdb) s NumChain<int>::RemoveBox (this=0x10012008, item_to_remove=@0xffffe200) at gdbtest2.cpp:37 37 NumBox<T> *current = pointer; (gdb) display *pointer 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s 38 NumBox<T> *temp = 0; 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s 40 while (current != 0) { 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s 41 if (current->GetValue() == item_to_remove) { 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s NumBox<int>::GetValue (this=0x100120a8) at gdbtest2.cpp:16 16 const T& GetValue () const { return Num; } (gdb) s NumChain<int>::RemoveBox (this=0x10012008, item_to_remove=@0xffffe200) at gdbtest2.cpp:42 42 if (temp == 0) { 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s 44 if (current->GetNext() == 0) { 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s NumBox<int>::GetNext (this=0x100120a8) at gdbtest2.cpp:14 14 NumBox<T>*GetNext() const { return Next; } (gdb) s NumChain<int>::RemoveBox (this=0x10012008, item_to_remove=@0xffffe200) at gdbtest2.cpp:50 50 delete current; 1: *this->pointer = {Num = 9, Next = 0x10012098} (gdb) s ~NumBox (this=0x100120a8) at gdbtest2.cpp:10 10 std::cout << "Number Box " <<"\"" << GetValue() <<"\"" <<" deleted" << std::endl; (gdb) s NumBox<int>::GetValue (this=0x100120a8) at gdbtest2.cpp:16 16 const T& GetValue () const { return Num; } (gdb) s Number Box "9" deleted ~NumBox (this=0x100120a8) at gdbtest2.cpp:11 11 Next = 0; (gdb) s NumChain<int>::RemoveBox (this=0x10012008, item_to_remove=@0xffffe200) at gdbtest2.cpp:51 51 current = 0; 1: *this->pointer = {Num = 0, Next = 0x0} (gdb) s 53 return 0; 1: *this->pointer = {Num = 0, Next = 0x0} (gdb) s 0x10000d1c 66 return -1; 1: *this->pointer = {Num = 0, Next = 0x0} |
從上面的跟蹤過程當中,您能夠看到 list->pointer
在刪除 Number Box "9" 以後指向了 Number Box "0"。這個邏輯並不正確,由於在刪除 Number Box "9" 以後,list->pointer
應該指向的是 Number Box "8"。如今很是顯然咱們應該在第 50 行以前添加一條語句pointer = pointer->GetNext();
,以下所示:
清單 18. 在第 50 行以前添加一條 pointer = pointer->GetNext(); 語句
49 } else { 50 pointer = pointer->GetNext(); 51 delete current; 52 current = 0; 53 } 54 return 0; |
將新修改以後的程序保存爲 gdbtest3.cpp,而後編譯並再次運行。
清單 19. 再次運行程序(gdbtest3.cpp)
# g++ -g -o gdbtest3 gdbtest3.cpp # ./gdbtest3 Number Box "0" created Number Box "1" created Number Box "2" created Number Box "3" created Number Box "4" created Number Box "5" created Number Box "6" created Number Box "7" created Number Box "8" created Number Box "9" created list created Number Box "9" deleted Number Box "8" deleted Number Box "7" deleted Number Box "6" deleted Number Box "5" deleted Number Box "4" deleted Number Box "3" deleted Number Box "2" deleted Number Box "1" deleted Number Box "0" deleted |
這纔是咱們指望的結果。
在 GDB 中有一些特殊的命令能夠用於多線程應用程序的調試。下面這個例子給出了一個死鎖狀況,並介紹瞭如何使用這些命令來檢查多線程應用程序中的問題:
清單 20. 多線程的例子
#include <stdio.h> #include "pthread.h> pthread_mutex_t AccountA_mutex; pthread_mutex_t AccountB_mutex; struct BankAccount { char account_name[1]; int balance; }; struct BankAccount accountA = {"A", 10000 }; struct BankAccount accountB = {"B", 20000 }; void * transferAB (void* amount_ptr) { int amount = *((int*)amount_ptr); pthread_mutex_lock(&AccountA_mutex); if (accountA.balance < amount) { printf("There is not enough memory in Account A!\n"); pthread_mutex_unlock(&AccountA_mutex); pthread_exit((void *)1); } accountA.balance -=amount; sleep(1); pthread_mutex_lock(&AccountB_mutex); accountB.balance +=amount; pthread_mutex_unlock(&AccountA_mutex); pthread_mutex_unlock(&AccountB_mutex); } void * transferBA (void* amount_ptr) { int amount = *((int*)amount_ptr); pthread_mutex_lock(&AccountB_mutex); if (accountB.balance < amount) { printf("There is not enough memory in Account B!\n"); pthread_mutex_unlock(&AccountB_mutex); pthread_exit((void *)1); } accountB.balance -=amount; sleep(1); pthread_mutex_lock(&AccountA_mutex); accountA.balance +=amount; pthread_mutex_unlock(&AccountB_mutex); pthread_mutex_unlock(&AccountA_mutex); } int main(int argc, char* argv[]) { int threadid[4]; pthread_t pthread[4]; int transfer_amount[4] = {100, 200, 300, 400}; int final_balanceA, final_balanceB; final_balanceA=accountA.balance-transfer_amount[0]- transfer_amount[1]+transfer_amount[2]+transfer_amount[3]; final_balanceB=accountB.balance+transfer_amount[0] +transfer_amount[1]-transfer_amount[2]-transfer_amount[3]; if (threadid[0] = pthread_create(&pthread[0], NULL, transferAB, (void*)&transfer_amount[0]) " 0) { perror("Thread #0 creation failed."); exit (1); } if (threadid[1] = pthread_create(&pthread[1], NULL, transferAB, (void*)&transfer_amount[1]) " 0) { perror("Thread #1 creation failed."); exit (1); } if (threadid[2] = pthread_create(&pthread[2], NULL, transferBA, (void*)&transfer_amount[2]) < 0) { perror("Thread #2 creation failed."); exit (1); } if (threadid[3] = pthread_create(&pthread[3], NULL, transferBA, (void*)&transfer_amount[3]) < 0) { perror("Thread #3 creation failed."); exit (1); } printf("Transitions are in progress.."); while ((accountA.balance != final_balanceA) && (accountB.balance != final_balanceB)) { printf(".."); } printf("\nAll the money is transferred !!\n"); } |
使用 gcc 來編譯這個程序,以下所示:
# gcc -g -o gdbtest2 gdbtest2.c -L/lib/tls -lpthread |
程序 gdbtest2 會掛起,不會返回一條 All the money is transferred !!
消息。
將 gdb 附加到正在運行的進程上,從而瞭解這個進程內部正在發生什麼。
清單 21. 將 gdb 附加到正在運行的進程上
# ps -ef |grep gdbtest2 root 9510 8065 1 06:30 pts/1 00:00:00 ./gdbtest2 root 9516 9400 0 06:30 pts/4 00:00:00 grep gdbtest2 # gdb -pid 9510 GNU gdb 6.2.1 Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "ppc-suse-linux". Attaching to process 9510 Reading symbols from /root/test/gdbtest2...done. Using host libthread_db library "/lib/tls/libthread_db.so.1". Reading symbols from /lib/tls/libpthread.so.0...done. [Thread debugging using libthread_db enabled] [New Thread 1073991712 (LWP 9510)] [New Thread 1090771744 (LWP 9514)] [New Thread 1086577440 (LWP 9513)] [New Thread 1082383136 (LWP 9512)] [New Thread 1078188832 (LWP 9511)] Loaded symbols for /lib/tls/libpthread.so.0 Reading symbols from /lib/tls/libc.so.6...done. Loaded symbols for /lib/tls/libc.so.6 Reading symbols from /lib/ld.so.1...done. Loaded symbols for /lib/ld.so.1 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 (gdb) info thread 5 Thread 1078188832 (LWP 9511) 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 4 Thread 1082383136 (LWP 9512) 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 3 Thread 1086577440 (LWP 9513) 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 2 Thread 1090771744 (LWP 9514) 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 1 Thread 1073991712 (LWP 9510) 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 (gdb) |
從 info thread
命令中,咱們能夠了解到除了主線程(thread #1)以外的全部線程都在等待函數 __lll_lock_wait ()
完成。
使用 thread apply threadno where
命令來查看每一個線程到底運行到了什麼地方:
清單 22. 查看每一個線程運行到了什麼地方
(gdb) thread apply 1 where Thread 1 (Thread 1073991712 (LWP 9510)): #0 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 #1 0x0ff4ac28 in __write_nocancel () from /lib/tls/libc.so.6 Previous frame identical to this frame (corrupt stack?) #0 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 (gdb) thread apply 2 where Thread 2 (Thread 1090771744 (LWP 9514)): #0 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 #1 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #2 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #3 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #4 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 Previous frame inner to this frame (corrupt stack?) #0 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 (gdb) thread apply 3 where Thread 3 (Thread 1086577440 (LWP 9513)): #0 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 #1 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #2 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #3 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #4 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 Previous frame inner to this frame (corrupt stack?) #0 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 (gdb) thread apply 4 where Thread 4 (Thread 1082383136 (LWP 9512)): #0 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 #1 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #2 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #3 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #4 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 Previous frame inner to this frame (corrupt stack?) #0 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 (gdb) thread apply 5 where Thread 5 (Thread 1078188832 (LWP 9511)): #0 0x0ffe94ec in __lll_lock_wait () from /lib/tls/libpthread.so.0 #1 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #2 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #3 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 #4 0x0ffe466c in pthread_mutex_lock () from /lib/tls/libpthread.so.0 Previous frame inner to this frame (corrupt stack?) #0 0x0ff4ac40 in __write_nocancel () from /lib/tls/libc.so.6 |
每一個線程都試圖對一個互斥體進行加鎖,可是這個互斥體倒是不可用的,多是由於有另一個線程已經對其進行加鎖了。從上面的證據咱們能夠判斷程序中必定存在死鎖。您還能夠看到哪一個線程如今擁有這個互斥體。
清單 23. 查看哪一個線程擁有互斥體
(gdb) print AccountA_mutex $1 = {__m_reserved = 2, __m_count = 0, __m_owner = 0x2527, __m_kind = 0, __m_lock = {__status = 1, __spinlock = 0}} (gdb) print 0x2527 $2 = 9511 (gdb) print AccountB_mutex $3 = {__m_reserved = 2, __m_count = 0, __m_owner = 0x2529, __m_kind = 0, __m_lock = {__status = 1, __spinlock = 0}} (gdb) print 0x2529 $4 = 9513 (gdb) |
從上面的命令中,咱們能夠看出 AccontA_mutex
是被線程 5(LWP 9511)加鎖(擁有)的,而 AccontB_mutex
是被線程 3(LWP 9513)加鎖(擁有)的。
爲了解決上面的死鎖狀況,能夠按照相同的順序對互斥體進行加鎖,以下所示:
清單 24. 按照相同的順序對互斥體進行加鎖
. . void * transferAB (void* amount_ptr) { int amount = *((int*)amount_ptr); pthread_mutex_lock(&AccountA_mutex); pthread_mutex_lock(&AccountB_mutex); if (accountA.balance < amount) { printf("There is not enough memory in Account A!\n"); pthread_mutex_unlock(&AccountA_mutex); pthread_exit((void *)1); } accountA.balance -=amount; sleep(1); accountB.balance +=amount; pthread_mutex_unlock(&AccountA_mutex); pthread_mutex_unlock(&AccountB_mutex); } void * transferBA (void* amount_ptr) { int amount = *((int*)amount_ptr); pthread_mutex_lock(&AccountA_mutex); pthread_mutex_lock(&AccountB_mutex); if (accountB.balance < amount) { printf("There is not enough memory in Account B!\n"); pthread_mutex_unlock(&AccountB_mutex); pthread_exit((void *)1); } accountB.balance -=amount; sleep(1); accountA.balance +=amount; pthread_mutex_unlock(&AccountA_mutex); pthread_mutex_unlock(&AccountB_mutex); } . . |
或者對每一個賬號單獨進行加鎖,以下所示:
清單 25. 對每一個賬號單獨進行加鎖
. . void * transferAB (void* amount_ptr) { int amount = *((int*)amount_ptr); pthread_mutex_lock(&AccountA_mutex); if (accountA.balance < amount) { printf("There is not enough memory in Account A!\n"); pthread_mutex_unlock(&AccountA_mutex); pthread_exit((void *)1); } accountA.balance -=amount; sleep(1); pthread_mutex_unlock(&AccountA_mutex); pthread_mutex_lock(&AccountB_mutex); accountB.balance +=amount; pthread_mutex_unlock(&AccountB_mutex); } void * transferBA (void* amount_ptr) { int amount = *((int*)amount_ptr); pthread_mutex_lock(&AccountB_mutex); if (accountB.balance < amount) { printf("There is not enough memory in Account B!\n"); pthread_mutex_unlock(&AccountB_mutex); pthread_exit((void *)1); } accountB.balance -=amount; sleep(1); pthread_mutex_unlock(&AccountB_mutex); pthread_mutex_lock(&AccountA_mutex); accountA.balance +=amount; pthread_mutex_unlock(&AccountA_mutex); } . . . |
要調試 64 位的應用程序(使用 GCC 的 –m64
選項或 IBM XL C/C++ 編譯器的 –q64
選項編譯),應該使用一個特別的 gdb 版本 gdb64。
Java? 調試器 JDB 是一個用來調試 Java 類的命令行調試器。它提供了對本地或遠程 Java 虛擬機(JVM)的檢查和調試功能。其二進制文件是 jdb。
JDB 是與 java 編譯器 javac 一塊兒打包的,在 java2 rpm 包中。
有不少方法均可以開始一個 jdb 會話。最多見的方法是讓 jdb 啓動一個新的 Java 虛擬機,其中運行要調試的應用程序的主類。這能夠經過在命令行中使用命令 jdb 替換 java 來實現。例如,若是您的應用程序的主類是 appClass,那麼就使用下面的命令在 JDB 中調試這個程序:
# jdb appClass |
另一種使用 jdb 的方法是將其附加到一個早已在運行的 JVM 上。要使用 jdb 進行調試的 VM 必須是使用下面的選項啓動的:
# java -Xdebug -Xnoagent - Xrunjdwp:transport=dt_socket,server=y,suspend=n, address=8888 -Djava.compiler=NONEappClass |
而後您就可使用下面的命令將 jdb 附加到這個 VM 上:
# jdb -attach 8888 |
jdb 最經常使用的命令與 gdb 的相似。詳細信息請參考 表 1。
使用圖形模式的調試器相對於命令行調試器的一個優勢是,在調試器中單步執行程序的同時能夠看到對應的每行源代碼。
GNU DDD(Data Display Debugger)就是一個調試器(例如 GDB 和 JDB)的圖形化前端。除了常見的前端特性(例如查看源代碼)以外,DDD 還經過將要顯示的數據結構以交互式的圖形化方式進行顯示而聞名。
對於 SLES 9 來講,用於 PowerPC 的 DDD 二進制文件是在 SUSE Linux SDK CD 中單獨提供的,也能夠從 Novell 公司的網站上進行下載(參閱 參考資料)。RedHat 在 RHEL AS 4 CD 中提供了 DDD 的 rpm 包。
圖 2 是在使用 DDD 來調試 清單 19 (gdbtest3.cpp)中的例子時的截圖。
圖 2. DDD 截屏
默認狀況下,DDD 使用 gdb 做爲後端調試器;要切換到 jdb,請使用 ddd -jdb 來啓動 DDD。
有關 DDD 的更多信息,請參考 GNU 項目 Web 站點上的 DDD 部分(參閱 參考資料)。
strace
命令是能夠在 Linux on POWER 架構上使用的一個功能很是強大的工具。它能夠顯示用戶空間的應用程序所執行的所有系統調用。strace
能夠以符號表的形式顯示這些調用的參數和返回值。strace
從內核接收信息,並不須要採用任何特殊的方式來構建內核。要跟蹤的應用程序也不須要爲 strace 從新進行編譯,當咱們沒法訪問應用程序的源代碼時,這是很是方便的一種方法。
下面的例子使用 strace
來跟蹤一個普通用戶執行 cat /etc/shadow 的過程,而後將跟蹤到的結果打印到 strace.cat.out
中。這個程序依然正常運行,可是在使用 strace
運行時速度稍微有些慢;最後,咱們獲得一個跟蹤文件。
$ strace -o strace.cat.out cat /etc/shadow cat: /etc/shadow: Permission denied $ |
跟蹤文件一般都很大。即便對這個簡單的例子來講,strace.cat.out
也有 111 行長。這個文件的最後包含下面的行:
清單 26. strace.cat.out 文件中的部分行
88 open("/usr/lib/locale/en_US.UTF-8/LC_NUMERIC", O_RDONLY) = -1 ENOENT (No such file or directory) 89 open("/usr/lib/locale/en_US.utf8/LC_NUMERIC", O_RDONLY) = 3 90 fstat64(3, {st_mode=S_IFREG|0644, st_size=54, ...}) = 0 91 mmap(NULL, 54, PROT_READ, MAP_PRIVATE, 3, 0) = 0x4010f000 92 close(3) = 0 93 open("/usr/lib/locale/en_US.UTF-8/LC_CTYPE", O_RDONLY) = -1 ENOENT (No such file or directory) 94 open("/usr/lib/locale/en_US.utf8/LC_CTYPE", O_RDONLY) = 3 95 fstat64(3, {st_mode=S_IFREG|0644, st_size=208464, ...}) = 0 96 mmap(NULL, 208464, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40110000 97 close(3) = 0 98 fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0 99 open("/etc/shadow", O_RDONLY|O_LARGEFILE) = -1 EACCES (Permission denied) 100 write(2, "cat: ", 5) = 5 101 write(2, "/etc/shadow", 11) = 11 102 open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 103 open("/usr/share/locale/en_US.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 104 open("/usr/share/locale/en_US/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 105 open("/usr/share/locale/en.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 106 open("/usr/share/locale/en.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 107 open("/usr/share/locale/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory) 108 write(2, ": Permission denied", 19) = 19 109 write(2, "\n", 1) = 1 110 close(1) = 0 111 exit_group(1) |
注意在第 99 行處,命令會失效,由於系統調用 open("/etc/shadow", O_RDONLY|O_LARGEFILE)
失效了,返回了一個 EACCESS 錯誤代碼,這說明權限不符合。
在有些狀況中,應用程序可能會掛起,而且不能響應諸如 ctrl+c(SIGINT)之類的信號。這說明應用程序正在調用的系統調用在其內核模式下掛起了,一直不會返回用戶模式。strace
能夠很是有用地用來判斷是哪個系統調用,以及傳給這個系統調用的參數是什麼。可能性最大的狀況是參數的位置不對而致使了問題。
與 gdb64 是用於 64 位應用程序的 gdb 相似,strace64
也用來跟蹤 64 位應用程序所請求的系統調用。
在 Linux on POWER 平臺上可使用不少工具來幫助調試應用程序。本文中介紹的工具能夠幫助您解決不少編碼的問題。諸如 Valgrind 之類的工具能夠顯示內存泄漏的位置、非法讀/寫以及相似的內容,這能夠解決內存管理的問題。
使用 gdb 和 jdb 有助於解決那些致使應用程序異常結束的問題,以及致使非預期或不想要的結果的 bug。DDD 工具能夠幫助簡化調試任務,方法是經過將代碼的執行與源代碼行聯繫在一塊兒,並在數據顯示窗口中可視化地顯示數據結構。另外,strace 是一個功能很是強大的工具,它能夠用來跟蹤應用程序的全部系統調用。所以,下一次當您要在 Linux 上修復 bug 時,能夠試用一下這些工具。
我要感謝 Linda Kinnunen 爲我提供文檔模板,並幫我審閱本文;感謝 John Engel 和 Chakarat Skawratananond 所提供的技術幫助以及對本文的審閱。
本文所介紹的信息僅僅限於本文的環境,並不提供任何擔保。
本文中介紹的全部客戶例子都是用來解釋這些客戶如何使用 IBM 產品的,以及他們達到了什麼結果。針對每一個客戶的實際環境,成本和性能可能會有很大的差別。
非 IBM 產品中的信息來源於這些產品的供應商所公開發表的資料,或能夠公開使用的源代碼,這並不說明 IBM 產品對它們的承認。非 IBM 產品的價格和性能都來源於可公開使用的信息,包括供應商的公告和供應商的全球主頁。IBM 沒有對這些產品進行測試,也不保證性能、能力數據的正確性,以及其餘有關非 IBM 產品的聲明的正確性。有關非 IBM 產品的功能的問題應該由這些產品的供應商來解決。
全部有關 IBM 未來方向和興趣的聲明均可能會不加任何通知而發生變化或撤銷,這只是爲了表示咱們的目標。有關具體的方向聲明的全文,請與您本地的 IBM 辦公室或 IBM 認證的分銷商聯繫。
有些信息是爲了解決預期未來的功能。這種信息不會做爲對未來任何產品所能實現特定級別的性能、功能或上市週期的承諾的最終聲明。這些承諾只在 IBM 產品聲明中作出。本文中提到這些信息是爲了說明 IBM 目前的投資和開發活動是真正爲了幫助客戶實現未來的計劃。
性能是在一個受控的環境中使用標準的 IBM 基準測試結果而得出的。用戶本身環境中的實際吞吐量或性能可能會有很大的不一樣,這取決於不少方面,例如用戶做業流中的程序數量,I/O 配置狀況,存儲配置,以及所處理的任務負載。所以,咱們並不保證每一個用戶能得到的吞吐量和性能方面的提升都與本文中介紹的相同。
- 參與論壇討論。
- 請參考如下資料:
- 「Debugging Memory Problems」(Linux Magazine,2003 年 5 月)介紹了更多有關 Valgrind 內存調試的內容。
- Valgrind HOW TO 也介紹了更多有關 Valgrind 內存調試的內容。
- Linux on Power Architecture Developer's Corner:介紹了更多有關 Linux on Power 的內容。您能夠在這裏找到不少技術文檔、培訓、下載和產品信息。
- developerWorks Linux 專區:能夠找到更多爲 Linux 開發人員準備的資料
- Linux at IBM:能夠找到硬件、軟件和服務,以及爲新手和專家提供的建議。
- 「從 Solaris 向 Linux on POWER 遷移指南」(developerWorks,2005 年 2 月)幫助您加速移植。學習 Solaris 和 Linux on POWER 上的區別是在移植過程當中常常碰到的一個問題。
- 得到產品和技術
Calvin Sze 是 IBM 的 IBM eServer Solutions Enablement 組織的一名 Linux 顧問。他住在德州奧斯汀。Calvin 的主要角色是幫助解決方案開發人員將本身的應用程序移植到 Linux on POWER 上。Calvin 參與 Linux 和 AIX 平臺上的軟件開發和系統集成已超過 10 年的時間了。您能夠經過 calvins@us.ibm.com 與 Calvin 聯繫。
http://www.ibm.com/developerworks/cn/linux/l-pow-debug/