Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CGO 交叉静态编译 #27

Open
eyasliu opened this issue Nov 20, 2019 · 8 comments
Open

CGO 交叉静态编译 #27

eyasliu opened this issue Nov 20, 2019 · 8 comments

Comments

@eyasliu
Copy link
Owner

eyasliu commented Nov 20, 2019

在新的项目中,由于必须要使用sqlite数据库,所以引入了 go-sqlite3 库,而且目标还是安卓开发板,板子是 aarch64 架构,也就是说,目的是要把启用了CGO的项目交叉编译到 aarch64 安卓系统。

本篇文章不局限于 aarch64 架构,其他架构同理,只是到时候下载的工具包不太一样

本文章使用的操作系统为 ubuntu

名词解释:

  • 交叉编译:是在一个平台上生成另一个平台上的可执行文件
  • 静态编译:在编译可执行文件的时候,将可执行文件需要调用的对应库都集成到可执行文件内部,使得可执行文件不需要其他任何依赖就能运行

无CGO项目的交叉静态编译

在不启用CGO的情况下,交叉编译是非常简单的,因为 golang 本身的交叉编译做的非常好

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags '-s -w --extldflags "-static -fpic"' main.go
  • CGO_ENABLED=0 这个值默认是1,也就是开启的,需要手动指定为关闭,因为CGO是不支持交叉编译的,使用 go env CGO_ENABLED 查看默认值
  • GOOS, GOARCH 构建的平台,GOOS=linux是因为安卓底层就是linux,aarch64 架构直接使用 arm64,如果GOARCH=arm,则要使用 GOARM=7,指定arm版本可选5,6,7,在这里看完整支持列表
  • -ldflags 编译选项,-s -w 去掉调试信息,可以减小构建后文件体积,--extldflags "-static -fpic" 完全静态编译,要交叉编译放到其他系统和架构中运行,静态编译是最好的,不然启动的时候会提示找不到依赖的so文件

这样编译生成的文件就可以任意放到指定平台下运行不会有问题。如果没有CGO,一切都很完美。

CGO 项目的交叉静态编译

记录一下在 CGO 交叉编译过程

交叉编译

刚开始,静态编译是不敢想的了,也只有交叉编译可以试一试,安装两个包,如果是其他架构和系统,安装其他对应的包就行了,本文章都以 aarch64 的 linux 为例子

sudo apt install -y cpp-aarch64-linux-gnu g++-aarch64-linux-gnu

安装好以后,指定gcc和g++编译器为上面两个

CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GOOS=linux GOARCH=arm64 go build -o server -ldflags '-s -w' main.go

命令解释

  • CGO_ENABLED=1 开启CGO,因为项目用到了C语言的代码
  • CC=aarch64-linux-gnu-gcc 指定gcc的编译器为 aarch64-linux-gnu-gcc,这个默认值是gcc,也就是当前操作系统和架构使用的gcc,使用命令 $(go env CC) --target-help 可以看看默认gcc支持什么平台
  • CXX=aarch64-linux-gnu-g++ 指定g++的编译器为 aarch64-linux-gnu-g++,规则和 CC 一样,只是用来编C++代码的,如果还用到了C++代码,必须指定该项
  • -ldflags '-s -w' go编译选项,-s -w 去掉调试信息,可以减小构建后文件体积

这样编出来是成功的,整个过程应该会很顺畅,但是把文件放到对应的平台运行,如果运气好,那肯定是运行起来了。如果运气一般会发现提示

./server: No such file or directory

这时候可以用 ldd 看一下

$ ldd server
libdl.so.2
libpthread.so.0
libc.so.6
ld-linux-aarch64.so.1

会发现依赖的so文件好像都有,可以去依赖目录/system/lib(这是安卓的)看看 ,但是运行的时候就是不成功,这里原因我不是太确定。我猜测是这样的,编译器依赖的这些 so 其实是编译器带的那些so,并不是系统的那些,所以在执行的时候这些依赖虽然有,但是并不是执行文件需要的。

这时候,把编译器下面的这几个依赖复制出来,然后执行放到和执行文件同一个目录,这样去执行,应该是可以成功执行的

cd /usr/aarch64-linux-gnu/lib/
cp libdl.so.2 libpthread.so.0 libc.so.6 ld-linux-aarch64.so.1 ~ # 去编译器目录把这几个文件复制出来
chmod +x libdl.so.2 libpthread.so.0 libc.so.6 ld-linux-aarch64.so.1 # 给这几个文件加上可执行权限
scp ...... # 想办法把上面这几个文件复制到对应平台和执行文件一个目录

# 对应平台执行
./libc.so.6 ./libdl.so.2 ./libpthread.so.0 ld-linux-aarch64.so.1 ./server 
# 也可以指定依赖目录
LD_LIBRARY_PATH=. ./server

这样子手动的执行依赖库,就能成功的启动起来了,如果还不行的话,继续往下看吧,下面有把所有so静态编译进去的方法。

静态编译

回看刚刚交叉编译时候的命令,我们是不是在 -ldflags 那里漏掉了刚开始的那个 --extldflags "-static -fpic",现在把它加上去

$ CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ GOOS=linux GOARCH=arm64 go build -o server -ldflags '-s -w --extldflags "-static -fpic"' main.go
# command-line-arguments
/tmp/go-link-972598095/000015.o: In function `unixDlOpen':
/home/eyas/.GOPATH/pkg/mod/github.com/mattn/go-sqlite3@v1.10.0/sqlite3-binding.c:38461: warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-972598095/000031.o: In function `mygetgrouplist':
/opt/golang/src/os/user/getgrouplist_unix.go:16: warning: Using 'getgrouplist' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-972598095/000030.o: In function `mygetgrgid_r':
/opt/golang/src/os/user/cgo_lookup_unix.go:38: warning: Using 'getgrgid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-972598095/000030.o: In function `mygetgrnam_r':
/opt/golang/src/os/user/cgo_lookup_unix.go:43: warning: Using 'getgrnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-972598095/000030.o: In function `mygetpwnam_r':
/opt/golang/src/os/user/cgo_lookup_unix.go:33: warning: Using 'getpwnam_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-972598095/000030.o: In function `mygetpwuid_r':
/opt/golang/src/os/user/cgo_lookup_unix.go:28: warning: Using 'getpwuid_r' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
/tmp/go-link-972598095/000004.o: In function `_cgo_26061493d47f_C2func_getaddrinfo':
/tmp/go-build/cgo-gcc-prolog:58: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

好吧,报错了。

我不太懂C语言,但是根据错误信息能得知,有些库文件是不能被静态链接的,gcc 编译器不支持。也就是说 cpp-aarch64-linux-gnu 这个编译器不支持静态编译。

MUSL GCC

事实上这卡住了我很长一段时间,直到后面在玩 RUST 的时候发现了个好东西,就是支持gcc静态编译的 musl gcc,才终于解决 CGO 静态编译

先下载 musl 对应的平台的编译器,进入 这里 能发现 musl 其实只能在 x86_64 架构下的 linux 系统才能运行,也就是说只能在 linux 系统下去交叉编译其他平台的执行文件(也不知道我说的对不对)

下载 aarch64 linux 的编译器: https://musl.cc/aarch64-linux-musl-cross.tgz

解压,然后把解压好的目录下 bin 文件路径放到 PATH 环境变量中,再把编译器换成这个

$ CGO_ENABLED=1 CC=aarch64-linux-musl-gcc CXX=aarch64-linux-musl-g++ GOOS=linux GOARCH=arm64 go build -o server -ldflags '-s -w --extldflags "-static -fpic"' main.go

没有报错,成功编译了,不用做什么其他操作,把可执行文件放到对应平台上运行,正常。

完成了,折腾到此结束

@eyasliu eyasliu changed the title CGO 交叉静态编译到任意平台 CGO 交叉静态编译 Nov 23, 2019
@ezhishui
Copy link

@ezhishui
Copy link

可以用这个地址:https://musl.cc/aarch64-linux-musl-cross.tgz

@eyasliu
Copy link
Owner Author

eyasliu commented Apr 30, 2020

可以用这个地址:https://musl.cc/aarch64-linux-musl-cross.tgz

感谢指正

@jinzhongjia
Copy link

我想问一下,如果我想在windows下交叉编译cgo到linux上,我该怎么安装或者寻找对应的linux上需要的gcc和g++编译器呢,我甚至都不知道搜索关键词该怎么查找。。。。
我试过搜索“windows交叉编译cgo到linux”,结果确实牛马不相及。。。。🥺

@eyasliu
Copy link
Owner Author

eyasliu commented Feb 17, 2022

@yingyi666 你需要的应该是运行在 windows 系统下能编译到 linux 的 gcc 和 g++ ,这个你可能在这里能找到 https://musl.cc/ ,具体是哪一个我也不确定,也可能没有,不过如果你换个方式,在 linux 下能交叉编译到其他平台的 gcc 应该还是很好找的

@jinzhongjia
Copy link

jinzhongjia commented Feb 17, 2022

感谢,我目前的代替方案是使用docker自行构建一个开启了cgo的镜像,然后创建容器编译

@anonymous-coder-x
Copy link

如果CGO程序中需要dlopen依赖库,是不是没法静态编译。我尝试在mac上交叉编译包含CGO的代码到linux平台的时候,使用musl-cross虽然能编译成功,但是在linux上执行的时候,会报错undefined symbol

@jinzhongjia
Copy link

如果CGO程序中需要dlopen依赖库,是不是没法静态编译。我尝试在mac上交叉编译包含CGO的代码到linux平台的时候,使用musl-cross虽然能编译成功,但是在linux上执行的时候,会报错undefined symbol

目前cgo是无法静态编译的,除非你可以配置好对应平台的工具链,然后在编译的期间指定工具链

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants