(轉自:https://www.cnblogs.com/xiaomanon/p/4203671.html)
ldd和nm是Linux下兩個非常實用的程式分析工具。其中,ldd是用來分析程式運作時需要依賴的動态連結庫的工具,nm是用來檢視指定程式中的符号表資訊的工具。
1 ldd
格式:ldd [options] file
功能:列出file運作所需的共享庫
參數:
-d 執行重定位并報告所有丢失的函數
-r 執行對函數和對象的重定位并報告丢失的任何函數或對象
[email protected]:~/Documents/sys_programming$ whereis ldd ldd: /usr/bin/ldd /usr/share/man/man1/ldd.1.gz [email protected]:~/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指令:
[email protected]:~/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指令的輸出結果:
[email protected]:~/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 [email protected]@GLIBC_2.0 08048674 T _fini U [email protected]@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 [email protected]@GLIBC_2.0 0804850d T main U [email protected]@GLIBC_2.0 U [email protected]@GLIBC_2.0 U [email protected]@GLIBC_2.0 08048480 t register_tm_clones U [email protected]@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。
? : 該符号類型沒有定義。