GDB內存調試初探一

  • 背景

嵌入式應用開發過程中,經常出現應用崩潰的缺陷,這些問題絕大多數與內存相關。根本的原因是,C/C++編程語言本身是內存、線程不安全的;當然還有次要的原因:開發隊伍能力不足,應用軟件結構不合理,偏重於多線程軟件開發而輕視多進程軟件方案的優勢等。這一切讓我想起了“劣幣驅逐良幣”的悲劇:因爲大多數嵌入式應用開發者只會C/C++編程,因此不得不沿用老掉牙的編程語言和開發模式。當然,這也與嵌入式開發團隊所在的環境相關:一個不尊重軟件開發理論、忽視軟件設計重要性的單位,一本正經地製造缺陷的時同,偶爾生成勉強可用的軟件——必然是搬起石頭砸自己的腳。

此類應用崩潰問題相當棘手,要解決這些問題需要多鍛鍊,多調試,多總結。本文及後續文章將以GNU Libc語言庫的內存分配模塊爲gdb調試對象,分享筆者的一些調試過程;希望對讀者有一定的幫助,也希望牛人大佬多多批評指證。

 

  • 調試環境說明

我們使用前一篇博客中提到的嵌入式調試開發工具,壓縮包名稱爲LinuxARM.tar.xz;安裝方法及下載鏈接詳見前文。這裏需要說明的是,該工具使用了linaro.org提供的交叉編譯器,編譯器中的glibc動態庫文件都是帶有調試信息的,安裝調試開發工具後,可以在lib/debug文件目錄中找到:

只要修改可執行文件的動態鏈接器爲/lib/ld-linux-armhf.so.d,並且依賴的C語言庫文件名修改爲libc.so.d,即可以加載帶有調試信息的C語言動態庫(其實是部分glibc庫),使用gdb調試起來就更方便了。

 

  • 下載GNU Libc 源碼

Linaro官方提供了上面提到的交叉編譯器中的glibc源碼(與GNU 官方的glibc源碼存在差異),可以通過以下指令下載得到:

git clone --depth 8 https://git.linaro.org/toolchain/glibc.git -b release/2.25/master

下圖爲筆者的下載的glibc源碼的最後一個提交:

下載該源碼並不是爲了編譯glibc,而是爲了分析glibc的內存分配模塊功能的實現,同時方便gdb調試。

 

  • 製作簡單調試應用

筆者編寫了一個簡單的C語言應用,在main函數執行之後調用了fgets(…)函數。很慶幸此時C語言庫中的內存分配模塊沒有被初始化,這樣調試可以簡單一些。讀者可以自行編寫簡單的應用,在main函數體中調用malloc(…)之類的內存分配函數即可。交叉編譯後,將可執行文件(如下圖可執行文件名爲memory)複製到手機或嵌入式設備上,並修改其依賴的動態鏈接器及libc動態庫:

修改之後使用ldd查看可執行文件依賴的動態庫,均爲兩個重要的debug版本的動態庫。

 

  • 調試Glibc的內存分配模塊初始化鉤子

通過查看glibc源碼,得知初始化glibc內存分配模塊的函數名稱爲ptmalloc_init;於是給此函數加上斷點後運行簡單應用:

結果不知爲何斷點加到malloc_hook_ini(…)函數中了。這是一個“美麗”的錯誤,具體原因待查:總之應用執行到malloc_hook_init(…)函數中便觸發斷點並停了下來。索性就來調試此函數吧:

__libc_malloc(…)函數在分配內存之前,會檢查__malloc_hook函數指針,此時因爲內存分配模塊未初始化,該函數指針指向了malloc_hook_ini(…)函數,它調用了ptmalloc_init(…)函數,完成內存分配模塊初始化的操作。

掌握gdb調試應用軟件的核心點是,能夠以彙編指令的視角,結合C/C++源碼,理解並能夠預測應用軟件的執行過程。接下來反彙編malloc_hook_ini(…)函數主體,結合上圖的代碼,理解彙編的大致功能:

在函數malloc_hook_ini中,有一條賦值語句:

__malloc_hook = NULL;

結合對此函數的反彙編,我們預測標紅色箭頭的r1寄存器即爲__malloc_hook靜態變量的內存地址,在寫入NULL之前,__malloc_hook靜態變量保存的即爲malloc_hook_ini函數的地址。首先,使用info address __malloc_hook查看靜態變理的地址爲0xb6fd3c10;之後在str指令處加上斷點。當斷點觸發後,查看r1寄存器,與info address …給出的靜態變量地址是相同的,這就驗證了我們的“預測”;其二,地址0xb6fd3c10保存的函數指針確實也指向malloc_hook_ini函數的入口地址(見上圖兩處標黃箭頭),偏移量爲0x1(thumb指令集的特點):這也再次驗證了我們的“預測”。

 

  • 總結

最後值得強調的是,使用gdb調試C/C++語言編寫的應用,應當以彙編指令的角度,結合源代碼理解並預測應用運行的流程。這就要求嵌入式開發者具有彙編的知識,也需要了解C/C++的調用規則。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章