Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 17730] ファイルの概要

このコミットは、Go言語のツールチェインを静的にリンクしてビルドする機能を追加するものです。これにより、特定のlibc(C標準ライブラリ)のバージョンに依存することなく、より幅広いLinuxディストリビューションやFreeBSD、OpenBSD、NetBSDなどのELFターゲットOSでGoツールチェインが動作するようになります。

コミット

commit a978b1e049268cd2726c521fa3526976c7af7351
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Oct 2 20:14:54 2013 -0400

    misc/dist: support building statically linked toolchain.
    so that we don't need worry about specifying the required
    libc version (note: as cmd/go will still be dynamically
    linked to libc, we still need to perform the build on OSes
    with an old enough libc. But as cmd/go doesn't rely on many
    libc symbols, the situation should be significantly better).
    
    Fixes #3564.
    
    R=golang-dev, adg
    CC=golang-dev
    https://golang.org/cl/14261043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a978b1e049268cd2726c521fa3526976c7af7351

元コミット内容

misc/dist: 静的にリンクされたツールチェインのビルドをサポート。 これにより、必要なlibcのバージョンを指定する心配がなくなります(注:cmd/goは引き続きlibcに動的にリンクされるため、古いlibcを持つOS上でビルドを実行する必要はありますが、cmd/goは多くのlibcシンボルに依存しないため、状況は大幅に改善されるはずです)。

Issue #3564 を修正。

変更の背景

Go言語のツールチェイン(goコマンド、コンパイラ、リンカなど)は、通常、システムにインストールされているC標準ライブラリ(libc)に動的にリンクされます。これは、多くのUnix系システムにおける一般的なソフトウェアの配布方法です。しかし、この動的リンクにはいくつかの問題がありました。

  1. libcのバージョン依存性: 異なるLinuxディストリビューションやOSバージョンでは、libcのバージョンが異なることがあります。新しいバージョンのlibcでビルドされたGoツールチェインが、古いバージョンのlibcしか持たないシステムで実行しようとすると、必要なシンボルが見つからずにエラーとなる可能性がありました。これは、特にGoのバイナリ配布において、ユーザーがGoをインストールする環境のlibcバージョンを考慮する必要があるという課題を生んでいました。
  2. 配布の複雑さ: Goの公式バイナリ配布は、特定のlibcバージョンに依存しないように、古いlibcバージョンを持つ環境でビルドされる必要がありました。これは、ビルドプロセスの複雑さを増し、特定のビルド環境の維持を必要としました。

このコミットは、これらの問題を緩和するために、Goツールチェインの主要部分を静的にリンクするオプションを提供します。これにより、ツールチェイン自体がlibcの特定のバージョンに依存しなくなり、よりポータブルなバイナリの配布が可能になります。ただし、コミットメッセージにもあるように、cmd/go(Goコマンド自体)は一部のlibcシンボルに動的にリンクされ続けるため、完全にlibcから独立するわけではありませんが、依存性は大幅に軽減されます。

前提知識の解説

1. 静的リンクと動的リンク

