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

[インデックス 17544] ファイルの概要

このコミットは、Go言語のランタイムの一部であるlibmachライブラリにおけるMach-O実行ファイルのCPUサブタイプチェックに関する修正です。具体的には、外部リンカが生成するOS Xバイナリが、CPU_SUBTYPE_LIB64ビット(1<<31)を設定している場合に、libmachがそのバイナリを正しく認識できるようにするための変更です。これにより、Goツールチェインが外部リンカによって生成された64ビットMach-Oバイナリを適切に処理できるようになります。

コミット

commit 8edf764fa3e2f760757d881d0e7e5eb80649bccb
Author: Arnaud Ysmal <arnaud.ysmal@gmail.com>
Date:   Tue Sep 10 11:50:34 2013 -0700

    libmach: accept OS X binary generated by external linker
    
    Fixes cpu subtype check when using external linker which sets the CPU_SUBTYPE_LIB64 bit (1<<31).
    Fixes #6197.
    
    R=golang-dev, minux.ma, rsc
    CC=golang-dev
    https://golang.org/cl/13248046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/8edf764fa3e2f760757d881d0e7e5eb80649bccb

元コミット内容

このコミットは、libmachが外部リンカによって生成されたOS Xバイナリを正しく受け入れるようにするためのものです。具体的には、外部リンカがCPU_SUBTYPE_LIB64ビット(1<<31)を設定する際に発生するCPUサブタイプチェックの問題を修正します。これにより、Goツールチェインがこれらのバイナリを適切に処理できるようになります。この変更は、Goの内部課題トラッカーにおけるIssue #6197を解決します。

変更の背景

Go言語のツールチェインは、コンパイルされたGoプログラムを実行可能なバイナリに変換する際に、様々なオペレーティングシステムやアーキテクチャに対応する必要があります。OS X(現在のmacOS)では、Mach-Oという独自の実行ファイル形式が使用されています。Goのツールチェインには、これらのMach-Oファイルを解析し、デバッグ情報やシンボル情報を読み取るためのlibmachというライブラリが含まれています。

問題は、Goの標準リンカではなく、外部のリンカ(例えば、Xcodeに付属するldなど)を使用してGoプログラムをコンパイルし、その結果生成されたMach-Oバイナリをlibmachが処理しようとした場合に発生しました。外部リンカは、Mach-Oヘッダ内のCPUサブタイプフィールドに、Goのツールチェインが予期しないビット、具体的にはCPU_SUBTYPE_LIB64ビット(1<<31)を設定することがありました。

libmachの既存のCPUサブタイプチェックは、このCPU_SUBTYPE_LIB64ビットの存在を考慮していなかったため、外部リンカによって生成された有効な64ビットMach-Oバイナリであっても、「bad MACH cpu subtype - not amd64」というエラーで拒否してしまっていました。この挙動は、Goプログラムを外部ツールと連携させる際に互換性の問題を引き起こし、開発者のワークフローを妨げる可能性がありました。

このコミットは、この互換性の問題を解決し、libmachが外部リンカによって生成されたMach-Oバイナリを正しく認識し、処理できるようにすることを目的としています。

前提知識の解説

Mach-Oファイル形式

Mach-O(Mach Object)は、AppleのmacOS、iOS、watchOS、tvOSなどのオペレーティングシステムで使用される実行可能ファイル、オブジェクトコード、共有ライブラリ、動的にロードされるコード、およびコアダンプの標準ファイル形式です。Mach-Oファイルは、以下の主要な部分で構成されます。

  1. ヘッダ (Header): ファイルの基本的な情報(CPUタイプ、サブタイプ、ファイルタイプなど)を含みます。
  2. ロードコマンド (Load Commands): ファイルの構造と、オペレーティングシステムがファイルをメモリにロードする方法を記述します。これには、セグメントの定義、シンボルテーブルの場所、ダイナミックリンカの情報などが含まれます。
  3. セグメント (Segments): 実行可能コード、データ、スタック、ヒープなどの実際のコンテンツを含みます。

CPUタイプとCPUサブタイプ

Mach-Oヘッダには、バイナリが対象とするCPUのタイプとサブタイプを示すフィールドがあります。

  • CPUタイプ (CPU Type): 大まかなCPUアーキテクチャ(例: X86, X86_64, ARMなど)を示します。
  • CPUサブタイプ (CPU Subtype): 特定のCPUタイプ内のより詳細なバリアントやモデルを示します。例えば、X86タイプには、X86_ALL, X86_ARCH1, X86_64_ALLなどのサブタイプが存在します。

これらのタイプとサブタイプは、オペレーティングシステムが適切なコードパスを選択し、バイナリを正しく実行するために重要です。

CPU_SUBTYPE_LIB64ビット

