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

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

このコミットは、Go言語のリンカである8lが、Darwin (macOS) 用のMach-OバイナリとLinux用のELFバイナリを生成する機能を追加するものです。これにより、Goプログラムがこれらの主要なオペレーティングシステム上でネイティブに実行可能な形式でビルドできるようになります。

コミット

commit 7d443bb67acad4313ad38f297890620ce8cf7d1d
Author: Russ Cox <rsc@golang.org>
Date:   Fri Mar 20 14:22:59 2009 -0700

    make 8l generate Darwin Mach-O and Linux ELF binaries
    
    R=ken
    OCL=26584
    CL=26589

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

https://github.com/golang/go/commit/7d443bb67acad4313ad38f297890620ce8cf7d1d

元コミット内容

8lリンカがDarwin (macOS) のMach-O形式とLinuxのELF形式の実行可能ファイルを生成するように変更されました。

変更の背景

Go言語は、その設計当初からクロスプラットフォーム対応を重視していました。初期のGoコンパイラとリンカは、特定のプラットフォーム(例えばPlan 9)向けのバイナリ生成に特化していましたが、より広範な採用を目指すためには、主要なデスクトップおよびサーバーOSであるLinuxとmacOSへの対応が不可欠でした。

このコミットが行われた2009年3月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。この時期に、GoプログラムがこれらのOS上で動作するための基盤を構築することは、Go言語の将来的な普及にとって極めて重要なステップでした。特に、Go言語は独自のツールチェイン(コンパイラ、アセンブラ、リンカなど)を持つことを特徴としており、外部のリンカに依存せずに多様なバイナリ形式を生成できる能力は、Goのビルドシステムの独立性と効率性を高める上で中心的でした。

この変更により、Go開発者は、それぞれのOSのネイティブな実行可能形式に準拠したバイナリを生成できるようになり、Goアプリケーションの配布と実行が大幅に簡素化されました。

前提知識の解説

このコミットを理解するためには、以下の概念が重要です。

  1. リンカ (Linker): リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するソフトウェアツールです。リンカの主な役割は以下の通りです。

    • シンボル解決: 異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。
    • 再配置: コード内のアドレス参照を、最終的なメモリ配置に合わせて調整します。
    • 実行可能形式の生成: オペレーティングシステムがロードして実行できる特定のファイル形式(例: ELF, Mach-O)で出力します。
  2. 8l: 8lは、Go言語の初期のツールチェインにおけるリンカの一つで、特にx86-64 (AMD64) アーキテクチャ向けのバイナリ生成を担当していました。Goのツールチェインは、各アーキテクチャとOSの組み合わせに対して専用のコンパイラ(例: 8g for x86-64 Go compiler)、アセンブラ(例: 8a for x86-64 assembler)、リンカ(例: 8l for x86-64 linker)を持っていました。現在では、これらのツールはgo tool compile, go tool asm, go tool linkといった形で統合されていますが、内部的には同様の役割を担っています。

  3. 実行可能ファイル形式: オペレーティングシステムは、プログラムをメモリにロードして実行するために、特定の構造を持つファイル形式を要求します。主要なものとして以下があります。

    • ELF (Executable and Linkable Format): Linux、BSD、Solarisなど、多くのUnix系オペレーティングシステムで標準的に使用されている実行可能ファイル形式です。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション(.text.data.bssなど)で構成されます。

      • ヘッダ: ファイルの基本的な情報(マジックナンバー、アーキテクチャ、OS ABIなど)を含みます。
      • プログラムヘッダテーブル: プログラムのロード方法を記述します。各エントリは「セグメント」を定義し、メモリにロードされるファイルの連続した領域を指します。
      • セクションヘッダテーブル: ファイル内の論理的な「セクション」(コード、初期化済みデータ、未初期化データなど)を記述します。
    • Mach-O (Mach Object file format): AppleのmacOS (旧OS X) およびiOSで主に使用されている実行可能ファイル形式です。Mach-Oファイルは、ヘッダ、ロードコマンド、およびセクションで構成されます。

      • ヘッダ: ファイルの基本的な情報(CPUタイプ、ファイルタイプなど)を含みます。
      • ロードコマンド: カーネルがプログラムをロードするために必要な情報(セグメントの定義、ダイナミックリンカの情報、シンボルテーブルの位置など)を記述します。
      • セグメント: メモリにロードされる領域を定義し、その中に複数のセクションを含めることができます。
  4. セクションとセグメント:

    • セクション: リンカが扱う論理的なデータの塊です。例えば、.textセクションは実行可能なコード、.dataセクションは初期化されたデータ、.bssセクションは初期化されていないデータを含みます。
    • セグメント: オペレーティングシステムがメモリ管理ユニット (MMU) を介してメモリにロードする際の、より大きな単位です。通常、複数のセクションが1つのセグメントにまとめられます(例: .textセクションは読み取り専用のコードセグメントに、.data.bssセクションは読み書き可能なデータセグメントに)。