プログラムが外部のライブラリ(例えば、C標準ライブラリであるlibc)の機能を利用する場合、そのライブラリをプログラムに結合する方法には大きく分けて「静的リンク」と「動的リンク」の2種類があります。

  • 静的リンク (Static Linking):

    • プログラムのビルド時に、必要なライブラリのコードが実行可能ファイルに直接コピーされ、組み込まれます。
    • 利点:
      • 実行可能ファイルが自己完結型になり、外部ライブラリの存在に依存しないため、異なるシステム間での移植性が高いです。
      • 実行時にライブラリのロードが不要なため、起動がわずかに速くなることがあります。
    • 欠点:
      • 実行可能ファイルのサイズが大きくなります。
      • 複数のプログラムが同じライブラリを使用する場合、それぞれのプログラムがライブラリのコピーを持つため、ディスクスペースの無駄が生じます。
      • ライブラリにセキュリティパッチが適用された場合、そのライブラリを静的リンクしているすべてのプログラムを再ビルドする必要があります。
  • 動的リンク (Dynamic Linking):

    • プログラムのビルド時には、ライブラリのコードは実行可能ファイルに組み込まれず、代わりにライブラリへの参照(シンボル情報など)のみが埋め込まれます。
    • プログラムの実行時に、オペレーティングシステム(OS)のローダーが、必要なライブラリをメモリにロードし、プログラムと結合します。
    • 利点:
      • 実行可能ファイルのサイズが小さくなります。
      • 複数のプログラムが同じライブラリを使用する場合、ライブラリのコードはメモリ上に一度だけロードされ、共有されるため、メモリ効率が良いです。
      • ライブラリにセキュリティパッチが適用された場合、ライブラリを更新するだけで、そのライブラリを使用するすべてのプログラムが恩恵を受けられます(再ビルド不要)。
    • 欠点:
      • プログラムを実行するシステムに、必要なライブラリが適切なバージョンでインストールされている必要があります。ライブラリが見つからない、またはバージョンが合わない場合、プログラムは実行できません(「DLL地獄」や「共有ライブラリの依存性問題」として知られる)。
      • 実行時にライブラリのロードとリンクのオーバーヘッドがあります。

2. libc (C標準ライブラリ)

libcは、C言語で書かれたプログラムがOSの機能(ファイルI/O、メモリ管理、文字列操作、数学関数など)を利用するための標準的なインターフェースを提供するライブラリです。Unix系OSでは、glibc (GNU C Library) や musl libc など、いくつかの実装が存在します。GoプログラムはC言語で書かれていないものの、OSとの低レベルなやり取りや、一部のネットワーク操作、DNS解決などにおいて、内部的にlibcの機能を利用することがあります。

3. ELF (Executable and Linkable Format)

ELFは、Unix系OS(Linux、FreeBSD、Solarisなど)で広く使用されている実行可能ファイル、オブジェクトコード、共有ライブラリ、コアダンプファイルの標準フォーマットです。このコミットでは、「ELFターゲット」という言葉が出てきますが、これはELFフォーマットを使用するOS(主にLinux、BSD系)を指します。WindowsやmacOSはそれぞれ独自の実行可能ファイルフォーマット(PE、Mach-O)を使用しています。

4. Goツールチェイン

Goツールチェインとは、Go言語でプログラムを開発・実行するために必要な一連のツール群を指します。これには、以下のようなものが含まれます。

  • goコマンド: ビルド、テスト、実行、フォーマットなど、Go開発の主要な操作を行うためのコマンドラインツール。
  • go build: Goソースコードを実行可能ファイルにコンパイルするコンパイラ。
  • go link: オブジェクトファイルを結合して実行可能ファイルを生成するリンカ。
  • go vet: 静的解析ツール。
  • go fmt: コードフォーマッタ。
  • go test: テストフレームワーク。

これらのツール自体もGo言語で書かれており、実行可能ファイルとして提供されます。

技術的詳細

