星期一, 5月 12, 2008

轉載 [新人培訓之2 ] 如何交叉編譯 應用程序,技巧,注意事項

轉載自: http://blog.chinaunix.net/u/22617/showart_372057.html
作者: bob_zhang2004@163.com
潤飾: Richman

最近大家都涉及 cross compile , 感覺大家的路子有點偏 ,覺得有必要糾正一下。

一般的應用程序,編譯的步驟無外乎 ./configure && make && make install

但是對於 cross compile 不能照搬,尤其要注意不能輕易 make install (當然如果指定了 --prefix就無所謂了,否則可能會覆蓋標準路徑的程式就慘了)
這裡有兩個思路:

1. 對於剛開始 cross compile 的人來說,往往很暈,總想借助 ./configure 後面加一堆參數來解決,比如
./configure --target=arm-9tdmi-linux-gnu --host=arm-9tdmi-linux-gnu

對於一般的小的程序來說,應該沒有問題,而且也推薦大家這樣用。
但是要注意 , 這樣作之前,先要
./configure --help | grep --host
看看有沒有這樣的選項,如果沒有呢?
想想也可能, 如果程序的作者根本沒有考慮到除了x86的平台呢?你只能自己改寫 Makefile 了。

所以 ./configure 不是萬能的,而且語法很混亂,不要指望 ./configure 幫你做好所有的事。而且侷限很大。

2. 所以這個時候 ,就要求 cross compile 的第二個層次,自己改寫 Makefile,想怎麼改就怎麼改,靈活性最大。
一開始先 ./configure , 跟平台有關的參數一律不加。

./configure 過後就會生成 Makefile 了, 裡面的 gcc 相關的參數,包括 lib 的路徑當然是 x86 下的了, 比如 /usr/local/lib, /usr/lib, /lib 什麼的, 改掉就是了。 或者註釋掉。

gcc 要換成 arm-linux-gcc 一類的 cross compiler,(如果不想每次都改, 參考下面的 include prerules.mk的做法)。總之, 這可讓你把 Makefile 掌握的很熟練,思路就是邊編譯,邊發現問題,再改,即使一開始 Makefile 不熟練,到後來,也熟練了。是個練習 Makefile 的好方式。

總之, 我們最後要的就是 Makefile,看你怎麼能得到它。
一個最標準的Makefile (去掉很多無用的東西)

透過 ./configure 生成的 Makefile,你會發現冗餘的地方非常多,其實關鍵的地方,就那麼20幾條,可以試著精簡一下,這樣對該軟體的組織架構會熟悉的快一些,畢竟 Makefile 反應了程序(具體就是 .c 和 .h )之間的依賴關係 。

openssh 的 Makefile 我沒有精簡過(當然要精簡也很容易), 舉個 telnetd 的例子,
說明一下:

----------------telnetd -------------------------------

#-----------------------------------------------------
TOPDIR := $(shell /bin/pwd)
TOPDIR := $(TOPDIR)/..

#prerules.mk 包含了這些變量的定義, 比如 $CC , $CPP , $CXX , $CFLAGS 等等。
#儘量不要在這裡出現 CC=arm-linux-gcc 這樣的定義,擴展性不好,儘量用全局變量,便於管理和拓展。
include $(TOPDIR)/prerules.mk
#-----------------------------------------------------

EXEC = telnetd

#好的 Makefile 都是這樣寫的, 也就是具體生成一個可執行文件或者 lib 庫, 需要哪些 .o , 這 些 .o 會依據後面的 .c.o : 規則來編譯出來的。
OBJS = telnetd.o state.o termstat.o slc.o sys_term.o \
utility.o global.o authenc.o logwtmp.o logout.o

#$(CC) 的編譯選項,一般程序自己帶的,不要改它,而且一般都是 += ,不要用 = ,
CFLAGS += -DEMBED -DPARANOID_TTYS -DUSE_TERMIO -DKLUDGELINEMODE -D_GNU_SOURCE -Wall

ifdef CONFIG_DEFAULTS_LIBC_UCLIBC
LDLIBS := -lutil $(LDLIBS)
endif

all: $(EXEC)
#很顯然 all 是最關鍵的了,也要發在最開始的地方。 這樣 make 就相當於 make all , 這是大家的潛規則。

.c.o:
$(CC) -c -o $@ $< $(CFLAGS) -I../include/ -I. -Ixxx 在 cross compile 的時候, 要在這個後面添上自己的標頭檔的路徑。

$(EXEC): $(OBJS) $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS$(LDLIBS_$@))
#這裡的LDFLAGS=-lcrypt -lzlib -L../lib -L. 總之根據自己的需要往裡面增加。

$(STRIP) telnetd
#如果不需要調試, 一定要經過 strip, 比如 15MB 的 file ,strip 過後,可能變成 3MB,還不影響功能。

install: cp $(EXEC) $(T_USBIN)
#自己寫install,不要用原來的,可以 copy 到自己的 ramdisk 中去。

clean: -rm -f $(EXEC) *.elf *.gdb *.[do]

