ldd和nm是Linux下兩個非常實用的程序分析工具。其中,ldd是用來分析程序運行時需要依賴的動態鏈接庫的工具,nm是用來查看指定程序中的符號表信息的工具。
1 ldd
格式:ldd [options] file
功能:列出file運行所需的共享庫
參數:
-d 執行重定位並報告所有丟失的函數
-r 執行對函數和對象的重定位並報告丟失的任何函數或對象
tanghuaming@Thm:~/Documents/sys_programming$ whereis ldd ldd: /usr/bin/ldd /usr/share/man/man1/ldd.1.gz tanghuaming@Thm:~/Documents/sys_programming$ ll /usr/bin/ldd -rwxr-xr-x 1 root root 5420 3月 26 14:59 /usr/bin/ldd*
ldd能夠顯示可執行模塊的dependency,其原理是通過設置一系列的環境變量,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。當LD_TRACE_LOADED_OBJECTS環境變量不為空時,任何可執行程序在運行時,它都會只顯示模塊的dependency,而程序並不真正執行。要不你可以在shell終端測試一下,如下:
(1) export LD_TRACE_LOADED_OBJECTS=1
(2) 再執行任何的程序,如ls等,看看程序的運行結果。
ldd顯示可執行模塊的dependency的工作原理,其實質是通過ld-linux.so(elf動態庫的裝載器)來實現的。我們知道,ld-linux.so模塊會先於executable模塊程序工作,並獲得控制權,因此當上述的那些環境變量被設置時,ld-linux.so選擇了顯示可執行模塊的dependency。
實際上可以直接執行ld-linux.so模塊,如:/lib/ld-linux.so.2 --list program(這相當於ldd program)ldd命令使用方法(摘自ldd --help)
我們選擇一段待測試的應用程序,代碼如下:
//@file tooltest.c //@brief resource sharing between parent-process and sub-process #include <stdio.h> #include <stdlib.h> #include <unistd.h> int global = 1; /*global variable, stored at data section*/ int main(void) { pid_t pid;//to store pid value int stack = 1;//local variable, stored at stack int *heap;//pointer to a heap variable heap = (int *)malloc(sizeof(int)); *heap = 2;//set the heap value to 2 pid = fork();//create a new process if (pid < 0) { //error perror("fail to fork"); exit(-1); } else if (pid == 0) { //sub-process, change values global++; stack++; (*heap)++; //print all values printf("In sub-process, global: %d, stack: %d, heap: %d\n", global, stack, *heap); exit(0); } else { //parent process sleep(2);//sleep 2 secends to make sure the sub-process runs first printf("In parent-process, global: %d, stack: %d, heap: %d\n", global, stack, *heap); } return 0; }
然后,我們編譯並運行ldd命令:
xiaomanon@xiaomanon-machine:~/Documents/c_code$ ldd tooltest linux-gate.so.1 => (0xb775b000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7595000) /lib/ld-linux.so.2 (0xb775c000)
我們可以將ldd的輸出結果分為3列來看:
■ 第一列:程序需要依賴什么庫
■ 第二列:系統提供的與程序需要的庫對應的庫名稱
■ 第三列:依賴庫加載的開始地址
通過上面的這些信息,我們可以總結出下面的用途:
(1) 通過對比第一列和第二列,我們可以知道程序需要的動態鏈接庫和系統實際提供的是否相比配。
(2) 通過第三列,我們可以知道當前動態鏈接庫中的符號在進程地址空間中的起始位置。
2 nm
格式:nm [options] file
功能:列出file中的所有符號
參數:
-C 將符號轉化為用戶級的名字
-s 當用於.a文件即靜態庫時,輸出把符號名映射到定義該符號的模塊或成員名的索引
-u 顯示在file外定義的符號或沒有定義的符號
-l 顯示每個符號的行號,或為定義符號的重定義項
下面是運行nm命令的輸出結果:
xiaomanon@xiaomanon-machine:~/Documents/c_code$ nm tooltest 0804a038 B __bss_start 0804a038 b completed.6590 0804a02c D __data_start 0804a02c W data_start 08048450 t deregister_tm_clones 080484c0 t __do_global_dtors_aux 08049f0c t __do_global_dtors_aux_fini_array_entry 0804a030 D __dso_handle 08049f14 d _DYNAMIC 0804a038 D _edata 0804a03c B _end U exit@@GLIBC_2.0 08048674 T _fini U fork@@GLIBC_2.0 08048688 R _fp_hw 080484e0 t frame_dummy 08049f08 t __frame_dummy_init_array_entry 080487e0 r __FRAME_END__ 0804a034 D global 0804a000 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 08048354 T _init 08049f0c t __init_array_end 08049f08 t __init_array_start 0804868c R _IO_stdin_used w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 08049f10 d __JCR_END__ 08049f10 d __JCR_LIST__ w _Jv_RegisterClasses 08048670 T __libc_csu_fini 08048600 T __libc_csu_init U __libc_start_main@@GLIBC_2.0 0804850d T main U malloc@@GLIBC_2.0 U perror@@GLIBC_2.0 U printf@@GLIBC_2.0 08048480 t register_tm_clones U sleep@@GLIBC_2.0 08048410 T _start 0804a038 D __TMC_END__ 08048440 T __x86.get_pc_thunk.bx
上面便是tooltest這個程序中所有的符號,首先介紹一下上面輸出內容的格式:
■ 第一列:當前符號的地址。
■ 第二列:當前符號的類型(關於類型的說明,可以查看手冊頁man nm詳細閱讀)。
■ 第三列:當前符號的名稱。
使用nm主要有一下幾個方面的幫助:
(1) 判斷指定的程序中有沒有指定的符號,比較常用的方式為:nm –C program | grep symbol
(2) 解決程序編譯時undefined reference的錯誤,以及multiple definition的錯誤。
(3) 查看某個符號的地址,以及在進程空間的大概位置(.bss, .data, .text段,具體可以通過第二列的類型來判斷)。
部分符號類型說明
A : 該符號的值是絕對的,在以后的鏈接過程中,不允許進行改變。這樣的符號值,常常出現在中斷向量表中,例如用符號來表示各個中斷向量函數在中斷向量表中的位置。
B : 該符號的值出現在非初始化數據段(.bss)中。例如,在一個文件中定義全局static int test。則該符號test的類型為b,位於bss section中。其值表示該符號在bss段中的偏移。一般而言,bss段分配於RAM中 。
C : 該符號為common。common symbol是未初始話數據段。該符號沒有包含於一個普通section中。只有在鏈接過程中才進行分配。符號的值表示該符號需要的字節數。例如在一個c文件中,定義int test,並且該符號在別的地方會被引用,則該符號類型即為C。否則其類型為B。
D : 該符號位於初始化數據段中。一般來說,分配到.data section中。例如定義全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},則會分配於初始化數據段中。
G : 該符號也位於初始化數據段中。主要用於small object提高訪問small data object的一種方式。
I : 該符號是對另一個符號的間接引用。
N : 該符號是一個debugging符號。
R : 該符號位於只讀數據段。例如定義全局const int test[] = {123, 123};則test就是一個只讀數據區的符號。注意在cygwin下如果使用gcc直接編譯成MZ格式時,源文件中的test對應_test,並且其符號類型為D,即初始化數據段中。但是如果使用m6812-elf-gcc這樣的交叉編譯工具,源文件中的test對應目標文件的test,即沒有添加下划線,並且其符號類型為R。一般而言,位於rodata section。值得注意的是,如果在一個函數中定義const char *test = “abc”, const char test_int = 3。使用nm都不會得到符號信息,但是字符串“abc”分配於只讀存儲器中,test在rodata section中,大小為4。
S : 符號位於非初始化數據段,用於small object。
T : 該符號位於代碼段text section。
U : 該符號在當前文件中是未定義的,即該符號的定義在別的文件中。例如,當前文件調用另一個文件中定義的函數,在這個被調用的函數在當前就是未定義的;但是在定義它的文件中類型是T。但是對於全局變量來說,在定義它的文件中,其符號類型為C,在使用它的文件中,其類型為U。
V : 該符號是一個weak object。
W : The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
- : 該符號是a.out格式文件中的stabs symbol。
? : 該符號類型沒有定義。