動態庫和Linux調試技術

Linux支持共享庫已經有悠久的歷史了,不再是什麼新概念了。大家都知道如何編譯、連接以及動態加載(dlopen/dlsym/dlclose) 共享庫。但是,可能很多人,甚至包括一些高手,對共享庫相關的一些環境變量認識模糊。當然,不知道這些環境變量,也可以用共享庫,但是,若知道它們,可能就會用得更好。下面介紹一些常用的環境變量,希望對家有所幫助:

ld的環境變量

LD_LIBRARY_PATH 這個環境變量是大家最爲熟悉的,它告訴loader:在哪些目錄中可以找到共享庫。可以設置多個搜索目錄,這些目錄之間用冒號分隔開。在linux下,還提供了另外一種方式來完成同樣的功能,你可以把這些目錄加到/etc/ld.so.conf中,或則在/etc/ld.so.conf.d裏創建一個文件,把目錄加到這個文件裏。當然,這是系統範圍內全局有效的,而環境變量只對當前shell有效。按照慣例,除非你用上述方式指明,loader是不會在當前目錄下去找共享庫的,正如shell不會在當前目前找可執行文件一樣。

LD_PRELOAD 這個環境變量對於程序員來說,也是特別有用的。它告訴loader:在解析函數地址時,優先使用LD_PRELOAD裏指定的共享庫中的函數。這爲調試提供了方便,比如,對於C/C++程序來說,內存錯誤最難解決了。常見的做法就是重載malloc系列函數,但那樣做要求重新編譯程序,比較麻煩。使用LD_PRELOAD機制,就不用重新編譯了,把包裝函數庫編譯成共享庫,並在LD_PRELOAD加入該共享庫的名稱,這些包裝函數就會自動被調用了。在linux下,還提供了另外一種方式來完成同樣的功能,你可以把要優先加載的共享庫的文件名寫在/etc/ld.so.preload裏。當然,這是系統範圍內全局有效的,而環境變量只對當前shell有效。

LD_ DEBUG 這個環境變量比較好玩,有時使用它,可以幫助你查找出一些共享庫的疑難雜症(比如同名函數引起的問題)。同時,利用它,你也可以學到一些共享庫加載過程的知識。它的參數如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW 這個環境變量與dlopen中的flag的意義是一致,只是dlopen中的flag適用於顯示加載的情況,而BIND_NOW/BIND_NOT適用於隱式加載。

LD_PROFILE/LD_PROFILE_OUTPUT:爲指定的共享庫產生profile數據,LD_PROFILE指定共享庫的名稱,LD_PROFILE_OUTPUT指定輸出profile文件的位置,是一個目錄,且必須存在,默認的目錄爲/var/tmp/或/var/profile。通過profile數據,你可以得到一些該共享庫中函數的使用統計信息。

Linux下的調試工具

隨着XP的流行,人們越來越注重軟件的前期設計、後期的實現,以及貫穿於其中的測試工作,經過這個過程出來的自然是高質量的軟件。甚至有人聲稱XP會淘汰調試器!這當然是有一定道理的,然而就目前的現實來看,這還是一種理想。在日常工作中,調試工具還是必不可少的。在Linux下,調試工具並非只有gdb,還有很多其它調試工具,它們都各有所長,側重方面也有所不同。本文介紹幾種筆者常用的調試工具:

1.         mtrace
在linux下開發應用程序,用C/C++語言的居多。內存泄露和內存越界等內存錯誤,無疑是其中最頭疼的問題之一。glibc爲解決內存錯誤提供了兩種方案:

一種是hook內存管理函數。hook內存管理函數後,你可以通過記下內存分配的歷史記錄,在程序終止時查看是否有內存泄露,這樣就可以找出內存泄露的地方了。你也可以通過在所分配內存的首尾寫入特殊的標誌,在釋放內存時檢查該標誌是否被破壞了,這樣就可以達到檢查內存越界問題的目的。

另外一種方法更簡單,glibc已經爲第一種方案提供了默認的實現,你要做的只是在特定的位置調用mtrace/muntrace兩個函數,它們的函數原型如下:
       #include <mcheck.h>
       void mtrace(void);
void muntrace(void);
你可能會問,在哪裏調這兩種函數最好?這沒有固定的答案,要視具體情況而定。對於小程序來說,在進入main時調用mtrace,在退出main函數時調用muntrace。對於大型軟件,這樣做可能會記錄過多的信息,分析這些記錄會比較慢,這時可以在你所懷疑代碼的兩端調用。

