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

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

このコミットは、Go言語のリンカ (cmd/ld) および AMD64 アーキテクチャ向けのリンカ (cmd/6l) における Solaris/AMD64 環境への対応の第二弾です。具体的には、動的リンクにおける再配置 (relocation) とシンボルテーブルの扱いに関する変更が含まれています。

コミット

commit 4687b5414789581f635ff96d401e23a50da8024b
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Feb 11 18:43:05 2014 -0500

    cmd/ld, cmd/6l: part 2 of solaris/amd64 linker changes.
    Second part of the solaris/amd64 linker changes: relocation and symbol table.
    
    LGTM=iant
    R=golang-codereviews, iant
    CC=golang-codereviews
    https://golang.org/cl/61330043

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

https://github.com/golang/go/commit/4687b5414789581f635ff96d401e23a50da8024b

元コミット内容

このコミットは、GoリンカがSolaris/AMD64プラットフォームで正しく動作するための変更の第二段階です。主な焦点は、動的リンクにおける再配置処理とシンボルテーブルの生成方法の調整にあります。具体的には、外部関数への参照をGlobal Offset Table (GOT) を介して解決するように変更し、PC相対アドレス指定における動的インポートシンボル (SDYNIMPORT) の扱いを修正し、シンボルテーブル内で動的インポートシンボルを適切に表現するようにしました。

変更の背景

Go言語はクロスプラットフォーム対応を重視しており、様々なオペレーティングシステムやアーキテクチャで動作するように設計されています。このコミットが行われた2014年当時、GoリンカはSolaris/AMD64プラットフォームにおける動的リンクの特定の挙動に対応する必要がありました。特に、ELF (Executable and Linkable Format) 形式のバイナリにおける再配置とシンボル解決のメカニズムは、OSによって微妙な違いがあるため、Solarisの慣習に合わせた調整が求められました。

この「part 2」という記述から、Solaris/AMD64対応が段階的に進められていたことが伺えます。第一段階では基本的なリンカの機能が実装され、この第二段階ではより複雑な動的リンクの側面、特に外部ライブラリからのシンボルインポートに関する詳細な調整が行われたと考えられます。

前提知識の解説

