簡單:printf
在合理關鍵的路徑位置添加打印信息是簡單,有時也是有效的方法。如果程序的問題總是在固定的代碼位置復現,那么通過在路徑上添加打印信息就是快速的定位問題出現在哪一代碼行的方法。除了直接使用printf函數外,可以借鑒日志文件的記錄格式來獲取更多的信息,下面是我自己常用來替代日志記錄的宏封裝,可以指定打印信息的級別以便于分析和篩選:
printf是輸出到標準輸出或者終端的,需要人為地實時進行跟蹤或者保存,否則就會失去這些信息。對于中大型的項目來說,大量地使用printf來進行跟蹤和調試是不現實的,也不可能用眼睛去掃描那不斷刷新的打印屏幕。程序運行過程的日志文件記錄是實用規范的用來跟蹤程序運行路徑的方法。
實現一套日志文件記錄接口沒有什么難度,簡單的只需要將上面宏中的printf改成fprintf就可以,因此在嵌入式開發中,往往不同的項目常常會實現自己的日志記錄接口。這只是一種輕量的重復勞動,倒問題不大,除了不利于程序的移植外。此外日志記錄還是有不少細節需要考慮的,比如:循環覆蓋寫、不同線程或進程的同步寫等問題,有時實現很容易會疏漏。這里推薦一個用C語言實現開源日志庫:zlog,它提供了線程安全的日志記錄接口和獨立的日志接口行為配置文件,因此在不修改項目代碼的情況下,你可以通過修改外部的zlog日志格式和行為配置文件來改變日志記錄的格式和是否記錄到日志文件中。
獲取線程ID和名稱
在多線程的程序中,獲取線程的ID有現成的接口,但是獲取線程的名稱沒有。在Linux2.6及更低版本的linux中,線程是使用clone系統調用創建進程來實現的,此時pthread的線程ID和線程本身的進程ID是不一致的。這里提供一個參考的實現來獲取線程ID和名稱:
在linux上常用的調試工具就是gdb,它也是給力的調試利器。因此嵌入式開發中遇到棘手的問題是,首先想到的就是gdb工具。使用gdb進行嵌入式程序的調試有三種途徑:
交叉編譯gdb
如果開發板上的資源(內存和flash)充足的話,可以考慮下載gdb的源代碼直接交叉編譯得到可在開發板上運行的gdb工具,然后在編譯嵌入式程序時加-g參數以保留調試符號信息,就可以直接在開發板上進行gdb的調試了。
gdbserver遠程調試
交叉編譯出的gdb一般會比較大,我這邊使用arm-none-linux-gnueabi工具鏈編出的stripped后的都有3.3M。考慮到資源情況就需要使用gdbserver遠程調試的方法,詳細的步驟參考前文:使用gdbserver遠程調試。這里使用時的說明幾個注意點:
gdb和gdbserver的版本必須是一致的,PC上運行的應該是編譯工具鏈提供的gdb。
PC上加載的程序和庫應該是debug版本的,即not stripped的,以提供符號和代碼信息。
PC上使用的庫的版本應該和開發板上使用的庫版本一致,否則會出現庫不匹配的warning。
PC上使用gdb時首先應該設置好相應的環境變量及信號處理方式,以有效控制程序行為。
PC上可以通過寫gdb的配置文件來實現環境和信號處理方式的設置,使用示例,下面是ak-gdbinit配置文件:
像我在項目中遇到的問題:程序運行10幾個小時或者幾天后偶爾才出現段錯誤,這讓我使用gdbserver時很郁悶,開著gdb+gdbserver等了兩天都不出現段錯誤,而一不小心把gdb給關了就前功盡棄了。這種場景就是使用gdb+core文件調試的選擇了。
首先你得確定你的問題會不會生成core文件,SIGSEGV信號產生時,linux系統會生成core文件,所以我可以使用這種方法來調試。參考《APUE》P235可以知道還有以下信號的默認動作是終止+core:
SIGABRT
SIGBUS
SIGEMT
SIGFPE
SIGILL
SIGIOT
SIGQUIT
SIGSEGV
SIGSYS
SIGTRAP
SIGXCPU
SIGXFSZ
然后就是設置保存core文件的環境和使用,參考之前的:Linux系統中core文件調試方法。
gdb加載core文件時如果出現下面的警告:
使用gdb調試時,對于多線程,以下的一些gdb命令是比較有用的: