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アセンブリファイルが1つ必要です。
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/Archの組み合わせではなく、各アーキテクチャの.sysoファイルを準備するだけで済みます(明らかにOS固有の構成体を使用していないと仮定します)。Goリンカは、たとえばMach-OオブジェクトファイルをELFバイナリにリンクすることができます。そのため、sysoファイルには`file_amd64.syso`、`file_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:
さらに2つのファイル。まず、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`はパッケージ(またはメインパッケージの場合は`main`)のimportステートメントで使用される名前、`name`はパッケージで定義されている文字列変数の名前、`val`はその変数に設定する文字列です。goツールを使用する場合は、`-ldflags`オプションを使用して`-X`オプションをリンカに渡します。
このファイルが`company/buildinfo`パッケージの一部であるとしましょう。
package buildinfo
var BuildTime string
`go build -ldflags="-X 'company/buildinfo.BuildTime=$(date)''"`を使用してプログラムをビルドし、ビルド時間を文字列に記録できます。(`$(date)`の使用は、Unixスタイルのシェルを使用していると仮定しています。)
文字列変数は存在する必要があり、定数ではなく変数である必要があり、その値は関数呼び出しによって初期化されてはなりません。`-X`オプションで間違った名前を使用しても警告はありません。使用する名前は、プログラムで`go tool nm`を実行することで見つけることができますが、パッケージ名にASCII以外の文字、または`"`文字や`%`文字が含まれている場合は失敗します。
このコンテンツは、Go Wikiの一部です。