当CGO遇到交叉编译时的一些小技巧
当CGO遇到交叉编译时的一些小技巧
Section titled “当CGO遇到交叉编译时的一些小技巧”Go本身比较完美地支持交叉编译,通常只要指定==GOOS==和==GOARCH==就可以在本机为不同的操作系统和CPU架构编译出相应的二进制程序。
但如果涉及CGO了,就带来一些问题,当然这也不是Go带来的问题,本质上还是C的问题。
因此,这里总结一下相关使用技巧,以被后续再遇到时可以参考。
PS:我这里构建的机器是X86架构下ubuntu 20.04。
我们以比较常见的mattn/go-sqlite3为例,实例代码如下:
package main
import ( "database/sql" "fmt" "log" _ "github.com/mattn/go-sqlite3")
func main() { // 打开或创建数据库 db, err := sql.Open("sqlite3", "./example.db") if err != nil { log.Fatal(err) } defer db.Close()
// 创建表 sqlStmt := ` CREATE TABLE IF NOT EXISTS foo (id INTEGER NOT NULL PRIMARY KEY, name TEXT); DELETE FROM foo; ` _, err = db.Exec(sqlStmt) if err != nil { log.Printf("%q: %s\n", err, sqlStmt) return }
// 插入数据 tx, err := db.Begin() if err != nil { log.Fatal(err) } stmt, err := tx.Prepare("INSERT INTO foo(id, name) VALUES (?, ?)") if err != nil { log.Fatal(err) } defer stmt.Close() _, err = stmt.Exec(1, "Hello") if err != nil { log.Fatal(err) } tx.Commit()
// 读取数据 rows, err := db.Query("SELECT id, name FROM foo") if err != nil { log.Fatal(err) } defer rows.Close() for rows.Next() { var id int var name string err = rows.Scan(&id, &name) if err != nil { log.Fatal(err) } fmt.Println(id, name) } err = rows.Err() if err != nil { log.Fatal(err) }}不同Linux版本的GLIBC版本不一致,低版本无法运行高版本的产物
Section titled “不同Linux版本的GLIBC版本不一致,低版本无法运行高版本的产物”我在ubuntu 20.04编译得到的二进制,放到centos7上就无法运行了。在centos7上ldd的结果如下:
./test: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by ./test) linux-vdso.so.1 => (0x00007fff3b153000) libdl.so.2 => /lib64/libdl.so.2 (0x00007f13c2e3b000) libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f13c2c1f000) libc.so.6 => /lib64/libc.so.6 (0x00007f13c2851000) /lib64/ld-linux-x86-64.so.2 (0x00007f13c303f000)这里需要两方面的调整:
- 使用musl libc来替代glibc
- 使用静态链接,而非动态链接
ubuntu 20.04下安装musl libc比较简单:
apt install -y musl-tools然后指定使用musl-gcc和-ldflags '-extldflags -static':
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=musl-gcc CXX=musl-gcc go build -ldflags '-extldflags -static'通过file命令可以看出编译得到的产物是静态链接的:
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=mIKpQ1G9dBl-hHZFDqUn/2Ub5ZRjfBAXEUgn9agR5/vlAboSCCToT3-frNQy9m/XXW5y9zlGcjE5CWwkWc4, with debug_info, not stripped如何在X86架构的Linux编译得到针对ARM架构的Linux二进制
Section titled “如何在X86架构的Linux编译得到针对ARM架构的Linux二进制”正如前面所言,Go的交叉编译是比较吸引人的特性。现在我们使用了musl libc来实现静态链接,但上面安装的musl libc是X86架构下的,想直接用它来编译针对ARM的二进制,还是不行的:
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=musl-gcc CXX=musl-gcc go build -ldflags '-extldflags -static'gcc_arm64.S: Assembler messages:gcc_arm64.S:30: Error: no such instruction: `stp x29,x30,[sp,'gcc_arm64.S:34: Error: too many memory references for `mov'gcc_arm64.S:36: Error: no such instruction: `stp x19,x20,[sp,'gcc_arm64.S:39: Error: no such instruction: `stp x21,x22,[sp,'gcc_arm64.S:42: Error: no such instruction: `stp x23,x24,[sp,'gcc_arm64.S:45: Error: no such instruction: `stp x25,x26,[sp,'gcc_arm64.S:48: Error: no such instruction: `stp x27,x28,[sp,'gcc_arm64.S:52: Error: too many memory references for `mov'gcc_arm64.S:53: Error: too many memory references for `mov'gcc_arm64.S:54: Error: too many memory references for `mov'gcc_arm64.S:56: Error: no such instruction: `blr x20'gcc_arm64.S:57: Error: no such instruction: `blr x19'gcc_arm64.S:59: Error: no such instruction: `ldp x27,x28,[sp,'gcc_arm64.S:62: Error: no such instruction: `ldp x25,x26,[sp,'gcc_arm64.S:65: Error: no such instruction: `ldp x23,x24,[sp,'gcc_arm64.S:68: Error: no such instruction: `ldp x21,x22,[sp,'gcc_arm64.S:71: Error: no such instruction: `ldp x19,x20,[sp,'gcc_arm64.S:74: Error: no such instruction: `ldp x29,x30,[sp],'因此,我们需要在我当前X86架构的ubuntu上,先编译得到一个ARM架构的musl libc,回头用这个ARM架构下的musl-gcc来作为工具链。
我们首先需要一个Linux下GCC针对ARM64的编译工具链:
sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu然后下载musl-libc代码后,指定CC为aarch64-linux-gnu-gcc:
./configure --prefix=/opt/aarch64-musl CC=aarch64-linux-gnu-gccmakemake install然后在go build时,指定CC和CXX为这个刚安装的musl libc:/opt/aarch64-musl/bin/musl-gcc:
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=/opt/aarch64-musl/bin/musl-gcc CXX=/opt/aarch64-musl/bin/musl-gcc go build -ldflags '-extldflags -static'对产出物使用file来参看一下,确实是ARM架构下的:
test: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=n4wfJ_5W4z89w7rfoX91/R0HZHgZUXmp8dcwUM3kj/3Abaky1qezrDfOBLuwym/_u0wqEHF3h8CnMKg3KPB, with debug_info, not strippedWindows下的CC和CXX应该指定成什么
Section titled “Windows下的CC和CXX应该指定成什么”ARM架构下的Windows比较少,可以先不考虑。
安装gcc-mingw-w64-x86-64:
apt install gcc-mingw-w64-x86-64 gcc-mingw-w64-i686针对64位的Windows:
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ go build -ldflags '-extldflags -static'针对32位的Windows:
CGO_ENABLED=1 GOOS=windows GOARCH=386 CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ go build -ldflags '-extldflags -static'