Go Wiki: GcToolchainTricks

このページでは、gc ツールチェイン (および Go ツール) のあまり知られていない (おそらく高度な) いくつかのテクニックについて説明します。

cgo を使用しない C コード

syso ファイルを使用して任意の自己完結型 C コードを埋め込む

基本的に、アセンブリ言語を GNU as(1) フォーマットで記述しますが、すべてのインターフェース関数が Go の ABI (すべてスタック上など、詳細についてはGo 1.2 アセンブラ入門を参照してください) を使用していることを確認します。

最も重要なステップは、そのファイルを file.syso にコンパイルすること (gcc -c -O3 -o file.syso file.S) で、生成された syso をパッケージソースディレクトリに配置します。次に、アセンブリ関数が Func という名前であると仮定すると、それを呼び出すためのスタブcmd/asm アセンブリファイルが必要です。

TEXT ·Func(SB),$0-8 // please set the correct parameter size (8) here
    JMP Func(SB)

その後、パッケージ内で Func を宣言して使用するだけで、go build は syso を選択してパッケージにリンクできます。

  • 生成されるバイナリは cgo を使用せず、オーバーヘッドは完全に分岐予測できる無条件 JMP だけです。ただし、cgo を使用しないため、アセンブリ関数は Go スタック上で実行され、あまり多くのスタックを使用すべきではない (安全な値は ~100 バイト未満) ことに注意してください。そうしないと恐ろしいことが起こります。計算カーネルの場合、この要件はそれほど制限的ではありません。
  • C コードにすべてのライブラリ依存関係を含めていることを確認してください。libc は使用できません。特に、libgcc も使用できません (特に gcc __builtin_funcs を使用している場合、nm(1) を使用してファイルに未定義のシンボルが含まれていないことを再確認してください)。
  • C コードから Go 関数を呼び出すことも可能ですが、これは読者の演習として残します。
  • このテクニックはすべての Go 1.x リリースでサポートされています。
  • Go リンカは非常に優れており、OS/アーキテクチャの組み合わせごとではなく、各アーキテクチャごとに .syso ファイルを準備するだけで済みます (OS 固有の構造を使用しないと仮定した場合)。Go リンカは、例えば Mach-O オブジェクトファイルを ELF バイナリにリンクすることができます。したがって、syso ファイルには file_amd64.sysofile_386.syso のような名前を付けるようにしてください。

データを Go バイナリにバンドルする

Go バイナリにデータをバンドルする方法はたくさんあります。例えば、

  • データファイルをzip圧縮し、その zip ファイルを Go バイナリの末尾に追加し、zip -A progを使用してバンドルされた zip ヘッダーを調整します。archive/zipを使用してプログラムを zip ファイルとして開き、その内容に簡単にアクセスできます。これに役立つ既存のパッケージもあります。例えば、https://pkg.go.dev/bitbucket.org/tebeka/nrsc; これはプログラムバイナリの事後処理が必要であり、静的データを必要とする非メインパッケージには適していません。また、すべてのデータファイルを1つの zip ファイルにまとめる必要があるため、このメソッドを利用する複数のパッケージを使用することは不可能です。
  • Go プログラムにバイナリファイルを string または []byte として埋め込む。この方法は推奨されません。生成される Go ソースファイルがバイナリファイル自体よりもはるかに大きくなるだけでなく、静的な大きな []byte がパッケージのコンパイルを遅くし、gc コンパイラがコンパイルに多くのメモリを使用するためです (これは gc の既知のバグです)。例えば、tools/godoc/static パッケージを参照してください。
  • 同様の syso テクニックを使用してデータをバンドルする。GNU as(1).incbin 擬似命令を使用して、データファイルを syso ファイルとしてプリコンパイルする。

3番目の選択肢の重要なトリックは、gc ツールチェインのリンカが、異なるアーキテクチャの COFF オブジェクトファイルを問題なくバイナリにリンクできることです。そのため、サポートされているすべてのアーキテクチャの syso ファイルを提供する必要はありません。syso ファイルに命令が含まれていない限り、1つだけを使用してデータを埋め込むことができます。

COFF .syso ファイルを生成するためのアセンブリテンプレート

/* data.S, as -o data.syso */
.section .rdata,"dr" /* put in COFF section .rdata */
.globl _bindataA /* no longer need to prepend package name here */
.globl _ebindataA
_bindataA:
.incbin "dataA"
_ebindataA:

.globl _bindataB /* no longer need to prepend package name here */
.globl _ebindataB
_bindataB:
.incbin "dataB"
_ebindataB:

そして、Go のためのスライスを組み立てる Plan 9 C ソースファイルが最初に、

/* slice.c */
#include "runtime.h"
extern byte _bindataA[], _bindataB[], _ebindataA, _ebindataB;

void ·getDataSlices(Slice a, Slice b) {
  a.array = _bindataA;
  a.len = a.cap = &_ebindataA - _bindataA;
  b.array = _bindataB;
  b.len = b.cap = &_ebindataB - _bindataB;
  FLUSH(&a);
  FLUSH(&b);
}

最後に、埋め込まれたスライドを使用する Go ファイル

/* data.go */
package bindata

func getDataSlices() ([]byte, []byte) // defined in slice.c

var A, B = getDataSlices()

注: COFF syso ファイルを生成できる as(1) が必要です。Unix では簡単にビルドできます。

wget http://ftp.gnu.org/gnu/binutils/binutils-2.22.tar.bz2   # any newer version also works
tar xf binutils-2.22.tar.bz2
cd binutils-2.22
mkdir build; cd build
../configure --target=i386-foo-pe --enable-ld=no --enable-gold=no
make
# use gas/as-new to assemble your data.S
# all the other file could be discarded.

この問題の欠点は、cgo と互換性がないように見えることです。そのため、少なくとも今のところは、cgo を使用しない場合にのみ使用してください。(minux は) なぜ互換性がないのかを解明しようとしています。

実行可能ファイルにビルド情報を含める

gc ツールチェインリンカである cmd/link は、リンク時に Go の文字列変数に任意の情報を記録するために使用できる -X オプションを提供します。フォーマットは -X importpath.name=val です。ここで importpath はパッケージの import ステートメントで使用される名前 (またはメインパッケージの場合は main)、name はパッケージで定義されている文字列変数の名前、val はその変数に設定したい文字列です。go ツールを使用する場合、リンカに -X オプションを渡すためにその -ldflags オプションを使用します。

このファイルがパッケージ company/buildinfo の一部であると仮定しましょう。

package buildinfo

var BuildTime string

go build -ldflags="-X 'company/buildinfo.BuildTime=$(date)'" を使用してプログラムをビルドし、ビルド時間を文字列に記録することができます。($(date) の使用は Unix スタイルのシェルを使用していることを前提としています。)

文字列変数は存在する必要があり、定数ではなく変数である必要があり、その値は関数呼び出しによって初期化されていてはなりません。-X オプションで間違った名前を使用しても警告はありません。プログラムで go tool nm を実行することで使用する名前を見つけることができますが、パッケージ名に ASCII 以外の文字や " または % の文字が含まれている場合は失敗します。


このコンテンツはGo Wikiの一部です。