url: https://dminor11th.blogspot.com/2011/07/mach-o.html
title: "Mach-Oファイル形式と、それにまつわるツール"
description: "「Mach-O」とは Mac OS X におけるオブジェクトファイルのフォーマットのこと(LinuxにおけるELFみたいなもの)。詳しいことは apple.com のドキュメント「 Mac OS X ABI Mach-O File Format Reference 」にとても分か..."
host: dminor11th.blogspot.com
favicon: https://dminor11th.blogspot.com/favicon.ico
url: https://zenn.dev/edom18/articles/mach-o-format
title: "Mach-O フォーマットを調べがてら otool 風出力をしてみる"
host: zenn.dev
image: https://res.cloudinary.com/zenn/image/upload/s--l142Pxvu--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:Mach-O%2520%25E3%2583%2595%25E3%2582%25A9%25E3%2583%25BC%25E3%2583%259E%25E3%2583%2583%25E3%2583%2588%25E3%2582%2592%25E8%25AA%25BF%25E3%2581%25B9%25E3%2581%258C%25E3%2581%25A6%25E3%2582%2589%2520otool%2520%25E9%25A2%25A8%25E5%2587%25BA%25E5%258A%259B%25E3%2582%2592%25E3%2581%2597%25E3%2581%25A6%25E3%2581%25BF%25E3%2582%258B%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:Kazuya%2520Hiruma%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2YxNTFjMzAzY2MuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png

Mach-O とは?

Imgur

url: https://ja.wikipedia.org/wiki/Mach-O
title: "Mach-O - Wikipedia"
host: ja.wikipedia.org
favicon: https://ja.wikipedia.org/static/favicon/wikipedia.ico

Mach-O は オブジェクトファイルや実行ファイルのフォーマットの 1 つです。 #OSX においてはこれが標準のバイナリファイルフォーマットになっています。

Mach-O プログラムを調べるには otool というコマンドを使うとよいです。 これは Linux における readelf です。

OTOOL-CLASSIC(1)                                                                                           General Commands Manual                                                                                           OTOOL-CLASSIC(1)
 
NAME
       otool-classic - object file displaying tool
 
SYNOPSIS
       otool-classic [ option ...  ] [ file ...  ]
 
DESCRIPTION
       The otool-classic command displays specified parts of object files or libraries. It is the preferred tool for inspecting Mach-O binaries, especially for binaries that are bad, corrupted, or fuzzed. It is also useful in situations
       when inspecting files with new or "bleeding-edge" Mach-O file format changes.

otool で実行ファイルを調べてみる

-h で Mach-O ファイルのヘッダを表示します。 CPU の種類や EXECUTE 実行可能タイプである、などが記されています。

playground/c-compile-assemble on feature/learn-c-compile-assemble via C v17.0.0-clang [☁️ ]
 otool -hv a.out
a.out:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64        ALL  0x00     EXECUTE    18       1208   NOUNDEFS DYLDLINK TWOLEVEL PIE

Mach-O において最初にきます。

text

-t で .text つまり主記憶装置メモリにロードされる命令領域を表示できます。

main, doubled, add というラベル (関数) が存在し、それらのアセンブリコードが機械語で書かれていることがわかります。

playground/c-compile-assemble on feature/learn-c-compile-assemble via C v17.0.0-clang [☁️ ]
 otool -tv a.out
