今年1月份的時候在ubuntu上搭建過arm環境,當時只裝了:
也不清楚裝的是什麽,根據名字估計是編譯時需要的庫。搭建完后直接./file或用qemu-arm(需加上-L path)都能運行。但有時gdb調試時,會遇到“符號未定義”的錯誤。
前兩天編譯寫的匯編文件時,直接一堆格式報錯:
這也tcl… 回顧一下整個過程,似乎都沒有涉及到set architecture arm。運行主機是x86的,程式是arm的。即便裝上了arm的依賴庫(按理說是裝全了的,但目前看來并不是),還是會少了些什麽,因此來學下交叉編譯的原理。(希望學完能解決這些問題😢)
1. 概念
1.1 什麽是交叉編譯
(基本上你點開任何一篇深入理解交叉編譯的文章都能看到這個問題)
這個概念是和本地編譯相對的。先來回顧下從源碼至可執行文件的整個過程:編輯 — 編譯— 鏈接。
- 編輯:就是程序員在編輯器上寫的源程式,產生的是源文件。
- 編譯:將人能識別的源文件轉換為機器所能識別的二進制文件(擴展名:.o/.obj),由編譯器完成。 別忘了源文件是高級語言,不能直接轉到機器語言的,中間還有一層匯編語言。這其中便包含了以下兩步:
- 源碼 → 匯編:gcc调用cc1(編譯器)进行预编译和编译,产生的汇编代码保存在/tmp下的文件ccYBInzt.s(随机)
- 匯編 →機器碼:gcc调用as(匯編器)进行汇编,汇编过程产生的目标文件保存在/tmp/ccj54pkM.o。文件有text、date、symbol等段。
- 鏈接:把目標文件(.obj/.o)與lib文件等鏈接,生成elf、libc等文件。在這一步中完成了重定向和符號解析。
更詳細的過程可參考:
之後運行可執行文件,是先load可執行文件到内存中。如果是動態鏈接的方式,則先不把庫加載到内存中,而是運行時再映射。因此它只能在加載運行時完成符號的重定向,不能在鏈接生成elf文件時完成(靜態鏈接可以,動態鏈接只生成一份重定位表)。
本地編譯的動態庫就是本地的,本地運行那必定能load到:
【本地編譯器+本地匯編器+本地鏈接器+本地動態庫】
但是如果要放在別的平臺上運行,動態庫不一樣,運行時就可能會出現未定義符號的錯誤。
編譯的環境和運行的環境不一樣,也就是所謂的“交叉”。
顯然關鍵點在鏈接過程所依賴的庫中。但實際上編譯器GCC也會依賴C庫,因此不能簡單地說 “只更改庫文件就能完成交叉編譯”。
1.2 工具鏈的構建
下面這篇文章已經講得很透徹了:
所謂工具鏈就是完成上面所説的本地/交叉編譯的一系列工具,對應著生成文件過程來看就是:
- 編譯器 — GCC工具:對C庫有依賴性。
- 二進制文件處理 — Binutils工具:匯編器as、鏈接器ld等,幾乎無依賴。
- 鏈接時需要的庫文件 — Glibc:包括C庫(封裝了内核調用)、動態鏈接器等。
出於對本地庫文件依賴的原因,不能用本地 hosted 實現的GCC進行交叉編譯,但可以構建一個支持 freestanding 實現的GCC。它只要求支持部分庫標準,如果用它編譯構建目標系統的C庫,就能再基於此編譯出一個實現交叉編譯的編譯器了。
關於arm的交叉編譯工具鏈有好幾種現成的,包括但不限於:
- arm-none-eabi-gcc:ARM程序接口
- arm-linux-gnueabi-gcc/arm-none-linux-gnueabi-gcc:GUN程序接口
- arm-eabi-gcc:安卓ARM編譯器
詳情參考:
常用的應該是第二個,它能交叉编译ARM系统中所有环节的代码(第一個只支持boot、kernel的編譯,也就是裸機系統)。
可以看到我之前裝的就是這個交叉編譯器:
之前用的匯編器是as,是跟著azeria-labs做的,下載直接用的apt-get install as。沒留意到這個是基於樹莓派開發的,環境本來就是arm…
檢查一下as和ld version。果不其然,就是x86【哭..
現在用 arm-linux-gnueabi-gcc 來編譯就ok了:
可因爲本地環境的lib庫與arm環境的不同,所以運行時報錯。這個問題後面再解決哈。
2. 手動編譯交叉編譯器
用工具生成一個交叉編譯器比從頭開始製作要簡單多了。後面的參考鏈接中有給出相關的工具,ELDK、crosstool-NG等。常用一點的可能是buildroot。但畢竟它主要是用來編譯内核的,這裏只需要編譯器,所以黨gcc編譯完后就可以中斷、直接拿該編譯器出來用了。
還有一種用本地編譯器編譯出交叉編譯器的方法,感覺比較有意思。後面的參考鏈接中會給出原文。奈何作者停更,我跟著做到生成支持freestanding的GCC那一步就沒做下去了。這個需要對這些編譯器都很熟悉哈哈,我暫時還不行emm,先鴿著。(buildroot小白操作不香嗎)