GCC 各工具使用簡介

GCC:GNU開發的程序編譯器
GNU:“GNU‘s NotUnix”,最初是爲了實現一個類似unix的自由操作系統,感覺現在已經通常泛指遵循GPL自由軟件精神的組織。
GPL:GNU通用公共許可證(GNU GeneralPublic License),簡單的說就是遵循GPL的代碼任意用戶可以複製發佈;使用或者修改了GPL的代碼也必須遵循GPL精神;遵循GPL的代碼已源代碼發佈;GPL並不排斥對自由軟件進行商業性質的包裝和發行,也不限制在自由軟件的基礎上打包發行其他非自由軟件。(所以在產品中要小心使用GPL的軟件,否則將涉及到一些商業祕密)
交叉編譯:
    在嵌入式領域,目標運行平臺通常使用的是隻能完成某部分特定功能的CPU,而爲了提供開發和編譯連接的效率,通常開發、編譯、鏈接是發生在功能更強大的PC上。
    但是,目標板上的CPU通常和編譯、鏈接的PC上的CPU屬於不同的體系架構。PC上通常是inter或者是AMD的CPU,都屬於X86的體系架構;但是嵌入式的CPU卻可能是如ARM、MIPS、PowerPC、SH、alpha等。不同的體系架構最直觀的區別就是指令集的不同。
   所以,需要通過運行在PC上的工具鏈將目標板上的程序編譯成目標板的指令集,這個工具鏈就叫交叉工具鏈;編譯過程就叫交叉編譯
交叉編譯工具:
常見的誤區:GCC只是linux下的編譯工具。實際上我們在STB裏面接觸到的所有方案的編譯工具,都是移植或者與GCC的功能大致相同的,其與目標平臺所使用的操作系統沒有關係。比如st平臺使用的操作系統是OS21,MSD平臺使用的是ECOS,但是他們的編譯工具集的基本功能都是一樣的。
      交叉工具鏈GCC的任意工具在名字前都帶有該體系架構的交叉編譯前綴。如MSD5043平臺屬於MIPS的架構,其前綴是mipsisa32-elf-;st平臺是時候sh4架構的,其前綴是sh-superh-elf-;nxp24507平臺屬於ARM的架構,其前綴是arm-linux-uclibcgnueabi-

下面是我在pnx8473平臺工具鏈中看到的GCC工具集
arm-linux-uclibcgnueabi-addr2line        arm-linux-uclibcgnueabi-gcc-4.4.0 
arm-linux-uclibcgnueabi-objcopy          arm-linux-uclibcgnueabi-ar       
arm-linux-uclibcgnueabi-gccbug          arm-linux-uclibcgnueabi-objdump
arm-linux-uclibcgnueabi-as                 arm-linux-uclibcgnueabi-gcov      
arm-linux-uclibcgnueabi-ranlib            arm-linux-uclibcgnueabi-c++       
arm-linux-uclibcgnueabi-gdb              arm-linux-uclibcgnueabi-readelf
arm-linux-uclibcgnueabi-cc                arm-linux-uclibcgnueabi-gprof     
arm-linux-uclibcgnueabi-size              arm-linux-uclibcgnueabi-c++filt   
arm-linux-uclibcgnueabi-ld                arm-linux-uclibcgnueabi-strings
arm-linux-uclibcgnueabi-cpp              arm-linux-uclibcgnueabi-ldconfig  
arm-linux-uclibcgnueabi-strip             arm-linux-uclibcgnueabi-g++       
arm-linux-uclibcgnueabi-ldd               arm-linux-uclibcgnueabi-gcc      
arm-linux-uclibcgnueabi-nm 
學習方法
途徑一:網絡搜索
途徑二:在linux系統下使用man命令查看手冊。比如想知道鏈接命令的使用方法和鏈接選項,直接在linux系統的命令行manld即可查看。