技術的詳細

このコミットの核心は、8lリンカがELFとMach-Oという異なるバイナリ形式の構造を理解し、それらを適切に生成するためのコードを追加した点にあります。

src/cmd/8l/asm.c の変更

このファイルは、リンカの主要なアセンブリ出力ロジックを含んでいます。変更の大部分は、asmb関数内に新しいケースを追加し、HEADTYPE(ヘッダタイプ)に基づいて異なるバイナリ形式のヘッダ、セグメント、セクションを書き出すための関数呼び出しを導入しています。

  • wputl, lputl, vputl, strnput 関数: これらの新しいヘルパー関数は、リトルエンディアン形式でワード(16ビット)、ロングワード(32ビット)、および64ビット値を書き込むためのものです。また、固定長で文字列を書き込むstrnputも追加されています。これは、バイナリ形式のヘッダや構造体が特定のバイトオーダーと固定長フィールドを持つため、正確なバイナリ出力を保証するために不可欠です。

  • asmb 関数内の HEADTYPE 処理: asmb関数は、最終的なバイナリファイルを構築する中心的な関数です。

    • case 6: Darwin (Mach-O) 形式の生成ロジックが追加されました。
      • Mach-Oヘッダ(マジックナンバー、CPUタイプ、ファイルタイプなど)を書き込みます。
      • machseg関数を呼び出して、__PAGEZERO (NULLページ保護)、__TEXT (コードセグメント)、__DATA (データセグメント)、__SYMDAT (シンボルデータ) などのセグメントを定義します。
      • machsect関数を呼び出して、__TEXTセグメント内の__textセクション、__DATAセグメント内の__dataおよび__bssセクションを定義します。
      • machdylink関数を呼び出して、ダイナミックリンカに関する情報を追加します。これは、macOSのdtraceのようなツールがバイナリを認識するために重要です。
      • machstack関数を呼び出して、初期レジスタ状態(特にエントリポイント)を設定するスレッドコマンドを追加します。
    • case 7: Linux (ELF) 形式の生成ロジックが追加されました。
      • ELFヘッダ(マジックナンバー、クラス、データエンコーディング、バージョン、OS ABI、タイプ、マシンアーキテクチャなど)を書き込みます。
      • elfphdr関数を呼び出して、プログラムヘッダ(PT_LOADタイプでテキストセグメントとデータセグメントを定義)を書き込みます。
      • elfshdr関数を呼び出して、セクションヘッダ(.text, .data, .bss, .shstrtab, .gosymtab, .gopclntabなど)を書き込みます。
      • elfstrtable関数とputstrtab関数は、ELFのセクション名文字列テーブルを構築するために使用されます。
  • シンボルテーブルとPC-Lineテーブルの配置: デバッグ情報やプロファイリング情報(シンボルテーブルとPC-Lineテーブル)の配置も、ELFとMach-Oの構造に合わせて調整されています。

src/cmd/8l/l.h の変更

リンカのヘッダファイルで、新しい関数プロトタイプが追加されています。

  • machheadr, elfheadr: Mach-OおよびELFヘッダのサイズを計算する関数。
  • whatsys: システム情報を取得する関数(goroot, goarch, goos)。
  • wput関数のプロトタイプが変更され、wputbが削除されています。これは、バイトオーダーの扱いをより汎用的なwputllputlに統一するためと考えられます。

src/cmd/8l/obj.c の変更

このファイルはリンカのメインエントリポイントと初期化ロジックを含んでいます。

  • whatsys() の呼び出し: main関数内でwhatsys()が呼び出され、goarch(アーキテクチャ)とgoos(オペレーティングシステム)の情報を取得するようになりました。これにより、リンカは実行環境に応じて適切なバイナリ形式を選択できるようになります。
  • HEADTYPE の自動設定: HEADTYPEが明示的に指定されていない場合、goosの値に基づいて自動的に設定されるようになりました。
    • goosが"linux"の場合、HEADTYPE7 (ELF) に設定されます。
    • goosが"darwin"の場合、HEADTYPE6 (Mach-O) に設定されます。
    • それ以外の場合は警告が出力されます。
  • INITTEXT, INITDAT, INITRND の設定: 各HEADTYPE(特にMach-OとELF)に応じて、テキストセグメントの開始アドレス(INITTEXT)、データセグメントの開始アドレス(INITDAT)、およびアラインメント(INITRND)が適切に設定されます。
  • エントリポイントの動的決定: 以前は_main_mainpといった固定のエントリポイント名が使われていましたが、このコミットにより、_rt0_%s_%sという形式でgoarchgoosに基づいたエントリポイント名が動的に生成されるようになりました(例: _rt0_amd64_linux)。これは、各プラットフォームのランタイム初期化コード(rt0)へのエントリポイントを指します。
  • ランタイムライブラリのロード: goroot/lib/rt0_%s_%s.%cgoroot/lib/lib_%s_%s.aというパスから、プラットフォーム固有のランタイムオブジェクトファイルとライブラリをロードするロジックが追加されました。これにより、Goプログラムが特定のOS環境で正しく動作するために必要な低レベルの初期化コードがリンクされるようになります。

src/cmd/8l/span.c の変更

このファイルは、リンカがコードとデータを配置する際の「スパン」処理に関連しています。

  • asmdyn関数内で、wputb(ra)wput(ra)に変更されています。これは、バイトオーダーの扱いを統一するための小さな修正です。

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

このコミットのコアとなる変更は、主にsrc/cmd/8l/asm.casmb関数内に集約されています。

  1. asmb関数内のswitch(HEADTYPE)ブロック:

    • case 6: Mach-Oバイナリ生成のための新しいロジックが追加されました。これには、machheadr, machseg, machsect, machdylink, machstackといったMach-O固有の構造を書き込む関数呼び出しが含まれます。
    • case 7: ELFバイナリ生成のための新しいロジックが追加されました。これには、elfheadr, elfphdr, elfshdr, elfstrtableといったELF固有の構造を書き込む関数呼び出しが含まれます。
  2. 新しいヘルパー関数:

    • wputl, lputl, vputl, strnput: バイトオーダーを考慮したデータ書き込みと固定長文字列書き込みのための低レベル関数。
    • machseg, machsymseg, machsect, machdylink, machstack, machheadr: Mach-O形式のセグメント、セクション、ロードコマンド、ヘッダなどを構築するための関数。
    • elfphdr, elfshdr, elfstrtable, putstrtab, elfheadr: ELF形式のプログラムヘッダ、セクションヘッダ、文字列テーブル、ヘッダなどを構築するための関数。
  3. src/cmd/8l/obj.cmain関数:

    • whatsys()の呼び出しと、goosに基づいてHEADTYPEを自動設定するロジック。
    • エントリポイント名とランタイムライブラリのパスを動的に決定するロジック。

コアとなるコードの解説

asmb関数におけるバイナリ形式の分岐

asmb関数は、リンカが最終的な実行可能ファイルをディスクに書き込む際の中心的な役割を担います。この関数内でHEADTYPEという変数に基づいてswitch文が使用されており、これが生成するバイナリ形式を決定します。

  • Mach-O (HEADTYPE = 6): Mach-OはmacOSのネイティブな実行可能形式です。このセクションでは、まず0xfeedfaceというマジックナンバー(32ビットMach-Oを示す)と、CPUタイプ(x86)、ファイルタイプ(実行可能ファイル)などの基本情報が書き込まれます。 次に、machseg関数が繰り返し呼び出され、__PAGEZERO__TEXT__DATA__SYMDATといったセグメントが定義されます。これらのセグメントは、プログラムのコード、データ、シンボル情報がメモリ上でどのように配置されるかをOSに伝えます。 machsect関数は、これらのセグメント内の具体的なセクション(例: __TEXTセグメント内の__textコードセクション、__DATAセグメント内の__data初期化済みデータセクション、__bss未初期化データセクション)を定義します。 特筆すべきは、machdylinkmachstackの呼び出しです。machdylinkは、macOSのダイナミックリンカ(dyld)がバイナリを認識し、dtraceのようなデバッグツールが機能するために必要なロードコマンド(LC_SYMTAB, LC_DYSYMTAB, LC_LOAD_DYLINKER)を追加します。machstackは、プログラムのエントリポイント(entryvalue()で取得)を初期レジスタとして設定するスレッド状態コマンドを追加し、プログラムが正しく開始できるようにします。

  • ELF (HEADTYPE = 7): ELFはLinuxの標準的な実行可能形式です。このセクションでは、まず\177ELFというマジックナンバーで始まるELFヘッダが書き込まれます。これには、ファイルのクラス(32ビット)、データエンコーディング(リトルエンディアン)、バージョン、OS ABI、ファイルタイプ(実行可能)、マシンアーキテクチャ(AMD64)などの情報が含まれます。 次に、elfphdr関数が呼び出され、プログラムヘッダテーブルが構築されます。これは、OSがバイナリをメモリにロードする際のセグメント(例: PT_LOADタイプでコードとデータをロードするセグメント)を記述します。 その後、elfshdr関数が呼び出され、セクションヘッダテーブルが構築されます。これには、.text(コード)、.data(初期化済みデータ)、.bss(未初期化データ)、.shstrtab(セクション名文字列テーブル)、.gosymtab(Goシンボルテーブル)、.gopclntab(Go PC-Lineテーブル)といったセクションの詳細が含まれます。elfstrtableputstrtabは、これらのセクション名を格納する文字列テーブルを効率的に構築するために使用されます。

obj.cにおけるプラットフォーム検出と初期化

src/cmd/8l/obj.cmain関数は、リンカの起動時に実行されます。 whatsys()関数の呼び出しは、Goの環境変数(GOROOT, GOARCH, GOOS)から現在のビルドターゲットのOSとアーキテクチャを検出します。この情報に基づいて、HEADTYPEが自動的に設定され、リンカがどのバイナリ形式を生成すべきかを決定します。 また、エントリポイント名が_rt0_%s_%sという形式で動的に生成されることで、各OSとアーキテクチャに特化したランタイム初期化コード(rt0)がリンクされるようになります。これは、GoプログラムがOSの起動規約に準拠し、正しく実行を開始するために不可欠です。

これらの変更により、8lリンカは、単一のコードベースから複数の主要なOS向けのネイティブ実行可能ファイルを生成する能力を獲得し、Go言語のクロスプラットフォーム対応の基盤を確立しました。

関連リンク

参考にした情報源リンク