開發中的輔助工具(六)
在嵌入式的開發中,我們在整個項目中的代碼編寫及目標構建上一般只花費 20% 的時間,剩下的 80% 的時間適用於測試、調試以及 bug 修復的。那麽我們該如何提高開發效率呢?工欲善其事必先利其器,我們可以借助於一些工具,從而提高開發的效率。GNU 為 GCC 編譯器提供了配套的輔助工具集(Binutils)
工具名 | 功能簡介 |
add2line | 將代碼地址轉換為對應的程序引導 |
strip | 提出可執行程序中的調試信息 |
ar | 將目標文件打包成為靜態庫 |
nm | 列出目標文件中的符號及對應地址 |
objdump | 查看程序段信息及反匯編 |
size | 查看目標文件中的段大小 |
strings | 查看目標文件中的字符串 |
下來我們來一一的介紹下這幾個工具。
1、addr2line : 將制定復制轉換為對應的文件名和行號,常用於分析和定位內存訪問錯誤的問題。來個示例代碼進行分析說明
func.c 源碼
#include <stdio.h> int* g_pointer; int main() { *g_pointer = (int)"D.T.Software"; return 0; }
test.c 源碼
#include <stdio.h> int g_global = 0; int g_test = 1; extern int* g_pointer; extern void func(); int main(int argc, char *argv[]) { printf("&g_global = %p\n", &g_global); printf("&g_test = %p\n", &g_test); printf("&g_pointer = %p\n", &g_pointer); printf("g_pointer = %p\n", g_pointer); printf("&func = %p\n", &func); printf("&main = %p\n", &main); func(); return 0; }
我們先來分析下這份代碼。我們在前面直接定義 int 類型的指針,但是並沒有將其指為 NULL。那麽此時 g_pointer 已經指向了內存的 0 地址處,在 main 函數中操作 0 地址處,這肯定會引起段錯誤。我們來看看編譯運行結果
我們看到 g_pointer 是個空指針,後面就直接發生段錯誤,如果這是個很大的項目,幾十萬行的代碼,相信我們定位問題就很困難了。那麽此時我們該如何定位問題呢?addr2line 工具便出場了。使用的步驟如下:1、開啟 core dump 選項:ulimit -c unlimited;2、運行程序,並生產崩潰時的 core 文件,執行導致程序崩潰的測試用例;3、讀取 core 文件,獲取 IP 寄存器的值:dmesg core;4、使用 add2line 定位代碼行:addr2line 地址 -f -e test.out。如下
我們看到此時已經生成了 core 文件,我們來 dmesg core 看看 IP 寄存器的值是什麽
我們看到 IP 寄存器的值是 0x080484b8,然後利用 addr2line 工具就可以直接定位到問題的所在了。
2、strip:用於剔除程序文件中的調試信息,減少目標程序的大小。一般在程序發布前都需要將調試信息剔除,過多的調試信息可能影響程序的執行效率。我們來看看它的用法,strip test.out
我們看到在經過剔除之後,它的文件大小幾乎減少了一半。使用它的註意事項:1、幾乎所有的調試工具都依賴於目標文件中的調試信息,調試信息的運用能夠快速定位問題;2、使用 gcc 編譯程序時使用 -g 選項生成調試信息,發布程序時再考慮是否使用 strip 剔除調試信息。那麽這時如果想要利用 core 文件定位問題就不可以了,因為 core 文件只能定位出調試版本的信息
我們看到它是定位不出問題的所在的。
3、ar : 打包目標文件,ar crs libname.a x.o y.o;解壓目標文件,ar x libname.a。下來看看是如何使用的
我們利用打包和解壓命令從而就可以直接給別人發第三方庫文件。如果我們只有第三方的庫文件而想使用其中的一個 .o 文件,那麽就可以使用解壓命令來進行使用其中的一個文件。
4、nm:用於列出目標文件中的標識符(變量名、函數名),輸出結果由三部分組成{地址、段以及標識符}。示例如下
段標識說明如下
我們來看看 func.o 和 test.o 文件中的標識符
我們看到在 func.o 文件中 func 是位於代碼段的,它的偏移量為0。在這沒有經過鏈接,所以顯示的都是相對地址。g_pointer 屬於未定義的標識符,相對偏移量為 4。下來我們看看鏈接後的地址
我們看到經過鏈接後的地址是絕對地址。
5、objdump : 反匯編目標問阿金,查看匯編到源碼的映射。objdump -d func.o 或者 objdump -S func.o。查看目標文件中的詳細段信息,objdump -h test.out。
objdump -h 的輸出說明如下
使用輸出信息如下
我們再來看看鏈接後的 test.out 的詳細信息。
我們看到它的 VMA 和 LMA 是一樣的,也就是說,虛存地址和加載地址是一樣的。在進行運行程序的時候,首先是為可執行文件分配虛存,接著通過 file off 獲取到相對位置,通過復制段信息過去,加載目標地址到虛存上。最後是執行程序。
6、size 用來獲取目標文件中的所有段大小,size test.out;strings 用來獲取目標文件中的所有字符串常量。如下
那麽我們獲取它們的大小和字符串有什麽意義呢?在嵌入式的開發中,資源往往是非常受限的,因此我們就必須得嚴格控制目標文件的大小,以防止超出其界限造成不可預料的錯誤;我們如果想要獲取某些特定的字符串時就不必去看代碼了,直接用 strings 就可以看到全部的字符串了,以提高開發效率。
開發中的輔助工具(六)