今天我們來看一看開發(fā)中的輔助工具,那么什么是開發(fā)環(huán)境呢?在我們的印象中,開發(fā)環(huán)境就指的是編寫代碼的環(huán)境。其實(shí)不然,開發(fā)環(huán)境包括三大部分:構(gòu)建環(huán)境、調(diào)試環(huán)境以及測試環(huán)境。構(gòu)建環(huán)境便指得是代碼編寫、程序編譯以及版本控制等;調(diào)試環(huán)境則指的是用于定位問題的輔助工具集;測試環(huán)境指的是用于驗(yàn)證目標(biāo)程序是否滿足用戶的顯性需求和隱形需求。顯性需求指的是客戶的要求,而隱形需求則指的是一些用戶沒有要求到的但是必須具備的要求。比如一個(gè)應(yīng)用程序在 win7 系統(tǒng)上可以運(yùn)行起來,在 win10 系統(tǒng)上也要能運(yùn)行起來。
我們提供的服務(wù)有:網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、大冶ssl等。為上1000+企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的大冶網(wǎng)站制作公司
在嵌入式的開發(fā)中,我們?cè)谡麄€(gè)項(xiàng)目中的代碼編寫及目標(biāo)構(gòu)建上一般只花費(fèi) 20% 的時(shí)間,剩下的 80% 的時(shí)間適用于測試、調(diào)試以及 bug 修復(fù)的。那么我們?cè)撊绾翁岣唛_發(fā)效率呢?工欲善其事必先利其器,我們可以借助于一些工具,從而提高開發(fā)的效率。GNU 為 GCC 編譯器提供了配套的輔助工具集(Binutils),網(wǎng)址是 http://www.gnu.org/software/binutils/ ;其中的一些工具介紹如下
工具名 | 功能簡介 |
add2line | 將代碼地址轉(zhuǎn)換為對(duì)應(yīng)的程序引導(dǎo) |
strip | 提出可執(zhí)行程序中的調(diào)試信息 |
ar | 將目標(biāo)文件打包成為靜態(tài)庫 |
nm | 列出目標(biāo)文件中的符號(hào)及對(duì)應(yīng)地址 |
objdump | 查看程序段信息及反匯編 |
size | 查看目標(biāo)文件中的段大小 |
strings | 查看目標(biāo)文件中的字符串 |
下來我們來一一的介紹下這幾個(gè)工具。
1、addr2line : 將制定復(fù)制轉(zhuǎn)換為對(duì)應(yīng)的文件名和行號(hào),常用于分析和定位內(nèi)存訪問錯(cuò)誤的問題。來個(gè)示例代碼進(jìn)行分析說明
func.c 源碼
#includeint* g_pointer; int main() { *g_pointer = (int)"D.T.Software"; return 0; }
test.c 源碼
#includeint 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; }
我們先來分析下這份代碼。我們?cè)谇懊嬷苯佣x int 類型的指針,但是并沒有將其指為 NULL。那么此時(shí) g_pointer 已經(jīng)指向了內(nèi)存的 0 地址處,在 main 函數(shù)中操作 0 地址處,這肯定會(huì)引起段錯(cuò)誤。我們來看看編譯運(yùn)行結(jié)果
我們看到 g_pointer 是個(gè)空指針,后面就直接發(fā)生段錯(cuò)誤,如果這是個(gè)很大的項(xiàng)目,幾十萬行的代碼,相信我們定位問題就很困難了。那么此時(shí)我們?cè)撊绾味ㄎ粏栴}呢?addr2line 工具便出場了。使用的步驟如下:1、開啟 core dump 選項(xiàng):ulimit -c unlimited;2、運(yùn)行程序,并生產(chǎn)崩潰時(shí)的 core 文件,執(zhí)行導(dǎo)致程序崩潰的測試用例;3、讀取 core 文件,獲取 IP 寄存器的值:dmesg core;4、使用 add2line 定位代碼行:addr2line 地址 -f -e test.out。如下
我們看到此時(shí)已經(jīng)生成了 core 文件,我們來 dmesg core 看看 IP 寄存器的值是什么
我們看到 IP 寄存器的值是 0x080484b8,然后利用 addr2line 工具就可以直接定位到問題的所在了。
2、strip:用于剔除程序文件中的調(diào)試信息,減少目標(biāo)程序的大小。一般在程序發(fā)布前都需要將調(diào)試信息剔除,過多的調(diào)試信息可能影響程序的執(zhí)行效率。我們來看看它的用法,strip test.out
我們看到在經(jīng)過剔除之后,它的文件大小幾乎減少了一半。使用它的注意事項(xiàng):1、幾乎所有的調(diào)試工具都依賴于目標(biāo)文件中的調(diào)試信息,調(diào)試信息的運(yùn)用能夠快速定位問題;2、使用 gcc 編譯程序時(shí)使用 -g 選項(xiàng)生成調(diào)試信息,發(fā)布程序時(shí)再考慮是否使用 strip 剔除調(diào)試信息。那么這時(shí)如果想要利用 core 文件定位問題就不可以了,因?yàn)?core 文件只能定位出調(diào)試版本的信息
我們看到它是定位不出問題的所在的。
3、ar: 打包目標(biāo)文件,ar crs libname.a x.o y.o;解壓目標(biāo)文件,ar x libname.a。下來看看是如何使用的
我們利用打包和解壓命令從而就可以直接給別人發(fā)第三方庫文件。如果我們只有第三方的庫文件而想使用其中的一個(gè) .o 文件,那么就可以使用解壓命令來進(jìn)行使用其中的一個(gè)文件。
4、nm:用于列出目標(biāo)文件中的標(biāo)識(shí)符(變量名、函數(shù)名),輸出結(jié)果由三部分組成{地址、段以及標(biāo)識(shí)符}。示例如下
段標(biāo)識(shí)說明如下
我們來看看 func.o 和 test.o 文件中的標(biāo)識(shí)符
我們看到在 func.o 文件中 func 是位于代碼段的,它的偏移量為0。在這沒有經(jīng)過鏈接,所以顯示的都是相對(duì)地址。g_pointer 屬于未定義的標(biāo)識(shí)符,相對(duì)偏移量為 4。下來我們看看鏈接后的地址
我們看到經(jīng)過鏈接后的地址是絕對(duì)地址。
5、objdump: 反匯編目標(biāo)問阿金,查看匯編到源碼的映射。objdump -d func.o 或者 objdump -S func.o。查看目標(biāo)文件中的詳細(xì)段信息,objdump -h test.out。
objdump -h 的輸出說明如下
使用輸出信息如下
我們?cè)賮砜纯存溄雍蟮?test.out 的詳細(xì)信息。
我們看到它的 VMA 和 LMA 是一樣的,也就是說,虛存地址和加載地址是一樣的。在進(jìn)行運(yùn)行程序的時(shí)候,首先是為可執(zhí)行文件分配虛存,接著通過 file off 獲取到相對(duì)位置,通過復(fù)制段信息過去,加載目標(biāo)地址到虛存上。最后是執(zhí)行程序。
6、size用來獲取目標(biāo)文件中的所有段大小,size test.out;strings用來獲取目標(biāo)文件中的所有字符串常量。如下
那么我們獲取它們的大小和字符串有什么意義呢?在嵌入式的開發(fā)中,資源往往是非常受限的,因此我們就必須得嚴(yán)格控制目標(biāo)文件的大小,以防止超出其界限造成不可預(yù)料的錯(cuò)誤;我們?nèi)绻胍@取某些特定的字符串時(shí)就不必去看代碼了,直接用 strings 就可以看到全部的字符串了,以提高開發(fā)效率。