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

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

コミット

  • Author: Ian Lance Taylor iant@golang.org
  • Date: Tue May 20 21:36:50 2014 -0700
  • Commit Hash: f85600859dbe0ebad85f997f158f8e0224a3c02f
  • Files Changed:
    • misc/cgo/nocgo/nocgo.go (新規ファイル)
    • misc/cgo/nocgo/nocgo_test.go (新規ファイル)
    • src/cmd/ld/lib.c
    • src/run.bash

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

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

元コミット内容

cmd/ld: really import runtime/cgo for external link

Fixes #8032.

LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/95580043

変更の背景

このコミットは、Goリンカ (cmd/ld) が外部リンキング (-linkmode=external) を使用する際に、runtime/cgo パッケージを適切にインポートしないことによって発生していた問題(Issue #8032)を修正します。

GoプログラムがCgoを明示的に使用しない場合でも、外部リンキングを行う際には、GoランタイムがOSのスレッドモデルと連携するために runtime/cgo パッケージが内部的に必要となる場合があります。特に、GoのgoroutineがOSのスレッドにマッピングされる際や、外部リンカが生成するCのスタートアップコードとGoランタイムが連携する際に、runtime/cgo が提供する機能が不可欠です。

以前のリンカの挙動では、runtime/cgo が必要であるにもかかわらず、そのオブジェクトファイルがリンカによって適切に処理されず、結果として実行時に必要なシンボルが見つからない、あるいは予期せぬ動作を引き起こす可能性がありました。この問題は、特に静的リンク (-static) を使用した場合に顕著になることがありました。

このコミットの目的は、外部リンキングを使用するGoプログラム(Cgoを明示的に使用しないものも含む)が、runtime/cgo の依存関係を正しく解決し、安定して動作するようにすることです。

前提知識の解説

Goリンカ (cmd/ld)

Goリンカは、Goコンパイラによって生成されたオブジェクトファイルと、必要なライブラリ(Go標準ライブラリ、Cgo関連ライブラリなど)を結合して、実行可能なバイナリを生成するツールです。Goのビルドプロセスにおいて非常に重要な役割を担っています。

Cgo

Cgoは、GoプログラムからC言語のコードを呼び出すためのGoの機能です。Cgoを使用すると、既存のCライブラリをGoから利用したり、パフォーマンスが重要な部分をCで記述したりすることができます。Cgoを使用するプログラムは、GoコンパイラとCコンパイラ(通常はGCC)の両方によって処理され、最終的に外部リンカによってリンクされます。

外部リンキング (-linkmode=external)

Goのリンカには、内部リンキングと外部リンキングの2つのモードがあります。

  • 内部リンキング (internal linking): Goリンカがすべてのリンク処理を自身で行います。Goの標準ライブラリやGoで書かれたコードのみで構成されるプログラムに適しています。
  • 外部リンキング (external linking): Goリンカがオブジェクトファイルを生成した後、最終的なリンク処理をシステムにインストールされている外部リンカ(例: GCC)に委ねます。Cgoを使用する場合や、特定のシステムライブラリ(例: libc)に依存する場合に必要となります。外部リンカは、GoランタイムがOSと連携するために必要なCのスタートアップコードや、スレッド管理に関連するライブラリをリンクする役割も担います。

静的リンク (-static)

静的リンクは、プログラムが実行時に必要とするすべてのライブラリコードを、実行可能ファイル自体に含めるリンク方式です。これにより、プログラムは外部の共有ライブラリに依存せず、単一のファイルとして配布・実行できるようになります。しかし、静的リンクはバイナリサイズを大きくし、ライブラリの更新があった場合に再コンパイルが必要になるという欠点もあります。

runtime/cgo パッケージ

runtime/cgo パッケージは、GoランタイムとCgoの間の橋渡しをする役割を担います。Cgoを明示的に使用しない場合でも、外部リンキングを使用するGoプログラムでは、runtime/cgo が内部的に必要となることがあります。これは、GoのgoroutineがOSのスレッドにマッピングされる際の処理や、外部リンカがリンクするCのコード(例えば、スレッドの初期化やシグナルハンドリング)とGoランタイムが連携するためのメカニズムを提供するためです。runtime/cgo は、GoのスケジューラがOSのスレッドを管理し、goroutineを効率的に実行するために必要な低レベルのフックを提供します。

Issue #8032

このコミットが修正するIssue #8032は、Goのリンカが外部リンキングを行う際に、runtime/cgo パッケージのオブジェクトファイルを適切に処理しない、あるいはインポートしないことによって発生していた問題です。これにより、Cgoを明示的に使用していないGoプログラムでも、外部リンキングを行うと、runtime/cgo に関連するシンボルが見つからずにリンクエラーになったり、実行時にクラッシュしたりする可能性がありました。特に、goroutineの作成やチャネル操作など、Goランタイムの基本的な機能が外部リンキング環境下で正しく動作しないケースが報告されていたと考えられます。

技術的詳細

このコミットの主要な変更点は、Goリンカ (cmd/ld) が外部リンキングを行う際に、runtime/cgo パッケージのオブジェクトファイルを確実にインポートし、リンクプロセスに含めるようにしたことです。

Goのリンカは、プログラムが依存するパッケージを解決し、それらのオブジェクトファイルを結合します。外部リンキングの場合、Goリンカは最終的なリンク処理を外部リンカに委ねる前に、Goのオブジェクトファイルを準備します。この準備段階で、runtime/cgo が必要であるにもかかわらず、そのオブジェクトファイルが適切に処理されないことが問題でした。

具体的には、src/cmd/ld/lib.c 内の loadlib 関数において、runtime/cgo が内部的にロードされる際に、そのオブジェクトファイルがリンカのライブラリリストに適切に追加され、処理されるように修正が加えられました。これにより、runtime/cgo が提供するシンボルが最終的なバイナリに確実に含まれるようになります。

また、この変更の検証のために、misc/cgo/nocgo という新しいテストパッケージが追加されました。このテストは、Cgoを明示的に使用しないGoプログラムが、外部リンキングおよび静的リンクの条件下で正しく動作することを確認します。nocgo.go 内の NoCgo 関数は、goroutineを作成し、チャネルを介して値を送受信するという、Goランタイムの基本的な機能を使用しています。これは、runtime/cgo が正しく機能していることを間接的に検証するためのものです。

src/run.bash に追加されたテストコマンドは、nocgo パッケージを様々なリンキングモード(特に外部リンキングと静的リンク)でビルドし、実行することで、この修正が意図通りに機能していることを確認します。これにより、Goのビルドシステムが、Cgoを使用しない外部リンクされたプログラムの安定性を保証できるようになりました。

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

src/cmd/ld/lib.c の変更

--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -205,6 +205,8 @@ loadlib(void)
 		// whether to initialize the TLS.  So give it one.  This could
 		// be handled differently but it's an unusual case.
 		loadinternal("runtime/cgo");
+		if(i < ctxt->libraryp)
+			objfile(ctxt->library[i].file, ctxt->library[i].pkg);
 
 		// Pretend that we really imported the package.
 		s = linklookup(ctxt, "go.importpath.runtime/cgo.", 0);

新規ファイル misc/cgo/nocgo/nocgo.go

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Test that -static works when not using cgo.  This test is in
// misc/cgo to take advantage of the testing framework support for
// when -static is expected to work.

package nocgo

func NoCgo() int {
	c := make(chan int)

	// The test is run with external linking, which means that
	// goroutines will be created via the runtime/cgo package.
	// Make sure that works.
	go func() {
		c <- 42
	}()

	return <-c
}

新規ファイル misc/cgo/nocgo/nocgo_test.go

// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package nocgo

import "testing"

func TestNop(t *testing.T) {
	i := NoCgo()
	if i != 42 {
		t.Errorf("got %d, want %d", i, 42)
	}
}

src/run.bash の変更

--- a/src/run.bash
+++ b/src/run.bash
@@ -145,6 +145,9 @@ dragonfly-386 | dragonfly-amd64 | freebsd-386 | freebsd-amd64 | freebsd-arm | li
 		        echo "No support for static linking found (lacks libc.a?), skip cgo static linking test."
 		else
 			go test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../testtls || exit 1
+			go test ../nocgo || exit 1
+			go test -ldflags '-linkmode=external' ../nocgo || exit 1
+			go test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../nocgo || exit 1
 		fi
 		;;
 	esac

コアとなるコードの解説

src/cmd/ld/lib.c の変更点

loadlib 関数は、リンカがライブラリをロードする際の主要なロジックを含んでいます。この変更は、loadinternal("runtime/cgo"); の呼び出しの直後に追加されています。

  • loadinternal("runtime/cgo");: これは、runtime/cgo パッケージをリンカの内部リストにロードする既存の行です。しかし、この行だけでは、runtime/cgo のオブジェクトファイルが実際にリンクプロセスに適切に組み込まれることを保証していませんでした。
  • if(i < ctxt->libraryp) objfile(ctxt->library[i].file, ctxt->library[i].pkg);: この追加された行が、問題の核心を修正します。
    • ctxt->libraryp は、リンカが現在処理しているライブラリの数を表すポインタ(またはインデックス)です。
    • ctxt->library[i] は、ロードされたライブラリの情報を保持する構造体です。i は、loadinternal によって runtime/cgo がロードされた後の、そのライブラリのインデックスを指していると考えられます。
    • objfile(ctxt->library[i].file, ctxt->library[i].pkg);: この関数呼び出しは、指定されたファイル(runtime/cgo のオブジェクトファイル)をリンカの処理対象として明示的に追加し、そのパッケージ情報 (pkg) を関連付けます。これにより、runtime/cgo のシンボルが確実に解決され、最終的なバイナリに含まれるようになります。

この変更により、runtime/cgo が内部的にロードされた後、そのオブジェクトファイルがリンカによって適切に処理されることが保証され、外部リンキングを使用するGoプログラムにおける runtime/cgo 関連のリンクエラーや実行時エラーが解消されます。

misc/cgo/nocgo/ パッケージの追加

  • nocgo.go:
    • このファイルは、Cgoを明示的に使用しないGoコードの例を提供します。
    • NoCgo() 関数は、チャネルを作成し、新しいgoroutineを起動してチャネルに値を送信し、その値を受信するというシンプルな処理を行います。
    • コメントにあるように、「テストは外部リンキングで実行され、goroutineは runtime/cgo パッケージを介して作成される」という点が重要です。これは、Cgoを直接使わない場合でも、外部リンキングが有効な環境では runtime/cgo がgoroutineの作成と管理に間接的に関与することを示唆しています。このテストは、その間接的な依存関係が正しく機能することを確認します。
  • nocgo_test.go:
    • nocgo.go で定義された NoCgo() 関数を呼び出し、期待される結果(42)が返されることを検証するGoのテストファイルです。
    • このテストの成功は、外部リンキングと静的リンクの条件下で、runtime/cgo が適切に機能し、Goの基本的な並行処理機能が正しく動作することを示します。

src/run.bash の変更点

src/run.bash は、Goのテストスイートを実行するためのスクリプトです。このコミットでは、misc/cgo/nocgo パッケージに対する新しいテストコマンドが追加されました。

  • go test ../nocgo || exit 1: nocgo パッケージをデフォルトのリンキングモード(通常は内部リンキング)でテストします。
  • go test -ldflags '-linkmode=external' ../nocgo || exit 1: nocgo パッケージを外部リンキングモードでテストします。これが、runtime/cgo の問題が顕在化する可能性のある主要なテストケースです。
  • go test -ldflags '-linkmode=external -extldflags "-static -pthread"' ../nocgo || exit 1: nocgo パッケージを外部リンキングかつ静的リンクモードでテストします。-extldflags "-static -pthread" は、外部リンカ(通常はGCC)に対して静的リンクを行い、POSIXスレッドライブラリをリンクするように指示します。この組み合わせは、runtime/cgo の依存関係が最も厳密にテストされるシナリオの一つです。

これらのテストの追加により、Goのビルドシステムは、将来的に同様の問題が再発しないように、外部リンキング環境下での runtime/cgo の正しい動作を継続的に検証できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (Goのリンキング、Cgo、ランタイムに関する一般的な情報)
  • Goのソースコード (特に src/cmd/ld および src/runtime/cgo ディレクトリ)
  • GoのIssueトラッカー (Issue #8032の具体的な内容は確認できませんでしたが、コミットメッセージからの推測に基づいています)