常見文件的內幕
Obj:由源文件編譯而成的目標文件,包括程序段自身能輸出的符號表以及未鏈接的符號表(即重定位符號表)
Lib:由一個或者多個目標文件打包在一起的文件。簡單的說就是一堆.o打包而成的文件。這樣做主要目的就是提供一個手段可以讓開發者將一個單一的模塊以二進制方式提供給該模塊的應用人員
.out:需要說明的是,這裏指的.out文件是我們工作中看到的.out文件。比如基於MSD平臺編譯時,在integration\product目錄下都會生成一個a.out文件。這個文件實際上就是鏈接後可執行程序了。
但是他是elf文件格式。像linux這樣的系統是直接可以運行elf文件格式的可執行程序【類似windows中運行.exe】,但是其他系統如OS21和ecos不支持該格式的直接運行,其運行需要將其轉換成.bin。但是,.out文件裏面包含了很多與調試信息相關的內容,我們後面說到的很多工具就會基於它來分析。
【用GCC編譯時不指定輸出文件名,就默認生產a.out, gcctst.c ==>a.out  在linux下直接可以運行該文件./a.out】
.bin:純粹二進制執行程序。由.out轉換而來。.bin往往比.out文件小很多,其內部只包含了程序執行所需要的代碼段、數據段。

Readelf-讀取文件頭
顧名思義,用於讀取elf文件信息的工具。具體如下:
1.讀取elf文件 文件頭:
命令:readelf -hintegration/product/a.out 
  ELF Header:
  Magic:   7f 45 4c 46 01 0101 00 00 00 00 00 00 00 00 00   //魔數
  Class:                     ELF32      //文件格式
  Data:                      2‘s complement, littleendian //大小端
  Version:                   1 (current)
  OS/ABI:                    UNIX - System V//編譯宿主機操作系統
  ABI Version:                0 //ABI 版本
  Type:                      EXEC (Executable file)//表明是可執行文件
  Machine:                   MIPS R3000 //目標機體系架構
  Version:                   0x1
  Entry point address:         0x80000224
  Start of program headers:     52 (bytesinto file)
  Start of section headers:     23916900(bytes into file)
  Flags:                     0x50003001, noreorder,eabi32, mips32
  Size of this header:         52 (bytes)
  Size of program headers:      32 (bytes)
  Number of program headers:    1
  Size of section headers:      40 (bytes)
  Number of section headers:    38
  Section header string table index: 35
    典型問題:鏈接的時候提示庫的格式錯誤之類的莫名錯誤。這時,其中一個可能原因就是用成了其他平臺的庫,或者該庫是其他工具鏈編譯出來的。遇到此類問題就可以去比較報錯的庫的文件頭裏面的信息是否與其他庫一樣,這些信息主要包括上面紅色部分標明的內容,如ABI規劃和目標機類型等。

   命令:readelf -lintegration/product/a.out
   Elf file type is EXEC(Executable file)
   Entry point0x80000224
   There are 1 programheaders, starting at offset 52
  Program Headers:
  Type    Offset        VirtAddr      PhysAddr    FileSiz     MemSiz     Flg    Align
  LOAD   0x000000 0x80000000 0x80000000 0xb23928 0x2035fc0RWE 0x1000

 Section to Segment mapping:
  Segment Sections...
  00    .rom_vectors .text .rodata .data .eh_frame .gcc_except_table .ctors.dtors .devtab .sdata .sbss .bss 
主要作用:
1.顯示程序開始運行的內存地址,如上面顯示的0x80000000。
2. 顯示程序由那些段組成。
注意:只有可執行文件纔有程序頭,目標文件和庫文件沒有該信息。

Readelf-讀取段表
命令:readelf -Sintegration/product/a.out 
該命令將文件中存在的段的信息列出,通常情況下我們比較關注的就是代碼段(.text),只讀數據段(.rodata),數據段(.data),未初始化數據段(.bss).下面以代碼段爲例簡要說明下主要幾項的意義:
      [Nr]Name        Type             Addr        Off        Size    ES   Flg  Lk Inf  Al
      [ 5].text            PROGBITS   80001000 001000  77f9f8 00   AX   0   0   4
第一項(name)爲段名,這裏是代碼段(.text)
第二項(type)爲段類型,具體可以參考ELF規範文檔
第三項(addr)爲段在運行時的加載地址,爲虛擬地址。
第四項(off)爲該段起始地址在文件中的偏移。
第五項(size)爲該段大小
第七項(Flg)包含了程序的控制信息。在命令的輸出下面有說明:
     W(write), A (alloc), X (execute), M (merge), S (strings)
     I(info), L (link order), G (group), x (unknown)
    O (extra OS processingrequired) o (OS specific), p (processor specific)
addr2line
作用:將程序地址轉換成行號。
    這個工具在我們日常開發中非常有用。他可以快速的定位到程序死機的位置。注意,這裏說的死機是指程序因爲非法地址訪問,除數爲0,地址未對其訪問(部分平臺有此限制),buserror等錯誤造成的程序崩潰。不包含死鎖、程序死循環等造成的死機現象。
    在說明該工具的用法之前,先了解兩個概念:
     1.epc:在學校我們學習彙編的時候知道pc是CPU保存當前運行指令地址的寄存器,那麼這個epc就是errorpc。保存的是當程序崩潰時,造成指令異常的那條指令的地址。也就是問題的第一現場。比如,程序因爲非法地址訪問造成了死機,那麼epc保存的就是直接造成非法地址訪問的那條指令的地址。
     2.ra:當前程序返回地址。當程序進行函數調用時更新該寄存器。當程序死機時,該地址就是第二現場。
       一般程序崩潰時都有epc、ra的地址打印出來。
