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.go
和go/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等方式进行进程间通信
更多推荐
所有评论(0)