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

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

このコミットは、Go言語のリンカ (cmd/ld) における、cgo_import_staticcgo_import_dynamic の両方としてマークされたシンボルの扱いに関するバグ修正です。特に、SWIG を内部モードでリンクする際に発生する問題に対処しています。

コミット

  • コミットハッシュ: a555758909530b36d5b7fc5b2897698924222d14
  • Author: Ian Lance Taylor iant@golang.org
  • Date: Wed May 1 14:30:19 2013 -0700

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

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

元コミット内容

cmd/ld: fix syms that are both cgo_import_static & cgo_import_dynamic

This is needed for SWIG when linking in internal mode. In
internal mode if a symbol was cgo_import_static we used to
forget that it was also cgo_import_dynamic.

R=rsc, r
CC=golang-dev
https://golang.org/cl/9080043

変更の背景

このコミットは、Go リンカ (cmd/ld) が Cgo を使用して外部の C/C++ コードと連携する際に発生する特定のリンキング問題を解決するために導入されました。

Go プログラムが Cgo を介して C/C++ ライブラリを使用する場合、シンボル(関数や変数など)は静的にインポートされるか (cgo_import_static)、動的にインポートされるか (cgo_import_dynamic) のいずれかとして扱われます。静的インポートは、リンカが最終バイナリにシンボルを直接含めることを意味し、動的インポートは、実行時に共有ライブラリからシンボルを解決することを意味します。

問題は、SWIG (Simplified Wrapper and Interface Generator) を Go の内部モードで使用する際に顕在化しました。SWIG は、C/C++ コードを他の言語(Goを含む)から呼び出せるようにするためのラッパーコードを生成するツールです。内部モードでは、SWIG は特定のシンボルを cgo_import_staticcgo_import_dynamic の両方としてマークすることがありました。

しかし、Go リンカの以前のバージョンでは、シンボルが一度 cgo_import_static として識別されると、それが cgo_import_dynamic でもあるという情報が失われてしまうバグがありました。これにより、リンカがシンボルを正しく解決できず、SWIG を使用したアプリケーションのビルドが失敗したり、実行時に問題が発生したりする可能性がありました。

このコミットは、この「忘れ」の問題を修正し、シンボルが静的インポートと動的インポートの両方の特性を持つ場合に、動的インポートの情報を保持するようにリンカの動作を改善することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について基本的な知識が必要です。

  1. Go リンカ (cmd/ld): Go プログラムをコンパイルする際、最終的な実行可能ファイルを生成する役割を担うのがリンカです。リンカは、コンパイルされたオブジェクトファイル、ライブラリ、およびその他のリソースを結合し、すべてのシンボル参照を解決して、実行可能なバイナリを作成します。cmd/ld は Go の標準リンカです。

  2. Cgo: Cgo は、Go プログラムが C コードを呼び出したり、C コードから Go コードを呼び出したりするためのメカニズムを提供する Go の機能です。これにより、既存の C/C++ ライブラリを Go プロジェクトで再利用したり、パフォーマンスが重要な部分を C/C++ で記述したりすることが可能になります。Cgo を使用すると、Go コンパイラは C コードをコンパイルし、Go コードとリンクするための適切なリンカフラグを生成します。

  3. シンボル (Symbol): プログラミングにおいて、シンボルは関数、変数、クラスなどの識別子を指します。リンキングの過程で、リンカはこれらのシンボルを解決し、それらがメモリ内のどこに配置されるかを決定します。

  4. cgo_import_static: Cgo において、このディレクティブは、指定されたシンボルが静的にインポートされるべきであることをリンカに伝えます。つまり、そのシンボルが定義されているコードは、最終的な実行可能ファイルに直接組み込まれます。これにより、実行時に外部ライブラリに依存することなく、自己完結型のバイナリが生成されます。

  5. cgo_import_dynamic: Cgo において、このディレクティブは、指定されたシンボルが動的にインポートされるべきであることをリンカに伝えます。これは、シンボルが実行時に共有ライブラリ(例: .so.dll.dylib ファイル)からロードされることを意味します。これにより、バイナリのサイズを小さくしたり、複数のプログラムで同じライブラリを共有したりすることが可能になります。

  6. SWIG (Simplified Wrapper and Interface Generator): SWIG は、C/C++ で書かれたライブラリを、Python, Java, C#, Ruby, Go など、さまざまなスクリプト言語やプログラミング言語から利用できるようにするためのツールです。SWIG は、C/C++ ヘッダーファイルから、ターゲット言語のラッパーコードを自動的に生成します。このラッパーコードは、C/C++ 関数を呼び出すためのインターフェースを提供し、データ型の変換などを処理します。SWIG が生成するコードは、Cgo を利用して Go と C/C++ の間の橋渡しをします。

  7. シンボルタイプ (SHOSTOBJ, SDYNIMPORT, SXREF): Go リンカの内部では、シンボルはさまざまなタイプで分類されます。

    • SHOSTOBJ: ホストオブジェクト(CgoによってインポートされたCのシンボルなど)を表します。静的インポートされる可能性のあるシンボルに設定されます。
    • SDYNIMPORT: 動的にインポートされるシンボルを表します。
    • SXREF: 外部参照シンボルを表します。リンカがまだ定義を見つけていないシンボルや、外部ライブラリから解決されるべきシンボルに設定されます。
    • 0 (ゼロ): シンボルタイプが未設定、または不明な状態を表します。

