GOでクロスコンパイルしたバイナリをエミュレータ ( QEMU ) で動かすまで


![icon.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705474/070ba006-b7a3-df70-3c79-7f3c74feff8f.jpeg)


※ Goのロゴは[Go's New Brand](https://go.dev/blog/go-brand)より引用
※ QEMUのアイコンは[Welcome to QEMU’s documentation!](https://www.qemu.org/docs/master/index.html)より引用


**この記事では**

- [Go](https://go.dev/)プログラムをクロスコンパイルする方法を示します
- コンパイルしたバイナリを[QEMU](https://www.qemu.org/)を利用して実行する方法を示します 

なお、本記事の内容とは関係ありませんがGithub Actions上でQEMUを利用する例を[https://github.com/t-katsumura/qemu-actions](https://github.com/t-katsumura/qemu-actions)で公開しています。

# 要約

- Goでは、[GOOS/GOARCH](https://go.dev/doc/install/source#environment)を用いて異なるCPUアーキテクチャ向けのバイナリをビルドできる
- クロスコンパイルしたバイナリの実行環境がなくてもある程度はエミュレータ（[QEMU](https://www.qemu.org/)）で実行できる
    - ただし簡単に実行できるのはLinuxとFreeBSDの環境に限る
- GOのテストは`-c`オプションでテストバイナリを作成してからQEMUで実行すると簡単

# 1. 異なるプラットフォーム向けにクロスコンパイルする

## 1.1 `GOOS`と`GOARCH`

GOでバイナリをクロスコンパイルする際は`GOOS`と`GOARCH`の２つの環境変数を利用します。
環境変数に関する説明は[Installing Go from source #environment](https://go.dev/doc/install/source#environment)に記載されています。
各CPUアーキテクチャの設定を行うために`GO<ARCH>`の環境変数も存在しています。

なお、**本記事内ではOSは常に`linux`であることを前提とします。**
また`GO<ARCH>`形式の環境変数も扱わないのでご了承ください。
**したがって、本記事内で設定するのは`GOARCH`のみとなります。**

`GOOS/GOARCH`の組み合わせに関しては、[Installing Go from source #environment](https://go.dev/doc/install/source#environment)に記載がありますが、`go tool dist list`コマンドで確認することもできます。
筆者の環境では以下のように表示されます。

```bash
$ 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
freebsd/riscv64
illumos/amd64
ios/amd64
ios/arm64
js/wasm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/loong64
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/ppc64
openbsd/riscv64
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
wasip1/wasm
windows/386
windows/amd64
windows/arm
windows/arm64
```

例えば`windows/arm`向けにビルドする際は`GOOS=windows`、`GOARCH=arm`の環境変数を設定します。
Linux上でクロスコンパイルするのであれば、例えば以下のようなコマンドになります。

```bash
$ GOOS=windows GOARCH=arm go build ./...
```

## 1.2 クロスコンパイルしてみる

検証に使用するコードを以下に示します。
このコードでは`ok`と返すだけのHTTPサーバをポート`:8080`で起動しています。

```go:main.go
package main

import (
	"net/http"
	"runtime"
)

func main() {
	println("GOOS: " + runtime.GOOS)
	println("GOARCH: " + runtime.GOARCH)
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("ok"))
	})
	println("server listening on :8080")
	http.ListenAndServe(":8080", nil)
}
```

ではまずネイティブコンパイルしてみます。
※ ビルド環境は`linux/amd64`となっています。

```bash:ビルドしてサーバ起動
$ go build -o server main.go
$ ./server
GOOS: linux
GOARCH: amd64
server listening on :8080
```

問題なくサーバを起動できました。
もちろん以下のようにcurlでリクエストを投げるとレスポンスが返ってきます。

```bash
$ curl localhost:8080
ok
```

続いてクロスコンパイルで作成したバイナリを実行してみます。
GOOSはlinuxのまま（設定されなければビルド環境と同じ）、`GOARCH=arm`を指定することとします。


```bash:linux/arm 向けにビルドしてサーバ起動
$ GOARCH=arm go build -o server main.go
$ ./server
-bash: ./server: cannot execute binary file: Exec format error
```

> **cannot execute binary file: Exec format error**

とエラーが発生しました。
実行環境は**linux/amd64**なのにたいして、バイナリが**linux/arm**用になっているので当然の結果です。

ではこの**linux/arm**用のバイナリを**linux/amd64**の環境で実行したいときにはどうすればよいか？
ここでエミュレータである[QEMU](https://www.qemu.org/)の登場です。

QEMUの使い方は次のセキュションに記載します。

# 2. クロスコンパイルしたバイナリを実行する。

## 2.1 QEMUのインストール

ここではQEMUのインストール方法を示します。

バイナリを実行するだけであれば、[QEMU User space emulator](https://www.qemu.org/docs/master/user/main.html)が便利なので`qemu-user`をインストールします。
User space emulatorを利用すると、バイナリを実行するのにわざわざ仮想マシンを作成する必要がありません。
ただし、ドキュメントに記載されている通り、User space emulatorは以下の環境でしか使えません。
MacやWindowsでは使えないのでご注意ください。

- Linux (referred as qemu-linux-user)
- BSD (referred as qemu-bsd-user)

なお、筆者の検証環境は`linux/amd64`（Ubuntu）なのでUser space emulatorが使えます。

User space emulatorは以下のようにインストールします。
OSのバージョンやパッケージマネージャによっては、かなり古いバージョンのQEMUがインストールされることがあるのでご注意ください。
バージョンが古すぎるとバイナリの実行に失敗することがあります。

```bash:QEMU User space emulatorのインストール
# できるだけ新しいバージョンのQEMUをインストールするためにレポジトリを追加。
sudo add-apt-repository -y ppa:canonical-server/server-backports

sudo apt -y update
sudo apt -y install qemu-user
```

## 2.2 QEMUでバイナリを実行する

QEMUがインストールできたところで[1.2 クロスコンパイルしてみる](#12-クロスコンパイルしてみる)でビルドした**linux/arm**向けのバイナリを実行してみます。
利用するコマンドは`qemu-arm`になります。

```bash
$ qemu-arm ./server
GOOS: linux
GOARCH: arm
server listening on :8080
```

**linux/amd64**の環境で**linux/arm**のバイナリを実行できました！！

もちろん、curlでリクエストを投げるとちゃんとレスポンスが返ってきます。

```bash
$ curl localhost:8080
ok
```

今回はarm向けのバイナリを実行するので`qemu-arm`を利用しましたが、対象のCPUアーキテクチャによってコマンドは異なります。
利用可能なコマンドは[QEMU User space emulator](https://www.qemu.org/docs/master/user/main.html)で確認できます。

例えば

- qemu-arm
- qemu-i386
- qemu-x86_64
- qemu-mips64
- qemu-ppc64
- qemu-riscv64

などになります。

## 2.3 Goのテストを実行する

さて、クロスコンパイルしたバイナリはQEMU User space emulatorで実行できることが確認できましたが、
`linux/amd64`の環境で`linux/arm`のテストを実行したいときはどうすればよいでしょうか。

まずはとりあえず、`qemu-arm go`のコマンドを実行してみます。
結果は以下の通り、エラーが発生しました。

```bash
$ qemu-arm go test ./...
Error while loading go: No such file or directory
```

goの実行ファイルがないと怒られているので、goバイナリを絶対パスで指定します。
結果は以下の通り、またしてもエラーが発生しました。

```bash
$qemu-arm /usr/local/go/bin/go test ./...
qemu-arm: /usr/local/go/bin/go: Invalid ELF image for this architecture
```

goバイナリ自体は`linux/amd64`ようなので`qemu-arm`コマンドに渡しても実行できないのは当然ではあります。
そこで別の方法を試してみます。

クロスコンパイルでテスト用のバイナリを生成し、生成したバイナリを`qemu-arm`へ渡してみます。
GOでは`go test`コマンドに`-c`オプションを付けるとテスト用のバイナリが生成されます。
なお、この時生成されたバイナリの拡張子は`.test`となります。

以下は適当なテストコードを実行した結果となります。

```bash
$ GOARCH=arm go test -c ./...
$ qemu-arm cross_compile.test
PASS
```

上記の結果からテストが正常に実行されていることがわかります。
つまり、特定のCPUアーキテクチャにおけるGOのテストは、テストバイナリを生成した後にエミュレータで実行することで実現できます。

# まとめ

GOではクロスコンパイルする際に`GOOS`と`GOARCH`を設定します。
LinuxやFreeBSDでは、クロスコンパイルしたバイナリを[QEMU User space emulator](https://www.qemu.org/docs/master/user/main.html)を利用して簡単に実行することができます。

エミュレータ上でGOのテストを実行したいときは`go test -c`コマンドでテスト用のバイナリを生成したうえで、テストバイナリをQEMUに渡すことで実現できます。

GOでは簡単にクロスコンパイルができますが、テスト環境を用意することは容易ではありません。
ただ、オープンソースの開発などで複数のCPUアーキテクチャ向けのバイナリを配布したいケースはあるので、QEMUなどのエミュレータをうまく活用していきたいと思いました、