程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> C語言基礎知識 >> 基於c語言中調試工具的用法匯總(不包含gdb)

基於c語言中調試工具的用法匯總(不包含gdb)

編輯:C語言基礎知識

是不是只有編譯的時候才知道程序寫了錯誤?有沒有在未編譯的時候就讓機器幫你檢查錯誤的工具呢?
答案是:有!!

splint工具.用一個最簡單的HELLO WORLD來表述:
=====================================
代碼如下:

/*錯誤很明顯*/
#include <stdio.h>

int main(void)
{
   print("hello world\n", s);
   return
}

-----------------------------------------------------
casio$ splint -strict foo.c
Splint 3.1.1 --- 03 Nov 2006

foo.c: (in function main)
foo.c:5:2: Unrecognized identifier: print <-------找到print不是printf
Identifier used in code has not been declared. (Use -unrecog to inhibit
warning)
foo.c:5:25: Unrecognized identifier: s <-------未定義變量s
foo.c:5:2: Statement has no effect (possible undected modification through call
to unconstrained function print): print("hello wor... <---------不存在prinf函數
Statement has no visible effect --- no values are modified. It may modify
something through a call to an unconstrained function. (Use -noeffectuncon to
inhibit warning)
foo.c:7:2: Parse Error. (For help on parse errors, see splint -help <------對應return語法錯誤
parseerrors.)
*** Cannot continue.

=============================================

cxref

cxref程序分析C源代碼並且生成一個交叉引用。他顯示了每一個符號在程序中何處被提到。他使用標記星號的每一個符號定義位置生成一個排序列表,如下所示:

SYMBOL FILE FUNCTION LINE
BASENID prog.c — *12 *96 124 126 146 156 166
BINSIZE prog.c — *30 197 198 199 206
BUFMAX prog.c — *44 45 90
BUFSIZ /usr/include/stdio.h — *4
EOF /usr/include/stdio.h — *27
argc prog.c — 36
prog.c main *37 61 81
argv prog.c — 36
prog.c main *38 61
calldata prog.c — *5
prog.c main 64 188
calls prog.c — *19
prog.c main 54

在作者的機子上,前面的輸入在程序的源碼目錄中使用下面的命令來生成的:

$ cxref *.c *.h

但是實際的語法因為版本的不同而不同。查看我們系統的文檔或是man手冊可以得到更多的信息。

cflow <使用時輸入cflow *.c就可以了.可以馬上搞清除什麼函數調用了什麼.>

cflow程序會輸出一個函數調用樹,這是一個顯示函數調用關系的圖表。這對於查看程序結構來了解他是如何操作的以及了解對於一個函數有哪些影響是十分有用的。一些版本的cflow可以同時作用於目標文件與源代碼。查看手冊頁我們可以了解更為詳細的操作。

下面是由一個cflow版本(cflow-2.0)所獲得的例子輸出,這個版本的cflow版本是由Marty Leisner維護的,並且可以網上得到。

1 file_ungetc {prcc.c 997}
2 main {prcc.c 70}
3 getopt {}
4 show_all_lists {prcc.c 1070}
5 display_list {prcc.c 1056}
6 printf {}
7 exit {}
8 exit {}
9 usage {prcc.c 59}
10 fprintf {}
11 exit {}

從這個輸出中我們可以看到main函數調用show_all_lists,而show_all_lists調用display_list,display_list本身調用printf。

這個版本cflow的一個選項就是-i,這會生成一個反轉的流程圖。對於每一個函數,cflow列出調用他的其他函數。這聽起來有些復雜,但是實際上並不是這樣。下面是一個例子。

19 display_list {prcc.c 1056}
20 show_all_lists {prcc.c 1070}
21 exit {}
22 main {prcc.c 70}
23 show_all_lists {prcc.c 1070}
24 usage {prcc.c 59}
...
74 printf {}
75 display_list {prcc.c 1056}
76 maketag {prcc.c 487}
77 show_all_lists {prcc.c 1070}
78 main {prcc.c 70}
...
99 usage {prcc.c 59}
100 main {prcc.c 70}

例如,這告訴我們調用exit的函數有main,show_all_lists與usage。

使用prof/gprof執行性能測試

當我們試著追蹤一個程序的性能問題時一個十分有用的技術就是執行性能測試(execution profiling)。通常被特殊的編譯器選項以及輔助程序所支持,一個程序的性能顯示他在哪裡花費時間。

prof程序(以及其GNU版本gprof)會由性能測試程序運行時所生成的執行追蹤文件中輸出報告。一個可執行的性能測試是由指定-p選項(對prof)或是-pg選項(對gprof)所生成的:

$ cc -pg -o program program.c

這個程序是使用一個特殊版本的C庫進行鏈接的並且被修改來包含監視代碼。對於不同的系統結果也許不同,但是通常是由安排頻繁被中斷的程序以及記錄執行位置來做到的。監視數據被寫入當前目錄中的一個文件,mon.out(對於gprof為gmon.out)。

$ ./program
$ ls -ls
2 -rw-r--r-- 1 neil users 1294 Feb 4 11:48 gmon.out

然後用命令:gprof ./program可以查看到下面的報告

prof/gprof程序讀取這些監視數據並且生成一個報告。查看其手冊頁可以詳細了解其程序選項。下面以gprof輸出作為一個例子:

cumulative self self total
time seconds seconds calls ms/call ms/call name
18.5 0.10 0.10 8664 0.01 0.03 _doscan [4]
18.5 0.20 0.10 mcount (60)
14.8 0.28 0.08 43320 0.00 0.00 _number [5]
9.3 0.33 0.05 8664 0.01 0.01 _format_arg [6]
7.4 0.37 0.04 112632 0.00 0.00 _ungetc [8]
7.4 0.41 0.04 8757 0.00 0.00 _memccpy [9]
7.4 0.45 0.04 1 40.00 390.02 _main [2]
3.7 0.47 0.02 53 0.38 0.38 _read [12]
3.7 0.49 0.02 w4str [10]
1.9 0.50 0.01 26034 0.00 0.00 _strlen [16]
1.9 0.51 0.01 8664 0.00 0.00 strncmp [17]

內存調試

富含bug而且難於跟蹤調試的一個區域就是動態內存分配。如果我們編譯一個使用malloc與free來分配內存的程序,很重要的一點就是我們要跟蹤我們所分配的內存塊,並且保證不要使用已經釋放的內存塊。

通常,內存是由malloc分配並且賦給一個指針變量的。如果指針變量被修改了,而又沒有其他的指針來指向這個內存塊,他就會變為不可訪問的內存塊。這就是一個內存洩露,而且會使得我們程序尺寸變大。如果我們洩露了大量的內存,那麼我們的系統就會變慢並且會最終用盡內存。

如 果我們在超出一個分配的內存塊的結束部分(或是在一個內存塊的開始部分)寫入數據,我們很有可能會破壞malloc庫來跟蹤分配所用的數據結構。在這種情 況下,在將來的某個時刻,調用malloc,或者甚至是free,就會引起段錯誤,而我們的程序就會崩潰。跟蹤錯誤發生的精確點是非常困難的,因為很可能 他在引起崩潰的事件發生以前很一段時間就已經發生了。

不必奇怪的是,有一些工具,商業或是自由的,可以有助於處理這兩種問題類型。例如,有許多不同的malloc與free版本,其中的一些包含額外的代碼在分配與回收上進行檢測嘗試檢測一個內存塊被釋放兩次或是其他一些濫用類型的情況。

ElectricFence

ElectricFence 庫是由Bruce Perens開發的,並且在一些Linux發行版本中作為一個可選的組件來提供,例如RedHat,而且已經可以在網絡上獲得。他嘗試使用Linux的虛 擬內存工具來保護malloc與free所使用的內存,從而在內存被破壞時終止程序。

試驗--ElectricFence

下面的程序,efence.c,使用malloc分配一個內存塊,然後在超出塊結束處寫入數據。讓我們看一下會發生什麼情況。
代碼如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
   char *ptr = (char *) malloc(1024);
   ptr[0] = 0;
   /* Now write beyond the block */
   ptr[1024] = 0;/*寫非法*/
   exit(0);
}