$(OBJS): defs.h ext.h pathnames.h telnetd.h logwtmp.h logout.h

cross compile 成功後, 就萬事大吉了, 這才萬里長征的第一步。 剩下的也許更麻煩呢。

==================================================================

首先拿到一個軟體,我們首先要讓它在 pc 上 run 起來才行,至少我們要稍微瞭解了一下它, 才可以開始我們的 cross compile 的工作。
至少, 我們要瞭解要 run 這個程式,哪些東西是需要的,哪些是不需要的。
一開始,誰也不會瞭解的那麼多,只能一步步的拿到板子上跑跑看了。

準備工作:
1. 我們可以用 ldd 命令來查看某程式執行時需要哪些必要的 library。

2. 看看需要哪些配置文件,也就是 conf 文件。

其實如果想知道上面的這些,還有個辦法,就是先在 pc 上編譯, 安裝
./configure --prefix=/work/bob (改成你自己的目錄即可)
make && make install
看看/work/bob/下面到底生成了哪些 file , 你不就心裡有數了嗎。

==================================================================

先把你知道的程式 copy 到板子上去,執行一下,如果缺少哪些 library ,畫面上會秀出來一些錯誤訊息的。
缺什麼 ,就 copy 什麼到板子上好了,多半缺的都是庫(.so 文件) .

如果還是莫名其妙的出什麼問題(ps 結果就是沒有該 process),有可能是缺少什麼配置文件, 可以用 strace 來查查看。

==================================================================

如果程式執行的結果和 pc 上不太一樣 。 就要注意幾個根本的問題了。

1. 板子的 endian 是什麼類型的呢? x86 是 little endian,arm 的板子可能是 little endian,也可能是 big endian ,如果是 big endian , 就要注意了。要在程式裡面改,添加什麼 le32_to_cpu() 這樣的函數來轉換的。

2. 對齊問題 , x86 和 arm 的對齊處理方式是不一樣的。

3. 中文的問題, 有些程式需要支持中文,繁體,什麼的。pc 上可以, 拿到板子上就不可以了。你要考慮一下 glibc 上面是否支持 locale,libiconv 一類的 library。

==================================================================
生成動態鏈接庫的一個例子,也是標準的 Makefile

#Start of Makefile
#-----------------------------------------------------
TOPDIR := $(shell /bin/pwd)
TOPDIR := $(TOPDIR)/../../

include $(TOPDIR)/prerules.mk
#-----------------------------------------------------
SRCS = download.c curl_err.c DownloadStatusQuery.c
OBJS = download.o curl_err.o DownloadStatusQuery.o

CFLAGS += -I../../include -Wall # -g -ggdb

all: libdownload.so.1.0.0

#test_main:
# $(CC) $(CFLAGS) -I../../../include/ -o main main.c $(LIBS) -ldownload -L. -L../../../lib

%.o:%.c 或者 .c.o: 均可
$(CC) -c -o $@ $(CFLAGS)
$(CC) -c -o $@ $(CFLAGS) $< style="color:darkred;">lt;
libdownload.so.1.0.0 : $(OBJS)
$(CC) -shared -Wl,-soname,libdownload.so.1.0 -o libdownload.so.1.0.0 $(OBJS)
$(STRIP) libdownload.so.1.0.0
rm -rf libdownload.so.1.0
rm -rf libdownload.so
ln -s libdownload.so.1.0.0 libdownload.so.1.0
ln -s libdownload.so.1.0 libdownload.so
cp -afv libdownload.so* $(COMM_LIB_PATH)
cp -f download_operation.h $(COMM_INC_PATH)
cp -f operation.h $(COMM_INC_PATH)
cp -rf curl $(COMM_INC_PATH)
cp -f curl_err.h $(COMM_INC_PATH)
cp -f DownloadStatusQuery.h $(COMM_INC_PATH)

install: # copy到ramdisk裡面就好了
cp -afv libdownload.so* $(T_LIB) #用-afv 參數比較好,保證一模一樣

clean:
rm -rf libdownload.so*
rm -rf $(COMM_LIB_PATH)/libdownload.so*
rm -rf $(COMM_INC_PATH)/download_operation.h
rm -rf $(COMM_INC_PATH)/operation.h
rm -rf $(COMM_INC_PATH)/curl/
rm -rf $(COMM_INC_PATH)/curl_err.h
rm -rf $(COMM_INC_PATH)/DownloadStatusQuery.h
rm -rf *.o
===================
最後解釋一下:

$(CC) -shared -Wl,-soname,libdownload.so.1.0 -o libdownload.so.1.0.0 $(OBJS)

會得到文件,libdownload.so.1.0.0 ,

我們通常要作兩個鏈接

ln -s libdownload.so.1.0.0 libdownload.so.1.0
(這個是在板子上運行的時候一定要有的,因為,-Wl,-soname,libdownload.so.1.0 了,寫死了。 )

ln -s libdownload.so.1.0 libdownload.so
(這個是給別人鏈接的時候用的)

沒有留言: