5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GoAdvent Calendar 2024

Day 6

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

Last updated at Posted at 2024-12-05

icon.jpg

※ GoのロゴはGo's New Brandより引用
※ QEMUのアイコンはWelcome to QEMU’s documentation!より引用

この記事では

  • Goプログラムをクロスコンパイルする方法を示します
  • コンパイルしたバイナリをQEMUを利用して実行する方法を示します

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

要約

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

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

1.1 GOOSGOARCH

GOでバイナリをクロスコンパイルする際はGOOSGOARCHの2つの環境変数を利用します。
環境変数に関する説明はInstalling Go from source #environmentに記載されています。
各CPUアーキテクチャの設定を行うためにGO<ARCH>の環境変数も存在しています。

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

GOOS/GOARCHの組み合わせに関しては、Installing Go from source #environmentに記載がありますが、go 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
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=windowsGOARCH=armの環境変数を設定します。
Linux上でクロスコンパイルするのであれば、例えば以下のようなコマンドになります。

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

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

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

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となっています。

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

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

$ curl localhost:8080
ok

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

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の登場です。

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

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

2.1 QEMUのインストール

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

バイナリを実行するだけであれば、QEMU User space emulatorが便利なので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がインストールされることがあるのでご注意ください。
バージョンが古すぎるとバイナリの実行に失敗することがあります。

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 クロスコンパイルしてみるでビルドしたlinux/arm向けのバイナリを実行してみます。
利用するコマンドはqemu-armになります。

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

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

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

$ curl localhost:8080
ok

今回はarm向けのバイナリを実行するのでqemu-armを利用しましたが、対象のCPUアーキテクチャによってコマンドは異なります。
利用可能なコマンドはQEMU User space emulatorで確認できます。

例えば

  • 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のコマンドを実行してみます。
結果は以下の通り、エラーが発生しました。

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

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

$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となります。

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

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

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

まとめ

GOではクロスコンパイルする際にGOOSGOARCHを設定します。
LinuxやFreeBSDでは、クロスコンパイルしたバイナリをQEMU User space emulatorを利用して簡単に実行することができます。

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

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

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?