Mach-OのCPUサブタイプフィールドは、単なる列挙値だけでなく、ビットフラグを含むことがあります。CPU_SUBTYPE_LIB64は、特定のCPUサブタイプに設定されるビットフラグの一つで、バイナリが64ビットライブラリであることを示すために使用されることがあります。このビットは、特に外部リンカがMach-Oファイルを生成する際に、64ビットの特性を明示するために設定されることがあります。

libmachライブラリ

libmachは、Go言語のツールチェイン内部で使用されるライブラリで、様々なアーキテクチャやオペレーティングシステムにおける実行可能ファイル形式(ELF、Mach-Oなど)を解析するための低レベルな機能を提供します。Goのデバッガやプロファイラ、その他のツールが、コンパイルされたバイナリの内部構造を理解し、シンボル情報やデバッグ情報を抽出するために利用されます。

技術的詳細

このコミットの技術的な核心は、libmachがMach-OファイルのCPUサブタイプをチェックするロジックの変更にあります。

従来のlibmachmachdotout関数(src/libmach/executable.c内)では、Mach-OバイナリのCPUサブタイプがMACH_CPU_SUBTYPE_X86であるかどうかを厳密にチェックしていました。これは、Goが想定する64ビットAMD64(x86-64)アーキテクチャのバイナリに対しては適切でしたが、外部リンカが生成するバイナリがMACH_CPU_SUBTYPE_X86に加えてCPU_SUBTYPE_LIB64ビットを設定している場合、このチェックに失敗していました。

CPU_SUBTYPE_LIB64ビットは、Mach-Oのmacho.hヘッダで定義されているように、1<<31という値を持つビットフラグです。つまり、CPUサブタイプがMACH_CPU_SUBTYPE_X86(値は3)である場合でも、外部リンカがCPU_SUBTYPE_LIB64ビットを設定すると、実際のcpusubtypeの値は (1<<31) | 3、すなわち0x80000003となります。

このコミットでは、この問題を解決するために、machdotout関数内のCPUサブタイプチェックを拡張しています。具体的には、mp->cpusubtype != MACH_CPU_SUBTYPE_X86という条件に加えて、mp->cpusubtype != MACH_CPU_SUBTYPE_X86_64という条件を追加しています。

ここで重要なのは、MACH_CPU_SUBTYPE_X86_64が新たにmacho.hで定義され、その値が(1<<31)|3、つまりCPU_SUBTYPE_LIB64ビットが設定されたMACH_CPU_SUBTYPE_X86と同じ値になっている点です。これにより、libmachは、従来のMACH_CPU_SUBTYPE_X86だけでなく、外部リンカがCPU_SUBTYPE_LIB64ビットを設定したMACH_CPU_SUBTYPE_X86_64も有効なCPUサブタイプとして認識できるようになります。

この変更により、Goのツールチェインは、外部リンカによって生成された64ビットMach-Oバイナリを、そのCPUサブタイプフィールドにCPU_SUBTYPE_LIB64ビットが設定されていても、正しく解析し、処理できるようになります。

コアとなるコードの変更箇所

このコミットによる変更は、以下の2つのファイルにわたります。

  1. src/libmach/executable.c
  2. src/libmach/macho.h

src/libmach/executable.cの変更

--- a/src/libmach/executable.c
+++ b/src/libmach/executable.c
@@ -1076,7 +1076,7 @@ machdotout(int fd, Fhdr *fp, ExecHdr *hp)
 			return 0;
 		}
 
