gnu binutils

GNU binutils是一組二進制工具集。包括:addr2line ar gprof nm objcopy objdump ranlib size strings strip. 本文歸納他們的常用法。arar用於建立、修改、提取檔案檔案(archive)。archive是一個包含多個被包含檔案的單一檔案(也稱之為庫檔案),其結構保證了可以從中檢索並得到原始的被包含檔案(稱之為archive中的member)。member的原始檔案內容、模式(許可權)、時間戳、所有著和組等屬性都被保存在 archive中。member被提取後,他們的屬性被恢復到初始狀態。

ar主要用於創建C庫檔案
創建靜態庫
(1) 生成目標檔案:
$ gcc -Wall -c file1.c file2.c file3.c
不用指定生成.o檔案名稱(默認生成file1.o, file2.o, file3.o)。
(2) 從.o目標檔案創建靜態連線庫:
$ ar rv libNAME.a file1.o file2.o file3.o
ar生成了libNAME.a庫,並列出庫中的檔案。
r : 將flie1.o, file2,o, file3.o插入archive,如故原先archive中已經存在某檔案,則先將該檔案刪除。
v : 顯示ar操作的附加信息
創建動態庫(利用gcc,未用ar)
(1) 生成目標檔案
$ gcc -Wall -c -fpic file1.c file2.c file3.c
-fpic: 指定生成的.o目標檔案可被重定址. pic是position idependent code的縮寫: 位置無關代碼.
(2)生成動態庫檔案
$ gcc -shared -o libNAME so file1.o file2.o file3.o
一般地, 連線器使用main()函式作為程式入口. 但在動態共享庫中沒有這樣的入口. 所以就要指定-shared選項來避免編譯器顯示出錯信息.
實際上, 上述的兩條命令可以合併為下面這條:
$ gcc -Wall -shared -fpic -o libNAME so file1.c file2.c file3.c
此後,將main函式所在的程式與libNAME so連線
至此,與動態庫連線的函式編譯成了一個執行檔。貌似成功了,但還差最後一步。如果直接運行該程式,會給出這樣的錯誤信息:
error while loading shared libraries: libhello so:
cannot open shared object file: No such file or directory
這是因為與動態庫連線的程式在運行時,首先將該動態庫載入到記憶體中,而gcc默認載入動態庫檔案所在目錄為/usr/local/lib, /usr/lib。剛才的程式雖然能編譯成功,但如果我們自己建立的動態庫沒有位於默認目錄中,則執行時會應為無法找到它而失敗。
解決辦法:改變載入路徑對應的環境變數,然後再執行。
export LD_LIBRARY_PATH=動態庫所在目錄:$LD_LIBRARY_PATH
查看archive內容
$ ar tv archiveNAME
t : 顯示archive中member的內容,若不指定member,則列出所有。
v : 與t結合使用時,顯示member的詳細信息。
要想進了解ar的詳細選項,參考ar的on-line manual
nm
nm用來列出目標檔案中的符號,可以幫助程式設計師定位和分析執行程式和目標檔案中的符號信息和它的屬性。
如果沒有目標檔案作為參數傳遞給nm, nm假定目標檔案為a.out.
這裡用一個簡單的示例程式來介紹nm的用法:
main.c:
int main(int argc, char *argv[])
{
hello();
bye();
return 0;
}
hello.c:
void hello(void)
{
printf("hello!\n");
}
bye.c:
void bye(void)
{
printf("good bye!\n");
}
運行下列命令:
$ gcc -Wall -c main.c hello.c bye.c
gcc生成main.o, hello.o, bye.o三個目標檔案(這裡沒有聲明函式原型,加了-Wall,gcc會給出警告)
$ nm main.o hello.o bye.o
結果顯示如下:
main.o:
U bye
U hello
00000000 T main
hello.o:
00000000 T hello
U puts
bye.o:
00000000 T bye
U puts
結合這些輸出結果,以及程式代碼,可以知道:
對於main.o, bye和hello未被定義, main被定義了
對於hello.o, hello被定義了, puts未被定義
對於bye.o, bye被定義了,puts未被定義
幾個值得注意的問題:
(1)"目標檔案"指.o檔案, 庫檔案, 最終的執行檔
.o : 編譯後的目標檔案,即含有最終編譯出的機器碼,但它裡面所引用的其他檔案中函式的記憶體位置尚未定義.
(2)如果用nm查看執行檔, 輸出會比較多, 仔細研究輸出, 可以對nm用法有更清醒的認識.
(3)在上述hello.c, bye.c中, 調用的是printf(), 而nm輸出中顯示調用的是puts(), 說明最終程式實際調用的puts(), 如果令hello.c或bye.c中的printf()使用格式化輸出,則nm顯示調用printf(). ( 如: printf("%d", 1); )
關於nm的參數選項,參考on-line manual
objcopy
objcopy可以將一種格式的目標檔案轉化為另外一種格式的目標檔案. 它使用GNU bfd庫進行讀/寫目標檔案.使用BFD, objcopy就能將原格式的目標檔案轉化為不同格式的目標檔案.
以我們在nm中使用的hello.o目標檔案和hello可執行為例:
$ file hello.o hello
file命令用來判別檔案類型, 輸出如下:
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not Stripped
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.0, dynamically linked (uses shared libs), not stripped
現在運行objcopy來改變hello的檔案類型: 原先它是ELF格式的可執行程式, 現將它轉換為srec格式. srec格式檔案是Motolora S-Record格式的檔案, 主要用來在主機和目標機之間傳輸數據.
$ objcopy -O srec hello hello_srec
$ file hello.o hello
file命令結果: hello_srec: Motorola S-Record; binary data in text format
注意objcopy的格式, "-O"指定輸出檔案類型; 輸入檔案名稱和輸出檔案名稱位於命令末尾. 關於objcopy命令的詳細選項, 參考on-line manual
Objdump
objdump用來顯示目標檔案的信息. 可以通過選項控制顯示那些特定信息. objdump一個最大的用處恐怕就是將C代碼反彙編了. 在嵌入式軟體開發過程中, 也可以用它查看執行檔案或庫檔案的信息.
下面我們用上文提到的hello執行檔和hello_srec執行檔為例, 介紹objdump的簡單用法:
$ objdump -f hello hello_srec
輸出如下:
hello: file format elf32-i386
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x080482c0
hello_srec: file format srec
architecture: UNKNOWN!, flags 0x00000000:
start address 0x00000000080482c0
-f : 顯示目標檔案的頭檔案概要信息.
生成反彙編代碼:
$ objdump -d hello.o
顯示如下:
hello.o: file format elf32-i386
Disassembly of section .text:
00000000 <hello>:
0: 55 push %EBP
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: 83 ec 0c sub $0xc,%esp
9: 68 00 00 00 00 push $0x0
e: e8 fc ff ff ff call f <hello+0xf>
13: 83 c4 10 add $0x10,%esp
16: c9 leave
17: c3 ret
-d : 顯示目標檔案中機器指令使用的彙編語言. 只反彙編那些應該含有指令機器碼的節(顯示.text段); 如果用-D, 則反彙編所有節的內容.
關於objcopy命令的詳細選項, 參考on-line manual
readelf
readelf用來顯示ELF格式目標檔案的信息.可通過參數選項來控制顯示哪些特定信息.(注意: readelf不支持顯示archive文檔, 也不支持64位的ELF檔案).
下面利用先前的hello執行檔演示readelf的簡單用法:
$ readelf -h hello
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0x1
Entry point address: 0x80482c0
Start of program headers: 52 (bytes into file)
Start of section headers: 3848 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 7
Size of section headers: 40 (bytes)
Number of section headers: 34
Section header string table index: 31
注意: readelf只能用於ELF格式目標檔案, 且選項中至少要指定一個(除V, H外)的選項!
gprof
gprof被用來測量程式的性能. 它記錄每個函式被調用的次數以及相應的執行時間. 這樣就能鎖定程式執行時花費時間最多的部分, 對程式的最佳化就可集中於對它們的最佳化.
用一個簡單的數值計算程式來掩飾gprof的用法:
collatz.c:
#include <stdio.h>
/* Computes the length of Collatz sequences */
unsigned int step (unsigned int x)
{
if (x % 2 == 0)
{
return (x / 2);
}
else
{
return (3 * x + 1);
}
}
unsigned int nseq (unsigned int x0)
{
unsigned int i = 1, x;
if (x0 == 1 || x0 == 0)
return i;
x = step (x0);
while (x != 1 && x != 0)
{
x = step (x);
i++;
}
return i;
}
int main (void)
{
unsigned int i, m = 0, im = 0;
for (i = 1; i < 500000; i++)
{
unsigned int k = nseq (i);
if (k > m)
{
m = k;
im = i;
printf ("sequence length = %u for %u\n", m, im);
}
}
return 0;
}
先將collatz.c編譯成目標檔案collatz.o, gcc通過 -pg選項來打開gprof支持:
$ gcc -Wall -c -pg collatz.c
$ gcc -Wall -pg -o collatz collatz.o
注意:兩條命令都要加 "-pg"選項。前一條命令生成collatz.o目標檔案。後一條命令生成執行檔,該執行檔中包含了記錄函式執行時間的指令。
生成collatz執行檔後,現執行它,結果與一般程式的執行無疑。但此時在PWD目錄生成一個名為"gmon.out"的檔案,gprof通過它來分析程式的執行。
如果不現執行程式,而直接用gprof來分析它,會提示“gmon.out: No such file or directory”。
gprof用法:
$ gprof ./collatz

相關詞條

熱門詞條

聯絡我們