このコミットは、Goツールチェインのビルドプロセスを制御するmisc/dist/bindist.goファイルに修正を加えています。具体的には、以下の機能が追加されています。

  1. -staticフラグの追加: bindist.goは、Goのバイナリ配布パッケージを生成するためのスクリプトです。このスクリプトに、--staticという新しいコマンドラインフラグが追加されました。このフラグがtrueに設定されると、ツールチェインのビルド時に静的リンクが試みられます。デフォルト値はtrueです。

  2. 静的リンクが可能なOSの定義: staticLinkAvailableという新しい変数(文字列スライス)が導入され、静的リンクがサポートされるOSのリストが定義されています。現時点では、"linux", "freebsd", "openbsd", "netbsd"といったELFターゲットのOSがリストされています。これは、静的リンクがOSのリンカやローダーの挙動に依存するため、すべてのOSで一律にサポートできるわけではないことを示しています。

  3. ビルドプロセスへの静的リンクオプションの組み込み: Build構造体にstaticというブール型のフィールドが追加され、このビルドが静的リンクを行うべきかどうかを保持します。 main関数内で、--staticフラグが有効で、かつ現在のビルドターゲットのOSがstaticLinkAvailableリストに含まれている場合、b.staticフィールドがtrueに設定されます。

  4. GO_DISTFLAGS環境変数の利用: Build構造体のenv()メソッドは、ビルドプロセスに渡される環境変数を設定します。このメソッド内で、b.statictrueの場合、GO_DISTFLAGS=-sという環境変数が追加されます。 このGO_DISTFLAGSは、Goの内部ビルドシステムが使用する環境変数であり、-sフラグはリンカに対してシンボルテーブルを削除する(stripする)ことを指示するだけでなく、静的リンクを強制する効果も持っています。これにより、Goツールチェインのバイナリがlibcなどの外部ライブラリに動的にリンクされるのを防ぎ、可能な限り静的にリンクされるようになります。

この変更により、Goのビルドシステムは、特定のOS上でツールチェインをビルドする際に、静的リンクを試みるようになります。これにより、生成されるツールチェインのバイナリは、より少ない外部依存性で動作し、異なるlibcバージョンを持つシステム間での互換性が向上します。

ただし、コミットメッセージにも明記されているように、cmd/go(Goコマンド自体)は、一部のlibcシンボルに動的にリンクされ続ける可能性があります。これは、GoのランタイムがOSの低レベルな機能(例えば、DNS解決など)を利用するために、どうしてもlibcの特定の機能に依存せざるを得ない場合があるためです。しかし、その依存性は最小限に抑えられ、以前よりも古いlibcバージョンでも動作する可能性が高まります。

コアとなるコードの変更箇所

misc/dist/bindist.goファイルに以下の変更が加えられています。

  1. 新しいフラグの追加:

    --- a/misc/dist/bindist.go
    +++ b/misc/dist/bindist.go
    @@ -38,6 +38,7 @@ var (
      	addLabel        = flag.String("label", "", "additional label to apply to file when uploading")
      	includeRace     = flag.Bool("race", true, "build race detector packages")
      	versionOverride = flag.String("version", "", "override version name")
    +	staticToolchain = flag.Bool("-static", true, "try to build statically linked toolchain (only supported on ELF targets)")
      
      	username, password string // for Google Code upload
      )
    
  2. 静的リンクが可能なOSのリスト定義:

    --- a/misc/dist/bindist.go
    +++ b/misc/dist/bindist.go
    @@ -106,6 +107,15 @@ var raceAvailable = []string{
      	"windows-amd64",
      }
      
    +// The OSes that support building statically linked toolchain
    +// Only ELF platforms are supported.
    +var staticLinkAvailable = []string{
    +	"linux",
    +	"freebsd",
    +	"openbsd",
    +	"netbsd",
    +}
    +
      var fileRe = regexp.MustCompile(
      	`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]))?)\.`)
      
    
  3. ビルド構造体へのstaticフィールド追加:

    --- a/misc/dist/bindist.go
    +++ b/misc/dist/bindist.go
    @@ -184,6 +201,7 @@ type Build struct {
      	Label  string
      	root   string
      	gopath string
    +	static bool // if true, build statically linked toolchain
      }
      
      func (b *Build) Do() error {
    
  4. ビルドプロセスでのstaticフラグの適用ロジック:

    --- a/misc/dist/bindist.go
    +++ b/misc/dist/bindist.go
    @@ -169,6 +179,13 @@ func main() {
      					}
      				}
      			}
    +			if *staticToolchain {
    +				for _, os := range staticLinkAvailable {
    +					if b.OS == os {
    +						b.static = true
    +					}
    +				}
    +			}
      		}
      		if err := b.Do(); err != nil {
      			log.Printf("%s: %v", targ, err)
    
  5. 環境変数GO_DISTFLAGSの設定:

    --- a/misc/dist/bindist.go
    +++ b/misc/dist/bindist.go
    @@ -582,6 +600,9 @@ func (b *Build) env() []string {\n      	"GOROOT_FINAL="+final,\n      	"GOPATH="+b.gopath,\n      )\n    +	if b.static {\n    +		env = append(env, "GO_DISTFLAGS=-s")\n    +	}\n      	return env
      }
      
    

コアとなるコードの解説

このコミットの核心は、misc/dist/bindist.goファイルに新しいビルドオプションを追加し、Goツールチェインのビルド時に静的リンクを有効にするロジックを組み込んだ点にあります。

  • staticToolchainフラグ: flag.Bool("-static", true, ...)によって、コマンドラインから-staticオプションを受け付けるようにしています。デフォルトでtrueに設定されているため、特に指定がなければ静的リンクが試みられます。これは、Goの公式バイナリ配布がデフォルトで静的リンクされたツールチェインを提供する意図があることを示唆しています。

  • staticLinkAvailableリスト: このリストは、静的リンクがサポートされるOSを明示的に定義しています。これは、静的リンクがOSのリンカやシステムコールインターフェースに依存するため、すべてのOSで一様に機能するわけではないという現実を反映しています。特に、WindowsやmacOSのようなELF以外のシステムでは、この静的リンクオプションは適用されません。

  • Build構造体のstaticフィールド: Build構造体は、特定のターゲットOS/アーキテクチャ向けのGoツールチェインのビルド設定をカプセル化します。このstaticフィールドは、そのビルドが静的リンクを行うべきかどうかを示す内部フラグとして機能します。

  • main関数内のロジック: main関数内で、各ビルドターゲット(b)に対して、staticToolchainフラグが有効であり、かつそのターゲットのOSがstaticLinkAvailableリストに含まれている場合にのみ、b.statictrueに設定されます。これにより、静的リンクはサポートされるプラットフォームでのみ試みられるようになります。

  • env()メソッドとGO_DISTFLAGS=-s: Build構造体のenv()メソッドは、Goのビルドコマンド(go tool compile, go tool linkなど)に渡される環境変数を生成します。 if b.static { env = append(env, "GO_DISTFLAGS=-s") }という行が最も重要です。 GO_DISTFLAGSはGoの内部ビルドシステムが使用する環境変数で、ビルドプロセスに特定のフラグを渡すために使われます。 -sフラグは、Goのリンカ(go tool link)に渡されると、通常はシンボルテーブルを削除する(stripする)ことを意味しますが、この文脈では、Goのツールチェインのビルドにおいて、可能な限り静的リンクを行うようにリンカに指示する役割も果たします。これにより、生成されるGoツールチェインのバイナリ(goコマンド、コンパイラ、リンカなど)が、システムにインストールされているlibcなどの共有ライブラリに動的に依存するのではなく、必要なコードをバイナリ内部に含めるようになります。

この一連の変更により、Goの公式バイナリ配布や、ユーザーがmisc/dist/bindist.goスクリプトを使用してツールチェインをビルドする際に、よりポータブルで依存性の少ないバイナリを生成できるようになりました。これは、特にコンテナ環境や、libcのバージョンが古いシステムでのGoの利用を容易にする上で大きな改善となります。

関連リンク

参考にした情報源リンク

  • 静的リンクと動的リンクに関する一般的な情報源 (例: Wikipedia, 各種プログラミングブログ)
  • C標準ライブラリ (libc) に関する一般的な情報源 (例: Wikipedia)
  • ELFフォーマットに関する一般的な情報源 (例: Wikipedia)
  • Go言語のビルドシステムに関するドキュメント (Goの公式ドキュメントやソースコード)
  • Go言語のIssueトラッカー (過去のIssueや議論)
  • Go言語のGerrit (コードレビューシステム)
  • コミットメッセージとコード差分自体