第一節 GNU Tools開發工具簡介
GNU開發工具
爲了有效地進行嵌入式開發,至少需要了解和掌握如下幾類工具:
編譯開發工具:即能夠把一個源程序編譯生成一個可執行程序的軟件,如gcc等。
調試工具:即能夠對執行程序進行源碼或彙編級調試的軟件,如gdb等。
軟件工程工具:用於協助多人開發或大型軟件項目的管理的軟件,如make、cvs等。
具體來說,我們需要對如下軟件有一定了解:
(1)GCC
很多人把GCC看成只是一個C編譯器,其實GCC是GNU Compiler Collection的簡稱,目前,GCC可以支持C、C++、ADA、Object C、JAVA、Fortran、PASCAL等多種高級語言。GCC主要包括如下一些工具。
cpp,GNU預處理器
gcc,符合ISO標準的C編譯器
g++,符合ISO標準的C++編譯器
gcj,gcj是GCC的java前端,可以生成執行速度更快的二進制本地執行碼,而不是java byte code。gcj爲把java程序編譯成機器代碼提供了試驗性的支持。要做到這點,用戶還需要安裝相關的java運行時庫。
gnat,是GCC的GNU ADA 95前端,該軟件包括開發工具、文檔及ADA 95編譯器。
(2)binutils
binutils是一組二進制工具程序集,它包括addr2line、ar、as、gprof、ld、nm、objcopy、objdump、ranlib、size、strings、strip等工具,是輔助GCC的主要軟件。
as,GNU彙編器(Assembler),用於把彙編代碼轉換成二進制代碼,並存放到一個object文件中。
ld,GNU鏈接器(Linker),主要用於確定相對地址,把多個object文件、起始代碼段、庫等鏈接起來,並最終形成一個可執行文件。
addr2line,把執行程序中的地址映射到源文件中的對應行。
ar,創建歸檔文件(Archive)、修改/替換庫中的object文件,向庫中添加/提取object文件。
c++filt,解碼C++符號名。
nm,列出object文件中的符號名。
objcopy,複製和轉換object文件。
objdump,用來顯示對象文件的信息。
ranlib,根據歸檔文件(Archive)中內容建立索引。
readelf,顯示elf格式執行文件中的各種信息。
size,顯示object文件和執行文件各節(Section)的大小。
strings,顯示可執行文件中字符串常量。
strip,去掉執行文件中多餘的信息(如調試信息),可以減少執行文件的大小。
gprof,用來顯示調用圖表檔案數據。
(3)gdb
gdb是GNU調試器,它允許調試用C、C++或其它語言編寫的程序。它的基本運行方式是在一個shell環境下用命令行方式調試程序和顯示數據。如果加上一些圖形前端(如DDD等軟件),則可以在此一個更方便的圖形環境下調試程序。
(4)make
GNU make是一個用來控制可執行程序的生成過程,從其它的源碼程序文件中生成可執行程序的GNU工具。GNU make允許用戶生成和安裝軟件包,而無需瞭解生成、安裝軟件包的具體執行過程。
(5)diff/diff3/sdiff
diff/diff3/sdiff是比較文本差異的工具,也可以用來產生補丁。
(6)patch
patch是補丁安裝程序,可根據diff生成的補丁來更新程序。
(7)CVS(Concurrent Version System)
CVS是一個版本控制系統。它能夠記錄文件的修改歷史(通常但並不總是包括源碼)。CVS只存儲版本之間的區別,而不是你創建的文件的每一個版本。CVS還保留一個記錄改變者、改變時間以及改變原因的日誌。CVS對於管理髮行版本和控制在多個作者間同時編輯源碼文件很有幫助。CVS爲一個層次化的目錄提供版本控制,目錄由修改控制的文件組成,而不是在一個目錄中爲一組文件提供版本控制。這些目錄和文件可以被合併起來構成一個軟件發行版本。
第二節 binutils開發工具
binutils是一組二進制工具程序集,它包括addr2line、ar、as、gprof、ld、nm、objcopy、objdump、ranlib、size、strings、strip等工具,下面分別介紹其中一些常用的軟件。
nm工具:
nm軟件主要功能是列出目標文件中的符號,這樣可以幫助程序員定位和分析執行程序和目標文件中的符號信息和它的屬性。如果沒有目標文件作爲參數被列出,nm假定目標文件爲”a.out”,通過下面的命令可以得到nm的一般幫助信息#nm -h。
對於每一個符號,nm將顯示下面的內容:
符號的值:用某種基數表示的數值。默認情況下用十六進制表示。
符號的類型:至少要用到下面的類型。也可以用其它類型,這依賴於目標文件的格式。
1. A:符號的值是絕對值,並且不會被將來的鏈接所改變。
2. B:符號位於未初始化數據部分(被認爲是BSS)。
3. C:符號是公共的。公共符號是未初始化的數據。在鏈接時,多個公共符號可能以相同的名字出現。如果符號在其他地方被定義,該符號會被當作未定義的引用來處理。
4. D:符號位於已初始化數據部分。
5. G:符號位於小型對象的已初始化數據部分,相對於大型的全局array,一些對象文件格式允許對小型數據對象更有效的存取,例如全局的int型變量。
6. I:符號是另一個符號的間接引用。這是對很少用到的a.out目標文件格式的GNU擴展。
7. N:符號是調試符號。
8. R:符號位於只讀數據部分。
9. S:符號位於小型對象的未初始化數據部分。
10. T:符號位於文本(代碼)部分。
11. U:符號未被定義。
12. W:符號是弱定義符號(Weak Symbol),也稱弱符號。當一個弱定義符號和一個已經定義的普通符號鏈接時,使用該已定義的普通符號不會引起錯誤。當一個未定義的弱符號被鏈接且該符號未被定義時,該weak符號的值被無錯誤的變爲0。
13. -:符號是一個a.out目標文件中的stabs符號。這種情況下,接下來要打印的值是stabs other域,stabs desc域,以及stab類型。stabs符號用於保留調試信息。
14. ?:符號類型是未知的,或目標文件格式特殊。
15. o:符號名
nm的命令行參數選項的長格式和短格式是等價的,下面列出了可供選擇的命令行參數格式。
1. -A/-o/--print-file-name: 在找到的各個符號的名字前加上輸入文件名(或歸檔文件元素),而不是在此文件中的所有符號前只出現一次輸入文件名。
2. -a/--debug-syms:顯示所有的調試符號,即使是僅用於調試的符號。通常情況下這些符號是不被列出的。
3. -B:等價於“--format=bsd”(爲了兼容MIPS nm)。
4. -C/--demangle:將低級符號名解碼成用戶級名字。另外去除任何由系統預先生成的初始的下劃線,這樣可以使C++函數名具有可讀性。
5. --no-demangle:不解碼低級符號名。這是默認的處理方式。
6. -D/--dynamic:顯示動態符號而不是普通符號。該選項僅對動態目標文件有意義,例如特定類型的共享庫。
7. -f format/--format = format:使用format格式輸出,format可以是bsd、sysv或posix。默認爲bsd。僅當format的第一個字符是有意義的,可以是大寫或小寫。
8. -g/--extern-only:只顯示外部符號。
9. -l/--line-numbers:對每個符號,使用調試信息去試圖找到文件名和行號。對於已定義的符號,查找符號地址的等號。對於未定義符號,查找指向符號重定位入口的行號。如果可以找到行號信息,在其他符號信息之後顯示行號信息。
10. -n/-v/--numeric-sort:按符號對應地址的順序排序,而不是按符號名的字符順序。
11. -p/--no-sort:不以任何順序對符號進行排序,按目標文件中遇到的符號順序顯示。
12. -P/--portablility:使用POSIX.2標準輸出格式代替默認的輸出格式。等價於“-f posix”。
13. -s/--print-armap:當列出歸檔文件中成員的符號時,包含索引,即名字和包含該名字定義的模塊的映射(通過ar或ranlib保存在歸檔文件中)。
14. -r/--reverse-sort:反轉排序的順序(按數字或字母順序)顯示。
15. --size-sort:按大小排列符號順序。該大小是按照一個符號的值與它下一個符號的值進行計算的。
16. -t radix/--radix = radix:使用radix進制顯示符號值。radix只能爲“d”表示十進制、“o”表示八進制或“x”表示十六進制。
17. --target = bfdname:指定一個目標代碼的格式,而非使用系統默認格式。
18. -u/--undefined-only:僅顯示沒有定義的符號(那些每個目標文件的外部符號)。
19. -defined-only:僅顯示每個目標文件中已經定義的符號。
20. -V/--version:顯示nm的版本號然後退出。
21. --help:顯示nm的所有選項然後退出。
下面介紹一個簡單的使用,按照我們剛纔介紹ar命令時的例子,執行如下命令:
#nm test.o
輸出結果如下:
U Add
00000000 T main
U Minus
U printf
執行命令:
#nm add.o
輸出結果如下:
00000000 T Add
執行命令:
#nm minus.o
輸出結果如下:
00000000 T Minus
命令nm test.o的輸出說明了test.o定義了main函數,但沒有定義Add、Minus和printf函數符號。命令nm add.o的輸出說明add.o定義了Add函數符號。命令nm minus.o的輸出說明了minus.o定義了Minus函數符號。test.o中沒有定義但使用了printf函數符號,printf函數實際上定義在libc.a庫中。
objdump工具:
objdump顯示一個或多個目標文件的信息。由其選項來控制顯示哪些特定的信息。這些信息只對那些編寫編譯工具的程序員有幫助,而對那些只想讓自己編寫的程序編譯和運行起來的程序員來說沒有更多意義。但在嵌入式系統級開發時,通過這個軟件可以方便地查看執行文件或庫文件的信息。如我們可以通過objdump軟件反彙編執行程序,看到執行程序的彙編格式。
當目標文件是歸檔文件時,objdump顯示的是歸檔文件中每個成員文件的信息。選項的長格式和短格式是等價的。下面描述了作爲可選擇的參數格式(除“-l”之外,至少要給出一個參數選項)。
1. -a/--archive-header:如果任何一個由object-file指定的文件是歸檔文件,則顯示該歸檔文件的頭信息(類似於“ls -l”的格式)。除了“ar -tv”能顯示的信息外,“objdump -a”還可以顯示每個歸檔文件成員的目標文件格式。
2. --adjust-vma=offset:當轉儲信息時,首先給所有的節地址加上一個偏移量offset。如果節地址對應不上符號表時,就可以使用該選項。當節被放在特殊的地址,而採用的是一種不能表示節地址的格式,例如a.out,就會發生節地址對應不上符號表的情況。
3. -b bfdname/--target = bfdname:指定目標文件的目標代碼格式爲bfdname。該選項可能不是必需的,因爲objdump能夠識別很多格式。如命令“objdump -b oasys -m vax -h fu.o”執行後,將顯示節頭中的概要信息。“-b oasys”表示用的是Oasys編譯器產生的目標文件格式。“-m vax”表示目標文件是VAX計算機上的目標文件。
4. -C/--demangle:將低級符號名解碼成用戶級名字。另外去除任何由系統預先生成的初始的下劃線,這樣可以使得C++函數名具有可讀性。
5. --debugging:顯示調試信息。該選項試圖解析保存在文件中的調試信息並且用C語言風格的語法打印這些信息。僅能對特定類型的調試信息實現這個功能。
6. -d/--disassemble:顯示目標文件中的機器指令使用的彙編語言。該選項僅僅反彙編那些應該含有指令機器碼的節。
7. -D/--disassemble-all:類似於“-d”,但是反彙編所有節的內容。該選項僅對那些應該含有指令機器碼的節有意義。
8. --prefix-addresses:反彙編時,打印每行的完整地址。這是一種更古老的反彙編格式。
9. --disassemble-zeroes:通常情況下,反彙編的輸出會跳過大塊的零。該選項將指引反彙編器去反彙編那些大塊的零,就像處理其他數據一樣。
10. -EB/-EL/--endian={big|little}:指定目標文件的字節順序。該選項只會影響反彙編。當反彙編像S-records這樣沒有描述字節順序的文件格式時,該選項是有用的。
11. -f/--file-header:顯示每個由object-file指定的所有目標文件的文件頭概要信息。
12. -h/--section-header/--header:顯示目標文件的節頭概要信息。ld使用了“-Ttext”、“-Tdata”或“-Tbss”這些選項時,可能會使得目標文件的各個節被重定向到非標準的地址。這樣通過這個選項,可以查出目標文件的各節的起始地址。然而,一些像a.out這樣的目標文件格式並沒有存儲文件段的起始地址。在這些情況下,儘管ld正確地重定位了每個節。但是使用“objdump -h”列出文件節頭時,並不能顯示其正確地址。
13. --help:顯示objdump的所有選項的概要信息然後退出。
14. -i/--info:列表顯示所有對“-b”或“-m”可用的體系結構和目標格式。
15. -j name/--section=name:只顯示由name指定的節。
16. -l/--line-numbers:用對應於目標代碼的文件名和行號來標註要顯示的信息(使用調試信息),僅僅和-d、-D或-r一起使用纔有效。
17. -m machine/--architecture = machine:當反彙編由object-file指定的目標文件時,標明所使用的體系結構。當反彙編一個像S-records這樣本身並沒有描述體系結構信息的文件的時候,這個選項是有用的。可以用-i選項列出可用的體系結構。
18. -p/--private-headers:顯示目標文件格式的特定信息。要顯示的信息依賴於目標文件的格式。對於某些目標文件格式,沒有附加的信息可供顯示。
19. -r/--reloc:顯示文件的重定位入口。如果和“-d”或者“-D”一起使用,重定位部分以反彙編後的格式顯示出來。
20. -R/-dynamic-reloc:顯示文件的動態重定位入口,僅僅對於動態目標文件有意義,例如特定類型的共享庫。
21. -s/--full-contents:顯示任何指定節的全部內容。
22. -S/--source:儘可能顯示與反彙編混和的源代碼。隱含了“-d”參數。
23. --show-raw-insn:反彙編機器指令的時候,用十六進制和符號形式同時顯示機器指令碼。然而並非所有的目標都能這樣正確地出來。
24. --no-show-raw-insn:反彙編機器指令的時候,不顯示指令類型。這是指定--prefix-addresses選項時的默認設置。
25. --stabs:顯示任何指定節的全部內容。顯示ELF格式目標文件中的.stab、.stab.index和.stab.excl節的內容。一般用於Solaris操作系統。在其他大部分執行文件格式中,調試符號表入口與鏈接符號交織在一起,並在打開了“--yms”參數選項時,在objdump的輸出中是可見的。
26. --start-address = address:從指定地址開始顯示數據,該選項影響打開-d、-r和-s選項時的輸出。
27. --stop-address = address:顯示數據直到指定地址爲止,該選項影響打開-d、-r和-s選項時的輸出。
28. -t/--syms:顯示文件中的符號表入口。類似於“nm”程序提供的信息。
29. -T/--dynamic-syms:顯示文件中的動態符號表入口。僅僅對於動態目標文件有意義,例如特定類型的共享庫。類似於打開了“-D”選項的“nm”程序提供的信息。
30. --version:顯示objdump的版本號然後退出。
31. -x/--all-header:顯示所有可用的頭信息,包括符號表和重定位入口,使用“-x”等價於指定了“-a -f -h -r -t”參數。
32. -w/--wide:對超過80列的輸出設備指定一些行的格式。
對於剛纔生成的test執行文件,我們執行
$objdump -f test
顯示執行文件文件頭概要信息。
使用“-d”或“-D”參數反彙編生成的目標代碼:
$objdump -d add.o
readelf工具:
readelf軟件顯示一個或多個ELF格式的目標文件信息。可通過各種參數選項來控制readelf軟件顯示目標文件中的特定信息。
size工具: List section sizes and total size
size顯示一個目標文件或者鏈接庫文件中的目標文件的各個段的大小。
1、輸出格式
size有兩種輸出格式,一種爲"sysv",另一種爲"berkeley",默認爲berkeley的格式。第一種格式可以用"-A"或者"--format=sysv"指定,第二種格式用"-B"或"--format=berkeley"指定
2、數字輸出格式
有三種格式,octal, decimal及hex,對應的參數爲"-o",
"-d"及"-x",也可以用"--radix=8","--radix=10"及"--radix=16"指定
3、彙總多個文件的各個段合計長度
"-t" 或者"--total",合計值將在最後輸出。
ar工具: Create, modify, and extract from archives
ar用來管理一種文檔。這種文檔中可以包含多個其他任意類別的文件。這些被包含的文件叫做這個文檔的成員。ar用來向這種文檔中添加、刪除、解出成員。成員的原有屬性(權限、屬主、日期等)不會丟失。
實際上通常只有在開發中的目標連接庫是這種格式的,所以儘管不是,我們基本可以認爲ar是用來操作這種目標鏈接庫(.a文件)的。
1、創建庫文件
我不知道怎麼創建一個空的庫文件。好在這個功能好像不是很需要。通常人們使用“ar cru liba.a a.o"這樣的命令來創建一個庫並把a.o添加進去。"c"關鍵字告訴ar需要創建一個新庫文件,如果沒有指定這個標誌則ar會創建一個文件,同時會給出一個提示信息,"u"用來告訴ar如果a.o比庫中的同名成員要新,則用新的a.o替換原來的。但是我發現這個參數也是可有可無的,可能是不同版本的ar行爲不一樣吧。實際上用"ar -r liba.a a.o"在freebsd5上面始終可以成功。
2、加入新成員
使用"ar -r liba.a b.o"即可以將b.o加入到liba.a中。默認的加入方式爲append,即加在庫的末尾。"r"關鍵字可以有三個修飾符"a", "b"和"i"。
"a"表示after,即將新成員加在指定成員之後。例如"ar -ra a.c liba.a b.c"表示將b.c加入liba.a並放在已有成員a.c之後;
"b"表示before,即將新成員加在指定成員之前。例如"ar -rb a.c liba.a b.c";
"i"表示insert,跟"b"作用相同。
3、列出庫中已有成員
"ar -t liba.a"即可。如果加上"v"修飾符則會一併列出成員的日期等屬性。
4、刪除庫中成員
"ar -d liba.a a.c"表示從庫中刪除a.c成員。如果庫中沒有這個成員ar也不會給出提示。如果需要列出被刪除的成員或者成員不存在的信息,就加上"v"修飾符。
5、從庫中解出成員
"ar -x liba.a b.c"
6、調整庫中成員的順序
使用"m"關鍵字。與"r"關鍵字一樣,它也有3個修飾符"a","b", "i"。如果要將b.c移動到a.c之前,則使用"ar -mb a.c liba.a b.c"