1. 程式人生 > >【軟體開發底層知識修煉】七 Binutils輔助工具之- ar工具與nm工具

【軟體開發底層知識修煉】七 Binutils輔助工具之- ar工具與nm工具

上一篇文章學習addr2line與strip工具。點選連結檢視上一篇文章:點選檢視

本篇文章學習兩個工具:ar與nm工具。

1、ar工具

ar工具很簡單,用於將目標檔案打包生成庫或者將目標檔案從庫中解壓出來

  • ar crs libname.a x.o y.o 將x.o y.o打包生成libname.a庫
  • ar x libname.a 將libname.a庫解壓

2、nm工具

nm工具用於列出目標檔案的符號的相關資訊:地址,屬性,名字等

使用nm工具,可以輸出三部分內容:地址,段,識別符號

如下圖的例子;
在這裡插入圖片描述

其中識別符號位於的段資訊,需要說明一下。上述的T代表程式碼段。還有很多其他段識別符號如下圖:
在這裡插入圖片描述

它們具體的信可以參考《程式設計師的自我修養》,參考這本書後,可以更加詳細的說明各個段標識代表什麼意思。如下表:

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。
? 該符號型別沒有定義。

2.1 程式碼案例分析

還是以上一篇文章的程式碼為例(程式碼是執行時錯誤的,但是這不影響我們的實驗);

test.c

#include <stdio.h>

int g_global = 0;
int g_test = 1;

extern int* g_pointer;
extern void func();

int main(int argc, char *argv[])
{
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&g_pointer = %p\n", &g_pointer);
    printf("g_pointer = %p\n", g_pointer);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
    
    func();
	
    return 0;
}

func.c

#include <stdio.h>

int* g_pointer;

void func()
{
	*g_pointer = (int)"D.T.Software";

    return;
}

對以上兩個程式分別進行如下編譯:

  • gcc -g -c func.c -o func.o
  • gcc -g -c test.c -o test.o

然後使用nm工具分別檢視func.o與test.o
在這裡插入圖片描述

通過以上結果,可以看出

在func.o中:

  • func符號位於程式碼段,這顯而易見
  • g_pointer符號是一個未定義儲存段的符號。參考上表即可知道它的意思

在test.o中

  • func未定義,因為它本身是在func.c中定義的
  • g_global位於.bss段
  • g_pointer未定義
  • g_test位於資料段
  • main位於程式碼段
  • printf未定義,因為它是引自標準庫中的程式碼

3、總結

學會使用ar工具與nm工具。瞭解nm輸出對應的符號的說明資訊,參考本文表格。

本文參考狄泰軟體學院相關課程
想學習的可以加狄泰軟體學院群,
群聊號碼:199546072

學習探討加個人(可以免費幫忙下載CSDN資源):
qq:1126137994
微信:liu1126137994