このコミットを理解するためには、以下の概念を把握しておく必要があります。

  • ELF (Executable and Linkable Format): Unix系OS(Linux, Solaris, BSDなど)で実行ファイル、共有ライブラリ、オブジェクトファイルなどのバイナリ形式として広く使われている標準フォーマットです。ELFファイルは、ヘッダ、セクション、セグメントなどから構成され、プログラムのコード、データ、シンボル情報、再配置情報などが格納されています。

  • リンカ (Linker): コンパイラによって生成された複数のオブジェクトファイルやライブラリを結合し、実行可能なプログラムや共有ライブラリを生成するツールです。リンカの主要な役割の一つは、シンボル解決と再配置です。

  • シンボル (Symbol): プログラム内の関数や変数などの名前を指します。シンボルは、その定義(どこで実装されているか)と参照(どこで使われているか)を持ちます。

  • シンボルテーブル (Symbol Table): ELFファイル内に含まれるテーブルで、ファイル内で定義されている、または参照されているすべてのシンボルとその属性(名前、型、アドレス、サイズなど)がリストされています。リンカはシンボルテーブルを使用して、異なるオブジェクトファイル間のシンボル参照を解決します。

  • 再配置 (Relocation): オブジェクトファイル内のコードやデータが、最終的な実行ファイルや共有ライブラリ内で実際に配置されるアドレスに基づいて、その参照を修正するプロセスです。コンパイル時には、関数呼び出しや変数アクセスのアドレスは相対的または仮のアドレスで記述されており、リンカが最終的なアドレスを決定し、これらの参照を「再配置」します。

  • 動的リンク (Dynamic Linking): プログラムの実行時に、必要な共有ライブラリ(例: .so ファイル)をメモリにロードし、プログラムとリンクする方式です。これにより、ディスクスペースの節約やメモリの共有、ライブラリの更新が容易になるなどの利点があります。

  • Global Offset Table (GOT): 動的リンクにおいて、共有ライブラリ内のグローバル変数や関数への参照を解決するために使用されるテーブルです。GOTはデータセクションに配置され、実行時に動的リンカによって実際のメモリアドレスが書き込まれます。プログラムはGOTエントリを介して外部シンボルにアクセスします。

  • Procedure Linkage Table (PLT): GOTと連携して、共有ライブラリ内の関数呼び出しを遅延解決(lazy binding)するために使用されるテーブルです。関数が初めて呼び出されたときに動的リンカが介入し、GOTエントリを更新して以降の呼び出しが直接関数にジャンプするようにします。

  • PC相対アドレス指定 (PC-relative addressing): 命令のオペランドのアドレスが、プログラムカウンタ (PC) の現在の値からのオフセットとして指定されるアドレス指定モードです。これは、コードがメモリ内のどこにロードされても正しく動作するように、位置独立コード (PIC: Position-Independent Code) を生成する際に特に重要です。

  • ELF再配置タイプ (ELF Relocation Types):

    • R_X86_64_PC32: x86-64アーキテクチャにおける32ビットPC相対再配置タイプです。命令の次のアドレスからのオフセットとして、32ビットの符号付き値を加算します。
    • R_X86_64_GOTPCREL: x86-64アーキテクチャにおけるGOT相対PC再配置タイプです。これは、GOT内のエントリへのPC相対オフセットを計算するために使用されます。位置独立コードで外部データや関数にアクセスする際によく使われます。
  • Goリンカのシンボルタイプ:

    • STEXT: コードセクション(テキストセクション)内のシンボル(関数など)を表します。
    • SDATA: データセクション内のシンボル(グローバル変数など)を表します。
    • SDYNIMPORT: 動的にインポートされるシンボル、つまり実行時に共有ライブラリから解決されるシンボルを表します。
    • SHOSTOBJ: ホストOSのオブジェクトファイルからリンクされるシンボルを表します。

技術的詳細

このコミットは、Solaris/AMD64環境におけるGoプログラムの動的リンクの正確性を確保するために、以下の技術的な変更を導入しています。

  1. 外部関数アドレスのGOT経由での解決 (src/cmd/6l/asm.c: adddynrel):

    • D_ADDR (アドレス再配置) タイプで、シンボルが STEXT (関数) であり、かつELF形式のバイナリを生成している場合 (iself が真の場合) に、特別な処理が追加されました。
    • 以前は、外部関数のアドレスを直接参照しようとしていた可能性がありますが、動的リンク環境では、外部関数のアドレスは実行時に動的リンカによって解決され、そのアドレスはGOTに格納されます。
    • この変更により、リンカは外部関数への参照を、その関数に対応するGOTシンボル (.got セクション内のエントリ) のアドレスに変換するようになりました。具体的には、addgotsym(targ) でGOTシンボルを追加し、再配置対象のシンボルを .got に変更し、オフセットを調整しています。これにより、生成されるコードはGOTを介して外部関数を呼び出すようになります。
  2. PC相対再配置における動的インポートシンボルの特殊処理 (src/cmd/6l/asm.c: elfreloc1):

    • D_PCREL (PC相対再配置) タイプで、再配置サイズが4バイトの場合 (r->siz == 4) の処理が変更されました。
    • 以前は、常に R_X86_64_PC32 再配置タイプを使用していましたが、この変更により、再配置対象のシンボル (r->xsym) が SDYNIMPORT (動的インポートシンボル) である場合は、R_X86_64_GOTPCREL 再配置タイプを使用するようになりました。
    • R_X86_64_GOTPCREL は、位置独立コード (PIC) において、GOT内のエントリへのPC相対オフセットを計算するために使用されます。動的にインポートされるシンボルは、実行時にロードされる共有ライブラリ内に存在するため、そのアドレスはコンパイル時には固定できません。R_X86_64_GOTPCREL を使用することで、リンカはGOTを介してこれらのシンボルにアクセスするための正しい再配置情報を生成し、PICの原則に則ったコードを生成できます。
  3. Solarisにおける動的インポートシンボルの再配置許可 (src/cmd/ld/data.c: relocsym):

    • relocsym 関数内で、以前は SDYNIMPORT タイプのシンボルに対する未処理の再配置 (unhandled relocation) を診断エラーとして報告するコードがありました。
    • この変更により、HEADTYPE != Hsolaris という条件が追加され、Solaris環境 (Hsolaris) では SDYNIMPORT シンボルに対する再配置が許可されるようになりました。これは、Solarisのリンカがこれらのシンボルを適切に処理できることを前提としており、GoリンカがSolarisの動的リンクの慣習に従うように調整されたことを意味します。
  4. 動的インポートシンボルのセクションチェックの緩和 (src/cmd/ld/data.c: relocsym):

    • relocsym 関数内の複数の箇所で、シンボルがセクションを持たない (rs->sect == nil) 場合に診断エラーを報告するチェックがありました。
    • このチェックの条件に rs->type != SDYNIMPORT が追加されました。これは、動的インポートシンボルは外部ライブラリに存在するため、Goリンカの視点からは独自のセクションを持たないことが期待されるためです。この変更により、SDYNIMPORT シンボルがセクションを持たないことによる誤ったエラー報告が回避されます。
  5. ELFシンボルテーブルにおける動的インポートシンボルの名前解決 (src/cmd/ld/symtab.c: asmelfsym):

    • asmelfsym 関数は、ELFシンボルテーブルを生成する役割を担っています。
    • 以前は、SHOSTOBJ タイプのシンボルのみを処理していましたが、この変更により SDYNIMPORT タイプのシンボルも処理対象に含まれるようになりました。
    • 最も重要な変更は、ELFシンボルエントリを生成する際に、SDYNIMPORT タイプのシンボルに対しては s->extname (外部名) を使用し、それ以外のシンボルに対しては s->name (内部名) を使用するようにした点です。
    • SDYNIMPORT シンボルは、Goの内部的なシンボル名とは異なる、外部ライブラリで定義されている名前を持つ場合があります。ELFシンボルテーブルに正しい外部名を記述することで、動的リンカが実行時にこれらのシンボルを正しく解決できるようになります。

これらの変更は、GoのリンカがSolaris/AMD64環境で、外部共有ライブラリと連携して動的にリンクされた実行ファイルを生成する際の、再配置とシンボル解決の複雑な要件を満たすために不可欠です。

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

src/cmd/6l/asm.c

--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -207,6 +207,15 @@ adddynrel(LSym *s, Reloc *r)
 		return;
 	
 	case D_ADDR:
+		if(s->type == STEXT && iself) {
+			// The code is asking for the address of an external
+			// function.  We provide it with the address of the
+			// correspondent GOT symbol.
+			addgotsym(targ);
+			r->sym = linklookup(ctxt, ".got", 0);
+			r->add += targ->got;
+			return;
+		}
 		if(s->type != SDATA)
 			break;
 		if(iself) {
@@ -273,9 +282,12 @@ elfreloc1(Reloc *r, vlong sectoff)
 		break;
 
 	case D_PCREL:
-		if(r->siz == 4)
-			VPUT(R_X86_64_PC32 | (uint64)elfsym<<32);
-		else
+		if(r->siz == 4) {
+			if(r->xsym->type == SDYNIMPORT)
+				VPUT(R_X86_64_GOTPCREL | (uint64)elfsym<<32);
+			else
+				VPUT(R_X86_64_PC32 | (uint64)elfsym<<32);
+		} else
 			return -1;
 		break;
 	

src/cmd/ld/data.c

--- a/src/cmd/ld/data.c
+++ b/src/cmd/ld/data.c
@@ -154,9 +154,9 @@ relocsym(LSym *s)
 		if(r->type >= 256)
 			continue;
 
-		if(r->sym != S && r->sym->type == SDYNIMPORT)
+		// Solaris needs the ability to reference dynimport symbols.
+		if(HEADTYPE != Hsolaris && r->sym != S && r->sym->type == SDYNIMPORT)
 			diag("unhandled relocation for %s (type %d rtype %d)", r->sym->name, r->sym->type, r->type);
-
 		if(r->sym != S && r->sym->type != STLSBSS && !r->sym->reachable)
 			diag("unreachable sym in relocation: %s %s", s->name, r->sym->name);
 
@@ -194,7 +194,7 @@ relocsym(LSym *s)
 				tr->xadd += symaddr(rs) - symaddr(rs->outer);
 				rs = rs->outer;
 			}
-			if(rs->type != SHOSTOBJ && rs->sect == nil)
+			if(rs->type != SHOSTOBJ && rs->type != SDYNIMPORT && rs->sect == nil)
 				diag("missing section for %s", rs->name);
 			tr->xsym = rs;
 
@@ -225,7 +225,7 @@ relocsym(LSym *s)
 				rs = rs->outer;
 			}
 			tr->xadd -= r->siz; // relative to address after the relocated chunk
-			if(rs->type != SHOSTOBJ && rs->sect == nil)
+			if(rs->type != SHOSTOBJ && rs->type != SDYNIMPORT && rs->sect == nil)
 				diag("missing section for %s", rs->name);
 			tr->xsym = rs;
 

src/cmd/ld/symtab.c

--- a/src/cmd/ld/symtab.c
+++ b/src/cmd/ld/symtab.c
@@ -171,6 +171,7 @@ void
 asmelfsym(void)
 {
 	LSym *s;
+	char *name;
 
 	// the first symbol entry is reserved
 	putelfsyment(0, 0, 0, (STB_LOCAL<<4)|STT_NOTYPE, 0, 0);
@@ -196,9 +197,13 @@ asmelfsym(void)
 	genasmsym(putelfsym);
 	
 	for(s=ctxt->allsym; s!=S; s=s->allsym) {
-		if(s->type != SHOSTOBJ)
+		if(s->type != SHOSTOBJ && s->type != SDYNIMPORT)
 			continue;
-		putelfsyment(putelfstr(s->name), 0, 0, (STB_GLOBAL<<4)|STT_NOTYPE, 0, 0);
+		if(s->type == SDYNIMPORT)
+			name = s->extname;
+		else
+			name = s->name;
+		putelfsyment(putelfstr(name), 0, 0, (STB_GLOBAL<<4)|STT_NOTYPE, 0, 0);
 		s->elfsym = numelfsym++;
 	}
 }

コアとなるコードの解説

src/cmd/6l/asm.c

  • adddynrel 関数内の D_ADDR 処理:

    • case D_ADDR: ブロック内に、if(s->type == STEXT && iself) という条件が追加されました。これは、再配置対象のシンボル s が関数 (STEXT) であり、かつELF形式のバイナリを生成している場合に適用されるロジックです。
    • コメントにあるように、「コードが外部関数のアドレスを要求している」状況に対応します。動的リンクでは、外部関数のアドレスは実行時にGOTを介して解決されるため、リンカは直接のアドレスではなくGOTエントリのアドレスを提供する必要があります。
    • addgotsym(targ): これは、対象のシンボル targ に対応するGOTエントリを生成または取得する関数呼び出しです。
    • r->sym = linklookup(ctxt, ".got", 0);: 再配置のシンボルを、特殊なシンボルである .got に変更します。これにより、この再配置がGOTエントリを指すようになります。
    • r->add += targ->got;: 再配置のオフセット (r->add) に、対象シンボル targ のGOTエントリ内のオフセット (targ->got) を加算します。これにより、最終的な再配置値がGOT内の正しいエントリを指すようになります。
  • elfreloc1 関数内の D_PCREL 処理:

    • case D_PCREL: ブロック内で、if(r->siz == 4) (32ビットPC相対再配置) の条件が変更されました。
    • if(r->xsym->type == SDYNIMPORT) という新しい条件が追加されています。これは、再配置対象のシンボル (r->xsym) が動的インポートシンボル (SDYNIMPORT) であるかどうかをチェックします。
    • もし SDYNIMPORT であれば、VPUT(R_X86_64_GOTPCREL | (uint64)elfsym<<32); が実行されます。R_X86_64_GOTPCREL は、GOT内のエントリへのPC相対オフセットを計算するための再配置タイプです。動的インポートシンボルは実行時に解決されるため、直接のアドレスではなくGOTを介してアクセスする必要があります。
    • SDYNIMPORT でなければ、以前と同様に VPUT(R_X86_64_PC32 | (uint64)elfsym<<32); が実行されます。これは一般的な32ビットPC相対再配置です。
    • この変更により、Goリンカは動的インポートシンボルへのPC相対参照を、Solaris/AMD64の動的リンクの慣習に従って正しく処理できるようになります。

src/cmd/ld/data.c

  • relocsym 関数内の SDYNIMPORT 診断チェックの変更:

    • if(r->sym != S && r->sym->type == SDYNIMPORT) という行の前に、// Solaris needs the ability to reference dynimport symbols. というコメントが追加され、その行の条件が if(HEADTYPE != Hsolaris && r->sym != S && r->sym->type == SDYNIMPORT) に変更されました。
    • これは、Solaris環境 (HEADTYPE == Hsolaris) では、SDYNIMPORT タイプのシンボルに対する再配置が「未処理」と見なされるべきではないことを意味します。Solarisのリンカはこれらのシンボルを適切に処理できるため、Goリンカがエラーを報告する必要がなくなりました。
  • relocsym 関数内のセクションチェックの緩和:

    • if(rs->type != SHOSTOBJ && rs->sect == nil) という条件が、if(rs->type != SHOSTOBJ && rs->type != SDYNIMPORT && rs->sect == nil) に変更された箇所が2箇所あります。
    • この変更は、動的インポートシンボル (SDYNIMPORT) が、Goリンカの内部的なセクション情報を持たないことが正常な状態であることを考慮に入れています。SDYNIMPORT シンボルは外部ライブラリに存在するため、Goリンカがそれらのセクションを直接管理する必要はありません。この緩和により、誤った「セクションが見つからない」という診断エラーが回避されます。

src/cmd/ld/symtab.c

  • asmelfsym 関数内のシンボルテーブル生成ロジックの変更:
    • LSym *s; の後に char *name; が追加され、シンボル名を一時的に保持する変数が導入されました。
    • シンボルをループ処理する for ループの条件が if(s->type != SHOSTOBJ) から if(s->type != SHOSTOBJ && s->type != SDYNIMPORT) に変更されました。これにより、SDYNIMPORT タイプのシンボルもELFシンボルテーブルに含める対象となります。
    • putelfsyment 関数を呼び出す直前に、シンボル名を選択するロジックが追加されました。
      • if(s->type == SDYNIMPORT) であれば、name = s->extname; となり、シンボルの外部名 (extname) が使用されます。
      • そうでなければ、name = s->name; となり、通常のシンボル名 (name) が使用されます。
    • この変更は、動的インポートシンボルがGoの内部的なシンボル名とは異なる、外部ライブラリで認識される名前を持つ可能性があるため重要です。ELFシンボルテーブルに正しい外部名を記述することで、実行時に動的リンカがこれらのシンボルを正しく解決できるようになります。

これらのコード変更は、Solaris/AMD64環境におけるGoの動的リンクの挙動を、そのプラットフォームのELFバイナリおよび動的リンカの期待に合わせるための、低レベルかつ重要な調整です。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分 (./commit_data/18458.txt)
  • Go言語のリンカのソースコード (GoのGitHubリポジトリ)
  • ELFフォーマット、再配置、動的リンクに関する一般的な知識 (上記「関連リンク」に挙げたWikipedia記事など)
  • Solarisのリンカの挙動に関する一般的な知識
  • Go言語のリリースノートやドキュメント (当時のGo 1.2周辺の情報を中心に)
  • google_web_search を用いて、Go linker SDYNIMPORT, ELF R_X86_64_GOTPCREL, Solaris AMD64 dynamic linking などのキーワードで関連情報を検索しました。