Go程序的交叉编译是每一位GO程序猿必须掌握的知识点。但是我没有。

在昨天,我编译了一个项目,生成了二进制,放到目标机器上,执行报错,错误如下:

bash: ./api.linux: cannot execute binary file: Exec format error

提示很明显,可执行文件格式错误,不能执行的二进制文件。

根据经验,直接定位到了,是目标机器的内核跟我的Macbook Pro不匹配;

接着在目标系统(Amazon Linux)下执行:

uname -a
 结果是:
 amzn2023.aarch64 #1 SMP Tue Jan  2 23:50:25 UTC 2024 aarch64 aarch64 aarch64 GNU/Linux

明显跟我的inter芯片的x86_64架构不一样,想到了交叉编译,没什么了不起。

此时我在AI 的指导下,开启了一系列的蛇皮操作

1. 安装 Amazon Linux 的交叉编译工具链: Amazon Linux 系统使用了 musl libc,因此我们需要安装相应的工具链。
$ brew tap aws/tap
$ brew install aws-sam-cli
*安装aws-sam-cli就曲折了很久,好几个包无法访问到。

2. 设置环境变量: 设置环境变量,以便 Go 能够找到 Amazon Linux 的交叉编译工具链。
$ export CGO_ENABLED=0
$ export GOOS=linux
$ export GOARCH=amd64
$ export GO111MODULE=on

3.执行交叉编译
go build -o output_binary_name

将生成的二进制文件,复制到目标系统。

进行测试,继续报错。方法不可行。



怀疑是否是Amazon Linux操作系统执行不了golang 二进制文件,于是我开启了这个小小测试:

第一步:我在目标机器上,复制了本地的一段测试代码。

package main

import (
        "fmt"
        "math/rand"
        "strings"
        "time"
)

func main() {
        //测试去除空字符串
        var a = " a "
        var b = " a b "
        a = strings.TrimSpace(a)
        b = strings.TrimSpace(b)
        fmt.Printf("%#v",a)
        fmt.Printf("%#v",b)


        random := rand.New(rand.NewSource(time.Now().UnixNano()))
        x := random.Uint64()
        y := random.Uint64()

        fmt.Println("x:", x)
        fmt.Println("y:", y)
        if x >= y {
                return
        }
        defer func() {
                fmt.Println("enter defer")
        }()

        fmt.Println("func end line")
}

编译执行,正常。排除了目标系统执行不了二进制文件的可能。

第二步:接着将我的项目打包上传到了目标系统。进行编译,执行。

结果是正常的!

现在开始蠢蠢怀疑Amazon Linux对GO程序的编译有特殊操作,不管了,程序可以跑即可,后续有时间再查。



今天得空了,我想搞明白这个问题。

因为项目文件大且依赖本地包,移到目标机器的步骤较为复杂,根本没有效率。

于是我就决定开启一个本地Amazon Linux (叫它模拟环境)本地docker镜像,做一个本地文件夹的挂载。

version: '3'


  # 定义自定义网络
networks:
  docker_net:
    external: true


services:
  aws-linux2:
    image: amazonlinux:2
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./data:/data
    networks:
      - docker_net
    environment:
      - MY_ENV_VARIABLE=value
    command: tail -f /dev/null

我做了一个猜测,系统一致,编译应该可以一致,编译出来的二进制可以在目标系统直接用。

结果:

在这个模拟环境里,进行编译,生成了二进制文件,放到目标系统里执行,还是同样的报错:

bash: ./api.linux: cannot execute binary file: Exec format error



然后我就比对了模拟环境和目标系统的内核:

模拟环境:UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

目标系统:UTC 2024 aarch64 aarch64 aarch64 GNU/Linux

顿时我就醒悟了:

  1. 我本地的docker,内核当然也是x86_64啊
  2. 我擦,aarch64 这并不是amd64架构啊。是我们昨天想当然了。

顿悟之后当然就简单了我,将我的编译参数指定更改为:

GOARCH=arm64 GOOS=linux go build -o api.linux .

将编译好的二进制,复制到目标系统,执行成功。到此疑团全部揭开。


总结:

交叉编译只跟目标机器内核有关,跟目标机器的操作系统无关。


最后:

附上GOARCH 设置值与内核对应关系。

amd64

x86-64 或 x64

386

32 位 x86 架构

arm

ARM 架构

arm64

64 位 ARM 架构,也称为 AArch64。

ppc64

64 位 PowerPC 架构

ppc64le

64 位 Little-Endian PowerPC

s390x

IBM System z 的 64 位版本,也称为 s390x







留一个问题给你回答, 请问编译windows系统的exe文件,GOARCH该使用什么值,请回答到评论区。