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

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

このコミットは、Go言語のリンカ(cmd/ld)が「弱いシンボル(weak symbols)」を適切に処理できるようにするための変更です。特に、compiler_rtライブラリが導入する、弱くかつ隠された(weak and hidden)シンボル_compilerrt_abort_implの取り扱いが焦点となっています。

コミット

commit 78a6f7524109d5c183e09767e44037ae7e5b0c96
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Oct 30 23:58:43 2012 +0800

    cmd/ld: handle weak symbols
    compiler_rt introduces a weak and hidden symbol compilerrt_abort_impl
    into our pre-linked _all.o object, we have to handle it.
    
    Fixes #4273.
    
    R=iant, rsc, r
    CC=golang-dev
    https://golang.org/cl/6783050

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

https://github.com/golang/go/commit/78a6f7524109d5c183e09767e44037ae7e5b0c96

元コミット内容

cmd/ld: handle weak symbols
compiler_rt introduces a weak and hidden symbol compilerrt_abort_impl
into our pre-linked _all.o object, we have to handle it.

Fixes #4273.

変更の背景

この変更の背景には、Go言語のビルドプロセスにおける特定のリンキングの問題があります。GoはCgo(GoとC/C++コードを連携させるためのメカニズム)を使用する際に、C/C++のランタイムライブラリやコンパイラが生成するオブジェクトファイルとリンクする必要があります。

問題は、compiler_rtというLLVMプロジェクトの一部であるランタイムライブラリが、_compilerrt_abort_implという特定のシンボルを「弱い(weak)」かつ「隠された(hidden)」属性で定義していたことにあります。このシンボルは、Goのリンカが処理する前の段階で生成される_all.oという中間オブジェクトファイルに組み込まれていました。

従来のGoリンカ(cmd/ld)は、このような弱いシンボル、特に隠された可視性を持つシンボルを適切に認識し、処理する機能が不足していました。その結果、リンキング時に予期せぬエラーや動作が発生する可能性がありました。この問題はGoのIssue #4273として報告されており、このコミットはその問題を解決するために行われました。

弱いシンボルは、複数の定義が存在する場合にリンカがどの定義を選択するかを決定する際に重要な役割を果たします。隠された可視性は、シンボルが外部から参照できないようにすることで、ライブラリの内部実装の詳細を隠蔽し、シンボル衝突のリスクを減らすために使用されます。Goリンカがこれらの属性を正しく解釈できないと、Cgoを介して外部Cライブラリと連携するGoプログラムのビルドが失敗したり、実行時に問題が生じたりする可能性がありました。

前提知識の解説

1. リンカ (Linker)

リンカは、コンパイラによって生成された複数のオブジェクトファイル(.oファイルなど)と、必要なライブラリファイル(.a.soなど)を結合して、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主な役割は以下の通りです。

  • シンボル解決: あるオブジェクトファイルで定義された関数や変数を、別のオブジェクトファイルやライブラリから参照できるように、それらのアドレスを解決します。
  • 再配置: オブジェクトファイル内の相対アドレスを、最終的な実行ファイル内の絶対アドレスに変換します。
  • セクション結合: 異なるオブジェクトファイルのコードセクションやデータセクションを結合し、最終的な実行ファイルのメモリレイアウトを決定します。

Go言語では、cmd/ldがGoプログラムのリンカとして機能します。

2. シンボル (Symbol)

シンボルは、プログラム内の関数、変数、またはその他のエンティティの名前を表す識別子です。リンカはこれらのシンボルを使って、プログラムの異なる部分を接続します。シンボルにはいくつかの属性があります。

3. 弱いシンボル (Weak Symbols)

弱いシンボルは、通常のシンボル(強いシンボル)とは異なるリンキング時の振る舞いを持ちます。

  • 定義の優先順位: 同じ名前のシンボルが複数定義されている場合、リンカは以下のルールに従います。
    • 強いシンボルと弱いシンボルが両方存在する場合、強いシンボルが優先されます。
    • 複数の強いシンボルが存在する場合、リンキングエラーとなります(多重定義エラー)。
    • 複数の弱いシンボルが存在する場合、リンカは通常、最初に見つかった定義を使用するか、特定のルールに基づいて一つを選択します。
  • 用途: 弱いシンボルは、ライブラリがデフォルトの実装を提供しつつ、ユーザーが独自のカスタム実装でそれをオーバーライドできるようにする場合によく使用されます。例えば、特定の機能がオプションである場合や、デバッグ用のフックを提供する場合などです。

4. シンボルの可視性 (Symbol Visibility)

シンボルの可視性は、そのシンボルがプログラムの外部から参照可能かどうかを制御します。

  • デフォルト (Default): シンボルは外部から参照可能です。
  • 隠された (Hidden): シンボルは、そのシンボルが定義されている共有オブジェクト(または実行ファイル)内でのみ参照可能です。外部の共有オブジェクトや実行ファイルからは参照できません。これにより、ライブラリの内部実装の詳細を隠蔽し、シンボル衝突のリスクを減らすことができます。
  • 内部 (Internal): シンボルは、そのシンボルが定義されているオブジェクトファイル内でのみ参照可能です。
  • 保護された (Protected): シンボルは、そのシンボルが定義されている共有オブジェクト内でのみ参照可能ですが、派生クラスからはオーバーライド可能です。

このコミットでは、特に「隠された(hidden)」可視性を持つ弱いシンボルが問題となっていました。

5. compiler_rt

compiler_rtは、LLVMプロジェクトの一部として提供されるランタイムライブラリです。これは、コンパイラが生成する特定の低レベル操作(例えば、浮動小数点演算、アトミック操作、ソフトウェアによる整数除算など)の実装を提供します。これらの操作は、ターゲットアーキテクチャのハードウェアで直接サポートされていない場合や、より効率的なソフトウェア実装が必要な場合に使用されます。compiler_rtは、C/C++コンパイラによって自動的にリンクされることが多く、GoのCgoのようなメカニズムを通じて間接的にGoプログラムに影響を与えることがあります。

6. ELF (Executable and Linkable Format)

ELFは、Unix系システム(Linuxなど)で広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。ELFファイルは、ヘッダ、セクション、セグメントなどの構造を持ち、シンボルテーブルもその一部です。リンカはELFファイルのシンボルテーブルを読み書きして、シンボル解決を行います。シンボルのバインディング(強い/弱い)や可視性(hiddenなど)は、ELFシンボルテーブルのエントリに格納されます。

技術的詳細

このコミットの技術的詳細は、Goリンカ(src/cmd/ld/ldelf.c)がELF形式のオブジェクトファイルからシンボル情報を読み取る際の処理に焦点を当てています。

具体的には、readsym関数内で、ELFシンボルテーブルのエントリを解析し、シンボルのバインディング(ElfSymBindWeakなど)と可視性(sym->otherフィールドに格納される)に基づいて、Goリンカ内部のシンボル表現(s->type)を適切に設定するロジックが追加されています。

変更前は、リンカはElfSymBindWeak(弱いシンボル)のケースを明示的に処理していませんでした。そのため、compiler_rtによって導入された_compilerrt_abort_implのような弱いシンボルが正しく認識されず、リンキング時に問題を引き起こしていました。

変更後、readsym関数にcase ElfSymBindWeak:が追加されました。このケースでは、以下の処理が行われます。

  1. needSymが真の場合(シンボル情報が必要な場合)、newsym(sym->name, 0)を呼び出して新しいシンボルを作成します。これは、弱いシンボルであっても、リンカがその存在を認識し、内部的に管理できるようにするためです。
  2. sym->other == 2の場合、これはELFのSTV_HIDDEN(隠された可視性)に対応します。この場合、Goリンカ内部のシンボルタイプs->typeSHIDDENに設定します。これにより、リンカは当該シンボルが外部から参照できない隠されたシンボルであることを認識し、適切なリンキング動作を適用できるようになります。

この修正により、Goリンカは弱いシンボル、特に隠された可視性を持つ弱いシンボルを正しく処理できるようになり、compiler_rtのような外部ライブラリとの連携がスムーズに行えるようになりました。

テストファイル(misc/cgo/test/issue4273.cmisc/cgo/test/issue4273b.c)は、この問題の再現と修正の検証のために追加されました。

  • issue4273.cでは、__attribute__((weak))__attribute__((visibility("hidden")))を使用して、問題の_compilerrt_abort_implシンボルをシミュレートしています。
  • issue4273b.cでは、この_compilerrt_abort_implシンボルをextern宣言し、別の関数__my_abortから呼び出すことで、リンカがこの弱い隠されたシンボルを正しく解決できるかどうかのテストケースを提供しています。

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

src/cmd/ld/ldelf.cファイルのreadsym関数内。

--- a/src/cmd/ld/ldelf.c
+++ b/src/cmd/ld/ldelf.c
@@ -807,6 +807,13 @@ readsym(ElfObj *obj, int i, ElfSym *sym, int needSym)
 				s->type = SHIDDEN;
 			}
 			break;
+		case ElfSymBindWeak:
+			if(needSym) {
+				s = newsym(sym->name, 0);
+				if(sym->other == 2)
+					s->type = SHIDDEN;
+			}
+			break;
 		default:
 			werrstr("%s: invalid symbol binding %d", sym->name, sym->bind);
 			return -1;

コアとなるコードの解説

上記の差分は、src/cmd/ld/ldelf.cファイル内のreadsym関数に対する変更を示しています。この関数は、ELFオブジェクトファイルからシンボル情報を読み取り、Goリンカ内部のシンボル構造体に変換する役割を担っています。

  • case ElfSymBindWeak: の追加:
    • これは、ELFシンボルのバインディングタイプがSTB_WEAK(弱いシンボル)である場合に実行される新しいケースです。
    • 以前のコードでは、このケースが明示的に処理されていなかったため、弱いシンボルが適切に扱われませんでした。
  • if(needSym):
    • needSymは、リンカがこのシンボルに関する詳細な情報(例えば、その定義や属性)を必要としているかどうかを示すフラグです。
    • シンボルが必要な場合にのみ、以下の処理が実行されます。
  • s = newsym(sym->name, 0);:
    • newsym関数は、指定された名前(sym->name)を持つ新しいシンボルをGoリンカの内部シンボルテーブルに作成します。
    • これにより、弱いシンボルであっても、リンカがその存在を認識し、後続のリンキングプロセスで参照できるようになります。
  • if(sym->other == 2):
    • sym->otherは、ELFシンボルテーブルのエントリにあるst_otherフィールドに対応します。このフィールドは、シンボルの可視性などの追加情報を格納するために使用されます。
    • 2という値は、ELFのSTV_HIDDEN(隠された可視性)に対応します。これは、シンボルがその定義されている共有オブジェクト内でのみ参照可能であり、外部からは参照できないことを意味します。
  • s->type = SHIDDEN;:
    • もしシンボルが隠された可視性を持つ場合、Goリンカ内部のシンボル構造体stypeフィールドをSHIDDENに設定します。
    • SHIDDENは、Goリンカが隠されたシンボルを識別するための内部的なタイプです。この設定により、リンカは後続のシンボル解決フェーズで、このシンボルが外部にエクスポートされないように適切に処理します。

この変更により、GoリンカはELF形式のオブジェクトファイルから弱いシンボル、特に隠された可視性を持つ弱いシンボルを正確に読み取り、Goのリンキングセマンティクスに沿って適切に処理できるようになりました。これにより、compiler_rtのような外部ライブラリが生成する特定のシンボルが原因で発生していたリンキングエラーが解消されます。

関連リンク

参考にした情報源リンク