[インデックス 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ファイルは、以下の主要な部分で構成されます。
- ヘッダ (Header): ファイルの基本的な情報(CPUタイプ、サブタイプ、ファイルタイプなど)を含みます。
- ロードコマンド (Load Commands): ファイルの構造と、オペレーティングシステムがファイルをメモリにロードする方法を記述します。これには、セグメントの定義、シンボルテーブルの場所、ダイナミックリンカの情報などが含まれます。
- セグメント (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サブタイプをチェックするロジックの変更にあります。
従来のlibmach
のmachdotout
関数(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つのファイルにわたります。
src/libmach/executable.c
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->cpusubtype
がMACH_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->cpusubtype
がMACH_CPU_SUBTYPE_X86
でもなく、かつMACH_CPU_SUBTYPE_X86_64
でもない場合にエラーを返す、という意味になります。
つまり、この修正により、libmach
は以下のいずれかのCPUサブタイプを持つMach-Oバイナリを有効なものとして受け入れるようになります。
MACH_CPU_SUBTYPE_X86
(値:3
)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
の値3
にCPU_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の元となるシステム)