最近两个月,一直在搞项目的国产化移植,把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等方式进行进程间通信

技术
©2020 ioDraw All rights reserved
【2020最牛小程序:你想要的各种资源都能搜的到?】C语言简易学生成绩管理系统运维工作梳理韭菜的愤怒:谷歌删除近10万条对Robinhood的负面评论Tampermonkey(油猴)的获取方法【技巧】简单几招解决Mac磁盘空间不足的问题惹什么猫都别惹熊猫!「功夫熊猫」20年对人类拿下4血为什么保持代码整洁如此重要?玩转指针重难点(1) 如何用SSL减少网站不必要的漏洞