Universal Binary

Universal Binaryについて調べてみる。
なお、今回から用語は基本的に英語で記述することにした。「ロードコマンド」とか書いててすげー気持ち悪かったから。

複数のアーキテクチャに対応したバイナリーファイル

複数のアーキテクチャに対応したバイナリーファイルには、以下のようなものがあります。

Universal Binaryと呼ばれるのは後者のみで、前者はPPC/PPC64 Binaryと呼びます。
それぞれのアーキテクチャ向けのオブジェクトは、バイナリーファイルの先頭からオフセットをとって配置されます。
複数のアーキテクチャ向けのオブジェクトを含んだファイルは、ファイルの先頭にfat_headerが存在し、その後に2つのfat_archと、それぞれのアーキテクチャ向けのオブジェクトが続きます。
なお、データはすべてbig-endianです。

fat_header

一つ以上のアーキテクチャ向けのオブジェクトを含んだファイルであることを表すヘッダのデータ構造です。mach-o/fat.hで定義されています。

struct fat_header {
	uint32_t	magic;		/* マジックナンバー */
	uint32_t	nfat_arch;	/* fat_archの数 */
};

magicはマジックナンバーです。big-endianでは0xCAFEBABEです。big-endianのCPUではFAT_MAGIC、little-endianのCPUではFAT_CIGAMを使って検証します。
nfat_archは後に続くfat_archの数です。このバイナリーに含まれる対象アーキテクチャの数に一致します。

fat_arch

複数のアーキテクチャ向けのオブジェクトを含んだファイルで、あるアーキテクチャ向けのオブジェクトがどこに存在するかを記述したデータ構造です。mach-o/fat.hで定義されています。

struct fat_arch {
	cpu_type_t	cputype;	/* CPUタイプ */
	cpu_subtype_t	cpusubtype;	/* CPUサブタイプ */
	uint32_t	offset;		/* このCPU向けオブジェクトまでのオフセット */
	uint32_t	size;		/* このCPU向けオブジェクトのサイズ */
	uint32_t	align;		/* 2のべき乗で表したページアラインメント */
};

cputypeとcpusubtypeは、対象のアーキテクチャを表します。arch(3)に代表的なアーキテクチャが掲載されています。Universal Binaryの場合はppci386が使用されるでしょう。offsetは対象アーキテクチャ向けオブジェクトまでのオフセットです。sizeは対象アーキテクチャ向けオブジェクトのサイズです。
alignは、アラインメントを2のべき乗で表現したものです。バイナリーファイルを変更する際に、アラインメントを保つために使用します。ppci386はどちらも4096byteですから、2^12=4096ということでalignは0x0cになります。

実際のUniversal Binary

それでは実際にUniversal Binaryがどうなっているかを見てみましょう。今回はUniversal Binaryの例として/usr/bin/fileを使用します。
Mac OS XのfileコマンドはUniversal Binaryを理解します。fileコマンドで/usr/bin/fileを調べると、以下のようにi386ppc、2つのアーキテクチャ向けのオブジェクトが存在することが分かります。

> file /usr/bin/file
/usr/bin/file: Mach-O universal binary with 2 architectures
/usr/bin/file (for architecture i386):  Mach-O executable i386
/usr/bin/file (for architecture ppc):   Mach-O executable ppc

otoolも複数アーキテクチャを理解します。-archオプションを使用して、アーキテクチャを指定します。ppc向けオブジェクトのMach-Oヘッダを表示してみましょう。

> otool -h -v -arch ppc /usr/bin/file
/usr/bin/file:
Mach header
      magic cputype cpusubtype   filetype ncmds sizeofcmds      flags
   MH_MAGIC     PPC        ALL    EXECUTE    12       1652   NOUNDEFS DYLDLINK TWOLEVEL SUBSECTIONS_VIA_SYMBOLS

バイナリアンとしては、odを実行しないわけにはいきません。まずはfat_headerとfat_archの部分を表示します。

> od -t x1 -A x /usr/bin/file | head -n 4
0000000    ca  fe  ba  be  00  00  00  02  00  00  00  07  00  00  00  03
0000010    00  00  10  00  00  00  eb  b8  00  00  00  0c  00  00  00  12
0000020    00  00  00  00  00  01  00  00  00  00  dd  38  00  00  00  0c
0000030    00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00

先頭にFAT_MAGIC(0xcafebabe)が存在し、複数のアーキテクチャ向けのオブジェクトが含まれていることが分かります。次の32bitがnfat_archです。0x2ですから2つのfat_archが続くことが分かります。そう、i386ppcです。
最初のfat_archを見てみましょう。以下の部分です。

0000000                                    00  00  00  07  00  00  00  03
0000010    00  00  10  00  00  00  eb  b8  00  00  00  0c                

cputypeとcpusubtypeはそれぞれ0x07(CPU_TYPE_I386)、0x03(CPU_SUBTYPE_I386_ALL)です。fileコマンドで調べたとおり、最初にi386アーキテクチャ向けのオブジェクトが格納されているようです。
offsetは0x1000で、先頭から0x001000のところにi386向けオブジェクトの先頭が存在します。sizeは0x00ebb8です。また、alignは0x0c(2^12 = 4096)になっていることが確認できます。
同様に、2番目のfat_archを見てみます。

0000010                                                    00  00  00  12
0000020    00  00  00  00  00  01  00  00  00  00  dd  38  00  00  00  0c

cputypeとcpusubtypeはそれぞれ0x12(CPU_TYPE_POWERPC)、0x00(CPU_SUBTYPE_POWERPC_ALL)です。こちらはppcアーキテクチャ向けのオブジェクトですね。
また、offsetは0x010000、sizeは0x00dd38、alignは0x0cであることが確認できます。