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

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

このコミットは、Go言語のリンカ (cmd/ld) における、COFF (Common Object File Format) および Mach-O (Mach Object) 形式のオブジェクトファイル内で発生する重複した静的シンボルの取り扱いに関するバグ修正です。特にCgoを使用する際に問題となる可能性があった、異なるCソースファイルで定義された同名の静的変数や関数がリンカによって重複とみなされ、エラーとなるケースを解決します。

コミット

commit 9add729a1f537f05941d80a10818cf1562a7ea6b
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Sep 18 22:27:25 2013 -0400

    cmd/ld: handle duplicate static symbols in COFF and Mach-O files.
    Fixes #5740.
    
    R=iant, rsc, luisbebop
    CC=gobot, golang-dev
    https://golang.org/cl/10345046

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

https://github.com/golang/go/commit/9add729a1f537f05941d80a10818cf1562a7ea6b

元コミット内容

このコミットは、Goリンカ (cmd/ld) がCOFFおよびMach-O形式のオブジェクトファイル内の重複する静的シンボルを適切に処理するように変更します。これにより、Go issue #5740 で報告された問題が修正されます。具体的には、静的シンボルが重複していてもリンカがエラーを出さないように dupok フラグを設定します。

変更の背景

Go言語はCgoを通じてC/C++コードと連携する機能を提供しています。Cgoを使用すると、GoプログラムからCの関数を呼び出したり、Cのデータ構造を利用したりできます。この連携の過程で、Goリンカ (cmd/ld) はCコンパイラによって生成されたオブジェクトファイル(WindowsではCOFF、macOSではMach-O形式)をリンクする必要があります。

Go issue #5740 では、以下のようなシナリオで問題が報告されました。 複数のCソースファイル(例: a.cb.c)がそれぞれ同じ名前の static 変数や関数を定義している場合、C言語の仕様上、これらの static シンボルはそれぞれのファイルスコープに限定され、外部からは見えません。したがって、異なるファイルで同じ名前の static シンボルが存在しても、通常はリンカエラーにはなりません。

しかし、Goリンカの以前の実装では、COFFやMach-O形式のオブジェクトファイルを処理する際に、これらの static シンボルを誤ってグローバルなシンボルとして扱い、重複定義エラー("duplicate symbol")を発生させてしまうことがありました。これは、リンカがシンボルテーブルを構築する際に、static シンボルのローカルな性質を正しく認識していなかったためと考えられます。

この問題は、特に大規模なC/C++ライブラリをCgo経由でGoに統合しようとする際に、予期せぬリンカエラーとして現れ、開発の妨げとなっていました。このコミットは、このリンカの誤った挙動を修正し、C言語の static キーワードのセマンティクスに沿ったリンキングを可能にすることを目的としています。

前提知識の解説

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

  1. リンカ (Linker): リンカは、コンパイラによって生成された複数のオブジェクトファイル(.o.obj ファイル)とライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は、未解決のシンボル参照(あるオブジェクトファイルで宣言され、別のオブジェクトファイルで定義されている関数や変数)を解決し、最終的なバイナリにすべてのアドレスを割り当てることです。

  2. シンボル (Symbol): プログラム内の関数名、変数名、ラベルなどがシンボルとして扱われます。シンボルには、そのスコープ(可視性)や型などの情報が付随します。

  3. 静的シンボル (Static Symbol): C/C++において static キーワードで宣言された変数や関数は、その定義されたファイル(翻訳単位)内でのみ可視となります。つまり、異なるファイルで同じ名前の static シンボルが定義されていても、それらは互いに独立しており、名前の衝突は発生しません。リンカは通常、これらのシンボルをファイルローカルなものとして扱い、グローバルなシンボルテーブルには登録しません。

  4. COFF (Common Object File Format): Microsoft Windowsオペレーティングシステムで広く使用されているオブジェクトファイルおよび実行可能ファイルのフォーマットです。.obj ファイル(オブジェクトファイル)や .exe ファイル(実行可能ファイル)、.dll ファイル(ダイナミックリンクライブラリ)などで使用されます。

  5. Mach-O (Mach Object): AppleのmacOS、iOS、watchOS、tvOSなどのオペレーティングシステムで使用されているオブジェクトファイル、実行可能ファイル、共有ライブラリ、コアダンプなどのフォーマットです。

  6. Goリンカ (cmd/ld): Go言語のツールチェインに含まれるリンカです。Goのソースコードから生成されたオブジェクトファイルだけでなく、Cgoを通じてC/C++コードがGoプログラムに組み込まれる際には、Cコンパイラが生成したオブジェクトファイルもこのリンカによって処理されます。

  7. Cgo: Go言語とC言語の相互運用を可能にするGoの機能です。GoコードからC関数を呼び出したり、CコードからGo関数を呼び出したりすることができます。Cgoを使用すると、GoプログラムはCコンパイラによって生成されたオブジェクトファイルにリンクされる必要があります。

  8. dupok フラグ: Goリンカ内部のシンボル管理において、dupok (duplicate OK) は、そのシンボルが重複して定義されていても許容されることを示すフラグです。通常、リンカは重複するグローバルシンボル定義をエラーとしますが、特定の状況(例えば、弱いシンボルや、このコミットで扱われるようなファイルローカルな静的シンボル)では重複が許容される場合があります。