當我們編譯運行這個程序時,我們並不會看到進一步的行為。然而,似乎malloc所分配的內存區域有一些問題,而我們實際上已經遇到了麻煩。

$ cc -o efence efence.c
$ ./efence
$

然而,如果我們使用ElectricFence庫,libefence.a來鏈接這個程序,我們就會得到一個即時的響應。

$ cc -o efence efence.c -lefence
$ ./efence
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <[email protected]>
Segmentation fault
$

在調試器下運行可以定位這個問題:

$ cc -g -o efence efence.c -lefence
$ gdb efence
(gdb) run
Starting program: /home/neil/BLP3/chapter10/efence
[New Thread 1024 (LWP 1869)]
Electric Fence 2.2.0 Copyright (C) 1987-1999 Bruce Perens <[email protected]>
Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 1024 (LWP 1869)]
0x080484ad in main () at efence.c:10
10 ptr[1024] = 0;
(gdb)

工作原理

Electric替換malloc並且將函數與計算機處理器的虛擬內存特性相關聯來阻止非法的內存訪問。當這樣的訪問發生時,就會拋出一個段錯誤信息從而可以終止程序。

valgrind

valgrind是一個可以檢測我們已經討論過的許多問題的工具。事實上,他可以檢測數據訪問錯誤與內存洩露。也許他並沒有被包含在我們的Linux發行版本中,但是我們可以在http://developer.kde.org/~sewardj處得到。

