使用 Golang 轻松进行交叉编译

我通过走出舒适区了解了 Go 的交叉编译能力。
120 位读者喜欢这个。
Person using a laptop

我在 Linux 上测试软件时,会使用具有各种架构(例如,Intel、AMD、Arm 等)的多个服务器。一旦我配置了一个 Linux 盒子并且服务器满足我的测试需求,我仍然需要执行许多步骤

  1. 下载并安装先决条件软件。
  2. 验证我在测试的软件的新测试包是否在构建服务器上可用。
  3. 获取并设置依赖软件包所需的 yum 存储库。
  4. 下载并安装新的测试包(基于第 2 步)。
  5. 获取并设置所需的 SSL 证书。
  6. 设置测试环境,获取所需的 Git 存储库,更改文件中的配置,重新启动守护程序等。
  7. 执行任何其他需要完成的操作。

用脚本自动化一切

这些步骤非常例行,因此将它们自动化并保存到中心位置(如文件服务器),以便在我需要时下载,是有意义的。我通过编写一个 100-120 行的 Bash shell 脚本来完成此操作,该脚本为我完成所有配置(包括错误检查)。该脚本通过以下方式简化了我的工作流程:

  1. 配置一个新的 Linux 系统(正在测试的架构)
  2. 登录到系统并从中心位置下载自动化的 shell 脚本
  3. 运行它来配置系统
  4. 开始测试

进入 Go

我一直想学习Golang,将我心爱的 shell 脚本转换为 Go 程序似乎是一个帮助我入门的好项目。 语法似乎很简单,在尝试了一些测试程序后,我开始提高我的知识并熟悉 Go 标准库。

我花了一个星期的时间在我的笔记本电脑上编写 Go 程序。 我经常在我的 x86 服务器上测试我的程序,以消除错误并改进程序。一切正常。

在我完成 Go 程序之前,我一直在依赖我的 shell 脚本。 然后,我将二进制文件推送到中央文件服务器,以便每次配置新服务器时,我所要做的就是 wget 二进制文件,设置可执行位并运行二进制文件。 我对早期的结果感到满意

$ wget http://file.example.com/<myuser>/bins/prepnode
$ chmod  +x ./prepnode
$ ./prepnode

然后,出现了一个问题

下个星期,我像往常一样从池中配置了一个全新的服务器,下载了二进制文件,设置了可执行位,然后运行了二进制文件。 它出错了——一个奇怪的错误

$ ./prepnode 
bash: ./prepnode: cannot execute binary file: Exec format error
$ 

起初,我以为可能没有设置可执行位。 但是,它已按预期设置

$ ls -l prepnode 
-rwxr-xr-x. 1 root root 2640529 Dec 16 05:43 prepnode

发生了什么? 我没有对源代码进行任何更改,编译没有抛出任何错误或警告,并且上次运行它时运行良好,所以我更仔细地查看了错误消息,format error

我检查了二进制文件的格式,一切看起来都没问题

$ file prepnode 
prepnode: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

我快速运行以下命令来识别我配置的测试服务器的架构以及二进制文件尝试运行的位置。 它是 Arm64 架构,但我编译的二进制文件(在我的 x86 笔记本电脑上)生成了一个 x86-64 格式的二进制文件

$ uname -m
aarch64

脚本编写人员的编译 101

在那之前,我从未考虑过这种情况(尽管我知道它)。 我主要从事脚本语言(通常是 Python)以及 shell 脚本。 Bash shell 和 Python 解释器在任何架构的大多数 Linux 服务器上都可用。 因此,之前一切运作良好。

但是,现在我正在处理一种编译语言 Go,它会生成可执行二进制文件。 编译后的二进制文件由操作码或汇编指令组成,这些指令与特定架构相关联。 这就是我收到格式错误的原因。 由于 Arm64 CPU(我运行二进制文件的地方)无法解释二进制文件的 x86-64 指令,因此它出错了。 以前,shell 和 Python 解释器为我处理了底层的操作码或特定于架构的指令。

使用 Go 进行交叉编译

我查看了 Golang 文档,发现要生成 Arm64 二进制文件,我所要做的就是在运行 go build 命令之前,在编译 Go 程序时设置两个环境变量。

GOOS 指的是操作系统(Linux、Windows、BSD 等),而 GOARCH 指的是要构建的架构。

$ env GOOS=linux GOARCH=arm64 go build -o prepnode_arm64

构建程序后,我重新运行了 file 命令,这次它显示 Arm AArch64 而不是之前的 x86。 因此,我能够为与我的笔记本电脑上的架构不同的架构构建二进制文件

