[インデックス 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アプリケーションの配布と実行が大幅に簡素化されました。
前提知識の解説
このコミットを理解するためには、以下の概念が重要です。
-
リンカ (Linker): リンカは、コンパイラによって生成されたオブジェクトファイル(機械語コードとデータを含む)を結合し、実行可能なプログラムやライブラリを作成するソフトウェアツールです。リンカの主な役割は以下の通りです。
- シンボル解決: 異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。
- 再配置: コード内のアドレス参照を、最終的なメモリ配置に合わせて調整します。
- 実行可能形式の生成: オペレーティングシステムがロードして実行できる特定のファイル形式(例: ELF, Mach-O)で出力します。
-
8l:8lは、Go言語の初期のツールチェインにおけるリンカの一つで、特にx86-64 (AMD64) アーキテクチャ向けのバイナリ生成を担当していました。Goのツールチェインは、各アーキテクチャとOSの組み合わせに対して専用のコンパイラ(例:8gfor x86-64 Go compiler)、アセンブラ(例:8afor x86-64 assembler)、リンカ(例:8lfor x86-64 linker)を持っていました。現在では、これらのツールはgo tool compile,go tool asm,go tool linkといった形で統合されていますが、内部的には同様の役割を担っています。 -
実行可能ファイル形式: オペレーティングシステムは、プログラムをメモリにロードして実行するために、特定の構造を持つファイル形式を要求します。主要なものとして以下があります。
-
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タイプ、ファイルタイプなど)を含みます。
- ロードコマンド: カーネルがプログラムをロードするために必要な情報(セグメントの定義、ダイナミックリンカの情報、シンボルテーブルの位置など)を記述します。
- セグメント: メモリにロードされる領域を定義し、その中に複数のセクションを含めることができます。
-
-
セクションとセグメント:
- セクション: リンカが扱う論理的なデータの塊です。例えば、
.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が削除されています。これは、バイトオーダーの扱いをより汎用的なwputlやlputlに統一するためと考えられます。
src/cmd/8l/obj.c の変更
このファイルはリンカのメインエントリポイントと初期化ロジックを含んでいます。
whatsys()の呼び出し:main関数内でwhatsys()が呼び出され、goarch(アーキテクチャ)とgoos(オペレーティングシステム)の情報を取得するようになりました。これにより、リンカは実行環境に応じて適切なバイナリ形式を選択できるようになります。HEADTYPEの自動設定:HEADTYPEが明示的に指定されていない場合、goosの値に基づいて自動的に設定されるようになりました。goosが"linux"の場合、HEADTYPEは7(ELF) に設定されます。goosが"darwin"の場合、HEADTYPEは6(Mach-O) に設定されます。- それ以外の場合は警告が出力されます。
INITTEXT,INITDAT,INITRNDの設定: 各HEADTYPE(特にMach-OとELF)に応じて、テキストセグメントの開始アドレス(INITTEXT)、データセグメントの開始アドレス(INITDAT)、およびアラインメント(INITRND)が適切に設定されます。- エントリポイントの動的決定:
以前は
_mainや_mainpといった固定のエントリポイント名が使われていましたが、このコミットにより、_rt0_%s_%sという形式でgoarchとgoosに基づいたエントリポイント名が動的に生成されるようになりました(例:_rt0_amd64_linux)。これは、各プラットフォームのランタイム初期化コード(rt0)へのエントリポイントを指します。 - ランタイムライブラリのロード:
goroot/lib/rt0_%s_%s.%cとgoroot/lib/lib_%s_%s.aというパスから、プラットフォーム固有のランタイムオブジェクトファイルとライブラリをロードするロジックが追加されました。これにより、Goプログラムが特定のOS環境で正しく動作するために必要な低レベルの初期化コードがリンクされるようになります。
src/cmd/8l/span.c の変更
このファイルは、リンカがコードとデータを配置する際の「スパン」処理に関連しています。
asmdyn関数内で、wputb(ra)がwput(ra)に変更されています。これは、バイトオーダーの扱いを統一するための小さな修正です。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主にsrc/cmd/8l/asm.cのasmb関数内に集約されています。
-
asmb関数内のswitch(HEADTYPE)ブロック:case 6: Mach-Oバイナリ生成のための新しいロジックが追加されました。これには、machheadr,machseg,machsect,machdylink,machstackといったMach-O固有の構造を書き込む関数呼び出しが含まれます。case 7: ELFバイナリ生成のための新しいロジックが追加されました。これには、elfheadr,elfphdr,elfshdr,elfstrtableといったELF固有の構造を書き込む関数呼び出しが含まれます。
-
新しいヘルパー関数:
wputl,lputl,vputl,strnput: バイトオーダーを考慮したデータ書き込みと固定長文字列書き込みのための低レベル関数。machseg,machsymseg,machsect,machdylink,machstack,machheadr: Mach-O形式のセグメント、セクション、ロードコマンド、ヘッダなどを構築するための関数。elfphdr,elfshdr,elfstrtable,putstrtab,elfheadr: ELF形式のプログラムヘッダ、セクションヘッダ、文字列テーブル、ヘッダなどを構築するための関数。
-
src/cmd/8l/obj.cのmain関数: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未初期化データセクション)を定義します。 特筆すべきは、machdylinkとmachstackの呼び出しです。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テーブル)といったセクションの詳細が含まれます。elfstrtableとputstrtabは、これらのセクション名を格納する文字列テーブルを効率的に構築するために使用されます。
obj.cにおけるプラットフォーム検出と初期化
src/cmd/8l/obj.cのmain関数は、リンカの起動時に実行されます。
whatsys()関数の呼び出しは、Goの環境変数(GOROOT, GOARCH, GOOS)から現在のビルドターゲットのOSとアーキテクチャを検出します。この情報に基づいて、HEADTYPEが自動的に設定され、リンカがどのバイナリ形式を生成すべきかを決定します。
また、エントリポイント名が_rt0_%s_%sという形式で動的に生成されることで、各OSとアーキテクチャに特化したランタイム初期化コード(rt0)がリンクされるようになります。これは、GoプログラムがOSの起動規約に準拠し、正しく実行を開始するために不可欠です。
これらの変更により、8lリンカは、単一のコードベースから複数の主要なOS向けのネイティブ実行可能ファイルを生成する能力を獲得し、Go言語のクロスプラットフォーム対応の基盤を確立しました。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- ELFファイル形式の概要 (Wikipedia): https://ja.wikipedia.org/wiki/Executable_and_Linkable_Format
- Mach-Oファイル形式の概要 (Wikipedia): https://ja.wikipedia.org/wiki/Mach-O
参考にした情報源リンク
- Go linker history: https://medium.com/@vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEPjL5KdlxCX4qVWR3SzFTIXYXu-7AfXAhcIREJZdoexDq9WVcdS_0sbej1TPhniIF-6PBR6xAQQp-w341zVhj-8_TWb8ljj-klAHty2C3SvKXGnYHQrNH-z4olycdyxrxCWx-UKI8LF1LpufyhKZYhxGvC5GhsxNsWAZECAqHcYnPYMYzPd7Y=
- Go Mach-O support: https://github.com/golang/go/blob/master/src/cmd/link/internal/macho/macho.go
- Go ELF support: https://github.com/golang/go/blob/master/src/cmd/link/internal/elf/elf.go
- Go toolchain documentation: https://go.dev/doc/