by falcon<zhangjinw@gmail.com>
2008-02-29
代碼寫完以后往往要做測試(或驗證)、調(diào)試,可能還要優(yōu)化。
關于測試(或驗證),通常對應著兩個英文單詞verification和validation,在資料[1]中有關于這個的定義和一些深入的討論,在資料[2]中,很多人給出了自己的看法。但是我想正如資料[2]提到的:
“The differences between verification and validation are unimportant
except to the theorist; practitioners use the term V&V to refer to
all ofthe activities that are aimed at making sure the software will
function as required.”
所以,無論測試(或驗證)目的都是為了讓軟件的功能能夠達到需求。測試和驗證通常會通過一些形式化(貌似可以簡單地認為有數(shù)學根據(jù)的)或者非形式化的方法去驗證程序的功能是否達到要求。
而調(diào)試對應英文debug,debug叫“驅(qū)除害蟲”,也許一個軟件的功能達到了要求,但是可能會在測試或者是正常運行時出現(xiàn)異常,因此需要處理它們。
關于優(yōu)化:debug是為了保證程序的正確性,之后就需要考慮程序的執(zhí)行效率,對于存儲資源受限的嵌入式系統(tǒng),程序的大小也可能是優(yōu)化的對象。
很多理論性的東西是在沒有研究過,暫且不說吧。這里只是想把一些需要動手實踐的東西先且記錄和總結(jié)一下,另外很多工具在這里都有提到和羅列,包括Linux內(nèi)核調(diào)試相關的方法和工具。關于更詳細更深入的內(nèi)容還是建議直接看后面的參考資料為妙。
下面的所有演示在如下環(huán)境下進行:
Quote: |
$ uname -a Linux falcon 2.6.22-14-generic #1 SMP Tue Feb 12 07:42:25 UTC 2008 i686 GNU/Linux $ echo $SHELL /bin/bash $ /bin/bash --version | grep bash GNU bash, version 3.2.25(1)-release (i486-pc-linux-gnu) $ gcc --version | grep gcc gcc (GCC) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2) $ cat /proc/cpuinfo | grep "model name" model name : Intel(R) Pentium(R) 4 CPU 2.80GHz
|
1、代碼測試
代
碼測試有很多方面呢,例如運行時間、函數(shù)調(diào)用關系圖、代碼覆蓋度、性能測試(profiling)、內(nèi)存訪問越界(segmentation
fault)、緩沖區(qū)溢出(stack smashing合法地進行非法的內(nèi)存訪問?所以很危險)、內(nèi)存泄露(memory leak)等。
1.1 測試程序的運行時間 time
shell提供了內(nèi)置命令time用于測試程序的執(zhí)行時間,默認顯示結(jié)果包括三部分:實際花費時間(real time)、用戶空間花費時間(user time)和內(nèi)核空間花費時間(kernel time)。
Quote: |
$ time pstree 2>&1 >/dev/null
real 0m0.024s user 0m0.008s sys 0m0.004s
|
time
命令給出了程序本身的運行時間。這個測試原理非常簡單,就是在程序運行(通過system函數(shù)執(zhí)行)前后記錄了系統(tǒng)時間(用times函數(shù)),然后進行求
差就可以。如果程序運行時間很短,運行一次看不到效果,可以考慮采用測試紙片厚度的方法進行測試,類似把很多紙張跌倒一起來測試紙張厚度一樣,我們可以讓
程序運行很多次。
如果程序運行時間太長,執(zhí)行效率很低,那么得考慮程序內(nèi)部各個部分的執(zhí)行情況,從而對代碼進行可能的優(yōu)化。具體可能會考慮到這兩點:
對于C語言程序而言,一個比較宏觀的層次性的輪廓(profile)是函數(shù)調(diào)用圖、函數(shù)內(nèi)部的條件分支構(gòu)成的語句塊,然后就是具體的語句。把握好這樣一個
輪廓后,就可以有針對性地去關注程序的各個部分,包括哪些函數(shù)、哪些分支、哪些語句最值得關注(執(zhí)行次數(shù)越多越值得優(yōu)化,術語叫hotspots)。
對于Linux下的程序而言,程序運行時涉及到的代碼會涵蓋兩個空間,即用戶空間和內(nèi)核空間。由于這兩個空間涉及到地址空間的隔離,在測試或調(diào)試時,可能
涉及到兩個空間的工具。前者絕大多數(shù)是基于gcc的特定參數(shù)和系統(tǒng)的ptrace調(diào)用,而后者往往實現(xiàn)為內(nèi)核的補丁,它們在原理上可能類似,但實際操作時
后者顯然會更麻煩,不過如果你不去hack內(nèi)核,那么往往無須關心后者。
1.2 函數(shù)調(diào)用關系圖 calltree
calltree可以非常簡單方便地反應一個項目的函數(shù)調(diào)用關系圖,雖然諸如gprof這樣的工具也能做到,不過如果僅僅要得到函數(shù)調(diào)用圖,calltree應該是更好的選擇。如果要產(chǎn)生圖形化的輸出可以使用它的-dot參數(shù)也可以參考資料[12]。從這里可以下載到它,ftp://ftp.berlios.de/pub/calltree/calltree-2.3.tar.bz2
關于calltree的實現(xiàn)原理,可以參考資料[13],關于它的詳細用法請參考資料[14]或者它的-h參數(shù)獲取幫助。
這里是一份演示結(jié)果,
Quote: |
$ calltree -b -np -m *.c main: | close | commitchanges | | err | | | fprintf | | ferr | | ftruncate | | lseek | | write | ferr | getmemorysize | modifyheaders | open | printf | readelfheader | | err | | | fprintf | | ferr | | read | readphdrtable | | err | | | fprintf | | ferr | | malloc | | read | truncatezeros | | err | | | fprintf | | ferr | | lseek | | read$
|
這
樣一份結(jié)果對于“反向工程”應該會很有幫助,它能夠呈現(xiàn)一個程序的大體結(jié)構(gòu),對于閱讀和分析源代碼來說是一個非常好的選擇。雖然cscope和ctags
也能夠提供一個函數(shù)調(diào)用的“即時”(在編輯vim的過程中進行調(diào)用)視圖(view),但是calltree卻給了我們一個宏觀的視圖。
不過這樣一個視圖只涉及到用戶空間的函數(shù),如果想進一步給出內(nèi)核空間的宏觀視圖,那么strace和KFT就可以發(fā)揮它們的作用。關于這兩個工具請參考條目[11]列出的相關資料。另外,該視圖也沒有給出庫中的函數(shù),如果要跟蹤呢?需要ltrace工具。
另外,我們發(fā)現(xiàn),calltree僅僅給出了一個程序的函數(shù)調(diào)用視圖,而沒有告訴我們各個函數(shù)的執(zhí)行次數(shù)等情況。如果要關注這些呢?我們有gprof。
1.3 性能測試工具 gprof & kprof
參考資料[3]詳細介紹了這個工具的用法,這里僅挑選其中一個例子來演示。gprof是一個命令行的工具,而KDE桌面環(huán)境下的kprof則給出了圖形化的輸出,這里僅演示前者。
首先來看一段代碼(來自資料[3]),算Fibonacci數(shù)列的,
Code:
[Ctrl+A Select All]
通過calltree看看這段代碼的視圖,
Quote: |
$ calltree -b -np -m *.c main: | fibonacci | | fibonacci .... | printf
|
可以看出程序主要涉及到一個fibonacci函數(shù),這個函數(shù)遞歸調(diào)用自己。為了能夠使用gprof,需要編譯時加上-pg選項,讓gcc加入相應的調(diào)試信息以便gprof能夠產(chǎn)生函數(shù)執(zhí)行情況的報告。
Quote: |
$ gcc -pg -o fib fib.c $ ls fib fib.c
|
運行程序并查看執(zhí)行時間,
Quote: |
$ time ./fib fibonnaci(0) = 0 fibonnaci(1) = 1 fibonnaci(2) = 1 fibonnaci(3) = 2 ... fibonnaci(41) = 165580141 fibonnaci(42) = 267914296
real 1m25.746s user 1m9.952s sys 0m0.072s $ ls fib fib.c gmon.out
|
上面僅僅選取了部分執(zhí)行結(jié)果,程序運行了1分多鐘,代碼運行以后產(chǎn)生了一個gmon.out文件,這個文件可以用于gprof產(chǎn)生一個相關的性能報告。
Quote: |
$ gprof -b ./fib gmon.out Flat profile:
Each sample counts as 0.01 seconds. % cumulative self self total time seconds seconds calls ms/call ms/call name 96.04 14.31 14.31 43 332.80 332.80 fibonacci 4.59 14.99 0.68 main
Call graph
granularity: each sample hit covers 2 byte(s) for 0.07% of 14.99 seconds
index % time self children called name <spontaneous> [1] 100.0 0.68 14.31 main [1] 14.31 0.00 43/43 fibonacci [2] ----------------------------------------------- 2269806252 fibonacci [2] 14.31 0.00 43/43 main [1] [2] 95.4 14.31 0.00 43+2269806252 fibonacci [2] 2269806252 fibonacci [2] -----------------------------------------------
Index by function name
[2] fibonacci [1] main
|
從
這份結(jié)果中可觀察到程序中每個函數(shù)的執(zhí)行次數(shù)等情況,從而找出值得修改的函數(shù)。在對某些部分修改之后,可以再次比較程序運行時間,查看優(yōu)化結(jié)果。另外,這
份結(jié)果還包含一個特別有用的東西,那就是程序的動態(tài)函數(shù)調(diào)用情況,即程序運行過程中實際執(zhí)行過的函數(shù),這和calltree產(chǎn)生的靜態(tài)調(diào)用樹有所不同,它
能夠反應程序在該次執(zhí)行過程中的函數(shù)調(diào)用情況。而如果想反應程序運行的某一時刻調(diào)用過的函數(shù),可以考慮采用gdb的backtrace命令。
類似測試紙片厚度的方法,gprof也提供了一個統(tǒng)計選項,用于對程序的多次運行結(jié)果進行統(tǒng)計。另外,gprof有一個KDE下圖形化接口kprof,這兩部分請參考資料[3]。
gprof雖然給出了函數(shù)級別的執(zhí)行情況,但是如果想關心具體哪些條件分支被執(zhí)行到,哪些語句沒有被執(zhí)行,該怎么辦?
1.4 代碼覆蓋率測試 gcov & ggcov
如果要使用gcov,在編譯時需要加上這兩個選項 -fprofile-arcs -ftest-coverage,這里直接用之前的fib.c做演示。
Quote: |
$ ls fib.c $ gcc -fprofile-arcs -ftest-coverage -o fib fib.c $ ls fib fib.c fib.gcno
|
運行程序,并通過gcov分析代碼的覆蓋度
Quote: |
$ ./fib $ gcov fib.c File 'fib.c' Lines executed:100.00% of 12 fib.c:creating 'fib.c.gcov'
|
12行代碼100%被執(zhí)行到,再查看分支情況,
Quote: |
$ gcov -b fib.c File 'fib.c' Lines executed:100.00% of 12 Branches executed:100.00% of 6 Taken at least once:100.00% of 6 Calls executed:100.00% of 4 fib.c:creating 'fib.c.gcov'
|
發(fā)
現(xiàn)所有函數(shù),條件分支和語句都被執(zhí)行到,說明代碼的覆蓋率很高,不過資料[3]gprof的演示顯示代碼的覆蓋率高并不一定說明代碼的性能就好,因為那些
被覆蓋到的代碼可能能夠被優(yōu)化成性能更高的代碼。那到底那些代碼值得被優(yōu)化呢?執(zhí)行次數(shù)最多的,另外,有些分支雖然都覆蓋到了,但是這個分支的位置可能并
不是理想的,如果一個分支的內(nèi)容被執(zhí)行的次數(shù)很多,那么把它作為最后一個分支的話就會浪費很多不必要的比較時間。因此,通過覆蓋率測試,可以嘗試著剔除那
些從未執(zhí)行過的代碼,通過性能測試,可以找出那些值得優(yōu)化的函數(shù)、分支或者是語句。
如果使用-fprofile-arcs -ftest-coverage參數(shù)編譯完代碼,可以接著用-fbranch-probabilities參數(shù)對代碼進行編譯,這樣,編譯器就可以對根據(jù)代碼的分支測試情況進行優(yōu)化。
Quote: |
$ wc -c fib 16333 fib $ ls fib.gcda #確保fib.gcda已經(jīng)生成,這個是運行fib后的結(jié)果,-fbranch-probabilities一來它 fib.gcda $ gcc -fbranch-probabilities -o fib fib.c #再次運行 $ wc -c fib 6604 fib $ time ./fib ... real 0m21.686s user 0m18.477s sys 0m0.008s
|
可見代碼量減少了,而且執(zhí)行效率會有所提高,當然,這個代碼效率的提高可能還跟其他因素有關,比如gcc還優(yōu)化了一些很平臺相關的指令。
如
果想看看代碼中各行被執(zhí)行的情況,可以直接看fib.c.gcov文件。這個文件的各列依次表示執(zhí)行次數(shù)、行號和該行的源代碼。次數(shù)有三種情況,如果一直
沒有執(zhí)行,那么用####表示;如果該行注釋、函數(shù)聲明等,用-表示;如果是純粹的代碼行,那么用執(zhí)行次數(shù)表示。這樣我們就可以直接分析每一行的執(zhí)行情
況。
gprof也有一個圖形化接口ggprof,是基于gtk+的,適合Gnome桌面的用戶。
現(xiàn)在都已經(jīng)關注到代碼行
了,實際上優(yōu)化代碼的前提是保證代碼的正確性,如果代碼還有很多bug,那么先要debug。不過下面的這些"bug"用普通的工具確實不太方便,雖然可
能,不過這里還是把它們歸結(jié)為測試的內(nèi)容,并且這里剛好承接上gcov部分,gcov能夠測試到每一行的代碼覆蓋情況,而無論是內(nèi)存訪問越界、緩沖區(qū)溢出
還是內(nèi)存泄露,實際上是發(fā)生在具體的代碼行上的。
1.5 內(nèi)存訪問越界 catchesegv, libSegFault.so
"segmentation fault"是很頭痛的一個問題,估計“糾纏”過很多人。這里僅僅演示通過catchsegv腳本測試段錯誤的方法,其他方法見資料[15]。
catchsegv利用系統(tǒng)動態(tài)鏈接的PRELOAD機制(請參考man ld-linux),把庫/lib/libSegFault.so提前l(fā)oad到內(nèi)存中,然后通過它檢查程序運行過程中的段錯誤。
Quote: |
$ cat test.c #include <stdio.h>
int main(void) { char str[10];
sprintf(str, "%s", 111);
printf("str = %s\n", str); return 0; } $ make test $ LD_PRELOAD=/lib/libSegFault.so ./test #等同于catchsegv ./test *** Segmentation fault Register dump:
EAX: 0000006f EBX: b7eecff4 ECX: 00000003 EDX: 0000006f ESI: 0000006f EDI: 0804851c EBP: bff9a8a4 ESP: bff9a27c
EIP: b7e1755b EFLAGS: 00010206
CS: 0073 DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000004 OldMask: 00000000 ESP/signal: bff9a27c CR2: 0000006f
Backtrace: /lib/libSegFault.so[0xb7f0604f] [0xffffe420] /lib/tls/i686/cmov/libc.so.6(vsprintf+0x8c)[0xb7e0233c] /lib/tls/i686/cmov/libc.so.6(sprintf+0x2e)[0xb7ded9be] ./test[0x804842b] /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe0)[0xb7dbd050] ./test[0x8048391] ...
|
從
結(jié)果中可以看出,代碼的sprintf有問題。經(jīng)過檢查發(fā)現(xiàn)它把整數(shù)當字符串輸出,對于字符串的輸出,需要字符串的地址作為參數(shù),而這里的111則剛好被
解釋成了字符串的地址,因此sprintf試圖訪問111這個地址,從而發(fā)生了非法訪問內(nèi)存的情況,出現(xiàn)"segmentation fault"。
1.6 緩沖區(qū)溢出 libsafe.so
緩
沖區(qū)溢出是指棧溢出(stack
smashing),通常發(fā)生在對函數(shù)內(nèi)的局部變量進行賦值操作時,超出了該變量的字節(jié)長度而引起對棧內(nèi)原有數(shù)據(jù)(比如eip,ebp等)的覆蓋,從而引
發(fā)內(nèi)存訪問越界,甚至執(zhí)行非法代碼,導致系統(tǒng)崩潰。關于緩沖區(qū)的詳細原理和實例分析見資料[16]。這里僅僅演示該資料中提到的一種用于檢查緩沖區(qū)溢出的
方法,它同樣采用動態(tài)鏈接的PRELOAD機制提前裝載一個名叫l(wèi)ibsafe.so的庫,你可以從這里獲取它,http://www.sfr-fresh.com/linux/misc/libsafe-2.0-16.tgz,下載下來以后,再解壓,編譯,得到libsafe.so,
下面,演示一個非常簡單的,但可能存在緩沖區(qū)溢出的代碼,并演示libsafe.so的用法。
Quote: |
$ cat test.c $ make test $ LD_PRELOAD=/path/to/libsafe.so ./test ABCDEFGHIJKLMN ABCDEFGHIJKLMN *** stack smashing detected ***: ./test terminated Aborted (core dumped)
|
資
料[6]分析到,如果不能夠?qū)彌_區(qū)溢出進行有效的處理,可能會存在很多潛在的危險。雖然libsafe.so采用函數(shù)替換的方法能夠進行對這類
stack
smashing進行一定的保護,但是無法根本解決問題,alert7大蝦在資料[17]中提出了突破它的辦法,資料[18]提出了另外一種保護機制。
1.7 內(nèi)存泄露 Memwatch, Valgrind, mtrace
堆
棧通常會被弄在一起叫,不過這兩個名詞卻是指進程的內(nèi)存映像中的兩個不同的部分,棧(stack)用于函數(shù)的參數(shù)傳遞、局部變量的存儲等,是系統(tǒng)自動分配
和回收的;而堆(heap)則是用戶通過malloc等方式申請而且需要用戶自己通過free釋放的,如果申請的內(nèi)存沒有釋放,那么將導致內(nèi)存泄露,進而
可能導致堆的空間被用盡;而如果已經(jīng)釋放的內(nèi)存再次被釋放(double-free)則也會出現(xiàn)非法操作。(如果要真正理解堆和棧的區(qū)別,需要理解進程的
內(nèi)存映像,請參考資料[22])
這里演示通過Memwatch來檢測程序中可能存在內(nèi)存泄露,你可以從這里下載到這個工具,http://www.linkdata.se/sourcecode.html
使用這個工具的方式很簡單,只要把它鏈接(ld)到你的可執(zhí)行文件中去,并在編譯時加上兩個宏開關-DMEMWATCH -DMW_STDIO。這里演示一個簡單的例子。
Quote: |
$ cat test.c #include <stdlib.h> #include <stdio.h> #include "memwatch.h"
int main(void) { char *ptr1; char *ptr2;
ptr1 = malloc(512); ptr2 = malloc(512);
ptr2 = ptr1; free(ptr2); free(ptr1); } $ gcc -DMEMWATCH -DMW_STDIO test.c memwatch.c -o test $ cat memwatch.log ============= MEMWATCH 2.71 Copyright (C) 1992-1999 Johan Lindh =============
Started at Sat Mar 1 07:34:33 2008
Modes: __STDC__ 32-bit mwDWORD==(unsigned long) mwROUNDALLOC==4 sizeof(mwData)==32 mwDataSize==32
double-free: <4> test.c(15), 0x80517e4 was freed from test.c(14)
Stopped at Sat Mar 1 07:34:33 2008
unfreed: <2> test.c(11), 512 bytes at 0x8051a14 {FE FE FE FE FE FE FE FE FE FE FE FE FE FE FE FE ................}
Memory usage statistics (global): N)umber of allocations made: 2 L)argest memory usage : 1024 T)otal of all alloc() calls: 1024 U)nfreed bytes totals : 512
|
通過測試,可以看到有一個512字節(jié)的空間沒有被釋放,而另外512字節(jié)空間卻被連續(xù)釋放兩次(double-free)。valgrind和mtrace也可以做類似的工作,請參考資料[4]和mtrace的手冊。
2、代碼調(diào)試
調(diào)試的方法很多,調(diào)試往往要跟蹤代碼的運行狀態(tài),printf是最基本的辦法,然后呢?靜態(tài)調(diào)試方法有哪些,非交互的呢?非實時的有哪些?實時的呢?用于調(diào)試內(nèi)核的方法有哪些?有哪些可以用來調(diào)試匯編代碼呢?
2.1 靜態(tài)調(diào)試:printf + gcc -D(打印程序中的變量)
利
用gcc的宏定義開關(-D)和printf函數(shù)可以跟蹤程序中某個位置的狀態(tài),這個狀態(tài)包括當前一些變量和寄存器的值。調(diào)試時需要用-D開關進行編譯,
在正式發(fā)布程序時則可把-D開關去掉。這樣做比單純用printf方便很多,它可以避免清理調(diào)試代碼以及由此帶來的誤刪除代碼等問題。
Quote: |
$ cat test.c #include <stdio.h> #include <unistd.h>
int main(void) { int i = 0;
#ifdef DEBUG printf("i = %d\n", i);
int t; __asm__ __volatile__ ("movl %%ebp, %0;":"=r"(t)::"%ebp"); printf("ebp = 0x%x\n", t); #endif
_exit(0); } $ gcc -DDEBUG -g -o test test.c $ ./test i = 0 ebp = 0xbfb56d98
|
上面演示了如何跟蹤普通變量和寄存器變量的辦法。跟蹤寄存器變量采用了內(nèi)聯(lián)匯編,關于Linux下的匯編語言開發(fā)請參考資料[19]。
不
過,這種方式不夠靈活,我們無法“即時”獲取程序的執(zhí)行狀態(tài),而gdb等交互式調(diào)試工具不僅解決了這樣的問題,而且通過把調(diào)試器拆分成調(diào)試服務器和調(diào)試客
戶端適應了嵌入式系統(tǒng)的調(diào)試,另外,通過預先設置斷點以及斷點處需要收集的程序狀態(tài)信息解決了交互式調(diào)試不適應實時調(diào)試的問題。
2.2 交互式的調(diào)試(動態(tài)調(diào)試):gdb(支持本地和遠程)/ald(匯編指令級別的調(diào)試)
2.2.1 嵌入式系統(tǒng)調(diào)試方法 gdbserver/gdb
估計大家已經(jīng)非常熟悉GDB(Gnu DeBugger)了,所以這里并不介紹常規(guī)的gdb用法,而是介紹它的服務器/客戶(gdbserver/gdb)調(diào)試方式。這種方式非常適合嵌入式系統(tǒng)的調(diào)試,為什么呢?先來看看這個:
Quote: |
$ wc -c /usr/bin/gdbserver 56000 /usr/bin/gdbserver $ which gdb /usr/bin/gdb $ wc -c /usr/bin/gdb 2557324 /usr/bin/gdb $ echo "(2557324-56000)/2557324" | bc -l .97810210986171482377
|
gdb
比gdbserver大了將近97%,如果把整個gdb搬到存儲空間受限的嵌入式系統(tǒng)中是很不合適的,不過僅僅5K左右的gdbserver即使在只有
8M Flash卡的嵌入式系統(tǒng)中也都足夠了。所以在嵌入式開發(fā)中,我們通常先在本地主機上交叉編譯好gdbserver/gdb。
如果是初次使用這種方法,可能會遇到麻煩,而麻煩通常發(fā)生在交叉編譯gdb和gdbserver時。在編譯gdbserver/gdb前,需要配置(./configure)兩個重要的選項:
--host,指定gdb/gdbserver本身的運行平臺,
--target,指定gdb/gdbserver調(diào)試的代碼所運行的平臺,
關
于運行平臺,通過$MACHTYPE環(huán)境變量就可獲得,對于gdbserver,因為要把它復制到嵌入式目標系統(tǒng)上,并且用它來調(diào)試目標平臺上的代碼,因
此需要把--host和--target都設置成目標平臺;而gdb因為還是運行在本地主機上,但是需要用它調(diào)試目標系統(tǒng)上的代碼,所以需要把--
target設置成目標平臺。
編譯完以后就是調(diào)試,調(diào)試時需要把程序交叉編譯好,并把二進制文件復制一份到目標系統(tǒng)上,并在本地需要保留一份源代碼文件。調(diào)試過程大體如下,首先在目標系統(tǒng)上啟動調(diào)試服務器:
Quote: |
$ gdbserver :port /path/to/binary_file ...
|
然后在本地主機上啟動gdb客戶端鏈接到gdb調(diào)試服務器,(gdbserver_ipaddress是目標系統(tǒng)的IP地址,如果目標系統(tǒng)不支持網(wǎng)絡,那么可以采用串口的方式,具體看手冊)
Quote: |
$ gdb ... (gdb) target remote gdbserver_ipaddress:2345 ...
|
其他調(diào)試過程和普通的gdb調(diào)試過程類似。
2.2.2 匯編代碼的調(diào)試 ald
用gdb調(diào)試匯編代碼貌似會比較麻煩,不過有人正是因為這個原因而開發(fā)了一個專門的匯編代碼調(diào)試器,名字就叫做assembly language debugger,簡稱ald,你可以從這里下載到,http://ald.sourceforge.net/
下載以后,解壓編譯后,我們來調(diào)試一個程序看看。
這里是一段非常簡短的匯編代碼,摘自參考資料[20]
Code:
[Ctrl+A Select All]
演示一下,
Quote: |
//匯編、鏈接、運行 $ as -o test.o test.s $ ld -o test test.o $ ./test "Hello World" Hello World //查看程序的入口地址 $ readelf -h test | grep Entry Entry point address: 0x8048054 //調(diào)試 $ ald test ald> display Address 0x8048054 added to step display list ald> n eax = 0x00000000 ebx = 0x00000000 ecx = 0x00000001 edx = 0x00000000 esp = 0xBFBFDEB4 ebp = 0x00000000 esi = 0x00000000 edi = 0x00000000 ds = 0x007B es = 0x007B fs = 0x0000 gs = 0x0000 ss = 0x007B cs = 0x0073 eip = 0x08048055 eflags = 0x00200292
Flags: AF SF IF ID
Dumping 64 bytes of memory starting at 0x08048054 in hex 08048054: 59 59 59 C6 41 0C 0A 31 D2 B2 0D 31 C0 B0 04 31 YYY.A..1...1...1 08048064: DB CD 80 31 C0 40 CD 80 00 2E 73 79 6D 74 61 62 ...1.@....symtab 08048074: 00 2E 73 74 72 74 61 62 00 2E 73 68 73 74 72 74 ..strtab..shstrt 08048084: 61 62 00 2E 74 65 78 74 00 00 00 00 00 00 00 00 ab..text........
08048055 59 pop ecx
|
可見ald在啟動時就已經(jīng)運行了被它調(diào)試的test程序,并且進入了程序的入口0x8048054,緊接著單步執(zhí)行時,就執(zhí)行了程序的第一條指令popl ecx。
ald的命令很少,而且跟gdb很類似,比如
這個幾個命令用法和名字都類似 help,next,continue,set args,break,file,quit,disassemble,enable,disable等
名字不太一樣,功能對等的,examine對x, enter 對 set variable {int}地址=數(shù)據(jù)
需
要提到的是:linux下的調(diào)試器包括上面的gdb和ald,以及strace等都用到了linux系統(tǒng)提供的ptrace()系統(tǒng)調(diào)用,這個調(diào)用為用戶
訪問內(nèi)存映像提供了便利,如果想自己寫一個調(diào)試器或者想hack一下gdb和ald,那么好好閱讀資料[10]和man ptrace吧。
2.3 實時調(diào)試:gdb tracepoint
對
于程序狀態(tài)受時間影響的程序,用上述普通的設置斷點的交互式調(diào)試方法并不合適,因為這種方式將由于交互時產(chǎn)生的通信延遲和用戶輸入命令的時延而完全改變程
序的行為。所以gdb提出了一種方法以便預先設置斷點以及在斷點處需要獲取的程序狀態(tài),從而讓調(diào)試器自動執(zhí)行斷點處的動作,獲取程序的狀態(tài),從而避免在斷
點處出現(xiàn)人機交互產(chǎn)生時延改變程序的行為。
這種方法叫tracepoints(對應breakpoint),它在gdb的user
manual(見資料[21])里頭有詳細的說明,不過在gdb的官方發(fā)行版中至今都沒有對它的實現(xiàn)。盡管如此,我們還是可以使用它,因為有其他組織做了
相關的工作,并以補丁的方式發(fā)布它。這個補丁你可以從這里獲取ftp://dslab.lzu.edu.cn/pub/gdb_tracepoints
獲
取這個補丁以后,要做的就是把它patch到對應的gdb版本中,然后就是編譯。因為tracepoints只定義在調(diào)試服務器和調(diào)試客戶端這種方式中,
因此在這個實現(xiàn)中也是這樣,如果想用它,你同樣需要編譯gdbserver和gdb,并類似嵌入式系統(tǒng)中的調(diào)試方法一樣調(diào)試它。
編譯好以后通過ftp://dslab.lzu.edu.cn/pub/gdb_tracepoints/paper/tp.pdf和資料[21]就可以使用它。
2.4 調(diào)試內(nèi)核
雖然這里并不會演示如何去hack內(nèi)核,但是相關的工具還是需要簡單提到的,資料[11]列出了絕大部分用于內(nèi)核調(diào)試的工具,這些對你hack內(nèi)核應該會有幫助的。
3、代碼優(yōu)化
除了資料[21]中的實踐之外,我想我“應該”沒有做過其他的項目優(yōu)化工作吧,所以很遺憾,這里無法進行討論了,不過我還是找了很多相關資料的,就讓大家一起分享吧,這些資料都列在條目
里。
實際上呢?“代碼測試”部分介紹的很多工具是為代碼優(yōu)化服務的,更多具體的細節(jié)請參考后面的資料,自己做實驗吧。有任何相關的感興趣的話題歡迎給我郵件zhangjinw@gmail.com。
參考資料:
[1] VERIFICATION AND VALIDATION
http://satc.gsfc.nasa.gov/assure/agbsec5.txt
[2] difference between verification and Validation
http://www.faqs.org/qa/qa-9060.html
[3] Coverage Measurement and Profiling(覆蓋度測量和性能測試,Gcov and Gprof)
http://www.linuxjournal.com/article/6758
[4] Valgrind Usage
A. Valgrind HOWTO
http://www.faqs.org/docs/Linux-HOWTO/Valgrind-HOWTO.html
B. Using Valgrind to Find Memory Leaks and Invalid Memory Use
http://www.cprogramming.com/debugging/valgrind.html
[5] MEMWATCH
http://www.linkdata.se/sourcecode.html
[6] Mastering Linux debugging techniques
http://www.ibm.com/developerworks/linux/library/l-debug/
[7] Software Performance Analysis
http://arxiv.org/pdf/cs.PF/0507073.pdf
[8] Runtime debugging in embedded systems
http://dslab.lzu.edu.cn/docs/publications/runtime_debug.pdf
[9] Tools Provided by System
ltrace,mtrace,strace
[10] Write your own debugger with the support ptrace()
A. Process Tracing Using Ptrace
http://linuxgazette.net/issue81/sandeep.html
http://linuxgazette.net/issue83/sandeep.html
B. Playing with ptrace
http://www.linuxjournal.com/article/6100
http://www.linuxjournal.com/node/6210/print
http://www.ecos.sourceware.org/ml/libc-hacker/1998-05/msg00277.html
[11] Kernel Debugging Relative Tools
A. KGDB
http://dslab.lzu.edu.cn/docs/publications/kernel_gdb.pdf
B. GCOV
http://linuxdevices.com/files/article062/der_herr_gcov.pdf
C. KFI & KFT
http://dslab.lzu.edu.cn/docs/publications/kfi.pdf
D. UML(User Mode Linux)
http://dslab.lzu.edu.cn/docs/publications/uml.pdf
E. GDB Tracepoint
http://dslab.lzu.edu.cn/docs/publications/tp.pdf
F. Tools Collections
http://dslab.lzu.edu.cn/docs/publications/tools.pdf
G. Linux系統(tǒng)內(nèi)核的調(diào)試
http://www.ibm.com/developerworks/cn/linux/l-kdb/
H. 嵌入式Linux內(nèi)核調(diào)試技術
http://www.eepw.com.cn/article/73300.htm
[12] 用Graphviz進行可視化操作──繪制函數(shù)調(diào)用關系圖
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1425.html
[13] 用 Graphviz 可視化函數(shù)調(diào)用
http://www.ibm.com/developerworks/cn/linux/l-graphvis/
[14] 介紹一個linux下生成C代碼調(diào)用樹的好工具calltree
http://www.linuxsir.org/bbs/printthread.php?t=246389
[15] 可惡的"Segmentation faults"之初級總結(jié)篇
http://oss.lzu.edu.cn/blog/article.php?tid_700.html
[16] Linux下緩沖區(qū)溢出攻擊的原理及對策
http://www.ibm.com/developerworks/cn/linux/l-overflow/index.html
[17] 繞過libsafe的保護--覆蓋_dl_lookup_versioned_symbol技術
http://www.xfocus.net/articles/200208/423.html
[18] 介紹Propolice怎樣保護stack-smashing的攻擊
http://www.xfocus.net/articles/200103/78.html
[19] Linux 匯編語言開發(fā)指南
http://www.ibm.com/developerworks/cn/linux/l-assembly/index.html
[20] 為你的可執(zhí)行文件“減肥”
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1547.html
[21] GDB Tracepoints
http://sourceware.org/gdb/current/onlinedocs/gdb_11.html#SEC84
[22] C語言程序緩沖區(qū)注入分析(第一部分:進程的內(nèi)存映像)
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1539.html
Code Optimize Relatie
A. Optimizing C Code
http://www.jukie.net/~bart/slides/c-opt/c-opt.ps
B. Performance programming for scientific computing
http://www.research.ibm.com/perfprog/course/course.html
C. Performance Programming
http://www-cse.ucsd.edu/users/carter/perfprog.html
D. Linux Profiling and Optimization
http://www.cs.princeton.edu/picasso/mats/mats_S07/Lucifredi_Lecture_Feb07.pdf
E. High-level code optimization
http://web.abo.fi/~mats/codeopt2007/handouts/High-level-opt.pdf
F. Code Optimization
http://library.simugraph.com/articles/opti/optimizing.html