技術的詳細

このコミットの技術的な核心は、GoリンカがCOFFおよびMach-O形式のオブジェクトファイルからシンボル情報を読み取る際に、static シンボルを正しく識別し、それらが重複してもエラーとしないようにすることです。

Goリンカは、オブジェクトファイルからシンボルを読み込む際に、各シンボルの名前、型、セクション情報などを解析し、内部のシンボルテーブルに登録します。この際、シンボルが外部リンケージを持つ(グローバルな)シンボルであるか、それとも内部リンケージを持つ(ファイルローカルな)静的シンボルであるかを区別する必要があります。

COFFとMach-Oのシンボルテーブルには、シンボルのリンケージに関する情報が含まれています。

  • Mach-O (ldmacho.c): Mach-Oのシンボル構造体 nlist には n_type フィールドがあり、これにはシンボルの種類やリンケージに関するビットフラグが含まれます。N_EXT フラグはシンボルが外部(グローバル)であるかどうかを示します。このコミットでは、!(sym->type&N_EXT) という条件で、シンボルが外部ではない(つまり、静的またはローカルな)場合に s->dupok = 1; を設定しています。
  • COFF (ldpe.c): COFFのシンボル構造体 IMAGE_SYMBOL には StorageClass フィールドがあり、シンボルのストレージクラス(例: IMAGE_SYM_CLASS_STATIC)を示します。このコミットでは、IMAGE_SYM_CLASS_STATIC のシンボルに対して s->dupok = 1; を設定しています。

s->dupok = 1; が設定されると、Goリンカは同じ名前のシンボルが複数回定義されていても、それを重複エラーとして報告しなくなります。これは、static シンボルが本来ファイルスコープに限定されるため、異なるファイルに同名の static シンボルが存在しても問題ないというC言語のセマンティクスに合致する挙動です。

この修正により、Cgoを使用するGoプログラムが、複数のCソースファイルで同名の静的変数や関数を使用しているCライブラリとリンクする際に、不必要なリンカエラーに遭遇することがなくなります。

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

変更は主にGoリンカのMach-OおよびCOFFファイル処理部分に集中しています。

  1. src/cmd/ld/ldmacho.c: Mach-Oオブジェクトファイルのシンボルを読み込む ldmacho 関数内で、シンボルが外部リンケージを持たない(N_EXT フラグが立っていない)場合に、そのシンボルに対応するGoリンカ内部のシンボル構造体 sdupok フラグを 1 に設定します。

    // src/cmd/ld/ldmacho.c
    // ...
    	if(!(sym->type&N_EXT)) // シンボルが外部リンケージを持たない場合
    		v = version;
    	s = lookup(name, v);
    	if(!(sym->type&N_EXT)) // 再度チェックし、静的シンボルであれば
    		s->dupok = 1;      // dupokフラグを立てる
    	sym->sym = s;
    // ...
    
  2. src/cmd/ld/ldpe.c: COFFオブジェクトファイルのシンボルを読み込む readsym 関数内で、シンボルのストレージクラスが IMAGE_SYM_CLASS_STATIC である場合に、そのシンボルに対応するGoリンカ内部のシンボル構造体 sdupok フラグを 1 に設定します。

    // src/cmd/ld/ldpe.c
    // ...
    		case IMAGE_SYM_CLASS_NULL:
    		case IMAGE_SYM_CLASS_STATIC: // 静的シンボルクラスの場合
    			s = lookup(name, version);
    			s->dupok = 1;            // dupokフラグを立てる
    			break;
    // ...
    
  3. テストケースの追加: misc/cgo/test/issue5740.go, misc/cgo/test/issue5740a.c, misc/cgo/test/issue5740b.c が追加されています。

    • issue5740a.cissue5740b.c は、それぞれ同じ名前の static int volatile val 変数と、その値を返す test5740a() / test5740b() 関数を定義しています。
    • issue5740.go はCgoを使用してこれらのC関数を呼び出し、その合計が期待値(2 + 3 = 5)になることを検証しています。このテストは、修正が適用される前はリンカエラーで失敗し、修正後は成功することを確認するために使用されます。

コアとなるコードの解説

Goリンカは、オブジェクトファイルからシンボルを読み込む際に、lookup 関数を使って内部のシンボルテーブルにシンボルを登録または検索します。この lookup 関数が返すシンボル構造体 s には、dupok というフィールドがあります。

  • s->dupok = 1; の設定は、リンカに対して「このシンボルは重複していても問題ない」という指示を与えます。
  • Mach-Oの場合、sym->type&N_EXT は、シンボルが外部(グローバル)リンケージを持つかどうかを判断するためのビット演算です。N_EXT がセットされていない場合(!(sym->type&N_EXT) が真の場合)、そのシンボルは外部からは見えないローカルなシンボル(静的シンボルなど)であると判断されます。
  • COFFの場合、IMAGE_SYM_CLASS_STATIC は、シンボルが静的ストレージクラスを持つことを明示的に示します。

これらの変更により、GoリンカはC/C++の static キーワードのセマンティクスを正しく解釈し、異なる翻訳単位(Cソースファイル)で定義された同名の静的シンボルが、リンキング時に不必要な重複エラーを引き起こすことを防ぎます。これにより、Cgoを利用したGoプログラムのビルドの堅牢性が向上します。

関連リンク

参考にした情報源リンク

  • Go issue #5740 の内容
  • Goリンカのソースコード (src/cmd/ld/)
  • COFFファイルフォーマットに関する一般的な情報 (Microsoft PE and COFF Specificationなど)
  • Mach-Oファイルフォーマットに関する一般的な情報 (Apple Developer Documentationなど)
  • C言語の static キーワードに関する情報
  • Go言語のCgoに関するドキュメント# [インデックス 17648] ファイルの概要

このコミットは、Go言語のリンカ (cmd/ld) における、COFF (Common Object File Format) および Mach-O (Mach Object) 形式のオブジェクトファイル内で発生する重複した静的シンボルの取り扱いに関するバグ修正です。特にCgoを使用する際に問題となる可能性があった、異なるCソースファイルで定義された同名の静的変数や関数がリンカによって重複とみなされ、エラーとなるケースを解決します。

コミット

commit 9add729a1f537f05941d80a10818cf1562a7ea6b
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed Sep 18 22:27:25 2013 -0400

    cmd/ld: handle duplicate static symbols in COFF and Mach-O files.
    Fixes #5740.
    
    R=iant, rsc, luisbebop
    CC=gobot, golang-dev
    https://golang.org/cl/10345046

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

https://github.com/golang/go/commit/9add729a1f537f05941d80a10818cf1562a7ea6b

元コミット内容

このコミットは、Goリンカ (cmd/ld) がCOFFおよびMach-O形式のオブジェクトファイル内の重複する静的シンボルを適切に処理するように変更します。これにより、Go issue #5740 で報告された問題が修正されます。具体的には、静的シンボルが重複していてもリンカがエラーを出さないように dupok フラグを設定します。

変更の背景

Go言語はCgoを通じてC/C++コードと連携する機能を提供しています。Cgoを使用すると、GoプログラムからCの関数を呼び出したり、Cのデータ構造を利用したりできます。この連携の過程で、Goリンカ (cmd/ld) はCコンパイラによって生成されたオブジェクトファイル(WindowsではCOFF、macOSではMach-O形式)をリンクする必要があります。

Go issue #5740 では、以下のようなシナリオで問題が報告されました。 複数のCソースファイル(例: a.cb.c)がそれぞれ同じ名前の static 変数や関数を定義している場合、C言語の仕様上、これらの static シンボルはそれぞれのファイルスコープに限定され、外部からは見えません。したがって、異なるファイルで同じ名前の static シンボルが存在しても、通常はリンカエラーにはなりません。

しかし、Goリンカの以前の実装では、COFFやMach-O形式のオブジェクトファイルを処理する際に、これらの static シンボルを誤ってグローバルなシンボルとして扱い、重複定義エラー("duplicate symbol")を発生させてしまうことがありました。これは、リンカがシンボルテーブルを構築する際に、static シンボルのローカルな性質を正しく認識していなかったためと考えられます。

この問題は、特に大規模なC/C++ライブラリをCgo経由でGoに統合しようとする際に、予期せぬリンカエラーとして現れ、開発の妨げとなっていました。このコミットは、このリンカの誤った挙動を修正し、C言語の static キーワードのセマンティクスに沿ったリンキングを可能にすることを目的としています。

前提知識の解説

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

  1. リンカ (Linker): リンカは、コンパイラによって生成された複数のオブジェクトファイル(.o.obj ファイル)とライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は、未解決のシンボル参照(あるオブジェクトファイルで宣言され、別のオブジェクトファイルで定義されている関数や変数)を解決し、最終的なバイナリにすべてのアドレスを割り当てることです。

  2. シンボル (Symbol): プログラム内の関数名、変数名、ラベルなどがシンボルとして扱われます。シンボルには、そのスコープ(可視性)や型などの情報が付随します。

  3. 静的シンボル (Static Symbol): C/C++において static キーワードで宣言された変数や関数は、その定義されたファイル(翻訳単位)内でのみ可視となります。つまり、異なるファイルで同じ名前の static シンボルが定義されていても、それらは互いに独立しており、名前の衝突は発生しません。リンカは通常、これらのシンボルをファイルローカルなものとして扱い、グローバルなシンボルテーブルには登録しません。

  4. COFF (Common Object File Format): Microsoft Windowsオペレーティングシステムで広く使用されているオブジェクトファイルおよび実行可能ファイルのフォーマットです。.obj ファイル(オブジェクトファイル)や .exe ファイル(実行可能ファイル)、.dll ファイル(ダイナミックリンクライブラリ)などで使用されます。

  5. Mach-O (Mach Object): AppleのmacOS、iOS、watchOS、tvOSなどのオペレーティングシステムで使用されているオブジェクトファイル、実行可能ファイル、共有ライブラリ、コアダンプなどのフォーマットです。

  6. Goリンカ (cmd/ld): Go言語のツールチェインに含まれるリンカです。Goのソースコードから生成されたオブジェクトファイルだけでなく、Cgoを通じてC/C++コードがGoプログラムに組み込まれる際には、Cコンパイラが生成したオブジェクトファイルもこのリンカによって処理されます。

  7. Cgo: Go言語とC言語の相互運用を可能にするGoの機能です。GoコードからC関数を呼び出したり、CコードからGo関数を呼び出したりすることができます。Cgoを使用すると、GoプログラムはCコンパイラによって生成されたオブジェクトファイルにリンクされる必要があります。

  8. dupok フラグ: Goリンカ内部のシンボル管理において、dupok (duplicate OK) は、そのシンボルが重複して定義されていても許容されることを示すフラグです。通常、リンカは重複するグローバルシンボル定義をエラーとしますが、特定の状況(例えば、弱いシンボルや、このコミットで扱われるようなファイルローカルな静的シンボル)では重複が許容される場合があります。

技術的詳細

このコミットの技術的な核心は、GoリンカがCOFFおよびMach-O形式のオブジェクトファイルからシンボル情報を読み取る際に、static シンボルを正しく識別し、それらが重複してもエラーとしないようにすることです。

Goリンカは、オブジェクトファイルからシンボルを読み込む際に、各シンボルの名前、型、セクション情報などを解析し、内部のシンボルテーブルに登録します。この際、シンボルが外部リンケージを持つ(グローバルな)シンボルであるか、それとも内部リンケージを持つ(ファイルローカルな)静的シンボルであるかを区別する必要があります。

COFFとMach-Oのシンボルテーブルには、シンボルのリンケージに関する情報が含まれています。

  • Mach-O (ldmacho.c): Mach-Oのシンボル構造体 nlist には n_type フィールドがあり、これにはシンボルの種類やリンケージに関するビットフラグが含まれます。N_EXT フラグはシンボルが外部(グローバル)であるかどうかを示します。このコミットでは、!(sym->type&N_EXT) という条件で、シンボルが外部ではない(つまり、静的またはローカルな)場合に s->dupok = 1; を設定しています。
  • COFF (ldpe.c): COFFのシンボル構造体 IMAGE_SYMBOL には StorageClass フィールドがあり、シンボルのストレージクラス(例: IMAGE_SYM_CLASS_STATIC)を示します。このコミットでは、IMAGE_SYM_CLASS_STATIC のシンボルに対して s->dupok = 1; を設定しています。

s->dupok = 1; が設定されると、Goリンカは同じ名前のシンボルが複数回定義されていても、それを重複エラーとして報告しなくなります。これは、static シンボルが本来ファイルスコープに限定されるため、異なるファイルに同名の static シンボルが存在しても問題ないというC言語のセマンティクスに合致する挙動です。

この修正により、Cgoを使用するGoプログラムが、複数のCソースファイルで同名の静的変数や関数を使用しているCライブラリとリンクする際に、不必要なリンカエラーに遭遇することがなくなります。

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

変更は主にGoリンカのMach-OおよびCOFFファイル処理部分に集中しています。

  1. src/cmd/ld/ldmacho.c: Mach-Oオブジェクトファイルのシンボルを読み込む ldmacho 関数内で、シンボルが外部リンケージを持たない(N_EXT フラグが立っていない)場合に、そのシンボルに対応するGoリンカ内部のシンボル構造体 sdupok フラグを 1 に設定します。

    // src/cmd/ld/ldmacho.c
    // ...
    	if(!(sym->type&N_EXT)) // シンボルが外部リンケージを持たない場合
    		v = version;
    	s = lookup(name, v);
    	if(!(sym->type&N_EXT)) // 再度チェックし、静的シンボルであれば
    		s->dupok = 1;      // dupokフラグを立てる
    	sym->sym = s;
    // ...
    
  2. src/cmd/ld/ldpe.c: COFFオブジェクトファイルのシンボルを読み込む readsym 関数内で、シンボルのストレージクラスが IMAGE_SYM_CLASS_STATIC である場合に、そのシンボルに対応するGoリンカ内部のシンボル構造体 sdupok フラグを 1 に設定します。

    // src/cmd/ld/ldpe.c
    // ...
    		case IMAGE_SYM_CLASS_NULL:
    		case IMAGE_SYM_CLASS_STATIC: // 静的シンボルクラスの場合
    			s = lookup(name, version);
    			s->dupok = 1;            // dupokフラグを立てる
    			break;
    // ...
    
  3. テストケースの追加: misc/cgo/test/issue5740.go, misc/cgo/test/issue5740a.c, misc/cgo/test/issue5740b.c が追加されています。

    • issue5740a.cissue5740b.c は、それぞれ同じ名前の static int volatile val 変数と、その値を返す test5740a() / test5740b() 関数を定義しています。
    • issue5740.go はCgoを使用してこれらのC関数を呼び出し、その合計が期待値(2 + 3 = 5)になることを検証しています。このテストは、修正が適用される前はリンカエラーで失敗し、修正後は成功することを確認するために使用されます。

コアとなるコードの解説

Goリンカは、オブジェクトファイルからシンボルを読み込む際に、lookup 関数を使って内部のシンボルテーブルにシンボルを登録または検索します。この lookup 関数が返すシンボル構造体 s には、dupok というフィールドがあります。

  • s->dupok = 1; の設定は、リンカに対して「このシンボルは重複していても問題ない」という指示を与えます。
  • Mach-Oの場合、sym->type&N_EXT は、シンボルが外部(グローバル)リンケージを持つかどうかを判断するためのビット演算です。N_EXT がセットされていない場合(!(sym->type&N_EXT) が真の場合)、そのシンボルは外部からは見えないローカルなシンボル(静的シンボルなど)であると判断されます。
  • COFFの場合、IMAGE_SYM_CLASS_STATIC は、シンボルが静的ストレージクラスを持つことを明示的に示します。

これらの変更により、GoリンカはC/C++の static キーワードのセマンティクスを正しく解釈し、異なる翻訳単位(Cソースファイル)で定義された同名の静的シンボルが、リンキング時に不必要な重複エラーを引き起こすことを防ぎます。これにより、Cgoを利用したGoプログラムのビルドの堅牢性が向上します。

関連リンク

参考にした情報源リンク

  • Go issue #5740 の内容
  • Goリンカのソースコード (src/cmd/ld/)
  • COFFファイルフォーマットに関する一般的な情報 (Microsoft PE and COFF Specificationなど)
  • Mach-Oファイルフォーマットに関する一般的な情報 (Apple Developer Documentationなど)
  • C言語の static キーワードに関する情報
  • Go言語のCgoに関するドキュメント