Golang交叉編譯中的那些坑

最近兩個月,一直在搞項目的國產化移植,把golang開發好的程序,運行在國產化平臺上,操作系統基本都是基於Linux,但是CPU架構除了x86,還有ARM和MIPS,我們平時的Golang都是運行於x86 && x64 架構的CPU上,因此移植過程中遇到了好多坑,記錄於此。

Golang交叉編譯

交叉編譯

在X64上的ubuntu 16.04系統上編譯出其他平臺的可執行程序

查看Golang支持的平臺和版本

go tool dist list

此命令會列出所有go語言支持的操作系統和cpu架構

golang的交叉編譯

其實go的交叉編譯非常簡單,只需要在編譯前指定系統和CPU架構,基本不會有任何問題,編譯出來講文件拷貝到對應平臺就能跑:

GOOS=linux GOARCH=arm64 go build xxx.go
# 有時候需要加上CGO_ENABLE=0
CGO_ENABLE=0 GOOS=linux GOARCH=arm64 go build xxx.go

go語言的交叉編譯支持非常好,只要按照上述步驟基本不會出什麼問題。坑,主要就坑在cgo!

採用cgo的交叉編譯

使用cgo,就必須指定CGO_ENABLE=1。並且必須指定CC參數爲對應架構的gcc的交叉編譯器。
假設我們變異64位ARM平臺的程序,就要提前下載aarch64版本的c++交叉編譯工具

CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=./aarch64-unknown-linux-gnueabi-5.4.0-2.23-4.4.6/bin/aarch64-unknown-linux-gnueabi-gcc go build xxx.go

如果調用的CGO調用的C程序中依賴各種庫,那麼這個編譯過程會報錯各種依賴的庫not found ,各種基本的函數未定義。而且都是系統中最基本的庫如libglibc、libgstream等。

解決方案是必須在編譯時,加上鍊接庫的參數,而鏈接的庫必須交叉編譯出的目標平臺的系統庫而不是當前系統的。

這個在下載交叉編譯工具鏈的時候,一般都會附帶,我這裏放到系統根目錄下,然後通過C++編譯時鏈接庫的語法將庫鏈接進去:
主要是三個參數:-I , -isystem , -L, -l
下面命令是個例子,假設項目中用到了phnono、curl、protobuf等組件

CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=./aarch64-unknown-linux-gnueabi-5.4.0-2.23-4.4.6/bin/aarch64-unknown-linux-gnueabi-gcc -Wall -std=c++11 -Llib -isystem/aarch64/usr/include -L/aarch64/lib -ldl -lpthread -Wl,-rpath-link,/aarch64/lib -L/aarch64/lib/aarch64-linux-gnu -L/aarch64/usr/lib -I/aarch64/usr/include -L/aarch64/usr/lib/aarch64-linux-gnu -ldl -lpthread -Wl,-rpath-link,/aarch64/usr/lib/aarch64-linux-gnu -lphonon -lcurl -lprotobuf go build xxx.go

到這一步,就基本解決了無法編譯的坑。

平臺差異的問題

在編譯ARM版本的代碼時,報錯好幾個系統調用找不到

  • undefined: syscall.Dup2
  • undefined: syscall.SYS_FORK

解決方案:對比golang源碼實現:go/src/syscall/zsyscall_linux_amd64.gogo/src/syscall/zsyscall_linux_arm64.go,發現arm平臺未實現Dup2但是提供了Dup3,參數略有差異,解決辦法是修改調用的地方:

// - syscall.Dup2(oldfd, newfd) 修改爲:
syscall.Dup3(oldfd,newfd,0)

而SYS_FORK的調用,查找之下發現golang的ARM實現根本沒有實現fork的系統調用,沒有SYS_FORK這個宏或替代品。
無奈只能修改項目代碼,將fork的系統調用改爲別的方式實現。

MIPS的大小端問題

報錯:go.o: compiled for a big endian system and target is little endian
主要體現在大小端字節序的問題,這是我在交叉編譯Mips版本發現的一個問題,仔細查看了我的編譯命令發現:

CGO_ENABLED=1 GOOS=linux GOARCH=mips64 CC=./mips64el-unknown-linux-gnu-5.4.0-2.12-2.6.32/bin/mips64el-unknown-linux-gnu-gcc go build xxx.go

這裏的命令中:CC指定的是mips64el的編譯器,el代表小端字節序,而GOARCH=mips64這是大端字節序,前後不一致導致編譯的報錯,
解決方案:go和gcc保持統一、以目標平臺爲準(龍芯是小端字節序)

  • 將GOARCH指定爲mips64le注意是le不是el
  • 最好加上LDFLAG=-EL
CGO_ENABLED=1 GOOS=linux GOARCH=mips64le CC=./mips64el-unknown-linux-gnu-5.4.0-2.12-2.6.32/bin/mips64el-unknown-linux-gnu-gcc LDFLAGS=-EL go build xxx.go

Tips

綜上所述:

  • golang程序開發少用原生的系統調用syscall
  • 能用go解決的,儘可能不要用cgo
  • 如果有模塊必須通過C/C++調用,推薦C++和golang分離,C++和Golang程序間使用socket等方式進行進程間通信
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章