程序並不需要使用valgrind重新編譯,而我們甚至可以調用一個正在運行的程序的內存訪問。他很值得一看,他已經用在主要的開發上,包含KDE版本3。

試驗--valgrind

下面的程序,checker.c,分配一些內存,讀取超過那塊內存限制的位置,在其結束處之外寫入數據,然後使其不能訪問。
代碼如下:

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
   char *ptr = (char *) malloc(1024);
   char ch;
   /* Uninitialized read */
   ch = ptr[1024];/*讀非法*/
   /* Write beyond the block */
   ptr[1024] = 0;/*寫非法*/
   /* Orphan the block */
   ptr = 0;/*野指針*/
   exit(0);
}

要使用valgrind,我們只需要簡單的運行valgrind命令,傳遞我們希望檢測的選項,其後是使用其參數運行的程序。

當我們使用valgrind來運行我們的程序時,我們可以看到診斷出許多問題:

$ valgrind --leak-check=yes -v ./checker
==3436== valgrind-1.0.4, a memory error detector for x86 GNU/Linux.
==3436== Copyright (C) 2000-2002, and GNU GPL'd, by Julian Seward.
==3436== Estimated CPU clock rate is 452 MHz
==3436== For more details, rerun with: -v
==3436==
==3436== Invalid read of size 1
==3436== at 0x8048397: main (checker.c:10)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436== Address 0x42AD1424 is 0 bytes after a block of size 1024 alloc'd
==3436== at 0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436== by 0x8048389: main (checker.c:6)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436==
==3436== Invalid write of size 1
==3436== at 0x80483A4: main (checker.c:13)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436== Address 0x42AD1424 is 0 bytes after a block of size 1024 alloc'd
==3436== at 0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436== by 0x8048389: main (checker.c:6)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436==
==3436== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
==3436== malloc/free: in use at exit: 1024 bytes in 1 blocks.
==3436== malloc/free: 1 allocs, 0 frees, 1024 bytes allocated.
==3436== For counts of detected errors, rerun with: -v
==3436== searching for pointers to 1 not-freed blocks.
==3436== checked 3468724 bytes.
==3436==
==3436== definitely lost: 1024 bytes in 1 blocks.
==3436== possibly lost: 0 bytes in 0 blocks.
==3436== still reachable: 0 bytes in 0 blocks.
==3436==
==3436== 1024 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3436== at 0x4003CA75: malloc (vg_clientfuncs.c:100)
==3436== by 0x8048389: main (checker.c:6)
==3436== by 0x402574F2: __libc_start_main (in /lib/libc.so.6)
==3436== by 0x80482D1: exit@@GLIBC_2.0 (in /home/neil/BLP3/chapter10/checker)
==3436==
==3436== LEAK SUMMARY:
==3436== definitely lost: 1024 bytes in 1 blocks.
==3436== possibly lost: 0 bytes in 0 blocks.
==3436== still reachable: 0 bytes in 0 blocks.
==3436== Reachable blocks (those to which a pointer was found) are not shown.
==3436== To see them, rerun with: --show-reachable=yes
==3436== $

這裡我們可以看到錯誤的讀取與寫入已經被捕獲,而所關注的內存塊與他們被分配的位置相關聯。我們可以使用調試器在出錯點斷開程序。

valgrind 有許多選項,包含特定的錯誤類型表達式與內存洩露檢測。要檢測我們的例子洩露,我們必須使用一個傳遞給valgrind的選項。當程序結束時要檢測內存洩 露,我們需要指定 --leak-check=yes。我們可以使用valgrind --help得到一個選項列表。

工作原理

我們的程序在valgrind的控制下執行,這會檢測我們程序所執行的各種動作,並且執行許多檢測,包括內存訪問。如果程序訪問一個已分配的內存塊並且訪問 是非法的,valgrind就會輸出一條信息。在程序結束時,一個垃圾收集例程就會運行來檢測是否在存在分配的內存塊沒有被釋放。這些孤兒內存也會被報告。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved