基础

cgo

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

go env
import "C"C.putsC.CString
import "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.o

1 directory, 6 files

有一个库number,定义如下:

//number.h
int 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.c
ar 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

-ldflags '-extldflags "-static"'
-ldflags '-extldflags "-static"'

external linking

-ldflags '-linkmode "external" -extldflags "-static"'-linkmode=external
交叉编译

没有C代码,禁用CGO

env CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} GOMIPS=${gomips} go build -trimpath -ldflags "-s -w" -o ${bin_name} ${package_name}
${os},${arch},${gomips}https://golang.org/doc/install/source#environment$GOARM(默认为6),$GOMIPS,$GOPPC64go tool dist list
$ go tool dist list
aix/ppc64
android/386
android/amd64
android/arm
android/arm64
darwin/amd64
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
freebsd/arm64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x
netbsd/386
netbsd/amd64
netbsd/arm
netbsd/arm64
openbsd/386
openbsd/amd64
openbsd/arm
openbsd/arm64
openbsd/mips64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
windows/386
windows/amd64
windows/arm
GOOS=windows GOARCH=amd64 go build ~/gotestgotest.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS WindowsGOOS=linux GOARCH=arm go build ~/gotestgotest: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, not stripped

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

有C代码,启用CGO -XGO

karalabe/xgo

最早的版本:https://github.com/karalabe/xgo
已经不维护了,最后的go支持到1.13.15,且不支持go mod,在此不再介绍。

techknowlogick/xgo

docker pull techknowlogick/xgo:go-1.16.3~/go/bin/xgo --pkg cmd/frps .Get "https://proxy.golang.org/github.com/armon/go-socks5/@v/v0.0.0-20160902184237-e75332964ef5.mod": dial tcp 172.217.160.81:443: i/o timeoutexport GOPROXY=https://goproxy.io,directGOPROXY=https://goproxy.io,direct ~/go/bin/xgo --pkg cmd/frps .-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)
最终编译出的一些文件信息如下,不确定是否算是静态连接:

-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 .
github.com/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

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

手动编译

musl-cross

brew install FiloSottile/musl-cross/musl-crossCC=x86_64-linux-musl-gccCGO_LDFLAGS="-static"

xgo

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/frpssudo apt install gcc-6-arm-linux-gnueabi g++-6-arm-linux-gnueabiELF 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"'
参考

《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静态编译机制的影响