-		if (mp->cpusubtype != MACH_CPU_SUBTYPE_X86) {
+		if (mp->cpusubtype != MACH_CPU_SUBTYPE_X86 && mp->cpusubtype != MACH_CPU_SUBTYPE_X86_64) {
 			werrstr("bad MACH cpu subtype - not amd64");
 			return 0;
 		}

この変更では、machdotout関数内のCPUサブタイプチェックの条件が修正されています。 変更前: if (mp->cpusubtype != MACH_CPU_SUBTYPE_X86) 変更後: if (mp->cpusubtype != MACH_CPU_SUBTYPE_X86 && mp->cpusubtype != MACH_CPU_SUBTYPE_X86_64)

これは、mp->cpusubtypeMACH_CPU_SUBTYPE_X86でもMACH_CPU_SUBTYPE_X86_64でもない場合にエラーを返すように変更されたことを意味します。

src/libmach/macho.hの変更

--- a/src/libmach/macho.h
+++ b/src/libmach/macho.h
@@ -87,6 +87,7 @@ enum {
 	MACH_CPU_TYPE_X86_64 = (1<<24)|7,
 	MACH_CPU_TYPE_X86 = 7,
 	MACH_CPU_SUBTYPE_X86 = 3,
+	MACH_CPU_SUBTYPE_X86_64 = (1<<31)|3,
 	MACH_EXECUTABLE_TYPE = 2,
 	MACH_SEGMENT_32 = 1,	/* 32-bit mapped segment */
 	MACH_SEGMENT_64 = 0x19,	/* 64-bit mapped segment */

この変更では、MACH_CPU_SUBTYPE_X86_64という新しい定数が追加されています。 MACH_CPU_SUBTYPE_X86_64は、(1<<31)|3と定義されており、これはCPU_SUBTYPE_LIB64ビット(1<<31)が設定されたMACH_CPU_SUBTYPE_X86(値は3)に相当します。

コアとなるコードの解説

src/libmach/executable.cの変更の解説

machdotout関数は、Mach-O形式の実行ファイルを解析する際に呼び出される関数です。この関数内で、バイナリのCPUサブタイプがGoが想定するアーキテクチャと一致するかどうかの検証が行われます。

変更前のコードでは、mp->cpusubtype != MACH_CPU_SUBTYPE_X86という条件で、CPUサブタイプがMACH_CPU_SUBTYPE_X86と厳密に一致しない場合にエラー("bad MACH cpu subtype - not amd64")を返していました。これは、Goがx86-64アーキテクチャをターゲットとする際に、Mach-OのCPUサブタイプとしてMACH_CPU_SUBTYPE_X86を期待していたためです。

しかし、外部リンカが生成する64ビットMach-Oバイナリでは、CPUサブタイプにCPU_SUBTYPE_LIB64ビットが設定されることがありました。このビットが設定されると、たとえベースとなるサブタイプがMACH_CPU_SUBTYPE_X86であっても、その値はMACH_CPU_SUBTYPE_X86とは異なるものになります。

変更後のコードでは、mp->cpusubtype != MACH_CPU_SUBTYPE_X86 && mp->cpusubtype != MACH_CPU_SUBTYPE_X86_64という条件に修正されました。これは論理AND演算子&&を使用しているため、mp->cpusubtypeMACH_CPU_SUBTYPE_X86でもなく、かつMACH_CPU_SUBTYPE_X86_64でもない場合にエラーを返す、という意味になります。

つまり、この修正により、libmachは以下のいずれかのCPUサブタイプを持つMach-Oバイナリを有効なものとして受け入れるようになります。

  1. MACH_CPU_SUBTYPE_X86 (値: 3)
  2. MACH_CPU_SUBTYPE_X86_64 (値: (1<<31)|3、すなわち0x80000003)

これにより、外部リンカがCPU_SUBTYPE_LIB64ビットを設定した64ビットMach-Oバイナリも、Goのツールチェインによって正しく処理されるようになります。

src/libmach/macho.hの変更の解説

このヘッダファイルは、Mach-Oファイル形式に関連する定数や構造体の定義を含んでいます。

追加されたMACH_CPU_SUBTYPE_X86_64 = (1<<31)|3という定義は、このコミットの核心をなすものです。

  • MACH_CPU_SUBTYPE_X86は、x86アーキテクチャの一般的なサブタイプとして値3を持っています。
  • (1<<31)は、CPU_SUBTYPE_LIB64ビットを表します。これは、31番目のビット(0から数えて)がセットされていることを意味し、通常、64ビットライブラリや特定のリンカの挙動を示すために使用されます。
  • |はビットごとのOR演算子です。したがって、(1<<31)|3は、MACH_CPU_SUBTYPE_X86の値3CPU_SUBTYPE_LIB64ビットを論理ORで結合した新しいサブタイプ値を定義しています。

この新しい定数を定義することで、executable.cでのチェックがより明確になり、外部リンカが生成する特定の64ビットMach-OバイナリのCPUサブタイプを正確に識別できるようになりました。

関連リンク

  • Go issue #6197 (このコミットが修正した課題): この課題は、Goの内部課題トラッカーに存在したもので、現在の公開されているGitHubのIssueトラッカーでは直接検索できない可能性があります。しかし、コミットメッセージに明記されているため、このコミットが解決した問題の識別子として機能します。
  • Go CL 13248046: https://golang.org/cl/13248046 (このコミットに対応するGoのコードレビューシステム上の変更リスト)

参考にした情報源リンク

  • Mach-O File Format Reference: Apple Developer Documentation (Mach-Oの公式ドキュメントは、Appleのデベロッパーサイトで参照できますが、URLは頻繁に変わるため、具体的なリンクは省略します。Mach-O File Formatで検索してください。)
  • Wikipedia - Mach-O: https://en.wikipedia.org/wiki/Mach-O
  • Go言語のソースコード: src/libmachディレクトリ内のファイル群 (executable.c, macho.hなど)
  • Go言語のIssueトラッカー: https://github.com/golang/go/issues (ただし、#6197は古いまたは内部のIssueである可能性が高いです。)
  • Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (CL 13248046の元となるシステム)