- 论坛徽章:
- 0
|
GNU compiler及binary utilities簡介
1 各個部分的職能
通常為了把一個應用程式從原始檔案轉變為可執行的二進位碼,需要以下三個部分:
1. 編譯器
其中編譯器部分還可細分為預處理器,C編譯器和彙編編譯器等. GCC的功能是C編譯器.
2. (目標代碼)連接器
Binutils 最重要的成員是彙編編譯器和連接器,還包括一些二進位碼工具.
3. 程式庫
程式庫通常是C或C++標準庫.
注意這三部分是彼此獨立的,也就是說,GCC並不是非要Binutils中的工具,
也可以使用其他彙編編譯器和連接器,也可以使用其他C程式庫.
2 使用Binutils中的工具
Binutils中的工具可以幫助我們診斷許多問題.
2.1 readelf
readelf用來察看elf文件的內容. 用-a選項可以看見大部分內容:
arm-linux-readelf -a rt61ap.ko
2.2 objdump
objdump的一個重要作用是反彙編目標文件:
arm-linux-objdump -S rtmp_main.o
rtmp_main.o: file format elf32-bigar
Disassembly of section .text:
00000000 :
0: e1a0c00d mov ip, sp
4: e92dddf0 stmdb sp!, {r4, r5, r6, r7, r8, sl, fp, ip, lr, pc}
...
00000218 :
218: e1a0c00d mov ip, sp
21c: e92dd8f0 stmdb sp!, {r4, r5, r6, r7, fp, ip, lr, pc}
2.3 objcopy
objcopy有一個很重要的作用是把代碼從elf文件中抽取出來,形成可執行的機器碼:
arm-linux-objcopy -O binary -R .comment -R .note -S rt61ap.ko test.bin
形成的結果文件test.bin可以燒到flash或下載到記憶體中去.
2.4 nm
nm用來列出elf文件中使用到的symbol:
arm-linux-nm rt61ap.ko
00000854 D A_BAND_REGION_0_CHANNEL_LIST
00035890 T aes128k128d
U alloc_etherdev
T表示定義過的函數,U表示尚未定義的函數. 我們可以看出aes128k128d在rt61ap.ko裏定義了,
其他幾個函數alloc_etherdev在rt61ap.ko中被引用,但找不到定義,它們可能在其他.o文件或程式庫中定義.
nm對診斷連接錯誤很有幫助.
2.5 連接器ld
連接器的主要作用是把一個或多個目標文件(程式庫)轉變為一個可執行程式.
符號的重定位(relocation)是它最重要的工作. 傳給ld的主要參數有目標文件,-l 選項和-L選項. 例如:
ld -o crt0.o myapp main.o subs.o -lm -lc -L/usr/lib
注意-lc的寫法,它表示要與libc.a連接2;同樣,-lm表示要與數學庫libm.a連接. -Lpath指定庫的搜索路徑.
2.5.1 連接腳本
ld知道如何連接各段(section)以及各段的起始位置,因為這些資訊在ld本身被編譯的時候已經內置進ld了.
有時你可能需要自己來安排可執行文件中各段的分佈. 你可以自己寫一個連接腳本(link script),
用-T選項傳給ld. 這是一個簡單的連接腳本:
MEMORY
{
rom (rx) : ORIGIN = 0, LENGTH = 2M
ram (!rx): ORIGIN = 0x1000000, LENGTH = 8M
}
SECTIONS
{
.text : {
_ftext = . ;
*( .text )
}
. = ALIGN( 4 );
_etext = .;
PROVIDE(etext = .);
.data :
AT (_etext)
{
*( .data )
} >ram
_edata = .;
.bss : {
*(.bss)
}>ram
. = ALIGN( 4 );
_end = .;
}
它只指定了3個最重要的段:text,data和bss以及它們的位置. 定制的連接腳本在嵌入式系統中經常要用到,
因為ld內置的腳本可能不符合需要.
2.5.2 部分連接
ld是連接器. 有時我們可以用它來``部分連接''幾個文件以生成一個目標文件,
該目標文件以後還可以再次與其他目標文件連接. ``部分連接''在uClinux中的應用:
genromfs -d romfs -f romfs.img
ld -r -o romfs.o romfs.img
ld並不關心romfs.img的文件格式是什麼. 但生成的romfs.o是ELF格式,它以後還會和其他.o文件連接最終生成linux.elf.
2.6 ar
ar通常用來製作庫文件---即含有許多.o文件的.a文件.如果你想瞭解某個庫文件的內容,可以ar tv libabc.a
製作一個庫文件也很簡單:
ar rs libabc.a a.o b.o c.o
事實上你可以把任何文件打包成一個存檔文件,ar並不關心a.o,b.o,c.o等的格式. 你還可以把存檔文件裏的文件解開:
ar x libabc.a a.o
3 GCC作為編譯器和連接器的大門
3.1 統一的入口
GCC本身並不是編譯器或連接器. 它可以作為預處理器,C編譯器,彙編編譯器和連接器的入口. 例如,
你運行命令\n gcc -o a1.o -c a1.c
的時候,gcc自動調用了預處理器cpp,彙編編譯器as. 你也可以用gcc來編譯彙編文件:
gcc -o a1.o -c a1.S
在Makefile中我們通常定義變數LD為gcc,用gcc來連接目標文件:
LD = arm-elf-gcc
...
$(OUTPUT_NAME): $(OBJECTS)
echo "Linking... "
echo "Creating file $@..."
$(LD) -o $@ $(STARTUP) $^ $(LDFLAGS)
用gcc作為編譯器和連接器的入口的好處是:它會傳一些額外的參數給編譯器和連接器.
比如,通常程式會與標準C程式庫相連接.
如果你用ld來連接,必須傳給ld 許多參數,象crt0.o,-lc等等. 因為這些東西並不是生成可執行文件所必需的,
因此這些資訊不會內嵌在ld中. 如果你用gcc作為連接器,這些選項就會自動被加上去.
3.2 gcc的specs文件
specs 存放在/usr/lib/gcc-lib/$(ARCH)-$(OS)/$(VERSION)下面.
gcc的上述行為是通過它的specs文件實現的. 我們打開specs文件,選取一些片斷看一下:
*startfile:
crti%O%s crtbegin%O%s crt0%O%s
此條資訊告訴我們,startfile是crti.o,crtbegin.o和crt0.o. 在連接的時候它們會被置於你的應用程式的最前面.
*predefines:
-D__ELF__
ELF 會被預定義. 因此如果你在程式中有如下語句:
#ifdef __ELF__
do_something();
#endif
do something肯定會被編譯進代碼.
*link:
%{mbig-endian:-EB} -X
如果命令行中有形如-mbig-endian的參數,就傳給連接器``-EB -X''的參數
*lib:
%{!shared:%{g*:-lg} %{!p:%{!pg:-lc}}%{p:-lc_p}%{pg:-lc_p}}
連接的時候,會根據不同的選項,添加庫libc或libc p等.
*link_command:
%{!fsyntax-only:%{!c:%{!M:%{!MM:%{!E:%{!S: %(linker) %l %X %{o*}
%{A} %{d} %{e*} %{m} %{N} %{n} %{r} %{s} %{t} %{u*} %{x} %{z} %{Z}
%{!A:%{!nostdlib:%{!nostartfiles:%S}}} %{static:} %{L*}
%(link_libgcc) %o
%{!nostdlib:%{!nodefaultlibs:%(link_gcc_c_sequence)}}
%{!A:%{!nostdlib:%{!nostartfiles:%E}}} %{T*} }}}}}}
這是連接的規則,比較複雜. 不過我們可以看出,gcc為我們額外做了許多事情.
4 製作交叉編譯器
4.1 一些基本概念
製作交叉編譯器的時候,GCC有所謂的target,host和build的概念.
交叉編譯器所生成的代碼運行在target上面,交叉編譯器本身運行在host上,而製作交叉編譯器的機器是build.
因此我們可以在i386-pc-linux系統上製作一套在cygwin上運行的交叉編譯器,該編譯器生成在ARM處理器上運行的代碼.
4.2 準備工作
我們將在cygwin環境下製作一套arm-elf的交叉編譯器. 我們需要的套裝軟體有: GCC, Binutils和libc.
libc可以有多種選擇,我們在此選用newlib.
首先把下載好的套裝軟體解開:
mkdir c:/build
cd c:/build
tar -jxf binutils-2.13.1.tar.bz2
tar -zxf gcc-3.2.3.tar.gz
tar -zxf newlib-1.11.0.tar.gz
目錄binutils-2.13.1,gcc-3.2.3和newlib-1.11.0會生成.
這三個套裝軟體都建議原始檔案目錄和編譯目錄分開.
因此我們要另建三個目錄:
mkdir build-bin build-gcc build-newlib
4.3 開始編譯
製作交叉編譯器的步驟是:
1. 編譯binutils
2. 編譯一個最簡單的gcc
3. 編譯libc
4. 再次編譯gcc,生成功能完整的gcc
4.3.1 編譯binutils
編譯binutils很簡單:
cd /cygdrive/c/build/build-bin
/cygdrive/c/build/binutils-2.13.1/configure --target=arm-elf --prefix=/cygdrive/c/bar --nfp
make all install
4.3.2 編譯最簡gcc
然後我們編譯一個最簡的gcc,它只能編譯C程式:
cd /cygdrive/c/build/build-gcc
/cygdrive/c/build/gcc-3.2.3/configure --target=arm-elf
--prefix=/cygdrive/c/bar --with-newlib --without-headers \
--enable-languages=c --disable-threads --nfp
make all install
4.3.3 編譯newlib
再用剛才編譯好的gcc和binutils編譯newlib:
cd /cygdrive/c/build/build-newlib
CFLAGS=-O2 CXXFLAGS=-O2 /cygdrive/c/build/newlib-1.11.0/configure
--target=arm-elf --prefix=/cygdrive/c/bar \
--srcdir=/cygdrive/c/build/newlib-1.11.0 --nfp
make all install \
CC_FOR_TARGET=/cygdrive/c/bar/bin/arm-elf-gcc \
AS_FOR_TARGET=/cygdrive/c/bar/bin/arm-elf-as \
LD_FOR_TARGET=/cygdrive/c/bar/bin/arm-elf-ld \
AR_FOR_TARGET=/cygdrive/c/bar/bin/arm-elf-ar \
RANLIB_FOR_TARGET=/cygdrive/c/bar/bin/arm-elf-ranlib
4.3.4 再次編譯gcc
最後重新編譯gcc,這次它充分利用了剛編好的newlib,可以支援C之外的其他語言了:
cd /cygdrive/c/build/build-gcc
/cygdrive/c/build/gcc-3.2.3/configure --target=arm-elf \
--prefix=/cygdrive/c/bar --with-newlib \
--with-headers=/cygdrive/c/build/newlib-1.11.0/newlib/libc/include \
--enable-languages=c++ --disable-threads --nfp
make all install
這樣我們的交叉編譯器就做好了,它生成在目錄c:/bar裏.
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/29985/showart_231922.html |
|