技術的詳細

このコミットは、Go リンカの src/cmd/ld/go.csrc/cmd/ld/lib.c の2つのファイルに変更を加えています。

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

このファイルは、Cgo のインポート処理を担当する loadcgo 関数を含んでいます。変更前は、シンボル s のタイプが 0 (未設定) または SXREF (外部参照) の場合にのみ、そのシンボルを SDYNIMPORT (動的インポート) としてマークしていました。

変更後は、条件に s->type == SHOSTOBJ が追加されました。

--- a/src/cmd/ld/go.c
+++ b/src/cmd/ld/go.c
@@ -463,11 +463,12 @@ loadcgo(char *file, char *pkg, char *p, int n)\n 			s = lookup(local, 0);\n 			if(local != f[1])\n 				free(local);\n-			if(s->type == 0 || s->type == SXREF) {\n+			if(s->type == 0 || s->type == SXREF || s->type == SHOSTOBJ) {\n 				s->dynimplib = lib;\n 				s->extname = remote;\n 				s->dynimpvers = q;\n-				s->type = SDYNIMPORT;\n+				if(s->type != SHOSTOBJ)\n+					s->type = SDYNIMPORT;\n 				havedynamic = 1;\n 			}\n 			continue;

この変更により、シンボルが既に SHOSTOBJ (静的インポートされる可能性のあるCのシンボル) としてマークされている場合でも、動的インポートの情報を設定できるようになりました。 さらに重要なのは、s->type = SDYNIMPORT; の行が if(s->type != SHOSTOBJ) s->type = SDYNIMPORT; に変更された点です。これは、シンボルが既に SHOSTOBJ である場合は、そのタイプを SDYNIMPORT に上書きしないことを意味します。これは、SHOSTOBJ の状態を保持しつつ、動的インポートのメタデータ (dynimplib, extname, dynimpvers) を追加できるようにするためです。これにより、シンボルが静的インポートと動的インポートの両方の特性を持つことが可能になります。

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

このファイルは、リンキングの最終段階でシンボルを処理する loadlib 関数を含んでいます。特に、cgo_import_static 宣言を破棄するロジックが含まれています。

変更前は、SHOSTOBJ タイプのシンボルを見つけると、無条件にそのタイプを 0 (未設定) にリセットしていました。これは、「静的インポートはもう必要ない」という前提に基づいています。

--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -331,8 +331,16 @@ loadlib(void)\n 		// Drop all the cgo_import_static declarations.\n 		// Turns out we won't be needing them.\n 		for(s = allsym; s != S; s = s->allsym)\n-\t\t\tif(s->type == SHOSTOBJ)\n-\t\t\t\ts->type = 0;\n+\t\t\tif(s->type == SHOSTOBJ) {\n+\t\t\t\t// If a symbol was marked both\n+\t\t\t\t// cgo_import_static and cgo_import_dynamic,\n+\t\t\t\t// then we want to make it cgo_import_dynamic\n+\t\t\t\t// now.\n+\t\t\t\tif(s->extname != nil && s->cgoexport == 0) {\n+\t\t\t\t\ts->type = SDYNIMPORT;\n+\t\t\t\t} else\n+\t\t\t\t\ts->type = 0;\n+\t\t\t}\n \t}\n \t\n \t// Now that we know the link mode, trim the dynexp list.\n```
変更後は、`SHOSTOBJ` タイプのシンボルを処理する際に、それが動的インポート情報 (`s->extname != nil`) を持ち、かつエクスポートされていない (`s->cgoexport == 0`) 場合、そのシンボルタイプを `SDYNIMPORT` に変更するように修正されました。それ以外の場合は、以前と同様に `0` にリセットされます。

このロジックは、シンボルが `cgo_import_static` と `cgo_import_dynamic` の両方としてマークされていた場合に、静的インポートの情報を破棄するのではなく、動的インポートとして優先的に扱うことを保証します。これにより、SWIG の内部モードで発生していた問題が解決されます。

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

### `src/cmd/ld/go.c`

```diff
--- a/src/cmd/ld/go.c
+++ b/src/cmd/ld/go.c
@@ -463,11 +463,12 @@ loadcgo(char *file, char *pkg, char *p, int n)\n 			s = lookup(local, 0);\n 			if(local != f[1])\n 				free(local);\n-			if(s->type == 0 || s->type == SXREF) {\n+			if(s->type == 0 || s->type == SXREF || s->type == SHOSTOBJ) {\n 				s->dynimplib = lib;\n 				s->extname = remote;\n 				s->dynimpvers = q;\n-				s->type = SDYNIMPORT;\n+				if(s->type != SHOSTOBJ)\n+					s->type = SDYNIMPORT;\n 				havedynamic = 1;\n 			}\n 			continue;

src/cmd/ld/lib.c

--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -331,8 +331,16 @@ loadlib(void)\n 		// Drop all the cgo_import_static declarations.\n 		// Turns out we won't be needing them.\n 		for(s = allsym; s != S; s = s->allsym)\n-\t\t\tif(s->type == SHOSTOBJ)\n-\t\t\t\ts->type = 0;\n+\t\t\t\tif(s->type == SHOSTOBJ) {\n+\t\t\t\t// If a symbol was marked both\n+\t\t\t\t// cgo_import_static and cgo_import_dynamic,\n+\t\t\t\t// then we want to make it cgo_import_dynamic\n+\t\t\t\t// now.\n+\t\t\t\tif(s->extname != nil && s->cgoexport == 0) {\n+\t\t\t\t\ts->type = SDYNIMPORT;\n+\t\t\t\t} else\n+\t\t\t\t\ts->type = 0;\n+\t\t\t}\n \t}\n \t\n \t// Now that we know the link mode, trim the dynexp list.\n```

## コアとなるコードの解説

### `src/cmd/ld/go.c` の変更点

-   **`if(s->type == 0 || s->type == SXREF || s->type == SHOSTOBJ)`**:
    この条件式は、動的インポート情報をシンボルに適用するタイミングを拡張しています。以前は、シンボルがまだタイプが設定されていない (`0`) か、外部参照 (`SXREF`) の場合にのみ動的インポートとしてマークしていました。しかし、SWIG のようなツールが、シンボルを一時的に `SHOSTOBJ` (静的インポートされる可能性のあるCのシンボル) としてマークしつつ、同時に動的インポートの特性も持つ必要がある場合がありました。この変更により、`SHOSTOBJ` のシンボルに対しても動的インポートのメタデータ (`dynimplib`, `extname`, `dynimpvers`) を設定できるようになります。

-   **`if(s->type != SHOSTOBJ) s->type = SDYNIMPORT;`**:
    この行は、シンボルのタイプを `SDYNIMPORT` に設定するロジックをより洗練しています。
    -   もしシンボルが `SHOSTOBJ` でない場合(つまり、`0` または `SXREF` の場合)、そのタイプは `SDYNIMPORT` に設定されます。これは、純粋な動的インポートシンボルとして扱われることを意味します。
    -   しかし、もしシンボルが既に `SHOSTOBJ` である場合、そのタイプは `SHOSTOBJ` のまま維持されます。これは、シンボルが静的インポートの候補でありながら、動的インポートのメタデータも持つという「二重の性質」を許容するためです。この変更が、SWIG の内部モードでの問題解決の鍵となります。リンカは、このシンボルが最終的に静的または動的のどちらとして解決されるかを、リンキングプロセスの後半で決定できるようになります。

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

-   **`if(s->type == SHOSTOBJ) { ... }` ブロックの追加**:
    この変更は、リンキングの最終段階で `SHOSTOBJ` タイプのシンボルを処理する方法を改善します。以前は、`SHOSTOBJ` のシンボルは無条件にタイプ `0` にリセットされていました。これは、静的インポートの宣言はもう必要ないという前提に基づいています。

-   **`if(s->extname != nil && s->cgoexport == 0) { s->type = SDYNIMPORT; } else { s->type = 0; }`**:
    この新しいロジックは、`SHOSTOBJ` シンボルが「静的インポートと動的インポートの両方」としてマークされていた場合の挙動を定義します。
    -   `s->extname != nil`: これは、シンボルが動的インポートのための外部名(つまり、動的インポート情報)を持っているかどうかをチェックします。
    -   `s->cgoexport == 0`: これは、シンボルが Cgo によってエクスポートされていないことを確認します。エクスポートされたシンボルは異なる処理パスを持つため、ここでは考慮されません。
    -   上記の条件が両方とも真である場合、つまりシンボルが動的インポート情報を持つ `SHOSTOBJ` である場合、そのタイプは `SDYNIMPORT` に変更されます。これにより、リンカは最終的にこのシンボルを動的インポートとして解決します。
    -   それ以外の場合(動的インポート情報がない `SHOSTOBJ` の場合)、シンボルタイプは `0` にリセットされます。これは、純粋な静的インポートシンボルであり、最終的にバイナリに組み込まれるか、または不要と判断されることを意味します。

これらの変更により、Go リンカは、SWIG のようなツールが生成する複雑な Cgo シンボル定義をより正確に処理できるようになり、特にシンボルが静的インポートと動的インポートの両方の特性を持つ場合に、正しいリンキング動作を保証します。

## 関連リンク

-   Go CL (Code Review) リンク: [https://golang.org/cl/9080043](https://golang.org/cl/9080043)

## 参考にした情報源リンク

-   Go 言語の公式ドキュメント (Cgo): [https://go.dev/blog/c-go-is-not-go](https://go.dev/blog/c-go-is-not-go) (一般的な Cgo の概念理解のため)
-   SWIG 公式サイト: [http://www.swig.org/](http://www.swig.org/) (SWIG の一般的な概念理解のため)
-   Go リンカのソースコード (Go の GitHub リポジトリ): [https://github.com/golang/go/tree/master/src/cmd/ld](https://github.com/golang/go/tree/master/src/cmd/ld) (コードのコンテキスト理解のため)
-   Go のシンボルタイプに関する議論 (Go の issue やメーリングリストなど、必要に応じて検索)