IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Mac のバイナリファイルについて調べる

ユニバーサルバイナリとは

今の理解は、 PPC 用のバイナリと Intel 用のバイナリをくっつけたバイナリファイルの形式。
こういう複数のアーキテクチャのコードを含むバイナリを「FAT バイナリ」や「マルチアーキテクチャバイナリ」という。
つまり、ユニバーサルバイナリは FAT バイナリの一種。

ユニバーサルバイナリのヘッダ

ユニバーサルファイルの先頭には FAT ヘッダが付く。
ヘッダの形式は、以下の構造体の形式は /usr/include/mach-o/fat.h にある

struct fat_header {
    uint32_t    magic;      /* FAT_MAGIC */
    uint32_t    nfat_arch;  /* number of structs that follow */
};

struct fat_arch {
    cpu_type_t  cputype;    /* cpu specifier (int) */
    cpu_subtype_t   cpusubtype; /* machine specifier (int) */
    uint32_t    offset;     /* file offset to this object file */
    uint32_t    size;       /* size of this object file */
    uint32_t    align;      /* alignment as a power of 2 */
};

まず、マジックがあって、アーキテクチャの数があって。
アーキテクチャの数だけ fat_arch 構造体の形式のデータが続く
cputype と cpusubtype にアーキテクチャの種類が書かれている。
cputype と cpusubtype については /usr/include/mach/machine.h に定数の一覧が書かれている

#define CPU_TYPE_X86		((cpu_type_t) 7)
#define CPU_TYPE_I386		CPU_TYPE_X86		/* compatibility */
#define	CPU_TYPE_X86_64		(CPU_TYPE_X86 | CPU_ARCH_ABI64)
#define CPU_TYPE_MC98000	((cpu_type_t) 10)
#define CPU_TYPE_SPARC		((cpu_type_t) 14)
#define CPU_TYPE_I860		((cpu_type_t) 15)
#define CPU_TYPE_POWERPC		((cpu_type_t) 18)
#define CPU_TYPE_POWERPC64		(CPU_TYPE_POWERPC | CPU_ARCH_ABI64)#define CPU_SUBTYPE_PENTIUM_M			CPU_SUBTYPE_INTEL(9, 0)
#define CPU_SUBTYPE_PENTIUM_4			CPU_SUBTYPE_INTEL(10, 0)
#define CPU_SUBTYPE_PENTIUM_4_M			CPU_SUBTYPE_INTEL(10, 1)
#define CPU_SUBTYPE_ITANIUM				CPU_SUBTYPE_INTEL(11, 0)
#define CPU_SUBTYPE_ITANIUM_2			CPU_SUBTYPE_INTEL(11, 1)
#define CPU_SUBTYPE_XEON				CPU_SUBTYPE_INTEL(12, 0)
#define CPU_SUBTYPE_XEON_MP				CPU_SUBTYPE_INTEL(12, 1)

fat_arch の offset から size 分がそのアーキテクチャ用のバイナリになる。

自分のコンピュータのアーキテクチャを知る

以下のようにするらしい

#include <stdio.h>
#include <mach/mach.h>

int main() {
    mach_port_t host_port, task_port;
    kern_return_t result;
    struct host_basic_info info;    
    unsigned int count;

    host_port = mach_host_self();

    count = HOST_BASIC_INFO_COUNT;
    result = host_info(host_port, HOST_BASIC_INFO, (host_t)&info, &count);
    task_port = mach_task_self();
    mach_port_deallocate(task_port, host_port);

    if (result != KERN_SUCCESS) exit(-1);

    printf("host_basic_info:\n");
    printf("\tmax_cpus: %d\n", info.max_cpus);
    printf("\tavail_cpus: %d\n", info.avail_cpus);
    printf("\tmemory_size: %u\n", info.memory_size);
    printf("\tcpu_type: %d\n", info.cpu_type);
    printf("\tcpu_subtype: %d\n", info. cpu_subtype);
    printf("\tphysical_cpu: %d\n", info.physical_cpu);
    printf("\tphysical_cpu_max: %d\n", info.physical_cpu_max);
    printf("\tlogical_cpu: %d\n", info.logical_cpu);
    printf("\tlogical_cpu_max: %d\n", info.logical_cpu_max);
    printf("\tmax_mem: %u\n", info.max_mem);

    return 0;
}

実行するとこうなる

$ ./a.out
host_basic_info:
	max_cpus: 2
	host_basic_info: 2
	memory_size: 2147483648
	cpu_type: 7
	cpu_subtype: 2
	physical_cpu: 2
	physical_cpu_max: 2
	logical_cpu: 2
	logical_cpu_max: 2
	max_mem: 2147483648

この mach_task_self, mach_host_self, mach_port_deallocate は Mach というマイクロカーネルAPI ということだ。
意味ふ><
ちょっと寄り道して Mach およびマイクロカーネルについて調べてみる。

マイクロカーネルとは

汎用的な機能しか持たない小型化されたカーネルのこと

Mach とは

マイクロカーネルMac OS Xカーネルはこれらしい。

で、どういうことなのかなあ

この二つってどういう関係なんだろう。
同じ Mach カーネルの上に構築された Unix Hurd の説明を見ると
The GNU Hurd FAQ: 定義と概要
POSIXシステムコールマイクロカーネル API を使ってるにすぎないようなことが書いてあった。
マイクロカーネルAPI ってシステムコールよりも下のレイヤということになるのかな??

さらに

プロセス(カーネルを含む)がサーバとクライアントになって通信し合うような API らしい。
だから、システムコールをする場合は、カーネル(サーバ)にプロセスがクライアントとして通信しているという感じなのかな?
通信はポートというものを使う、ポートには権限(ポートライト)があって、送信できる権利(センドライト)と受信できる権利(レシーブライト)があるらしい

具体的なサーバとクライアントの書き方

ここの下のほうにありました。
http://www.rtmach.org/publications/transtech-mach-1.pdf

つまりさっきの

    host_port = mach_host_self();

    count = HOST_BASIC_INFO_COUNT;
    result = host_info(host_port, HOST_BASIC_INFO, (host_t)&info, &count);
    task_port = mach_task_self();
    mach_port_deallocate(task_port, host_port);

は、
mach_host_self() で取得したポートを介して、通信によってサーバとなっているプロセスの host_info を呼び出しているということか。
mach_host_self() で得られるポートについては以下に書いてあります。
Host Interface - The GNU Mach Reference Manual
task's host ってなんだろう。

寄り道しすぎた

さてと、話は FAT ヘッダまで行ったと。。
次は自分の cputype にあった fat_arch を選ぶ

host_info.cpu_type == fat_arch.cputype

になる fat_arch の情報を読む。
ここで、アーキテクチャごとに選択されるオブジェクトが分かれる。

選択されたオブジェクトは何か

Mach-O という形式のバイナリらしい

Mach-O とは

Mach-O とは Mac OS X で使われるオブジェクトファイルの形式。
ユニバーサルバイナリじゃない場合は全体がこの形式。
ユニバーサルバイナリの場合はこの形式のファイルが結合されて、先頭に FAT ヘッダがつく。

Mach-O のヘッダの形式

/usr/include/mach-o/loader.h にあった

/* * The 32-bit mach header appears at the very beginning of the object file for
 * 32-bit architectures.
 */
struct mach_header {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
};

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define MH_MAGIC    0xfeedface  /* the mach magic number */
#define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures. */
struct mach_header_64 {
    uint32_t    magic;      /* mach magic number identifier */
    cpu_type_t  cputype;    /* cpu specifier */
    cpu_subtype_t   cpusubtype; /* machine specifier */
    uint32_t    filetype;   /* type of file */
    uint32_t    ncmds;      /* number of load commands */
    uint32_t    sizeofcmds; /* the size of all the load commands */
    uint32_t    flags;      /* flags */
    uint32_t    reserved;   /* reserved */
};

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */

magic はマジックナンバー
cputype と cpusubtype は fat_arch にもあった情報。
filetype はなんだろう。 filetype には、以下の値が入るらしい

#define MH_OBJECT   0x1     /* relocatable object file */
#define MH_EXECUTE  0x2     /* demand paged executable file */
#define MH_FVMLIB   0x3     /* fixed VM shared library file */
#define MH_CORE     0x4     /* core file */
#define MH_PRELOAD  0x5     /* preloaded executable file */
#define MH_DYLIB    0x6     /* dynamically bound shared library */
#define MH_DYLINKER 0x7     /* dynamic link editor */
#define MH_BUNDLE   0x8     /* dynamically bound bundle file */
#define MH_DYLIB_STUB   0x9     /* shared library stub for static */

なるほどね。
実行形式か、再配置可能なオブジェクトか、ダイナミックリンクライブラリか、などなどいろいろなファイルの種類があるなあ。
flags もめっちゃ種類ある

/* Constants for the flags field of the mach_header */
#define MH_NOUNDEFS 0x1     /* the object file has no undefined
                       references */
#define MH_INCRLINK 0x2     /* the object file is the output of an
                       incremental link against a base file
                       and can't be link edited again */
#define MH_DYLDLINK 0x4     /* the object file is input for the
                       dynamic linker and can't be staticly
                       link edited again */
#define MH_BINDATLOAD   0x8     /* the object file's undefined
                       references are bound by the dynamic
                       linker when loaded. */
#define MH_PREBOUND 0x10        /* the file has its dynamic undefined
                       references prebound. */
#define MH_SPLIT_SEGS   0x20        /* the file has its read-only and
                       read-write segments split */
#define MH_LAZY_INIT    0x40        /* the shared library init routine is
                       to be run lazily via catching memory
                       faults to its writeable segments
                       (obsolete) */
#define MH_TWOLEVEL 0x80        /* the image is using two-level name
                       space bindings */
#define MH_FORCE_FLAT   0x100       /* the executable is forcing all images
                       to use flat name space bindings */
#define MH_NOMULTIDEFS  0x200       /* this umbrella guarantees no multiple
                       defintions of symbols in its
                       sub-images so the two-level namespace
                       hints can always be used. */
#define MH_NOFIXPREBINDING 0x400    /* do not have dyld notify the
                       prebinding agent about this
                       executable */
#define MH_PREBINDABLE  0x800           /* the binary is not prebound but can
                       have its prebinding redone. only used
                                           when MH_PREBOUND is not set. */
#define MH_ALLMODSBOUND 0x1000      /* indicates that this binary binds to
                                           all two-level namespace modules of
                       its dependent libraries. only used
                       when MH_PREBINDABLE and MH_TWOLEVEL
                       are both set. */ 
#define MH_SUBSECTIONS_VIA_SYMBOLS 0x2000/* safe to divide up the sections into
                        sub-sections via symbols for dead
                        code stripping */
#define MH_CANONICAL    0x4000      /* the binary has been canonicalized
                       via the unprebind operation */
#define MH_WEAK_DEFINES 0x8000      /* the final linked image contains
                       external weak symbols */
#define MH_BINDS_TO_WEAK 0x10000    /* the final linked image uses
                       weak symbols */

#define MH_ALLOW_STACK_EXECUTION 0x20000/* When this bit is set, all stacks 
                       in the task will be given stack
                       execution privilege.  Only used in
                       MH_EXECUTE filetypes. */
#define MH_ROOT_SAFE 0x40000           /* When this bit is set, the binary 
                      declares it is safe for use in
                      processes with uid zero */

#define MH_SETUID_SAFE 0x80000         /* When this bit is set, the binary 
                      declares it is safe for use in
                      processes when issetugid() is true */

#define MH_NO_REEXPORTED_DYLIBS 0x100000 /* When this bit is set on a dylib, 
                      the static linker does not need to
                      examine dependent dylibs to see
                      if any are re-exported */
#define MH_PIE 0x200000         /* When this bit is set, the OS will
                       load the main executable at a
                       random address.  Only used in
                       MH_EXECUTE filetypes. */

うわーーーー。全部調べたいけど大変そう><
PIE (アセンブラ中に相対参照しかない実行コード)とかはフラグがあるんだ。ぱっとみ分かるのが PIE くらいしかない><
時間があるときに調べてみよう。
で、 ncmds, sizecmds っていうのがロードコマンドの数を表しているらしい。
ということは、このあとにロードコマンドが続いていくというのが分かる

ロードコマンドとは

仮想記憶のレイアウト、シンボルテーブルの場所、などのセグメントの初期化情報やレジスタの初期値などが入っているらしい

ロードコマンドの形式

(つづく)

確認用に書いてるコード

// エラー処理は一切ありません

#include <mach/mach.h>
#include <stdio.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <libc.h>
#include <sys/mman.h>

#define FILE_NAME "/bin/ls"
#define SWAP32(a) (((a) << 24) | (((a) << 8) & 0x00ff0000) | (((a) >> 8) & 0x0000ff00) | ((unsigned int)(a) >> 24) )

