Mach-O形式の調査~導入編~

PEフォーマットは以前調査したので、今度はApple製品で採用されているMach-O(マークオー)形式の調査をしてみたいと思います。まずは最小サンプルの実行体を用意して既存ツールでちょっと確認してみます。

このようなサンプルを準備して、依存する他のライブラリの情報がどうなっているかを確認します。

$ otool -L sample01.out
sample01.out:
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

$ otool -L sample02.out 
sample02.out:
  /usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 56.0.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)

Mach-O形式は Appleのサイトに仕様がきちんと公開されているのでこれを見ればよいです(Mac OS X ABI Mach-O File Format Reference)。
この情報を元に自分のプログラムコードで各情報を取得&表示ということをしばらくはやってみたいと思います。

Mach-O概要

Mach-O(マーク・オー, Mach object)はMacで採用されているアプリバイナリインターフェース(ABI)です。ファイルの構造としては先頭にヘッダ構造を持ち、ロードコマンド、データ(各セグメントやセクション情報)、と記録されています。

ヘッダ部分では、マジックナンバーと対象とするアーキテクチャ情報、後続のデータに関する個数などの情報を記録しています。
ロードコマンド部分では、後続するデータ部分の位置・構造についての情報を保持しています。
データ部分には、セグメントが記録され、この中にセクション情報が複数個入っています。セグメントはアプリケーションをロードする際に仮想メモリにどのように領域をマッピングするかといった情報を定義しています。ページ属性もここに含まれます。

先頭のヘッダ部分についての構造は以下となります。32bit,64bitで最初から構造体が違うので注意が必要です。

これらの構造体は loader.h にて定義されています。
この cputypeメンバが CPU_TYPE_I386 や CPU_TYPE_x86_64 は Intel Macを示すとのことです。そもそも 32bit/64bitでは magicメンバがすでに違うようで、MH_MAGIC(0xfeedface) と MH_MAGIC_64(0xfeedfacf) とマジックが異なっていました。

この後続くロードコマンド部分は load_command構造体を先頭とするデータブロックの配列として記録されています。

このcmdメンバの値に応じて後続するデータの意味が変わり、ブロックのサイズとしては cmdSize が示す物となっています。cmdの指す内容は、 LC_SEGMENT, LC_SEGMENT_64, LC_SYMTAB,… と多岐にわたります。これについては公式リファレンスの Table 4 を参照して下さい。

mach_headerの部分を読み込んで表示してみるプログラムを作りました。
実行するとこのようになります。

Mach-O 64bit
  CPU TYPE      : x86_x64
  CPU SUB TYPE  : i386 ALL(Lib64)
  FileType      : MH_EXECUTE
  Cmd(s)        : 16
  CmdSize       : 1296
  Flag(s)       : MH_NOUNDEFS MH_TWOLEVEL MH_PIE (200085)

このプログラムはWindows(VisualStudio2012)で作成したので、定義を自前で行っているためコードが長めです。Mac環境ならば、loader.hやmach/machine.h のヘッダインクルードですむところが多いです。

このヘッダの後にはロードコマンドが続きます。とりあえずロードコマンドの中身のうち、後続のデータであるセクションに関する物(LC_SEGMENT,LC_SEGMENT_64)だけ表示して構造を追いかけてみます。

プログラムコードは以下のようになります。

実行結果

Load Command(s)
  [ 0] : LC_SEGMENT_64
    __PAGEZERO
    vmaddr=0x0000000000000000, vmsize=0x0000000100000000
    fileoff=0x0000000000000000, filesize=0x0000000000000000
    maxprot=0x00000000, initprot=0x00000000
    nSect(s)=0
  [ 1] : LC_SEGMENT_64
    __TEXT
    vmaddr=0x0000000100000000, vmsize=0x0000000000001000
    fileoff=0x0000000000000000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000005
    nSect(s)=6
  [ 2] : LC_SEGMENT_64
    __DATA
    vmaddr=0x0000000100001000, vmsize=0x0000000000001000
    fileoff=0x0000000000001000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000003
    nSect(s)=2
  [ 3] : LC_SEGMENT_64
    __LINKEDIT
    vmaddr=0x0000000100002000, vmsize=0x0000000000001000
    fileoff=0x0000000000002000, filesize=0x0000000000000130
    maxprot=0x00000007, initprot=0x00000001
    nSect(s)=0
  [ 4] : 0x80000022, 48
  [ 5] : 0x2, 24
  [ 6] : 0xb, 80
  [ 7] : 0xe, 32
  [ 8] : 0x1b, 24
  [ 9] : 0x24, 16
  [10] : 0x2a, 16
  [11] : 0x80000028, 24
  [12] : 0xc, 56
  [13] : 0x26, 16
  [14] : 0x29, 16
  [15] : 0x2b, 16

表示させてみてわかったことは LoadCommandのセグメント種が出現した後には、さらにセクション用のデータが続く場合があるということです。これも解釈して表示するようにしてみたところ以下の結果となりました。

Load Command(s)
  [ 0] : LC_SEGMENT_64
    __PAGEZERO
    vmaddr=0x0000000000000000, vmsize=0x0000000100000000
    fileoff=0x0000000000000000, filesize=0x0000000000000000
    maxprot=0x00000000, initprot=0x00000000
    nSect(s)=0
  [ 1] : LC_SEGMENT_64
    __TEXT
    vmaddr=0x0000000100000000, vmsize=0x0000000000001000
    fileoff=0x0000000000000000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000005
    nSect(s)=6
      section[0] : __TEXT [__text]
      section[1] : __TEXT [__stubs]
      section[2] : __TEXT [__stub_helper]
      section[3] : __TEXT [__cstring]
      section[4] : __TEXT [__unwind_info]
      section[5] : __TEXT [__eh_frame]
  [ 2] : LC_SEGMENT_64
    __DATA
    vmaddr=0x0000000100001000, vmsize=0x0000000000001000
    fileoff=0x0000000000001000, filesize=0x0000000000001000
    maxprot=0x00000007, initprot=0x00000003
    nSect(s)=2
      section[0] : __DATA [__nl_symbol_ptr]
      section[1] : __DATA [__la_symbol_ptr]
  [ 3] : LC_SEGMENT_64
    __LINKEDIT
    vmaddr=0x0000000100002000, vmsize=0x0000000000001000
    fileoff=0x0000000000002000, filesize=0x0000000000000130
    maxprot=0x00000007, initprot=0x00000001
    nSect(s)=0

このセクション用のデータとは以下の構造体です.
2014/05現在、Appleのリファレンスに記載してある section_64構造体が間違っています。下記のものが正しいです(ヘッダでもこうなっているので、ドキュメントのミスでしょう)

まとめ

PEフォーマットと比べるとシンプルな構造のように思えます。ここまでの関連性について図示してみるとこうなりました。ロードコマンドの部分もいわゆるチャンク構造のようになっています。このMach-o形式は実行体だけでなく中間形式でも使われているようです。

macho-format

シェアする

  • このエントリーをはてなブックマークに追加

フォローする