今天我們來看一看開發中的輔助工具,那么什么是開發環境呢?在我們的印象中,開發環境就指的是編寫代碼的環境。其實不然,開發環境包括三大部分:構建環境、調試環境以及測試環境。構建環境便指得是代碼編寫、程序編譯以及版本控制等;調試環境則指的是用于定位問題的輔助工具集;測試環境指的是用于驗證目標程序是否滿足用戶的顯性需求和隱形需求。顯性需求指的是客戶的要求,而隱形需求則指的是一些用戶沒有要求到的但是必須具備的要求。比如一個應用程序在 win7 系統上可以運行起來,在 win10 系統上也要能運行起來。
在嵌入式的開發中,我們在整個項目中的代碼編寫及目標構建上一般只花費 20% 的時間,剩下的 80% 的時間適用于測試、調試以及 bug 修復的。那么我們該如何提高開發效率呢?工欲善其事必先利其器,我們可以借助于一些工具,從而提高開發的效率。GNU 為 GCC 編譯器提供了配套的輔助工具集(Binutils),網址是 http://www.gnu.org/software/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 就可以看到全部的字符串了,以提高開發效率。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。