int main() {
    int little_endian = FALSE;
    int i;
    int fd;
    struct stat st;
    unsigned long size;
    char *pstart;
    char *p;
    uint32_t magic;
    struct fat_header fh;
    struct fat_arch fa;
    mach_port_t port;
    struct host_basic_info hi;
    unsigned int c;
    uint32_t obj_offset = 0;
    uint32_t obj_size = 0;
    struct mach_header mh;
    struct mach_header_64 mh64;

#ifdef __LITTLE_ENDIAN__
    little_endian = TRUE;
#endif

    // 自分のマシンの cpu_type と cpu_subtype を調べる
    // hi.cpu_type と hi.cpu_subtype でを見ればいい
    port = mach_host_self();
    c = HOST_BASIC_INFO_COUNT;
    host_info(port, HOST_BASIC_INFO, (host_t)&hi, &c);
    mach_port_deallocate(mach_task_self(), port);

    printf("host_basic_hi:\n");
    printf("\tmax_s: %d\n", hi.max_cpus);
    printf("\tavail_cpu: %d\n", hi.avail_cpus);
    printf("\tmemory_size: %u\n", hi.memory_size);
    printf("\tcpu_type: %d\n", hi.cpu_type);
    printf("\tcpu_subtype: %d\n", hi. cpu_subtype);
    printf("\tphysical_cpu: %d\n", hi.physical_cpu);
    printf("\tphysical_cpu_max: %d\n", hi.physical_cpu_max);
    printf("\tlogical_cpu: %d\n", hi.logical_cpu);
    printf("\tlogical_cpu_max: %d\n", hi.logical_cpu_max);
    printf("\tmax_mem: %u\n", hi.max_mem);

    // ファイルを開く
    fd = open(FILE_NAME, O_RDONLY);

    // ファイルサイズ
    fstat(fd, &st);
    size = st.st_size;

    // ファイルをメモリにマップする
    pstart = mmap(0, size, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
    p = pstart;
    
    // ファイルを閉じる
    close(fd);

    // 最初の 32 byte を読む
    // uint32_t のポインタとして代入
    magic = *(uint32_t *)p;

    // magic が FAT ヘッダの magic か見る
    if ((FAT_MAGIC == magic && !little_endian) ||
        (FAT_CIGAM == magic && little_endian)) {

        // FAT のマジックを読む
        printf("fat magic:\t0x%x\n", magic);

        // fat_header を読む
        fh = *(struct fat_header *)p;

        // big endian で書かれているので
        if (little_endian) {
            fh.nfat_arch = SWAP32(fh.nfat_arch);
        }

        printf("arch num:\t%d\n", fh.nfat_arch);

        // メモリの読み込む位置を fat_header のサイズ分進める
        p += sizeof(struct fat_header);

        for (i = 0; i < fh.nfat_arch; i ++) {

            // fat_arch を読む
            fa = *(struct fat_arch *)p;

            // big endian で書かれているので
            if (little_endian) {
                fa.cputype = SWAP32(fa.cputype);
                fa.cpusubtype = SWAP32(fa.cpusubtype);
                fa.offset = SWAP32(fa.offset);
                fa.size = SWAP32(fa.size);
                fa.align = SWAP32(fa.align);
            }

            printf("arch %d\n", i);
            printf("\tcputype %d:\t\n", fa.cputype);
            printf("\tcpusubtype %d:\t\n", fa.cpusubtype);
            printf("\toffset %d:\t\n", fa.offset);
            printf("\talign %d:\t\n", fa.align);

            // cputype が等しいのを選ぶ
            // cpusubtype の判定はかなりめんどくさそうだったので省略
            if(fa.cputype == hi.cpu_type) {
                puts("\tthis is my cputype object!");
                obj_offset = fa.offset;
                obj_size   = fa.size;
            }

            // 次の fat_arch に進む
            p += sizeof(struct fat_arch);
        }

        // 自分の cputype に合うオブジェクトがない場合は終了
        if (obj_offset == 0) {
            exit(-1);
        }
    }
    else {
        obj_offset = 0;
        obj_size = size;
    }

    p = pstart + obj_offset;

    // 32 bit 読む
    magic = *(uint32_t*)p;

    if (MH_MAGIC == magic) {

        // Mach-O ヘッダを読み込む
        mh = *(struct mach_header*)p;

        printf("mach-o magic:\t0x%x\n", mh.magic);
        printf("\tcputype: %d\n", mh.cputype);    
        printf("\tcpusubtype: %d\n", mh.cpusubtype); 
        printf("\tfiletype: 0x%x\n", mh.filetype);   
        printf("\tncmds: %d\n", mh.ncmds);      
        printf("\tsizeofcmds: %d\n", mh.sizeofcmds); 
        printf("\tflags: 0x%x\n", mh.flags);      

    }
    else if (MH_MAGIC_64 == magic) {

        // Mach-O ヘッダを読み込む
        mh64 = *(struct mach_header_64*)p;

        printf("mach-o magic:\t0x%x\n", mh64.magic);
        printf("\tcputype: %d\n", mh64.cputype);    
        printf("\tcpusubtype: %d\n", mh64.cpusubtype); 
        printf("\tfiletype: %x\n", mh64.filetype);   
        printf("\tncmds: %d\n", mh64.ncmds);      
        printf("\tsizeofcmds: %d\n", mh64.sizeofcmds); 
        printf("\tflags: 0x%x\n", mh64.flags);      

    }
    else {
        // Mach-O じゃない
        exit(-1);
    }

    return 0;
}

(つづく)