700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 浅谈go语言交叉编译

浅谈go语言交叉编译

时间:2020-01-15 17:55:23

相关推荐

浅谈go语言交叉编译

浅谈go语言交叉编译

基础cgocgo设置编译和链接参数静态库和动态库静态库动态库静态编译cgo的内部连接和外部连接internal linkingexternal linking交叉编译没有C代码,禁用CGO有C代码,启用CGO -XGOkaralabe/xgotechknowlogick/xgocrazy-max/xgo手动编译musl-crossxgotodo参考

基础

cgo

go语言自带的一个工具/特性,用来支持C语言函数调用,同时可以把go语言导出C动态库给其他语言使用。

默认情况下cgo是启用的,可以通过go env命令查看CGO_ENABLED变量确认启用

通过import "C"来启用cgo特性,同时包含C语言头文件<stdio.h>,来使用C.puts,C.CString

import "C"后的注释是C语言代码

把要调用的C和go写到一个文件,代码及执行效果:

把C代码和go代码分开,此时建立单独的hello.c文件,同时在项目目录运行go mod init gotest,新版本go mod默认启用,需要初始化后才可以编译包。

因为关注交叉编译,在此不再叙述如何用go导出C语言函数。

cgo设置编译和链接参数

通过import "C"前的注释语句设置

// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include// #cgo LDFLAGS: -L/usr/local/lib -lpng// #include <png.h>import "C"

编译阶段的参数:定义相关宏和指定头文件检索路径链接阶段的参数:指定库文件检索路径和要链接的库文件-D:定义宏,PNG_DEBUG=1-I:定义头文件包含的检索目录-L:指定链接库文件的检索目录-l:连接时需要连接的png库…

静态库和动态库

CGO在使用C/C++资源的时候三种形式:源码;静态链接库,动态链接库。使用源码就是第一部分讲的直接在源码中写C代码或者包含C源文件。

静态库

优势:生成的程序不会产生额外的运行时依赖

缺点:静态库包含了全部代码和符号信息,不同静态库可能出现符号冲突导致连接失败

目录结构:

(base) # susu @ susu-tools in ~/gotest [8:55:55] $ tree.├── go.mod├── main.go└── number├── libnumber.a├── number.c├── number.h└── number.o1 directory, 6 files

有一个库number,定义如下:

//number.hint number_add_mod(int a, int b, int mod);//number.c#include "number.h"int number_add_mod(int a, int b, int mod) {return (a+b)%mod;}

CGO使用gcc来编译连接C和Go桥接的代码,所以静态库需要用gcc来编译:

gcc -c -o number.o number.car rcs libnumber.a number.o

生成的libnumber.a就是静态链接库。

main.go代码:

package main//#cgo CFLAGS: -I./number//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber////#include "number.h"import "C"import "fmt"func main() {fmt.Println(C.number_add_mod(10, 5, 12))}

参数解释:

-I./number,把number库对应的头文件所在目录添加到检索路径-L${SRCDIR}/number -lnumber:添加静态库的检索路径,连接静态库

build的可执行文件信息及运行结果:

动态库

优点:节省内存和磁盘,隔离不同动态库减少符号冲突

缺点:运行时依赖对应的动态库

创建上述示例的动态库:

gcc -shared -o libnumber.so number.c

编译运行结果:

原因是没有把动态库文件放到默认的搜索路径去,所以执行的时候找不到。

执行sudo cp ./number/libnumber.so /usr/lib拷贝到/usr/lib/目录下,再运行就可以找到了

静态编译

默认情况下有动态库有静态库,会选择动态连接

可以通过命令强制静态连接:go build --ldflags '-extldflags "-static"' ~/gotest

编译出的文件信息及运行结果:

cgo的内部连接和外部连接

internal linking

internal linking的大致意思是若用户代码中仅仅使用了net、os/user等几个标准库中的依赖cgo的包时,cmd/link默认使用internal linking,而无需启动外部external linker(如:gcc、clang等),不过由于cmd/link功能有限,仅仅是将.o和pre-compiled的标准库的.a写到最终二进制文件中。因此如果标准库中是在CGO_ENABLED=1情况下编译的,那么编译出来的最终二进制文件依旧是动态链接的,即便在go build时传入-ldflags '-extldflags "-static"'亦无用,因为根本没有使用external linker

这样就会出现下文中命令行带参数-ldflags '-extldflags "-static"',编译出来的还是会显示为动态连接。

external linking

而external linking机制则是cmd/link将所有生成的.o都打到一个.o文件中,再将其交给外部的链接器,比如gcc或clang去做最终链接处理。如果此时,我们在cmd/link的参数中传入-ldflags '-linkmode "external" -extldflags "-static"',那么gcc/clang将会去做静态链接,将.o中undefined的符号都替换为真正的代码。我们可以通过-linkmode=external来强制cmd/link采用external linker

交叉编译

没有C代码,禁用CGO

不用启用CGO的交叉编译,最简单

基础命令:

env CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} GOMIPS=${gomips} go build -trimpath -ldflags "-s -w" -o ${bin_name} ${package_name}

其中编译参数-ldflags部分按需即可

${os},${arch},${gomips}的可选组合,可以参考/doc/install/source#environment

$GOARM(默认为6),$GOMIPS,$GOPPC64指定特定的架构版本

如何查看Go支持的系统和架构:go tool dist list

$ go tool dist listaix/ppc64android/386android/amd64android/armandroid/arm64darwin/amd64darwin/arm64dragonfly/amd64freebsd/386freebsd/amd64freebsd/armfreebsd/arm64illumos/amd64ios/amd64ios/arm64js/wasmlinux/386linux/amd64linux/armlinux/arm64linux/mipslinux/mips64linux/mips64lelinux/mipslelinux/ppc64linux/ppc64lelinux/riscv64linux/s390xnetbsd/386netbsd/amd64netbsd/armnetbsd/arm64openbsd/386openbsd/amd64openbsd/armopenbsd/arm64openbsd/mips64plan9/386plan9/amd64plan9/armsolaris/amd64windows/386windows/amd64windows/arm

举例编译windows x64的:GOOS=windows GOARCH=amd64 go build ~/gotest

文件信息:gotest.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows

编译arm的:GOOS=linux GOARCH=arm go build ~/gotest

文件信息:gotest: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped

纯go代码,禁用cgo,随便编译跨平台可执行文件,默认编译出的都是静态连接的

有C代码,启用CGO -XGO

karalabe/xgo

最早的版本:/karalabe/xgo

已经不维护了,最后的go支持到1.13.15,且不支持go mod,在此不再介绍。

techknowlogick/xgo

仍在维护,支持go mod,支持go 1.15,1.16版本,/techknowlogick/xgo

安装之类的按照github项目主页的readme来进行就可以。

如果需要使用特定的go版本,在docker pull的时候进行指定,如:docker pull techknowlogick/xgo:go-1.16.3

以frp为例,因为frp算是一个典型的go项目,引用了很多github上的库,有助于测试网络问题,同时在一个项目中需要编译两个可执行文件,启用了go mod。选择的xgo docker的tag是latest。

编译命令:~/go/bin/xgo --pkg cmd/frps .,参考这一节techknowlogick/xgo - Package selection

第一次编译,很明显因为网络问题挂了(注意这条命令不对,按上面写的命令输):

错误:Get "//armon/go-socks5/@v/v0.0.0-0902184237-e75332964ef5.mod": dial tcp 172.217.160.81:443: i/o timeout

解决方案:

配置环境变量export GOPROXY=https://goproxy.io,direct

或者在执行时带上环境变量:GOPROXY=https://goproxy.io,direct ~/go/bin/xgo --pkg cmd/frps .

成功编译:

注意,默认编译出来的在当前目录,带包名目录的下面,也就是不在frp根目录下,而是在frp//fatedier/目录下生成可执行文件。

可以看到编译出的是动态连接到ld-linux.so的,那么是否可以编译出静态连接的呢?

我尝试在运行xgo时加上参数-ldflags '-s -w -extldflags "-static"'但是会报警告:

/tmp/go-link-325119081/000004.o: In function `_cgo_26061493d47f_C2func_getaddrinfo':/tmp/go-build/cgo-gcc-prolog:57: warning: Using 'getaddrinfo' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking

查阅资料显示会有一些依赖动态的加载扩展,但是并不是常用的功能,对于大多数程序可以安全的忽略该警告。(参考Statically compiling Go programs)

最终编译出的一些文件信息如下,不确定是否算是静态连接:

更新一下,运行xgo时加上参数-ldflags '-linkmode "external" -extldflags "-static"',完整命令~/xgo -goproxy 'https://goproxy.io,direct' -ldflags '-linkmode "external" -extldflags "-static"' -pkg cmd/frps -x -docker-image crazymax/xgo:latest .,编译出的可执行文件为静态连接。

文件信息:/fatedier/frp-linux-arm-5: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=71378ee03acfeb967920dda792808d587ca772f3, not stripped

crazy-max/xgo

项目主页:/crazy-max/xgo

特性:支持命令行指定goproxy

按官方文档的说法是,交叉编译使用CGO的Go代码时,尤其是访问一些特定于操作系统的功能时(例如查询cpu负载),需要配置和维护单独的构建环境才可以,而xgo省略了这些事情。

编译命令:~/xgo -goproxy 'https://goproxy.io,direct' -dest ~/frp -pkg cmd/frps -docker-image crazymax/xgo:latest .

编译过程:

编译输出:

手动编译

musl-cross

brew项目:/FiloSottile/homebrew-musl-cross

编译器项目:/richfelker/musl-cross-make

编译工具链:/

mac下可以通过brew install FiloSottile/musl-cross/musl-cross安装编译器,在Go编译的时候指定CC=x86_64-linux-musl-gcc来选择需要的交叉编译器,通过CGO_LDFLAGS="-static"来指定静态编译。

xgo

查看xgo的dockerfile构建时的build.sh,可以看到实际编译时使用的命令,如arm-5的编译命令:

CC=arm-linux-gnueabi-gcc-6 CXX=arm-linux-gnueabi-g++-6 GOOS=linux GOARCH=arm GOARM=5 CGO_ENABLED=1 CGO_CFLAGS="-march=armv5" CGO_CXXFLAGS="-march=armv5" CGO_LDFLAGS="-static" go build -x -trimpath --ldflags='-s -w -extldflags "-static"' -o frps ./cmd/frps

上面的是我根据实际情况修改的,那么这样我们安装了交叉编译器sudo apt install gcc-6-arm-linux-gnueabi g++-6-arm-linux-gnueabi,然后运行上述命令,可以得到编译后的文件信息

ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.3, stripped

这样也可以根据实际情况来手动编译了。

todo

CGO_LDFLAGS="-static"--ldflags '-extldflags "-static"'有何区别?目前没有遇到需要编译复杂的CGO程序的问题,暂时就写这么多了。

参考

《Go语言高级编程》 - 第2章 CGO编程

知乎专栏 - Go语言涉及CGO的交叉编译(跨平台编译)解决办法

知乎专栏 - 含有CGO代码的项目如何实现跨平台编译

Go官方文档 - Installing Go from source

Gist - Go (Golang) GOOS and GOARCH

Blog - Statically compiling Go programs

推荐 - CGO_ENABLED环境变量对Go静态编译机制的影响

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。