[インデックス 1576] ファイルの概要
このコミットは、Go言語のリンカである6l
(64-bit x86アーキテクチャ向けリンカ)がmacOS上で生成する実行ファイル(特にsize 6.out
のような特定のテストケース)が正しく動作するように修正するものです。具体的には、Mach-O実行ファイルフォーマットにおける動的リンク関連のヘッダ情報(ロードコマンド)の生成方法を調整し、macOSのダイナミックリンカ(dyld
)が期待する形式に合わせることで、互換性の問題を解決しています。
コミット
commit 7fa5941fad63362655f7b9d0a8d230f890103525
Author: Russ Cox <rsc@golang.org>
Date: Tue Jan 27 15:40:36 2009 -0800
make "size 6.out" work on mac.
R=r
DELTA=11 (6 added, 0 deleted, 5 changed)
OCL=23629
CL=23631
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7fa5941fad63362655f7b9d0a8d230f890103525
元コミット内容
make "size 6.out" work on mac.
R=r
DELTA=11 (6 added, 0 deleted, 5 changed)
OCL=23629
CL=23631
変更の背景
このコミットの背景には、Go言語の初期開発段階におけるクロスプラットフォーム対応、特にmacOS(当時Mac OS X)上での実行ファイル生成と動作の課題がありました。6l
はGoプログラムをリンクして実行ファイルを生成するツールですが、macOSの実行ファイル形式であるMach-Oは、他のUnix系システムで一般的なELF形式とは構造が異なります。
コミットメッセージにある「size 6.out
work on mac」という記述は、おそらくGoのテストスイートの一部、または特定のリンカの挙動を検証するためのテストケースがmacOS上で期待通りに動作しなかったことを示唆しています。size
コマンドは実行ファイルのセクションサイズを表示するものであり、これが正しく機能しないということは、リンカが生成するMach-Oファイルの構造、特にロードコマンドやセクションの定義に問題があったことを意味します。
具体的には、macOSの動的リンカであるdyld
(Dynamic Link Editor)は、Mach-Oファイル内の特定のロードコマンド(LC_SYMTAB
, LC_DYSYMTAB
, LC_LOAD_DYLINKER
など)を解析して、プログラムの実行に必要な共有ライブラリのロードやシンボルの解決を行います。これらのロードコマンドのサイズやオフセットが正しくない場合、dyld
はファイルをロードできず、プログラムの実行に失敗したり、size
コマンドのようなツールが誤った情報を表示したりする可能性があります。
このコミットは、6l
が生成するMach-Oファイルのロードコマンドのメタデータ(特にバイト数やオフセット)をmacOSのdyld
が期待する正確な値に調整することで、この互換性の問題を解決しようとしています。
前提知識の解説
Go言語のリンカ (6l
)
Go言語のツールチェインにおいて、6l
は64-bit x86アーキテクチャ(AMD64/x86-64)向けのリンカです。Goのコンパイラ(例: 6g
)が生成したオブジェクトファイル(.6
ファイル)を結合し、実行可能なバイナリファイルを生成する役割を担います。Goのリンカは、C言語のリンカ(ld
)とは異なり、Goランタイムや標準ライブラリを静的にリンクすることが多く、クロスコンパイル環境でも動作するように設計されています。
Mach-O (Mach Object)
Mach-Oは、macOS、iOS、watchOS、tvOSなどのApple製オペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリ、およびコアダンプのファイル形式です。ELF(Executable and Linkable Format)がLinuxやBSDで広く使われるのと同様に、Mach-OはAppleのエコシステムにおける標準的なバイナリ形式です。
Mach-Oファイルは、以下の主要な部分で構成されます。
- Mach-O Header: ファイルの基本的な情報(CPUタイプ、ファイルタイプ、ロードコマンドの数など)を含みます。
- Load Commands: ファイルの残りの部分のレイアウトと、カーネルがプロセスをロードするために必要な情報(セグメント、シンボルテーブル、動的リンカ情報など)を記述します。各ロードコマンドは、コマンドタイプとコマンドのサイズを持ちます。
- Segment Data: 実行可能コード、データ、スタック、ヒープなどの実際のプログラムデータが含まれます。
Mach-O ロードコマンド (Load Commands)
Mach-Oファイル内のロードコマンドは、オペレーティングシステムがバイナリをメモリにロードし、実行するために必要な指示を提供します。このコミットで関連する主要なロードコマンドは以下の通りです。
LC_SYMTAB
(Symbol Table Command): シンボルテーブルの位置とサイズを定義します。シンボルテーブルには、関数名、変数名などのシンボルとそのアドレス情報が含まれます。デバッグや動的リンクに利用されます。LC_DYSYMTAB
(Dynamic Symbol Table Command): 動的シンボルテーブルの位置とサイズを定義します。これは、動的リンクに必要なシンボル(インポートされたシンボル、エクスポートされたシンボルなど)に特化した情報を含みます。LC_LOAD_DYLINKER
(Load Dynamic Linker Command): 動的リンカ(macOSでは通常/usr/lib/dyld
)のパスを指定します。OSは、このパスにある動的リンカをロードし、プログラムの実行を開始する前に必要な共有ライブラリのロードとシンボル解決を行わせます。
dyld
(Dynamic Link Editor)
dyld
はmacOSの動的リンカです。プログラムが起動されると、カーネルはまずdyld
をロードし、dyld
がMach-Oファイル内のLC_LOAD_DYLINKER
コマンドで指定されたパスを読み取ります。その後、dyld
はプログラムが依存するすべての共有ライブラリ(例: libc.dylib
, libSystem.B.dylib
など)をメモリにロードし、プログラム内の未解決のシンボル(関数呼び出しや変数参照)をこれらのライブラリ内の対応する定義に解決します。
lputl
と strnput
lputl(val)
: Goのリンカ内部で使用される関数で、指定された32ビットの値をリトルエンディアン形式で出力ストリーム(通常は生成中のバイナリファイル)に書き込む役割を担います。Mach-Oのロードコマンドのサイズやオフセットは通常32ビット値で表現されます。strnput(str, len)
: 指定された文字列str
を、最大len
バイトまで出力ストリームに書き込む関数です。このコミットでは、LC_LOAD_DYLINKER
コマンドの一部として動的リンカのパス(/usr/lib/dyld
)を書き込むために使用されています。
machheadr()
machheadr()
関数は、Mach-Oヘッダのサイズを計算し、リンカが生成するバイナリの先頭からのオフセットを決定するために使用されます。この関数は、様々なロードコマンドやセクションのサイズを考慮に入れて、ヘッダ全体の長さを算出します。この値が正しくないと、後続のセクションやデータが誤ったオフセットに配置され、バイナリが破損したり、OSが正しくロードできなかったりする原因となります。
技術的詳細
このコミットの技術的詳細は、Mach-Oファイルのロードコマンドの構造と、6l
リンカがそれらをどのように生成しているかに深く関連しています。
変更の中心は、src/cmd/6l/asm.c
ファイル内のasmb
、machdylink
、machheadr
の各関数です。
-
asmb
関数内の変更:if (!debug['d']) nl += 2;
がif (!debug['d']) nl += 3;
に変更されています。nl
は「number of loads」(ロードコマンドの数)を意味すると考えられます。-d
デバッグフラグがオフの場合(つまり、動的リンクが有効な場合)、ロードコマンドの数を2から3に増やしています。これは、後述するLC_SYMTAB
ロードコマンドの追加に対応しています。Mach-Oファイルには、動的リンクに必要なロードコマンドの他に、シンボルテーブルに関するロードコマンドも含まれるため、その数を調整しています。
-
machdylink
関数内の変更:- この関数は、動的リンクに関連するMach-Oロードコマンドを生成する役割を担っています。
LC_SYMTAB
の追加:+ lputl(2); /* LC_SYMTAB */ + lputl(24); /* byte count - 6 words*/ + for(i=0; i<4; i++) + lputl(0);
- 以前は生成されていなかった
LC_SYMTAB
ロードコマンドが追加されました。 lputl(2)
:LC_SYMTAB
のコマンドタイプ(2)を書き込みます。lputl(24)
:LC_SYMTAB
コマンドのバイト数を24バイトとして書き込みます。これは、コマンドタイプ(4バイト)とコマンドサイズ(4バイト)に加えて、シンボルテーブルのオフセット、シンボル数、文字列テーブルのオフセット、文字列テーブルのサイズなど、4つの32ビット値(4 * 4 = 16バイト)が続くため、合計で8 + 16 = 24バイトとなります。コメントの「6 words」は、24バイト / 4バイト/word = 6 words を意味します。for(i=0; i<4; i++) lputl(0);
: シンボルテーブルのオフセット、シンボル数、文字列テーブルのオフセット、文字列テーブルのサイズに相当する4つのゼロ値を書き込んでいます。これは、この時点では具体的なシンボル情報がまだ確定していないか、あるいはこの特定のテストケースではシンボルテーブルが空であることを示唆している可能性があります。
- 以前は生成されていなかった
LC_DYSYMTAB
のバイト数調整:- lputl(80); /* byte count */ + lputl(80); /* byte count - 20 words */
LC_DYSYMTAB
のバイト数は80バイトで変更はありませんが、コメントが「20 words」に更新されています。これは、80バイト / 4バイト/word = 20 words を意味し、コードの意図をより明確にしています。
LC_LOAD_DYLINKER
のバイト数調整と文字列書き込み:- lputl(28); /* byte count */ + lputl(32); /* byte count */ lputl(12); /* offset to string */ - strnput("/usr/lib/dyld", 16); + strnput("/usr/lib/dyld", 32-12);
LC_LOAD_DYLINKER
のバイト数が28から32に変更されました。これは、このコマンドの構造が変更されたか、または文字列のパディング要件が変更されたことを示唆しています。Mach-Oのロードコマンドは通常4バイト境界にアラインされるため、文字列の長さに応じてパディングが必要になります。strnput("/usr/lib/dyld", 16)
がstrnput("/usr/lib/dyld", 32-12)
に変更されました。32-12
は20バイトを意味します。これは、LC_LOAD_DYLINKER
コマンドの総バイト数32から、コマンドタイプ(4バイト)、コマンドサイズ(4バイト)、文字列オフセット(4バイト)の合計12バイトを引いた残りのバイト数です。つまり、文字列データのために20バイトを確保し、そこに/usr/lib/dyld
を書き込み、残りをゼロでパディングすることを意味します。これにより、文字列がコマンドの境界内で適切に配置されるようになります。
-
machheadr
関数内の変更:- この関数は、Mach-Oヘッダの合計サイズを計算します。ロードコマンドの追加やサイズ変更に伴い、この計算も更新する必要があります。
LC_SYMTAB
のサイズ加算:+ a += 6; /* symtab */
LC_SYMTAB
ロードコマンドが追加されたため、そのサイズ(6ワード = 24バイト)をヘッダサイズに加算しています。コメントの「symtab」はLC_SYMTAB
を指します。
LC_LOAD_DYLINKER
のサイズ調整:- a += 7; /* load dylinker */ + a += 8; /* load dylinker */
LC_LOAD_DYLINKER
のサイズが7ワード(28バイト)から8ワード(32バイト)に変更されたことに対応して、ヘッダサイズへの加算値も調整されています。
これらの変更は、macOSのdyld
がMach-Oファイルを正しく解析し、プログラムをロードするために必要な、ロードコマンドの正確な構造とサイズを提供することを目的としています。特に、LC_SYMTAB
の追加と、LC_LOAD_DYLINKER
のバイト数調整は、macOSのリンカの挙動に合わせた重要な修正と言えます。
コアとなるコードの変更箇所
変更はすべて src/cmd/6l/asm.c
ファイル内で行われています。
diff --git a/src/cmd/6l/asm.c b/src/cmd/6l/asm.c
index f1972f8200..50675fad96 100644
--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -361,7 +361,7 @@ asmb(void)
if (!debug['s'])
nl += 3;
if (!debug['d']) // -d = turn off "dynamic loader"
- nl += 2;
+ nl += 3;
lputl(nl); /* number of loads */
lputl(machheadr()-32); /* size of loads */
lputl(1); /* flags - no undefines */
@@ -846,15 +846,20 @@ machdylink(void)
if(debug['d'])
return;
+ lputl(2); /* LC_SYMTAB */
+ lputl(24); /* byte count - 6 words*/
+ for(i=0; i<4; i++)
+ lputl(0);
+
lputl(11); /* LC_DYSYMTAB */
- lputl(80); /* byte count */
+ lputl(80); /* byte count - 20 words */
for(i=0; i<18; i++)
lputl(0);
lputl(14); /* LC_LOAD_DYLINKER */
- lputl(28); /* byte count */
+ lputl(32); /* byte count */
lputl(12); /* offset to string */
- strnput("/usr/lib/dyld", 16);
+ strnput("/usr/lib/dyld", 32-12);
}
void
@@ -889,8 +894,9 @@ machheadr(void)
a += 20; /* bss sect */
a += 46; /* stack sect */
if (!debug['d']) {
+\t a += 6; /* symtab */
a += 20; /* dysymtab */
- a += 7; /* load dylinker */
+ a += 8; /* load dylinker */
}
if (!debug['s']) {
a += 18; /* symdat seg */
コアとなるコードの解説
asmb
関数
nl += 3;
への変更:- これは、動的リンクが有効な場合に、Mach-Oファイルに書き込まれるロードコマンドの総数を調整しています。以前は2つの動的リンク関連コマンド(
LC_DYSYMTAB
とLC_LOAD_DYLINKER
)を想定していましたが、この変更によりLC_SYMTAB
が追加されるため、合計で3つのコマンドが考慮されるようになりました。これにより、Mach-Oヘッダ内のロードコマンド数が正確に反映され、OSがファイルを正しく解析できるようになります。
- これは、動的リンクが有効な場合に、Mach-Oファイルに書き込まれるロードコマンドの総数を調整しています。以前は2つの動的リンク関連コマンド(
machdylink
関数
LC_SYMTAB
の追加:- このブロックは、Mach-Oファイルにシンボルテーブルロードコマンド(
LC_SYMTAB
)を明示的に追加します。 lputl(2)
:LC_SYMTAB
のコマンドタイプ(2)を書き込みます。lputl(24)
: このコマンドのバイトサイズ(24バイト)を書き込みます。これは、コマンドタイプ、コマンドサイズ、およびシンボルテーブルのオフセット、シンボル数、文字列テーブルのオフセット、文字列テーブルのサイズといった4つのフィールド(それぞれ4バイト)の合計です。for(i=0; i<4; i++) lputl(0);
: これらの4つのフィールドにゼロを書き込みます。これは、この時点ではシンボル情報が空であるか、後でパッチされることを意味します。macOSのdyld
は、たとえ空であってもLC_SYMTAB
が存在することを期待する場合があるため、この追加は互換性にとって重要です。
- このブロックは、Mach-Oファイルにシンボルテーブルロードコマンド(
LC_DYSYMTAB
のコメント変更:lputl(80); /* byte count - 20 words */
- バイト数自体は変更されていませんが、コメントに「- 20 words」が追加され、80バイトが20ワード(32ビットワード)に相当することが明確になりました。これはコードの可読性を向上させます。
LC_LOAD_DYLINKER
のバイト数と文字列書き込みの変更:lputl(32); /* byte count */
:LC_LOAD_DYLINKER
コマンドのバイト数を28から32に変更しました。これは、Mach-Oのロードコマンドが4バイト境界にアラインされるという要件を満たすため、またはdyld
が期待する特定のパディング要件に合わせるためと考えられます。strnput("/usr/lib/dyld", 32-12);
: 動的リンカのパス文字列/usr/lib/dyld
を書き込む際に、書き込むバイト数を32-12
(つまり20バイト)に指定しています。これは、コマンド全体のサイズ(32バイト)から、コマンドタイプ、コマンドサイズ、文字列オフセットの合計12バイトを引いた残りの領域に文字列を書き込み、残りをゼロでパディングすることを保証します。これにより、文字列がコマンドブロック内で正しく配置され、dyld
がパスを正確に読み取れるようになります。
machheadr
関数
a += 6; /* symtab */
の追加:machheadr
関数は、Mach-Oヘッダ全体のサイズを計算します。LC_SYMTAB
ロードコマンドが追加されたため、そのサイズ(6ワード = 24バイト)をヘッダの合計サイズに加算しています。これにより、ヘッダのサイズが正確に報告され、後続のセクションが正しいオフセットに配置されるようになります。
a += 8; /* load dylinker */
への変更:LC_LOAD_DYLINKER
コマンドのサイズが7ワード(28バイト)から8ワード(32バイト)に変更されたことに対応して、ヘッダサイズへの加算値も調整されています。これにより、ヘッダのサイズ計算が最新のロードコマンドのサイズと一致し、ファイル全体の構造が整合性を保つことができます。
これらの変更は、Goのリンカが生成するMach-Oファイルが、macOSのdyld
の厳密な要件に適合するように、ロードコマンドの構造とサイズを微調整したものです。特に、LC_SYMTAB
の追加と、LC_LOAD_DYLINKER
のパディングの調整は、macOS上での実行ファイルの互換性と安定性を確保するために不可欠な修正でした。
関連リンク
- Go言語の初期開発に関する情報: Go言語の公式ブログや初期のコミットログを辿ることで、当時の開発状況や課題についてさらに深く理解できる可能性があります。
- Mach-Oファイルフォーマットの仕様: AppleのDeveloper Documentationには、Mach-Oファイルフォーマットの詳細な仕様が記載されています。
dyld
の動作に関するドキュメント:dyld
の内部動作や、それがMach-Oファイルをどのように解析するかについての情報は、macOSのシステムプログラミングに関する資料やブログ記事で見つけることができます。
参考にした情報源リンク
- Mach-O File Format Reference - Apple Developer Documentation (一般的なMach-Oの構造理解のため)
- OS X ABI Mach-O File Format Reference (上記と同様、Mach-Oのロードコマンドの詳細理解のため)
- The Go Programming Language (Go言語の公式ウェブサイト)
- Go source code on GitHub (Go言語のソースコードリポジトリ)
- Understanding the Mach-O File Format (Mach-Oの概念的な理解を深めるため)
- Anatomy of a Mach-O executable (Mach-Oの構造とロードコマンドの役割を理解するため)
- dyld(1) man page (dyldの基本的な役割を理解するため)
- Go linker source code (src/cmd/link) (Goリンカの全体像を把握するため、ただしこのコミットは
6l
に関するものなので直接は参照していません)
これらの情報源は、Mach-Oファイルフォーマット、ロードコマンド、dyld
の動作、およびGo言語のリンカの基本的な理解を深めるために参照しました。