a.out:
(__TEXT,__text) section
_main:
00000001000004f8        sub     sp, sp, #0x30
00000001000004fc        stp     x29, x30, [sp, #0x20]
0000000100000500        add     x29, sp, #0x20
0000000100000504        mov     w8, #0x0
0000000100000508        str     w8, [sp, #0x8]
000000010000050c        stur    wzr, [x29, #-0x4]
0000000100000510        stur    w0, [x29, #-0x8]
0000000100000514        str     x1, [sp, #0x10]
0000000100000518        mov     w0, #0x5
000000010000051c        mov     w1, #0x8
0000000100000520        bl      _add
0000000100000524        str     w0, [sp, #0xc]
0000000100000528        ldr     w8, [sp, #0xc]
000000010000052c        mov     x9, sp
0000000100000530        str     x8, [x9]
0000000100000534        adrp    x0, 0 ; 0x100000000
0000000100000538        add     x0, x0, #0x5bc ; literal pool for: "Sum = %d\n"
000000010000053c        bl      0x1000005b0 ; symbol stub for: _printf
0000000100000540        ldr     w0, [sp, #0x8]
0000000100000544        ldp     x29, x30, [sp, #0x20]
0000000100000548        add     sp, sp, #0x30
000000010000054c        ret
_doubled:
0000000100000550        sub     sp, sp, #0x10
0000000100000554        str     w0, [sp, #0xc]
0000000100000558        ldr     w8, [sp, #0xc]
000000010000055c        lsl     w0, w8, #1
0000000100000560        add     sp, sp, #0x10
0000000100000564        ret
_add:
0000000100000568        sub     sp, sp, #0x20
000000010000056c        stp     x29, x30, [sp, #0x10]
0000000100000570        add     x29, sp, #0x10
0000000100000574        stur    w0, [x29, #-0x4]
0000000100000578        str     w1, [sp, #0x8]
000000010000057c        adrp    x8, 8 ; 0x100008000
0000000100000580        add     x8, x8, #0x0
0000000100000584        ldr     w8, [x8]
0000000100000588        str     w8, [sp, #0x4]
000000010000058c        ldur    w0, [x29, #-0x4]
0000000100000590        bl      _doubled
0000000100000594        ldr     w8, [sp, #0x4]
0000000100000598        add     w8, w8, w0
000000010000059c        ldr     w9, [sp, #0x8]
00000001000005a0        add     w0, w8, w9
00000001000005a4        ldp     x29, x30, [sp, #0x10]
00000001000005a8        add     sp, sp, #0x20
00000001000005ac        ret

reverse-assemblel せずにそのままバイトデータを表示するときは下記のように -t だけつけます。 d100c3ff = sub sp, sp, #0x30 であり、objdump を使ってみる の逆アセンブルでも同様の結果が得られています。

命令は 4 byte 単位の chunk で表され、たとえば d100c3ff = 4byte で 1 命令 (sub sp, sp, 0x30 という opcode + operand) です。

  • 00000001000004f8 ~ 4fb に d100c3ff
  • 00000001000004fc ~ 4ff に a9027bfd

と命令が格納されています。

playground/c-compile-assemble on feature/learn-c-compile-assemble via C v17.0.0-clang [☁️ ]
 otool -t a.out
a.out:
(__TEXT,__text) section
00000001000004f8 d100c3ff a9027bfd 910083fd 52800008
0000000100000508 b9000be8 b81fc3bf b81f83a0 f9000be1
0000000100000518 528000a0 52800101 94000012 b9000fe0
0000000100000528 b9400fe8 910003e9 f9000128 90000000
0000000100000538 9116f000 9400001d b9400be0 a9427bfd
0000000100000548 9100c3ff d65f03c0 d10043ff b9000fe0
0000000100000558 b9400fe8 531f7900 910043ff d65f03c0
0000000100000568 d10083ff a9017bfd 910043fd b81fc3a0
0000000100000578 b9000be1 90000048 91000108 b9400108
0000000100000588 b90007e8 b85fc3a0 97fffff0 b94007e8
0000000100000598 0b000108 b9400be9 0b090100 a9417bfd
00000001000005a8 910083ff d65f03c0

load command

url: https://zenn.dev/edom18/articles/mach-o-format#%E3%83%AD%E3%83%BC%E3%83%89%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89
title: "Mach-O フォーマットを調べがてら otool 風出力をしてみる"
host: zenn.dev
image: https://res.cloudinary.com/zenn/image/upload/s--l142Pxvu--/c_fit%2Cg_north_west%2Cl_text:notosansjp-medium.otf_55:Mach-O%2520%25E3%2583%2595%25E3%2582%25A9%25E3%2583%25BC%25E3%2583%259E%25E3%2583%2583%25E3%2583%2588%25E3%2582%2592%25E8%25AA%25BF%25E3%2581%25B9%25E3%2581%258C%25E3%2581%25A6%25E3%2582%2589%2520otool%2520%25E9%25A2%25A8%25E5%2587%25BA%25E5%258A%259B%25E3%2582%2592%25E3%2581%2597%25E3%2581%25A6%25E3%2581%25BF%25E3%2582%258B%2Cw_1010%2Cx_90%2Cy_100/g_south_west%2Cl_text:notosansjp-medium.otf_37:Kazuya%2520Hiruma%2Cx_203%2Cy_121/g_south_west%2Ch_90%2Cl_fetch:aHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL3plbm4tdXNlci11cGxvYWQvYXZhdGFyL2YxNTFjMzAzY2MuanBlZw==%2Cr_max%2Cw_90%2Cx_87%2Cy_95/v1627283836/default/og-base-w1200-v2.png

ヘッダに続いて配置されるデータがロードコマンドです。 OSX のローダーに対する命令であり、プログラムをメモリにロードするために必要な情報を OS に伝えます。

ロードコマンドをみることで、 OSX の Mach-O 実行ファイルのプログラムがどう主記憶装置メモリの番地に展開・ロードされるのかがわかります。

vmaddr = 仮想アドレスの 0x0000000000000000 に 0x0000000100000000 のサイズのロードコマンド 0 が配置される、ということがわかります。

 otool -l a.out
a.out:
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
   vmaddr 0x0000000000000000
   vmsize 0x0000000100000000
  fileoff 0
 filesize 0
  maxprot 0x00000000
 initprot 0x00000000
   nsects 0
    flags 0x0

また、 .text を詳しくみたときにでてきた 0x00000001000004f8 が再びでてきています。 これは 0x00000001000004f8 仮想アドレス番地から 0x00000000000000b8 のサイズだけ命令が配置される、ということがわかります。

Section
  sectname __text
   segname __TEXT
      addr 0x00000001000004f8
      size 0x00000000000000b8
    offset 1272
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x80000400
 reserved1 0
 reserved2 0

.data の仮想アドレス上の配置場所もわかります。

Section
  sectname __data
   segname __DATA
      addr 0x0000000100008000
      size 0x0000000000000004
    offset 32768
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x00000000
 reserved1 0
 reserved2 0

stack-frame やヒープの領域はロードコマンドからはわかりません。 実行時に動的に割り当てられるため、 gdb lldb でメモリマップを調べます。

data

-d でデータ領域を確認できます。 0000000100008000 に globalW の値 0x64 = 100 が格納されていることがわかります。

 otool -dv a.out
a.out:
(__DATA,__data) section
0000000100008000        00000064