[インデックス 18480] ファイルの概要
このコミットは、Go言語のdebug/macho
パッケージに、Mach-O形式の「fatバイナリ」(ユニバーサルバイナリとも呼ばれる)を読み込む機能を追加するものです。これにより、異なるCPUアーキテクチャ向けにコンパイルされた複数のバイナリイメージを単一のファイルにまとめたMach-OファイルをGoプログラムで解析できるようになります。
コミット
commit 5bf35df491cb41b696979764be7dddaf128781ba
Author: Robert Sesek <rsesek@google.com>
Date: Thu Feb 13 11:04:13 2014 +1100
debug/macho: Add support for opening fat/universal binaries.
New testdata was created from existing using:
$ lipo gcc-386-darwin-exec gcc-amd64-darwin-exec -create -output fat-gcc-386-amd64-darwin-exec
Fixes #7250.
LGTM=dave
R=golang-codereviews, dave, josharian, bradfitz
CC=golang-codereviews
https://golang.org/cl/60190043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5bf35df491cb41b696979764be7dddaf128781ba
元コミット内容
debug/macho
: fat/ユニバーサルバイナリを開くサポートを追加。
新しいテストデータは、既存のデータから以下のコマンドで作成されました:
$ lipo gcc-386-darwin-exec gcc-amd64-darwin-exec -create -output fat-gcc-386-amd64-darwin-exec
Issue #7250 を修正。
変更の背景
この変更の背景には、Go言語がmacOS(旧OS X)環境でMach-O形式のバイナリを扱う際の機能不足がありました。特に、macOSでは異なるCPUアーキテクチャ(例: Intel 32-bit, Intel 64-bit, ARMなど)向けのコードを単一のバイナリファイルにまとめる「ユニバーサルバイナリ」(またはfatバイナリ)という形式が広く用いられています。
Goのdebug/macho
パッケージは、Mach-O形式のバイナリを解析するためのライブラリですが、このコミット以前は単一アーキテクチャのMach-Oファイルのみをサポートしており、ユニバーサルバイナリを直接解析する機能がありませんでした。そのため、ユニバーサルバイナリに含まれる個々のアーキテクチャの情報を取得したり、特定のアーキテクチャのバイナリを抽出したりすることが困難でした。
コミットメッセージにあるFixes #7250
は、この機能不足を報告するIssueへの対応を示しています。このIssueは、debug/macho
パッケージがユニバーサルバイナリを扱えないことによる問題提起であったと推測されます。このコミットにより、debug/macho
パッケージがユニバーサルバイナリの構造を理解し、その内部に含まれる各アーキテクチャのMach-Oファイルを適切に読み込めるようになることで、macOS環境でのデバッグツールや解析ツールの開発が容易になります。
具体的には、lipo
コマンドで作成されたようなユニバーサルバイナリをGoプログラムから直接解析できるようになり、GoのツールチェインがmacOSのネイティブバイナリ形式により深く対応できるようになることが目的です。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
Mach-O (Mach Object) 形式:
- macOS、iOS、watchOS、tvOSなどのApple製OSで使用される実行可能ファイル、オブジェクトファイル、共有ライブラリ、コアダンプなどのバイナリファイル形式です。
- WindowsのPE (Portable Executable) やLinuxのELF (Executable and Linkable Format) に相当します。
- Mach-Oファイルは、ヘッダ、ロードコマンド、セグメント、セクションなどで構成され、プログラムの実行に必要な情報(コード、データ、シンボルテーブルなど)を含んでいます。
-
ユニバーサルバイナリ (Universal Binary) / Fat Binary:
- 複数の異なるCPUアーキテクチャ(例: PowerPCとIntel、またはIntel 32-bitとIntel 64-bit)向けのMach-Oバイナリイメージを単一のファイルにまとめたものです。
- macOSでは、
lipo
コマンドを使用してユニバーサルバイナリを作成・操作します。 - OSは、実行時に自身のCPUアーキテクチャに合ったイメージを自動的に選択して実行します。これにより、開発者は複数のアーキテクチャ向けに個別のバイナリを配布する必要がなくなり、ユーザーは単一のアプリケーションをダウンロードするだけで、自身のシステムに最適なバージョンが実行されるという利点があります。
- ユニバーサルバイナリの先頭には「fat header」と呼ばれる構造があり、その後に各アーキテクチャのバイナリイメージのオフセットとサイズを示す「fat arch header」が続きます。
-
lipo
コマンド:- macOSに標準で搭載されているコマンドラインツールで、ユニバーサルバイナリを作成、変更、または情報を表示するために使用されます。
- 例:
lipo -create -output universal_binary arch1_binary arch2_binary
-
Go言語の
io.ReaderAt
インターフェース:- Goの標準ライブラリ
io
パッケージで定義されているインターフェースです。 ReadAt(p []byte, off int64) (n int, err error)
メソッドを持ち、指定されたオフセットoff
からデータを読み込むことができます。- ファイルやメモリ上のデータなど、ランダムアクセスが可能なデータソースを抽象化するために使用されます。
os.File
はio.ReaderAt
を実装しています。
- Goの標準ライブラリ
-
Go言語の
encoding/binary
パッケージ:- Goの標準ライブラリで、数値データをバイト列に変換したり、バイト列から数値データに変換したりするための機能を提供します。
- 特に、ビッグエンディアン(BigEndian)やリトルエンディアン(LittleEndian)といったバイトオーダーを指定して読み書きする際に使用されます。Mach-Oのfatヘッダはビッグエンディアンで記述されます。
技術的詳細
このコミットの主要な技術的詳細は、debug/macho
パッケージがユニバーサルバイナリの構造をどのように解析し、その内部の個々のMach-Oイメージにアクセスできるようにするかという点にあります。
-
fat.go
の新規追加:- このファイルは、ユニバーサルバイナリ(fatバイナリ)の解析ロジックをカプセル化するために新しく追加されました。
FatFile
構造体は、ユニバーサルバイナリ全体を表し、マジックナンバー(MagicFat
)と、含まれる各アーキテクチャの情報を保持するArches
スライスを持ちます。FatArchHeader
構造体は、ユニバーサルバイナリ内の個々のMach-Oイメージのオフセット、サイズ、CPUタイプ、サブCPUタイプなどのメタデータを定義します。FatArch
構造体は、FatArchHeader
と、そのヘッダが指し示す実際のMach-Oファイル(*File
)へのポインタを組み合わせたものです。NewFatFile(r io.ReaderAt)
関数は、io.ReaderAt
インターフェースを実装する任意のデータソース(例:os.File
)からユニバーサルバイナリを読み込み、FatFile
構造体を構築します。- まず、ファイルの先頭からfatヘッダのマジックナンバー(
0xcafebabe
)をビッグエンディアンで読み込み、これがユニバーサルバイナリであることを確認します。もしマジックナンバーがMach-Oの単一バイナリのマジックナンバー(0xfeedface
または0xfeedfacf
)であれば、ErrNotFat
を返して、呼び出し元が単一Mach-Oとして処理できるようにします。 - 次に、fatヘッダの
narch
フィールド(含まれるアーキテクチャの数)を読み込みます。 narch
の数だけFatArchHeader
を読み込み、それぞれのヘッダが指すオフセットとサイズに基づいてio.NewSectionReader
を使って個々のMach-Oイメージのセクションリーダーを作成します。- 各セクションリーダーに対して既存の
NewFile
関数(macho.go
で定義されている)を呼び出し、個々のMach-Oファイルを解析します。 - 重複するアーキテクチャがないか、また全てのMach-Oイメージのタイプ(実行可能ファイル、ライブラリなど)が一致しているかどうかの検証も行います。
- まず、ファイルの先頭からfatヘッダのマジックナンバー(
OpenFat(name string)
関数は、ファイル名を引数に取り、os.Open
でファイルを開き、NewFatFile
を呼び出してユニバーサルバイナリを解析する便利なラッパーです。
-
file_test.go
の変更:TestOpenFat
関数が追加され、lipo
コマンドで作成された実際のユニバーサルバイナリ(testdata/fat-gcc-386-amd64-darwin-exec
)をOpenFat
関数で開くテストが行われます。- このテストでは、読み込んだ
FatFile
のマジックナンバー、含まれるアーキテクチャの数、各アーキテクチャのCPUタイプ、サブCPUタイプ、そして個々のMach-Oファイルのヘッダ情報が期待通りであるかを確認します。 TestOpenFatFailure
関数も追加され、Mach-Oファイルではないものや、単一のMach-OファイルをOpenFat
で開こうとした場合に、適切なエラー(ErrNotFat
)が返されることを検証します。
-
macho.go
の変更:MagicFat
という新しい定数(0xcafebabe
)が追加されました。これはユニバーサルバイナリのfatヘッダのマジックナンバーです。Type
列挙型にTypeDylib
(ダイナミックライブラリ)とTypeBundle
(バンドル)が追加されました。これは、ユニバーサルバイナリがこれらのタイプのMach-Oイメージを含む可能性があるため、より包括的なMach-Oタイプをサポートするためです。
これらの変更により、debug/macho
パッケージは、macOS環境で広く利用されているユニバーサルバイナリを透過的に扱えるようになり、Go言語でより高度なバイナリ解析ツールを開発するための基盤が強化されました。特に、io.ReaderAt
とio.NewSectionReader
を組み合わせることで、ファイル全体をメモリに読み込むことなく、必要な部分だけを効率的に読み込む設計になっている点が重要です。
コアとなるコードの変更箇所
src/pkg/debug/macho/fat.go
(新規追加)
このファイル全体が新規追加されており、ユニバーサルバイナリの解析ロジックを実装しています。
FatFile
構造体、FatArchHeader
構造体、FatArch
構造体の定義。ErrNotFat
エラー変数の定義。NewFatFile(r io.ReaderAt)
関数:io.ReaderAt
からユニバーサルバイナリを読み込み、解析する主要な関数。OpenFat(name string)
関数: ファイル名からユニバーサルバイナリを開くヘルパー関数。FatFile.Close()
メソッド: 開いたファイルを閉じるためのメソッド。
src/pkg/debug/macho/file_test.go
TestOpenFat
関数が追加され、ユニバーサルバイナリの読み込みと解析のテストケースが記述されています。TestOpenFatFailure
関数が追加され、不正なファイルや単一Mach-OファイルをOpenFat
で開いた際のエラーハンドリングがテストされています。
src/pkg/debug/macho/macho.go
MagicFat
定数 (0xcafebabe
) の追加。Type
列挙型にTypeDylib
(6
) とTypeBundle
(8
) の追加。
コアとなるコードの解説
src/pkg/debug/macho/fat.go
の NewFatFile
関数
func NewFatFile(r io.ReaderAt) (*FatFile, error) {
var ff FatFile
sr := io.NewSectionReader(r, 0, 1<<63-1) // 全体を読み込むためのSectionReader
// fat_headerのマジックナンバーをビッグエンディアンで読み込む
err := binary.Read(sr, binary.BigEndian, &ff.Magic)
if err != nil {
return nil, &FormatError{0, "error reading magic number", nil}
} else if ff.Magic != MagicFat { // MagicFat (0xcafebabe) でない場合
// 単一Mach-Oのマジックナンバーかチェック
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], ff.Magic)
leMagic := binary.LittleEndian.Uint32(buf[:])
if leMagic == Magic32 || leMagic == Magic64 {
return nil, ErrNotFat // 単一Mach-OであればErrNotFatを返す
} else {
return nil, &FormatError{0, "invalid magic number", nil} // それ以外は不正なマジックナンバー
}
}
offset := int64(4) // マジックナンバーのサイズ
// fat_headerのnarch(アーキテクチャ数)を読み込む
var narch uint32
err = binary.Read(sr, binary.BigEndian, &narch)
if err != nil {
return nil, &FormatError{offset, "invalid fat_header", nil}
}
offset += 4 // narchのサイズ
if narch < 1 {
return nil, &FormatError{offset, "file contains no images", nil}
}
seenArches := make(map[uint64]bool, narch) // 重複アーキテクチャ検出用
var machoType Type // 全てのイメージのMach-Oタイプが一致するかチェック用
ff.Arches = make([]FatArch, narch)
for i := uint32(0); i < narch; i++ {
fa := &ff.Arches[i]
// fat_archヘッダを読み込む
err = binary.Read(sr, binary.BigEndian, &fa.FatArchHeader)
if err != nil {
return nil, &FormatError{offset, "invalid fat_arch header", nil}
}
offset += fatArchHeaderSize // fat_archヘッダのサイズ
// 個々のMach-Oイメージのセクションリーダーを作成し、NewFileで解析
fr := io.NewSectionReader(r, int64(fa.Offset), int64(fa.Size))
fa.File, err = NewFile(fr) // 既存のNewFile関数を呼び出す
if err != nil {
return nil, err
}
// 重複アーキテクチャのチェック
seenArch := (uint64(fa.Cpu) << 32) | uint64(fa.SubCpu)
if o, k := seenArches[seenArch]; o || k {
return nil, &FormatError{offset, fmt.Sprintf("duplicate architecture cpu=%v, subcpu=%#x", fa.Cpu, fa.SubCpu), nil}
}
seenArches[seenArch] = true
// Mach-Oタイプの一致チェック
if i == 0 {
machoType = fa.Type
} else {
if fa.Type != machoType {
return nil, &FormatError{offset, fmt.Sprintf("Mach-O type for architecture #%d (type=%#x) does not match first (type=%#x)", i, fa.Type, machoType), nil}
}
}
}
return &ff, nil
}
この関数は、ユニバーサルバイナリの構造を順次読み込み、解析する中心的なロジックです。
io.ReaderAt
からio.NewSectionReader
を作成し、ファイル全体へのランダムアクセスを可能にします。binary.Read
を使って、まずfatヘッダのマジックナンバーを読み込み、それがMagicFat
(0xcafebabe
)であることを確認します。もし単一Mach-Oのマジックナンバーであれば、ErrNotFat
を返して、呼び出し元が適切な処理を行えるようにします。- 次に、含まれるアーキテクチャの数
narch
を読み込みます。 narch
の数だけループを回し、各FatArchHeader
を読み込みます。- 各
FatArchHeader
が示すオフセットとサイズを使って、io.NewSectionReader
を再度作成します。これにより、ユニバーサルバイナリ内の特定の範囲(個々のMach-Oイメージ)をあたかも独立したファイルのように扱うことができます。 - このセクションリーダーを引数として、既存の
NewFile
関数(単一Mach-Oファイルを解析する関数)を呼び出し、個々のMach-Oイメージを解析します。これにより、ユニバーサルバイナリ内の各アーキテクチャのMach-Oファイルが*File
構造体として表現され、既存のdebug/macho
の機能でアクセスできるようになります。 - 解析中に、重複するアーキテクチャがないか、また全てのMach-Oイメージのタイプが一致しているか(例えば、全てが実行可能ファイルであるべきかなど)を検証し、整合性を保ちます。
この設計により、debug/macho
パッケージは、ユニバーサルバイナリの複雑な構造を抽象化し、内部の個々のMach-Oイメージを既存のAPIで透過的に扱えるようにしています。
関連リンク
- Go言語の
debug/macho
パッケージのドキュメント (このコミットが取り込まれた後のバージョン): https://pkg.go.dev/debug/macho - Mach-Oファイル形式に関するAppleのドキュメント (開発者向け): https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
lipo
コマンドのmanページ (macOSのターミナルでman lipo
と入力)。
参考にした情報源リンク
- Go言語のコミット履歴: https://github.com/golang/go/commits/master
- Go言語のIssueトラッカー (Issue #7250): https://github.com/golang/go/issues/7250 (ただし、Issueはクローズされているため、詳細な議論はGoのコードレビューシステム (Gerrit) のリンクを参照する必要があるかもしれません。)
- Goのコードレビューシステム (Gerrit) の変更リスト (CL 60190043): https://golang.org/cl/60190043 (このリンクは現在、GitHubのコミットページにリダイレクトされる可能性がありますが、当時のレビュープロセスを理解する上で重要です。)
- Mach-Oファイル形式に関する一般的な情報源 (例: Wikipedia, 各種技術ブログなど)
- Go言語の
io
パッケージのドキュメント: https://pkg.go.dev/io - Go言語の
encoding/binary
パッケージのドキュメント: https://pkg.go.dev/encoding/binary - ユニバーサルバイナリに関する一般的な情報源 (例: Apple Developer Documentation, 各種技術記事)
lipo
コマンドに関する情報源 (例: Apple Developer Documentation, 各種技術記事)