$ file prepnode_arm64 
prepnode_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped

我将二进制文件从笔记本电脑复制到 Arm 服务器上。 现在,运行二进制文件(在设置可执行位之后)不会产生任何错误

$ ./prepnode_arm64  -h
Usage of ./prepnode_arm64:
  -c	Clean existing installation
  -n	Do not start test run (default true)
  -s	Use stage environment, default is qa
  -v	Enable verbose output

其他架构呢?

x86 和 Arm 是我测试软件的五种架构中的两种。 我担心 Go 可能不支持其他架构,但事实并非如此。 您可以使用以下方法找到 Go 支持的架构:

$ go tool dist list

Go 支持各种平台和操作系统,包括

  • AIX
  • Android
  • Darwin
  • Dragonfly
  • FreeBSD
  • Illumos
  • JavaScript
  • Linux
  • NetBSD
  • OpenBSD
  • Plan 9
  • Solaris
  • Windows

要查找它支持的特定 Linux 架构,请运行

$ go tool dist list | grep linux

如下面的输出所示,Go 支持我使用的所有架构。 虽然 x86_64 不在列表中,但 AMD64 与 x86_64 兼容,因此您可以生成 AMD64 二进制文件,它可以在 x86 架构上正常运行

$ go tool dist list | grep linux
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips
linux/mips64
linux/mips64le
linux/mipsle
linux/ppc64
linux/ppc64le
linux/riscv64
linux/s390x

处理所有架构

从我的 x86 笔记本电脑为我的测试下的所有架构生成二进制文件就像编写一个小的 shell 脚本一样简单

#!/usr/bin/bash
archs=(amd64 arm64 ppc64le ppc64 s390x)

for arch in ${archs[@]}
do
	env GOOS=linux GOARCH=${arch} go build -o prepnode_${arch}
done
$ file prepnode_*
prepnode_amd64:   ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=y03MzCXoZERH-0EwAAYI/p909FDnk7xEUo2LdHIyo/V2ABa7X_rLkPNHaFqUQ6/5p_q8MZiR2WYkA5CzJiF, not stripped
prepnode_arm64:   ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, Go BuildID=q-H-CCtLv__jVOcdcOpA/CywRwDz9LN2Wk_fWeJHt/K4-3P5tU2mzlWJa0noGN/SEev9TJFyvHdKZnPaZgb, not stripped
prepnode_ppc64:   ELF 64-bit MSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, Go BuildID=DMWfc1QwOGIq2hxEzL_u/UE-9CIvkIMeNC_ocW4ry/r-7NcMATXatoXJQz3yUO/xzfiDIBuUxbuiyaw5Goq, not stripped
prepnode_ppc64le: ELF 64-bit LSB executable, 64-bit PowerPC or cisco 7500, version 1 (SYSV), statically linked, Go BuildID=C6qCjxwO9s63FJKDrv3f/xCJa4E6LPVpEZqmbF6B4/Mu6T_OR-dx-vLavn1Gyq/AWR1pK1cLz9YzLSFt5eU, not stripped
prepnode_s390x:   ELF 64-bit MSB executable, IBM S/390, version 1 (SYSV), statically linked, Go BuildID=faC_HDe1_iVq2XhpPD3d/7TIv0rulE4RZybgJVmPz/o_SZW_0iS0EkJJZHANxx/zuZgo79Je7zAs3v6Lxuz, not stripped

现在,每当我配置一台新机器时,我只需运行这个 wget 命令来下载特定架构的二进制文件,设置可执行位并运行二进制文件

$ wget http://file.domain.com/<myuser>/bins/prepnode_<arch>
$ chmod +x ./prepnode_<arch>
$ ./prepnode_<arch>

但为什么呢?

您可能想知道,为什么我不坚持使用 shell 脚本或将程序移植到 Python 而不是编译语言,从而节省了所有这些麻烦。 所有公平的观点。 但是那样我就不会了解 Go 的交叉编译能力以及程序在 CPU 上执行时如何在底层工作。 在计算中,总是需要考虑权衡,但永远不要让它们阻止您学习。

接下来阅读什么
User profile image.
经验丰富的软件工程专业人士。 主要兴趣是安全、Linux、恶意软件。 喜欢使用命令行。 对底层软件和了解事物的运作方式感兴趣。 此处表达的意见是我自己的意见,不代表我雇主的意见

3 条评论

嗨,

我仍在寻找一些合适的解决方案。

https://www.myccpay.net/

谢谢 <a href="https://www.myccpay.net/">MyCCPay 登录</a>

谢谢 [url=https://www.myccpay.net/]MyCCPay 登录[/url]

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.