addr2line使用方法一
命令:addr2line -e integration/product/a.out 802f07a8 –f
其中integration/product/a.out爲造成死機對應的程序。 802f07a8爲地址。比如上面一張所說的epc           地址或者是ra地址。
示例一:
MSD5043 UNE項目時移進出老化死機,死機打印:
!!!CPU exception=4 epc=802f07a8
通過命令查找:
[root@localhost trunk]# addr2line -e integration/product/a.out802f07a8 -f
GetShowStateFromCSWND
??:0
這裏行數沒有找到,但是找到了死機函數:GetShowStateFromCSWND,進一步查找發現該函數是graphic庫裏面的。進一步分析,該graphic庫在很多項目上使用,所以該庫直接出問題的概率較小,再結合死機時的操作是在PVR的時移playbar上,再結合現象是要操作一段時間後纔出現,所以初步判斷爲playerbar的顯示有內存泄露,根據這個線索去查最後找到問題原因。
addr2line使用方法二
示例二:
MSD5043 CTH項目測試部隨機按鍵老化死機,死機打印:
!!!CPU exception=4 epc=801e6f0c
通過命令查找:
[root@localhost trunk]# addr2line -e integration/product/a.out802f07a8 –f
OnCommandWeeklyEPGView
integration/ui/c/hd_v10/EPG/WeeklyEpgView.c:912
這個示例中直接查找到了死機的文件已經具體行數。再根據該信息在代碼中發現是由於對某個變量取餘數時,發現除數爲0所導致。
Addr2line的優勢與不足
優勢:
可以不用重新跑程序就可以快速的定位到死機的位置。對於解決非必現的問題非常有幫助。可以做到隨時發現隨時定位。
不足:
只能看到死機當前地址和返回地址,不能顯示整個堆棧函數調用的信息。而某些問題,雖然是某個地方死機了,但是問題的根本原因不是死機的地方有問題,而可能是上幾層的調用邏輯關係錯誤。對於這類問題,通過addr2line只能看到問題的表象,問題的根本原因無法快速定位。
Addr2line的限制與工作中的改進
限制:
1. Addr2line要能準確找到死機位置要求代碼編譯時使用-g選項。所以,在實際使用過程中有時死在某個庫函數裏面,然後通過該工具只能找到死機的函數,無法準確定位到行,其原因就是該庫的編譯
沒有使用-g選項。
2. addr2line使用時的程序文件必須是未裁剪過的.out文件,而不能是.bin文件
工作中的改進:
基於上面的限制,所以,我們在提交測試時可以將編譯出來的中間文件:.out和map文件都放到測試包中。這樣一旦測試部測試時出現死機問題,可以根據打印和.out文件,用addr2line工具快速定位到問題點,提高解決問題的效率。
nm
Nm主要是用於查找目標文件庫文件庫文件實際上就是目標文件的打包)、elf格式可執行文件中的特定符號,這些符號主要是函數名和全局變量名。這個工具通常可以幫快速定位一些鏈接問題。
比如一個工程鏈接的時候提示某個函數找不到。則通過該工具在所有庫中去查找看未定義的函數到底在那個庫裏面,從而將該庫添加到makefile中。比如假設提示無法找到符號:CSUDIPlusOSTimerStop,則可以通過命令:
nm -A lib/MSD7853/release/*.a | grepCSUDIPlusOSTimerStop
獲得打印信息如下:
libkernel.a:event.o:        U   CSUDIPlusOSTimerStop
libkernel.a:dsm_sg.o:        U   CSUDIPlusOSTimerStop
libos_udi2_to_udi1.a:udi1_os.o:        U   CSUDIPlusOSTimerStop
libUDIPlus.a:udiplus_ostimer.o:00000308 T  CSUDIPlusOSTimerStop
   上面的打印中,可以看到那些庫裏面和這個函數有關係。
   上面的CSUDIPlusOSTimerStop前面的“U”表示undifined,即該庫裏面調用該函數但是該庫裏面並未定義該函數。而”T”就表示函數的定義。所以,從這個例子裏面可以看到CSUDIPlusOSTimerStop是在庫libUDIPlus.a的udiplus_ostimer.c文件中實現的,需要添加該庫的鏈接。
注意:對於符號前的各個字母的說明,即上例中紅色部分標出的內容,請用man nm查看手冊
Nm示例-查找重定義
有些項目工程爲了makefile寫的簡單,在編譯的時候往往通過shell命令找出該目錄下的所有.c文件,並將其編譯。而我們調試的時候有時爲了備份一個改動,往往會將改動的文件重命名一下,然後,從服務器再取一個新的。這樣,編譯鏈接時就報錯,說有重定義。或者有些平臺因爲makefile的原因不會報重定義,但是一運行,發現總是沒有按照自己預想的路徑運行。這樣我們也可以通過nm來確認下是否我們的鏈接庫裏面某個函數有多個定義。比如假設是PVRLite播放入口重定義,或者沒有按照我們的預想運行,我們找到入口函數CSPVRLitePlayerStart,運行命令:
nm -A ./*.a|grep CSPVRLitePlayerStart
輸出:
libcbb.a:CSPVRLitePlayer.o:00000534 TCSPVRLitePlayerStart
libcbb.a:CSPVRLitePlayer-bak.o:00000534 TCSPVRLitePlayerStart
libuihd_v10.a:CSPlayer.o:        UCSPVRLitePlayerStart
可以看到CSPVRLitePlayerStart有兩個”T”,即有兩個定義的地方,並且在不同文件,而且很容易看到是我們一個備份文件被編譯進去導致。
Nm示例-程序轉map文件
    nm還有作用就是直接通過可執行文件生成相應的map文件。當然,這個前提是改可執行文件沒有被strip命令裁剪過。命令如下:
nm  integration/product/a.out> flash.map

strings
Strings主要用來輸出目標文件、庫文件、程序文件中的字符串。比如下面中的字符串:
1.字符串變量的賦值:
Char *pProductName = “N8770C”;
此處的”N8770C”
2.打印:
Printf(“error:parameter error\n”);
此處的” error:parameter error”
使用命令:strings –f lib/MSD7853/release/libcbb.a
示例:
之前有位同事問我,他在一個必然會走到的函數入口處用printf添加了一個打印。但是,不管怎麼弄該打印就是沒有打印出來。於是他懷疑是否該平臺的printf打印不出來。
我們假設他加的打印是打印一句:”PvrEntry_start”,那麼,他打印不出來的一種原因是該修改根本就沒有被編譯到,或者使用的庫根本就不是他加過打印的庫。這樣我們可以用nm來證實下我們的推測
strings –f integration/product/a.out|grep PvrEntry_start
如果找到則表示修改的代碼已經編譯並鏈接到,則應該找其他沒有打印出來的原因;如果沒有找到,則要去查找編譯鏈接的原因。

ar
Ar的主要功能大家應該都很熟悉:將目標文件打包成庫文件。這在工程中的makefile基本都能找到他的使用場景。
這裏主要介紹一個大家不太瞭解但是有時有需要用到的功能。
示例一:幾個.o文件打包成一個.a文件
命令:ar -rcu libmain.a A.o B.o C.o
示例二:將兩個(libtest1.a和libtest2.a)庫文件合併成一個庫文件libfinal.a
步驟1,用ar命令將每個庫文件還原成.o文件:
ar –x libtest1.a
ar –x libtest2.a
步驟2,再將還原出來的所有.o文件打包成新的庫文件:
ar -rcu libfinal.a ./*.o
注意:在具體某個平臺下使用時,請在工具前加上交叉工具鏈前綴,比如MSD平臺則爲:mipsisa32-elf-ar

objdump
Objdump可以將目標文件或者程序文件中的段內容顯示或者反彙編。在日常工作中,我們可能用到的基本就是他的反彙編功能了。
有時候我們對死機地址用addr2line無法定位到時那行時,我們還可以用objdump就程序反彙編,然後在反彙編文件中查找死機地址在那個函數範圍內,這樣也將問題縮小在了很小的範圍,一定程度上提高解決問題的效率。

objcopy
Objcopy主要作用是完成目標文件或者程序文件的格式轉化。其功能比較強大,但是很底層,一般的應用開發並不需要使用到他。在我們的開發中,有兩種場景下用到了objcopy。
1. 在編譯鏈接完成後,需要將elf格式的.out文件轉化成bin文件。如MSD平臺大家留意下鏈接時有這樣一條命令打印:
mipsisa32-elf-objcopy -O  binary a.out   k1_ecos.bin
這就是將鏈接輸出的.out文件轉化成2進制的bin文件。因爲,MSD平臺使用的ECOS不支持ELF文件的執行。如果是linux系統,則.out文件可以直接運行。
2.在linux系統下,將.out程序文件裁剪掉程序正常運行不需要的段,比如符號表、重定位表、debug信息等。這樣裁剪後的程序相比原來的.out文件小很多。所以,如果在linux下,當flash放不下需要
裁剪應用程序的時候,我們首先就要確認燒錄的可執行程序是否已裁剪掉程序運行不需要的東西。下面是裁剪的一個命令例子:
arm-linux-uclibcgnueabi-objcopy -gS flash -O elf32-littlearmflash.bin
strip
Strip用於裁剪elf格式程序。其功能與上一章的objcopy的場景2一致。Strip就是objcopy工具的一個子集。所以,不再詳述。下面是命令使用示例:
mipsisa32-elf-strip -sintegration/product/a.out 
     大家有興趣可以用readelf工具將裁剪前.out的段表讀出和裁剪後的flash.bin的段表讀出對比,看下究竟哪些段被裁掉了。

其他命令
ranlib : 將目標文件打包成庫文件工具,通常在makefile中使用
Cpp 預編譯工具
As 彙編工具
Gcc 編譯工具,當然也可以用於鏈接
c++ C++代碼編譯工具
g++ java代碼編譯工具
Ld 鏈接工具
Gdb 軟件調試工具,具體使用方法請參見123上的gdb培訓文檔

1. 當程序運行起來後內存不夠,如何通過GCC工具查看內存主要消耗在了那些段上?
發佈了34 篇原創文章 · 獲贊 4 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章