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,還有很多其它調試工具,它們都各有所長,側重方面也有所不同。本文介紹幾種筆者常用的調試工具:
. 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軟體包裡)。
. strace
在程式設計時,檢查函數的傳回值是一種好習慣。對于像glibc等标準C的函數,光檢查傳回值是不夠的,還需要檢查errno的值。這樣的程式往往顯得冗長,不夠簡潔。同時也可能是出于偷懶的原因,大多數程式裡并沒有做這樣的檢查。
這樣的程式,一旦出現錯誤,用調試器一步一步定位錯誤,然後想法查出錯誤的原因,也是可以的,不過比較麻煩,對調試器來說有些大材小用,不太可取。這時,用strace指令可能會更友善一點。它可以顯示各個系統調用/信号的執行過程和結果。比如檔案打開出錯,一眼就看出來了,連錯誤的原因(errno)都知道。
. 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分析各個函數的使用情況,找出性能的瓶頸所在(這需要加編譯選項)。
. ld-linux
現在加載ELF可執行檔案的工作,已經落到ld-linux.so.頭上了。你可能會問,這與有調試程式有關系嗎?有的。比如,在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
. gdb
對于真正意義的調試器來說,gdb在linux下是獨一無二的。它有多種包裝,有字元界面的,也有圖形界面的,有單獨運作的,也有內建到IDE中的。gdb功能強大,圖形界面的gdb容易上手一點,但功能無疑受到了一些限制,相信大部分高手還是願意使用字元界面的。Gdb太常用了,這裡不再多說。
. gcc/boundschecker
相信很多人用過win32下的BoundsChecker(Compuware公司)和Purify(IBM公司)兩個工具吧。它們的功能實在太強大了,絕非能通過重載記憶體管理函數就可以做到,它們在編譯時插入了自己的調試代碼。
gcc也有個擴充,通過在編譯時插入調試代碼,來實作更強大的檢查功能。當然這要求重新編譯gcc,你可以到http://sourceforge.net/projects/boundschecking/ 下載下傳gcc的更新檔。它的可移植性非常好,筆者曾一個ARM 平台項目裡使用過,效果不錯。
. valgrind
最好的東西往往最後才見到。Valgrind是我的最愛,用習慣了,寫的程式不在valgrind下跑一遍,就像沒有寫單元測試程式一樣,有點放心不下。它有BoundsChecker/Purify的功能,而且速度更快。
有點遺憾的是valgrind目前隻支援x86平台,當然,這對大多數情況已經足夠了。
你可以到http://valgrind.org/ 下載下傳最新版本。