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

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

このコミットは、Go言語のツールチェイン、特にリンカ(cmd/ldcmd/6lcmd/5lcmd/8l)におけるSolaris/AMD64プラットフォームのサポート追加の第一弾です。Goのリンカは、異なるオペレーティングシステムやアーキテクチャ向けに実行ファイルを生成する役割を担っており、このコミットはSolaris環境でのGoプログラムのビルドと実行を可能にするための基盤を構築します。

コミット

commit 6ebf59b953776042452245a8b6b923c39993a0ec
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Feb 9 16:45:38 2014 -0500

    include, linlink, cmd/6l, cmd/ld: part 1 of solaris/amd64 linker changes.
    rsc suggested that we split the whole linker changes into three parts.
    This is the first one, mostly dealing with adding Hsolaris.
    
    LGTM=iant
    R=golang-codereviews, iant, dave
    CC=golang-codereviews
    https://golang.org/cl/54210050

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

https://github.com/golang/go/commit/6ebf59b953776042452245a8b6b923c39993a0ec

元コミット内容

このコミットは、GoリンカにおけるSolaris/AMD64プラットフォームサポートの変更の第一部です。主な目的は、GoツールチェインがSolarisをターゲットOSとして認識し、Solaris上で実行可能なバイナリを生成できるようにするための基盤を整備することです。具体的には、Goリンカの内部でSolarisを識別するための定数 Hsolaris の導入と、Solaris特有のリンカの挙動(特に動的リンカのパスやELFバイナリの生成に関する考慮事項)に対応するためのコード変更が含まれています。

変更の背景

Go言語は、その設計思想の一つとして、様々なプラットフォームへのクロスコンパイルと実行をサポートしています。このコミットが作成された2014年当時、GoはLinux、Windows、macOS、FreeBSD、OpenBSD、NetBSD、Plan 9、DragonFly BSDなどの主要なOSをサポートしていましたが、Solarisへの公式サポートはまだ限定的でした。

Solarisは、特にエンタープライズ環境で利用されることの多いUNIX系OSであり、GoプログラムをSolaris環境でネイティブに実行したいというニーズが存在しました。しかし、各OSには独自のバイナリフォーマット(例: Linux/BSDはELF、WindowsはPE、macOSはMach-O)やリンカの挙動、システムコール規約など、Goのツールチェインが対応すべき多くの差異があります。

このコミットは、Solaris/AMD64プラットフォームへの対応を段階的に進めるための最初のステップとして、GoリンカがSolarisを正しく認識し、Solarisの動的リンカの特性(特にELF形式における動的セクションの扱い)に合わせたバイナリを生成できるようにするための基盤的な変更を導入しました。コミットメッセージにある「rsc suggested that we split the whole linker changes into three parts」という記述は、この対応が複雑であり、段階的に進める必要があったことを示唆しています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が役立ちます。

  1. Go言語のツールチェインとクロスコンパイル:

    • Goは、GOOS(ターゲットOS)とGOARCH(ターゲットアーキテクチャ)という環境変数を用いて、異なるプラットフォーム向けのバイナリを簡単にクロスコンパイルできます。
    • Goのビルドプロセスは、コンパイラ(cmd/compile)、アセンブラ(cmd/asm)、リンカ(cmd/link、またはアーキテクチャ固有のcmd/6lcmd/8lなど)から構成されます。リンカは、コンパイルされたオブジェクトファイルと必要なライブラリを結合し、実行可能なバイナリを生成する最終段階を担います。
  2. リンカの役割:

    • リンカは、複数のオブジェクトファイルやライブラリを結合して、実行可能ファイルや共有ライブラリを生成するプログラムです。
    • 静的リンクと動的リンクの2種類があります。静的リンクでは、必要なライブラリコードがすべて実行ファイルに埋め込まれます。動的リンクでは、ライブラリは実行時に動的リンカによってロードされます。
  3. ELF (Executable and Linkable Format):

    • UNIX系OS(Linux、BSD、Solarisなど)で広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。
    • ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、そして様々なセクション(.text.data.rodata.symtab.strtab.plt.got.dynsym.dynstr.dynamicなど)で構成されます。
    • .dynamicセクション: 動的リンクに必要な情報(共有ライブラリの依存関係、動的リンカのパス、シンボル解決情報など)を格納します。このセクション内のエントリは「動的タグ」(DT_で始まる定数)と値のペアで表現されます。
      • DT_PLTREL: PLT(Procedure Linkage Table)で使用される再配置エントリのタイプ(DT_RELまたはDT_RELA)。
      • DT_PLTRELSZ: PLT再配置セクションのサイズ。
      • DT_JMPREL: PLT再配置セクションへのオフセット。
      • DT_REL / DT_RELA: 再配置エントリのタイプ。DT_RELAは加算(addend)を含む再配置で、x86-64アーキテクチャでよく使われます。DT_RELは加算を含みません。
    • PLT (Procedure Linkage Table)GOT (Global Offset Table): 動的リンクされた関数呼び出しを解決するために使用されるメカニズムです。PLTは、共有ライブラリ内の関数へのジャンプ命令を含み、GOTはこれらの関数の実際のアドレスを保持します。
    • 再配置セクション (.rel.plt, .rela.plt): PLTエントリが参照するシンボルを解決するための情報(再配置エントリ)を格納します。
  4. 動的リンカ (Dynamic Linker/Loader):

    • 実行可能ファイルが起動される際に、必要な共有ライブラリをメモリにロードし、シンボル解決(未解決の関数呼び出しや変数参照を実際のメモリ上のアドレスにマッピングすること)を行うシステムプログラムです。UNIX系OSでは通常 /lib/ld.so.1/lib64/ld-linux-x86-64.so.2 のようなパスに存在します。
  5. TLS (Thread Local Storage):

    • スレッドごとに独立したデータを保持するためのメカニズムです。Goランタイムは、ゴルーチン(Goの軽量スレッド)のコンテキスト切り替えやスケジューリングのために、TLSのようなメカニズムを内部的に利用することがあります。ELFシステムでは、FSまたはGSセグメントレジスタを介してTLSにアクセスすることが一般的です。

技術的詳細

このコミットの技術的な核心は、GoリンカがSolarisのELFバイナリを正しく生成し、特にSolarisの動的リンカの特定の挙動に対応することにあります。

  1. Hsolaris の導入:

    • include/link.hHsolaris という新しい enum 定数が追加されました。これは、GoのリンカがターゲットOSとしてSolarisを識別するための内部的なIDです。Goのツールチェインは、このHEADTYPEの値に基づいて、OS固有のバイナリフォーマットやリンカの挙動を決定します。
  2. 動的リンカパスの定義:

    • src/cmd/6l/asm.c (AMD64リンカ) と src/cmd/8l/asm.c (x86リンカ) に solarisdynld 変数が追加され、Solarisにおけるデフォルトの動的リンカのパスが定義されました。AMD64では /lib/amd64/ld.so.1、x86では /lib/ld.so.1 です。これは、Goが生成するELFバイナリのインタープリタパス(PT_INTERPセグメント)として埋め込まれ、OSがプログラムをロードする際にどの動的リンカを使用すべきかを指示します。
  3. ELFバイナリ生成の調整:

    • src/cmd/6l/asm.c および src/cmd/6l/obj.c では、Hsolaris が追加されたことで、Solaris向けバイナリも他のELFベースのOS(Linux, FreeBSD, NetBSD, OpenBSD, DragonFly BSD)と同様にELF形式で生成されるように設定されました。これには、64ビットアドレスの使用、シンボルオフセットの計算、elfinit() の呼び出しなどが含まれます。
  4. Solaris動的リンカの特殊な挙動への対応:

    • 最も重要な変更は src/cmd/ld/elf.c にあります。Solarisの動的リンカは、.rela.plt(または.rel.plt)セクションが空であるにもかかわらず、DT_JMPREL(PLT再配置セクションへのオフセット)という動的エントリが.dynamicセクションに存在すると、問題を引き起こす可能性があります。これは、動的リンカが空のセクションを処理する際に予期せぬエラーやクラッシュを引き起こす可能性があるためです。
    • この問題を回避するため、Goリンカは DT_PLTRELDT_PLTRELSZDT_JMPREL の各動的エントリを、対応する再配置セクション(.rela.plt または .rel.plt)のサイズが0より大きい場合にのみ出力するように変更されました。これにより、Solarisの動的リンカが期待する形式で.dynamicセクションが生成され、GoプログラムがSolaris上で正しく動的にリンクされるようになります。
  5. 静的リンクの制限:

    • src/cmd/ld/lib.c の変更は、Solarisでは静的リンクされたバイナリがサポートされていないことを示しています。Goのリンカは、通常、flag_shared(共有ライブラリとしてビルド)や havedynamic(動的ライブラリに依存)が偽の場合、静的リンクを試みます。しかし、Solarisでは、たとえこれらの条件が満たされても、動的リンクを強制するように変更されました。これは、Solarisのシステムライブラリやカーネルとのインタフェースが動的リンクに強く依存しているためと考えられます。
  6. TLSアクセス (FSレジスタの使用):

    • src/liblink/obj6.c では、Hsolaris が他のELFベースのOS(Linux, FreeBSDなど)と同様に、TLSアクセスに FS セグメントレジスタを使用する条件に追加されました。これは、Goランタイムがスレッドローカルなデータにアクセスする際の、OSおよびアーキテクチャ固有の規約に合わせた変更です。

これらの変更により、GoはSolaris/AMD64プラットフォームで実行可能なバイナリを生成できるようになり、Go言語のクロスプラットフォーム対応がさらに強化されました。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. include/link.h:

    • enum HEADTYPEHsolaris を追加。
    --- a/include/link.h
    +++ b/include/link.h
    @@ -451,6 +451,7 @@ enum {
     	Hnetbsd,
     	Hopenbsd,
     	Hplan9,
    +	Hsolaris,
     	Hwindows,
     };
    
  2. src/cmd/6l/asm.c:

    • solarisdynld 変数の定義と、asmb 関数内で Hsolaris に関連する処理(64ビットアドレス、シンボルオフセット計算、asmbelf呼び出し)の追加。
    --- a/src/cmd/6l/asm.c
    +++ b/src/cmd/6l/asm.c
    @@ -44,6 +44,7 @@ char freebsddynld[] = "/libexec/ld-elf.so.1";
     char openbsddynld[] = "/usr/libexec/ld.so";
     char netbsddynld[] = "/libexec/ld.elf_so";
     char dragonflydynld[] = "/usr/libexec/ld-elf.so.2";
    +char solarisdynld[] = "/lib/amd64/ld.so.1";
    
  3. src/cmd/ld/elf.c:

    • elfdynhash 関数内で、.rela.plt または .rel.plt セクションのサイズが0より大きい場合にのみ DT_PLTREL, DT_PLTRELSZ, DT_JMPREL を出力するロジックの追加。
    • doelf 関数から、上記の動的エントリを無条件に出力していた古いロジックの削除と、Solaris動的リンカの挙動に関するコメントの追加。
    --- a/src/cmd/ld/elf.c
    +++ b/src/cmd/ld/elf.c
    @@ -677,6 +677,23 @@ elfdynhash(void)
     		elfwritedynent(s, DT_VERNEEDNUM, nfile);
     		elfwritedynentsym(s, DT_VERSYM, linklookup(ctxt, ".gnu.version", 0));
     	}
    +
    +	if(thechar == '6') {
    +		sy = linklookup(ctxt, ".rela.plt", 0);
    +		if(sy->size > 0) {
    +			elfwritedynent(s, DT_PLTREL, DT_RELA);
    +			elfwritedynentsymsize(s, DT_PLTRELSZ, sy);
    +			elfwritedynentsym(s, DT_JMPREL, sy);
    +		}
    +	} else {
    +		sy = linklookup(ctxt, ".rel.plt", 0);
    +		if(sy->size > 0) {
    +			elfwritedynent(s, DT_PLTREL, DT_REL);
    +			elfwritedynentsymsize(s, DT_PLTRELSZ, sy);
    +			elfwritedynentsym(s, DT_JMPREL, sy);
    +		}
    +	}
    +
     	elfwritedynent(s, DT_NULL, 0);
     }
     
    @@ -1058,16 +1075,10 @@ doelf(void)
     		
     		elfwritedynentsym(s, DT_PLTGOT, linklookup(ctxt, ".got.plt", 0));
     
    -		if(thechar == '6') {
    -			elfwritedynent(s, DT_PLTREL, DT_RELA);
    -			elfwritedynentsymsize(s, DT_PLTRELSZ, linklookup(ctxt, ".rela.plt", 0));
    -			elfwritedynentsym(s, DT_JMPREL, linklookup(ctxt, ".rela.plt", 0));
    -		} else {
    -			elfwritedynent(s, DT_PLTREL, DT_REL);
    -			elfwritedynentsymsize(s, DT_PLTRELSZ, linklookup(ctxt, ".rel.plt", 0));
    -			elfwritedynentsym(s, DT_JMPREL, linklookup(ctxt, ".rel.plt", 0));
    -		}
    -		
    +		// Solaris dynamic linker can't handle an empty .rela.plt if
    +		// DT_JMPREL is emitted so we have to defer generation of DT_PLTREL,
    +		// DT_PLTRELSZ, and DT_JMPREL dynamic entries until after we know the
    +		// size of .rel(a).plt section.
     		elfwritedynent(s, DT_DEBUG, 0);
     
     		// Do not write DT_NULL.  elfdynhash will finish it.
    
  4. src/cmd/ld/lib.c:

    • 静的リンクを無効にする条件に HEADTYPE != Hsolaris を追加。
    --- a/src/cmd/ld/lib.c
    +++ b/src/cmd/ld/lib.c
    @@ -259,7 +259,9 @@ loadlib(void)
     	//
     	// Exception: on OS X, programs such as Shark only work with dynamic
     	// binaries, so leave it enabled on OS X (Mach-O) binaries.
    -	if(!flag_shared && !havedynamic && HEADTYPE != Hdarwin)
    +	// Also leave it enabled on Solaris which doesn't support
    +	// statically linked binaries.
    +	if(!flag_shared && !havedynamic && HEADTYPE != Hdarwin && HEADTYPE != Hsolaris)
     		debug['d'] = 1;
     	
     	importcycles();
    

コアとなるコードの解説

include/link.h の変更

enum HEADTYPE は、Goリンカがサポートする様々なオペレーティングシステムを識別するための列挙型です。ここに Hsolaris が追加されたことで、GoのビルドシステムはSolarisを正式なターゲットプラットフォームとして認識できるようになります。これは、GoがSolaris向けにバイナリを生成する際の、OS固有の処理分岐の起点となります。

src/cmd/6l/asm.c および関連するアセンブラファイルの変更

solarisdynld 変数は、Solarisシステムにおける動的リンカのデフォルトパスを定義します。Goが生成するELFバイナリには、PT_INTERP (Program Interpreter) セグメントというものが含まれており、ここに動的リンカのパスが埋め込まれます。OSのローダーは、このパスを読み取り、指定された動的リンカを起動してプログラムの実行を開始します。AMD64 (6l) では /lib/amd64/ld.so.1、x86 (8l) では /lib/ld.so.1 がSolarisの標準的な動的リンカパスです。

asmb 関数内の変更は、Hsolaris が他のELFベースの64ビットOSと同様に扱われることを保証します。これには、64ビットアドレス空間の利用や、シンボルテーブルのオフセット計算などが含まれます。asmbelf の呼び出しは、Solaris向けにもELF形式のバイナリが生成されることを意味します。

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

このファイルは、GoリンカがELF形式のバイナリを生成する際の中心的なロジックを含んでいます。

  • elfdynhash 関数の変更: 以前のGoリンカでは、DT_PLTRELDT_PLTRELSZDT_JMPREL といった動的エントリを、.rela.plt.rel.plt セクションのサイズに関わらず、常に.dynamicセクションに出力していました。しかし、Solarisの特定のバージョンの動的リンカは、これらのエントリが存在するにもかかわらず、対応する再配置セクションが空である場合に、エラーを引き起こすことが知られていました。 このコミットでは、この問題を解決するために、elfdynhash 関数内で、linklookup(ctxt, ".rela.plt", 0)linklookup(ctxt, ".rel.plt", 0) で取得したシンボル(セクション)の size が0より大きい場合にのみ、これらの動的エントリを出力するように変更されました。これにより、Solarisの動的リンカが期待する、有効な再配置情報がある場合にのみ関連する動的エントリが出力されるようになり、互換性の問題が解消されます。

  • doelf 関数の変更: doelf 関数から、上記の動的エントリを無条件に出力していた古いロジックが削除されました。そして、Solaris動的リンカの挙動に関する重要なコメントが追加されました。このコメントは、変更の理由を明確に説明しており、Solarisの動的リンカが空の.rela.pltセクションとDT_JMPRELの組み合わせを処理できないため、再配置セクションのサイズが確定した後にこれらの動的エントリの生成を遅延させる必要があることを示しています。

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

loadlib 関数内の変更は、Solarisにおける静的リンクの制限に対応するものです。Goは通常、可能な限り静的リンクされたバイナリを生成しようとしますが、OS X(Mach-O)と同様に、Solarisも静的リンクされたバイナリを完全にサポートしていない、または推奨していない場合があります。Solarisのシステムライブラリ(特にCライブラリやシステムコールインターフェース)は、動的リンクを前提としていることが多いため、静的リンクされたGoバイナリがSolaris上で正しく動作しない可能性があります。 この変更により、GoリンカはSolaris向けにビルドする際、flag_sharedhavedynamic の値に関わらず、常に動的リンクを有効にするように強制されます。これにより、Solaris上でのGoプログラムの互換性と安定性が確保されます。

src/liblink/obj6.c および src/liblink/sym.c の変更

src/liblink/obj6.cprogedit および load_g_cx 関数における変更は、Goランタイムがスレッドローカルストレージ(TLS)にアクセスする際の、OSおよびアーキテクチャ固有の規約に対応するものです。ELFベースのシステム(Linux、BSD、Solarisなど)では、TLSデータへのアクセスに FS セグメントレジスタが使用されることが一般的です。Hsolaris がこの条件に追加されたことで、Solaris向けGoバイナリも正しいTLSアクセス規約に従うようになります。

src/liblink/sym.c の変更は、Goリンカがコマンドライン引数や環境変数で指定された "solaris" という文字列を、内部の Hsolaris 定数に正しくマッピングできるようにするためのものです。これにより、ユーザーは GOOS=solaris のように指定してSolaris向けにGoプログラムをビルドできるようになります。

これらの変更は、Go言語がSolarisプラットフォームをより堅牢にサポートするための重要な一歩であり、Goのクロスプラットフォーム能力をさらに拡張するものです。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に cmd/ld, src/liblink, include/link.h ディレクトリ)
  • ELFフォーマットに関する一般的なドキュメントやチュートリアル
  • Solarisの動的リンカに関する技術文書 (特定のURLはコミット時点の情報を特定できないため省略)
  • Go言語のIssueトラッカーやコードレビューシステム (Gerrit CL: https://golang.org/cl/54210050)
  • Go言語のクロスコンパイルに関する公式ドキュメント