另外,還需要設置一個環境變量MALLOC_TRACE,它是一個文件名,要保證當前用戶有權限創建和寫入該文件。glibc的內存管理器會把內存分配的歷史信息寫入到MALLOC_TRACE指定的文件中。

程序運行完畢後,使用mtrace工具分析這些內存分配歷史信息,可以查出內存錯誤的位置(mtrace在glibc-utils軟件包裏)。

2.         strace
在編程時,檢查函數的返回值是一種好習慣。對於像glibc等標準C的函數,光檢查返回值是不夠的,還需要檢查errno的值。這樣的程序往往顯得冗長,不夠簡潔。同時也可能是出於偷懶的原因,大多數程序裏並沒有做這樣的檢查。

這樣的程序,一旦出現錯誤,用調試器一步一步定位錯誤,然後想法查出錯誤的原因,也是可以的,不過比較麻煩,對調試器來說有些大材小用,不太可取。這時,用strace命令可能會更方便一點。它可以顯示各個系統調用/信號的執行過程和結果。比如文件打開出錯,一眼就看出來了,連錯誤的原因(errno)都知道。

3.         binutil
binutil是一系列的工具,你可能根本不知道它們的存在,但是沒有它們你卻寸步難行。Binutil包括下列工具:
ld - the GNU linker.
as - the GNU assembler.
addr2line - Converts addresses into filenames and line numbers.
ar - A utility for creating, modifying and extracting from archives.
c++filt - Filter to demangle encoded C++ symbols.
gprof - Displays profiling information.
nlmconv - Converts object code into an NLM.
nm - Lists symbols from object files.
objcopy - Copys and translates object files.
objdump - Displays information from object files.
ranlib - Generates an index to the contents of an archive.
readelf - Displays information from any ELF format object file.
size - Lists the section sizes of an object or archive file.
strings - Lists printable strings from files.
strip - Discards symbols.
windres - A compiler for Windows resource files.
其中部分工具對調試極有幫助,如:
你可以用objdump反彙編,查看目標文件或可執行文件內部信息。
你可以用addr2line把機器地址轉換到代碼對應的位置。
你可以用nm查看目標文件或可執行文件中的各種符號。
       你可以用gprof分析各個函數的使用情況,找出性能的瓶頸所在(這需要加編譯選項)。

4.         ld-linux
現在加載ELF可執行文件的工作,已經落到ld-linux.so.2頭上了。你可能會問,這與有調試程序有關係嗎?有的。比如,在linux中,共享庫裏所有非static的函數/全局變量都是export的,更糟的是C語言中沒有名字空間這個概念,導致函數名極易衝突。在多個共享庫中,名字衝突引起的BUG是比較難查的。這時,你可以通過設置LD_ DEBUG環境變量,來觀察ld-linux.so加載可執行文件的過程,從中可以得到不少幫助信息。LD_ DEBUG的取值如下:
libs        display library search paths
reloc       display relocation processing
files       display progress for input file
symbols     display symbol table processing
bindings    display information about symbol binding
versions    display version dependencies
all         all previous options combined
statistics  display relocation statistics
unused      determined unused DSOs
help        display this help message and exit
5.         gdb
對於真正意義的調試器來說,gdb在linux下是獨一無二的。它有多種包裝,有字符界面的,也有圖形界面的,有單獨運行的,也有集成到IDE中的。gdb功能強大,圖形界面的gdb容易上手一點,但功能無疑受到了一些限制,相信大部分高手還是願意使用字符界面的。Gdb太常用了,這裏不再多說。

6.         gcc/boundschecker
相信很多人用過win32下的BoundsChecker(Compuware公司)和Purify(IBM公司)兩個工具吧。它們的功能實在太強大了,絕非能通過重載內存管理函數就可以做到,它們在編譯時插入了自己的調試代碼。

gcc也有個擴展,通過在編譯時插入調試代碼,來實現更強大的檢查功能。當然這要求重新編譯gcc,你可以到http://sourceforge.net/projects/boundschecking/ 下載gcc的補丁。它的可移植性非常好,筆者曾一個ARM 平臺項目裏使用過,效果不錯。

7.         valgrind
最好的東西往往最後才見到。Valgrind是我的最愛,用習慣了,寫的程序不在valgrind下跑一遍,就像沒有寫單元測試程序一樣,有點放心不下。它有BoundsChecker/Purify的功能,而且速度更快。

有點遺憾的是valgrind目前只支持x86平臺,當然,這對大多數情況已經足夠了。

你可以到http://valgrind.org/ 下載最新版本。
發佈了43 篇原創文章 · 獲贊 14 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章