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

[インデックス 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のネイティブバイナリ形式により深く対応できるようになることが目的です。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. Mach-O (Mach Object) 形式:

    • macOS、iOS、watchOS、tvOSなどのApple製OSで使用される実行可能ファイル、オブジェクトファイル、共有ライブラリ、コアダンプなどのバイナリファイル形式です。
    • WindowsのPE (Portable Executable) やLinuxのELF (Executable and Linkable Format) に相当します。
    • Mach-Oファイルは、ヘッダ、ロードコマンド、セグメント、セクションなどで構成され、プログラムの実行に必要な情報(コード、データ、シンボルテーブルなど)を含んでいます。
  2. ユニバーサルバイナリ (Universal Binary) / Fat Binary:

    • 複数の異なるCPUアーキテクチャ(例: PowerPCとIntel、またはIntel 32-bitとIntel 64-bit)向けのMach-Oバイナリイメージを単一のファイルにまとめたものです。
    • macOSでは、lipoコマンドを使用してユニバーサルバイナリを作成・操作します。
    • OSは、実行時に自身のCPUアーキテクチャに合ったイメージを自動的に選択して実行します。これにより、開発者は複数のアーキテクチャ向けに個別のバイナリを配布する必要がなくなり、ユーザーは単一のアプリケーションをダウンロードするだけで、自身のシステムに最適なバージョンが実行されるという利点があります。
    • ユニバーサルバイナリの先頭には「fat header」と呼ばれる構造があり、その後に各アーキテクチャのバイナリイメージのオフセットとサイズを示す「fat arch header」が続きます。
  3. lipo コマンド:

    • macOSに標準で搭載されているコマンドラインツールで、ユニバーサルバイナリを作成、変更、または情報を表示するために使用されます。
    • 例: lipo -create -output universal_binary arch1_binary arch2_binary
  4. Go言語の io.ReaderAt インターフェース:

    • Goの標準ライブラリioパッケージで定義されているインターフェースです。
    • ReadAt(p []byte, off int64) (n int, err error) メソッドを持ち、指定されたオフセットoffからデータを読み込むことができます。
    • ファイルやメモリ上のデータなど、ランダムアクセスが可能なデータソースを抽象化するために使用されます。os.Fileio.ReaderAtを実装しています。
  5. Go言語の encoding/binary パッケージ:

    • Goの標準ライブラリで、数値データをバイト列に変換したり、バイト列から数値データに変換したりするための機能を提供します。
    • 特に、ビッグエンディアン(BigEndian)やリトルエンディアン(LittleEndian)といったバイトオーダーを指定して読み書きする際に使用されます。Mach-Oのfatヘッダはビッグエンディアンで記述されます。

技術的詳細

このコミットの主要な技術的詳細は、debug/machoパッケージがユニバーサルバイナリの構造をどのように解析し、その内部の個々のMach-Oイメージにアクセスできるようにするかという点にあります。

  1. 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イメージのタイプ(実行可能ファイル、ライブラリなど)が一致しているかどうかの検証も行います。
    • OpenFat(name string)関数は、ファイル名を引数に取り、os.Openでファイルを開き、NewFatFileを呼び出してユニバーサルバイナリを解析する便利なラッパーです。
  2. 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)が返されることを検証します。
  3. macho.go の変更:

    • MagicFatという新しい定数(0xcafebabe)が追加されました。これはユニバーサルバイナリのfatヘッダのマジックナンバーです。
    • Type列挙型にTypeDylib(ダイナミックライブラリ)とTypeBundle(バンドル)が追加されました。これは、ユニバーサルバイナリがこれらのタイプのMach-Oイメージを含む可能性があるため、より包括的なMach-Oタイプをサポートするためです。

これらの変更により、debug/machoパッケージは、macOS環境で広く利用されているユニバーサルバイナリを透過的に扱えるようになり、Go言語でより高度なバイナリ解析ツールを開発するための基盤が強化されました。特に、io.ReaderAtio.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.goNewFatFile 関数

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
}

この関数は、ユニバーサルバイナリの構造を順次読み込み、解析する中心的なロジックです。

  1. io.ReaderAtからio.NewSectionReaderを作成し、ファイル全体へのランダムアクセスを可能にします。
  2. binary.Readを使って、まずfatヘッダのマジックナンバーを読み込み、それがMagicFat0xcafebabe)であることを確認します。もし単一Mach-Oのマジックナンバーであれば、ErrNotFatを返して、呼び出し元が適切な処理を行えるようにします。
  3. 次に、含まれるアーキテクチャの数narchを読み込みます。
  4. narchの数だけループを回し、各FatArchHeaderを読み込みます。
  5. FatArchHeaderが示すオフセットとサイズを使って、io.NewSectionReaderを再度作成します。これにより、ユニバーサルバイナリ内の特定の範囲(個々のMach-Oイメージ)をあたかも独立したファイルのように扱うことができます。
  6. このセクションリーダーを引数として、既存のNewFile関数(単一Mach-Oファイルを解析する関数)を呼び出し、個々のMach-Oイメージを解析します。これにより、ユニバーサルバイナリ内の各アーキテクチャのMach-Oファイルが*File構造体として表現され、既存のdebug/machoの機能でアクセスできるようになります。
  7. 解析中に、重複するアーキテクチャがないか、また全てのMach-Oイメージのタイプが一致しているか(例えば、全てが実行可能ファイルであるべきかなど)を検証し、整合性を保ちます。

この設計により、debug/machoパッケージは、ユニバーサルバイナリの複雑な構造を抽象化し、内部の個々のMach-Oイメージを既存のAPIで透過的に扱えるようにしています。

関連リンク

参考にした情報源リンク

  • 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, 各種技術記事)