[インデックス 10043] ファイルの概要
コミット
コミットハッシュ: 78ad19f214394a1cb1e96d448bacb84011204452
作成者: Mikkel Krautz mikkel@krautz.dk
日付: Tue Oct 18 16:31:03 2011 -0400
タイトル: ld: modify macho linkedit segment to enable OS X code signing
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/78ad19f214394a1cb1e96d448bacb84011204452
元コミット内容
ld: modify macho linkedit segment to enable OS X code signing
Move string table to the end of the __LINKEDIT segment.
This change allows Apple's codesign(1) utility to successfully sign
Go binaries, as long as they don't contain DWARF data (-w flag to
8l/6l). This is because codesign(1) expects the string table to be
the last part of the file.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5271050
変更の背景
2011年当時、GoコンパイラはmacOS(当時はOS X)でAppleのcode signing機能との互換性に問題を抱えていました。この問題は、GoのリンカーがMach-Oバイナリ形式を生成する際に、Apple独自の要求するファイル構造に従っていなかったことが原因でした。
AppleのcodesignユーティリティはMach-Oバイナリの__LINKEDIT
セグメント内で、文字列テーブル(string table)がファイルの最後の部分に配置されることを期待していました。しかし、Goリンカーは異なる順序でデータを配置しており、これがcode signingの失敗を引き起こしていました。
このコミットは、Goバイナリに対してAppleのcode signing機能を適用できるようにするための重要な修正でした。特に、DWARFデバッグ情報を含まないバイナリ(-w
フラグを使用してコンパイルされたもの)において、正常にcode signingができるようになりました。
前提知識の解説
Mach-Oバイナリ形式
Mach-Oは、macOSで使用されるオブジェクトファイル形式です。この形式は、実行可能ファイル、オブジェクトファイル、共有ライブラリ、動的ライブラリなどに使用されます。Mach-Oファイルは複数のセグメントから構成され、各セグメントは特定の種類のデータを格納します。
__LINKEDIT
セグメント
__LINKEDIT
セグメントは、Mach-Oファイルの重要な部分で、以下のような情報を格納します:
- 動的シンボルテーブル(dynamic symbol table)
- 文字列テーブル(string table)
- 再配置情報(relocation information)
- 間接シンボルテーブル(indirect symbol table)
- コード署名データ(code signature data)
このセグメントは、実行時にダイナミックリンカー(dyld)がバイナリを読み込む際に必要な情報を提供します。
Appleのcode signing
Appleのcode signing機能は、ソフトウェアの整合性と認証を保証するためのセキュリティ機能です。code signingにより、以下が実現されます:
- バイナリが改ざんされていないことの検証
- 開発者の身元確認
- macOSのセキュリティポリシーの適用
codesignユーティリティは、Mach-Oバイナリの構造に対して厳格な検証を行い、特定のレイアウト要件を満たしていない場合は署名を拒否します。
Goリンカーの歴史的背景
2011年当時、Goのツールチェーンは以下のような構成でした:
- 6l: amd64アーキテクチャ用のリンカー
- 8l: 386アーキテクチャ用のリンカー
- 5l: ARMアーキテクチャ用のリンカー
これらのリンカーは、Plan 9オペレーティングシステムのツールチェーンを基に設計されており、各アーキテクチャに特化した処理を行っていました。
DWARFデバッグ情報
DWARFは、デバッグ情報を格納するための標準的な形式です。Goコンパイラは、デバッグ情報を含むバイナリを生成する際にDWARF形式を使用します。しかし、このデバッグ情報がMach-Oバイナリの構造に影響を与え、code signingの問題を引き起こすことがありました。
-w
フラグを使用することで、リンカーはDWARFデバッグ情報を除去し、より軽量なバイナリを生成することができます。
技術的詳細
__LINKEDIT
セグメントの内部構造
__LINKEDIT
セグメントは、以下のような固定された順序でデータを配置する必要があります:
- 動的シンボルテーブル(
.dynsym
): 動的リンクに必要なシンボル情報 - PLT/GOT関連データ(
.linkedit.plt
,.linkedit.got
): 手続き連結テーブルとグローバルオフセットテーブル - 文字列テーブル(
.dynstr
): シンボル名などの文字列データ - コード署名データ: Apple固有の署名情報(署名時に追加)
Appleのcode signing要件
Appleのcodesignユーティリティは、以下の要件を満たすMach-Oバイナリのみを受け入れます:
__LINKEDIT
セグメントがファイルの最後に配置されている- 文字列テーブルが
__LINKEDIT
セグメント内の最後のデータ構造である - セグメント内のデータが連続して配置されている(隙間がない)
- ファイルサイズが
__LINKEDIT
セグメントの終端と一致している
LC_SYMTAB と LC_DYSYMTAB ロードコマンド
Mach-Oファイルには、各データ構造の位置とサイズを指定するロードコマンドが含まれています:
- LC_SYMTAB: シンボルテーブルと文字列テーブルの位置を指定
- LC_DYSYMTAB: 動的シンボルテーブルと間接シンボルテーブルの位置を指定
これらのロードコマンドは、データ構造の新しい配置に合わせて正確に更新される必要があります。
コアとなるコードの変更箇所
asmbmacho()関数の変更
// 変更前
s1 = lookup(".dynsym", 0);
s2 = lookup(".dynstr", 0);
s3 = lookup(".linkedit.plt", 0);
s4 = lookup(".linkedit.got", 0);
// 変更後
s1 = lookup(".dynsym", 0);
s2 = lookup(".linkedit.plt", 0);
s3 = lookup(".linkedit.got", 0);
s4 = lookup(".dynstr", 0);
LC_SYMTABロードコマンドの更新
// 変更前
ml->data[2] = linkoff + s1->size; /* stroff */
ml->data[3] = s2->size; /* strsize */
// 変更後
ml->data[2] = linkoff + s1->size + s2->size + s3->size; /* stroff */
ml->data[3] = s4->size; /* strsize */
LC_DYSYMTABロードコマンドの更新
// 変更前
ml->data[12] = linkoff + s1->size + s2->size; /* indirectsymoff */
ml->data[13] = (s3->size + s4->size) / 4; /* nindirectsyms */
// 変更後
ml->data[12] = linkoff + s1->size; /* indirectsymoff */
ml->data[13] = (s2->size + s3->size) / 4; /* nindirectsyms */
domacholink()関数の変更
// 変更前の順序
s1 = lookup(".dynsym", 0);
s2 = lookup(".dynstr", 0);
s3 = lookup(".linkedit.plt", 0);
s4 = lookup(".linkedit.got", 0);
// 変更後の順序
s1 = lookup(".dynsym", 0);
s2 = lookup(".linkedit.plt", 0);
s3 = lookup(".linkedit.got", 0);
s4 = lookup(".dynstr", 0);
文字列テーブルの4バイト境界整列
// 変更前
while(s2->size%4)
adduint8(s2, 0);
// 変更後
while(s4->size%4)
adduint8(s4, 0);
コアとなるコードの解説
1. データ構造の再配置
このコミットの核心は、__LINKEDIT
セグメント内のデータ構造の順序を変更することです。変更により、以下のような新しい配置が実現されます:
__LINKEDIT セグメント:
+-------------------+
| .dynsym | <- 動的シンボルテーブル
+-------------------+
| .linkedit.plt | <- PLT関連データ
+-------------------+
| .linkedit.got | <- GOT関連データ
+-------------------+
| .dynstr | <- 文字列テーブル(最後に移動)
+-------------------+
2. ロードコマンドの調整
データ構造の位置が変更されたため、それぞれのロードコマンドで指定されるオフセットも調整する必要があります:
- 文字列テーブルオフセット(stroff): 他のすべてのデータ構造のサイズを加えた位置に移動
- 間接シンボルオフセット(indirectsymoff): 動的シンボルテーブルの直後に配置
- 各サイズフィールド: 新しい変数割り当てに対応
3. 4バイト境界整列
Mach-Oファイルの各データ構造は、4バイト境界に整列される必要があります。文字列テーブルが最後に移動したため、この整列処理も対応する変数(s4
)に変更されています。
4. 一貫性の確保
asmbmacho()
関数とdomacholink()
関数の両方で同じ順序を使用することで、ファイル構造の一貫性を確保しています。コメント「must match domacholink below」は、この重要な要件を明記しています。
関連リンク
- Go 1.3 Linker Overhaul
- Apple Developer Documentation - Code Signing
- Mach-O Programming Topics
- Go Issue #2996 - pass arguments for linking with 8l,6l,5l
- Understanding the Mach-O File Format