今回は、表題の通り x86/x86-64 の macOS でシステムコールをアセンブラから呼んでみる。 ただし、前回のエントリで FreeBSD についても同じようにシステムコールをアセンブラから呼んだ。 macOS は BSD を先祖に持つ XNU カーネルで動いている。 そのため、大筋は FreeBSD の場合と違いはない。 ようするに System V x86/x86-64 ABI の規約にもとづいて呼び出してやればいいだけだ。
とはいえ、FreeBSD と全く違いがないわけではない。 なので、それについて見ていくことにしよう。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.14.6 BuildVersion: 18G103 $ uname -sr Darwin 18.7.0 $ nasm -v NASM version 2.14.02 compiled on Dec 27 2018 $ ld -v @(#)PROGRAM:ld PROJECT:ld64-450.3 BUILD 18:16:53 Apr 5 2019 configured to support archs: armv6 armv7 armv7s arm64 arm64e arm64_32 i386 x86_64 x86_64h armv6m armv7k armv7m armv7em LTO support using: LLVM version 10.0.1, (clang-1001.0.46.4) (static support for 22, runtime is 22) TAPI support using: Apple TAPI version 10.0.1 (tapi-1001.0.4.1)
もくじ
- もくじ
- 下準備
- x86 のアセンブラで exit(2) を呼び出すだけのプログラム
- x86-64 のアセンブラで exit(2) を呼び出すだけのプログラム
- x86-64 のアセンブラでハローワールド
下準備
最初に、アセンブラの実装として NASM (Netwide Assembler) を Homebrew でインストールしておく。
$ brew install nasm
GAS (GNU Assembler) を使っても問題ないけど、INTEL 記法と AT&T 記法でちょっと手直しが必要になる。
x86 のアセンブラで exit(2) を呼び出すだけのプログラム
先のエントリと同じように、まずは終了するだけのプログラムを書いてみよう。
macOS にも、他の Unix 系 OS と同じようにプログラムを終了するためのシステムコールとして exit(2)
がある。
macOS のカーネルである XNU のソースコードは、リリースからやや遅れはあるもののオープンソースとして公開されている。
そのため、次のようにシステムコールの識別子を確認できる。
上記から、exit(1)
に対応する識別子が 1
であることがわかる。
ただし、実は上記の値をそのまま使ってもエラーになってしまう。
実際には、上記の識別子に 0x2000000
を加えなきゃいけない。
その理由は以下のファイルを見るとわかる。
どうやら macOS のカーネルである XNU が Mach と BSD から生まれた影響で、何に由来するシステムコールなのか指定が必要らしい。 その指定が、BSD であれば 2 を 24 ビット左シフトした値、ということみたいだ。
前置きが長くなったので、そろそろサンプルコードを見ることにしよう。
以下が exit(2)
を呼び出すだけのサンプルコードになる。
FreeBSD の x86 版と異なるのは、前述したマスク値がひとつ。
もう一つがエントリポイントのシンボルが _start
ではなく _main
になっている。
まあ、これはデフォルト値の話なので、リンカで別途上書きしてしまっても良いはず。
global _main section .text _syscall: int 0x80 ret _main: ; exit system call (macOS / x86) push dword 0 mov eax, 0x2000001 ; 2 << 24 + 1 call _syscall add esp, byte 4 ; restore ESP ret ; don't reach here
x86 (32bit) 向けにビルドする
コードが用意できたので、x86 (32bit) 向けのバイナリとして Mach-O 32bit
フォーマットにアセンブルする。
$ nasm -f macho32 nop.asm $ file nop.o nop.o: Mach-O object i386
動作に必要な最低バージョンの指定を入れつつ実行可能オブジェクトファイルにリンクする。
$ ld -macosx_version_min 10.14 -lsystem -o nop nop.o
$ file nop
nop: Mach-O executable i386
できたファイルを実行してみよう。
$ ./nop $ echo $? 0
ちゃんと返り値もゼロが設定されている。 うまく動いているようだ。
ちなみに、今回使っている macOS のバージョンが Mojave (10.14) だからこそ上記は動く。 なぜなら、次のバージョン Catalina (10.15) からは 32bit アプリケーションのサポートがなくなってしまった。 なので、おそらくこのマシンも OS をアップデートしたら上記のバイナリが動かなくなるはずだ。
x86-64 (64bit) 向けにビルドする
じゃあ 64bit 向けにビルドすれば良いのか、というとそうもいかない。 なぜなら、先のエントリで取り扱った通り System V ABI は x86 と x86-64 で引数の渡し方が異なっている。 そのため、先ほどのサンプルコードは x86-64 (64bit) 向けのアプリケーションとしては動作しない。
やろうと思えば、一応は Mach-O 64bit フォーマットとしてアセンブリはできる。
$ nasm -f macho64 nop.asm
$ file nop.o
nop.o: Mach-O 64-bit object x86_64
$ ld -macosx_version_min 10.14 -lsystem -o nop nop.o
$ file nop
nop: Mach-O 64-bit executable x86_64
しかし、実行しようとするとエラーになってしまう。
$ ./nop zsh: illegal hardware instruction ./nop
x86-64 のアセンブラで exit(2) を呼び出すだけのプログラム
64 bit 向けのアプリケーションを作るためには、以下のように System V v86-64 ABI に準拠した呼び出しが必要になる。
global _main section .text _main: ; exit system call (macOS / x86-64) mov rax, 0x2000001 mov rdi, 0 syscall
上記を Mach-O 64 bit フォーマットとしてアセンブリする。
$ nasm -f macho64 nop.asm $ file nop.o nop.o: Mach-O 64-bit object x86_64
そして、リンクする。
$ ld -macosx_version_min 10.14 -lsystem -o nop nop.o
$ file nop
nop: Mach-O 64-bit executable x86_64
できたファイルを実行してみよう。 今度はエラーにならない。
$ ./nop $ echo $? 0
返り値についても、ちゃんとゼロが設定されている。
x86-64 のアセンブラでハローワールド
一応、ハローワールドについても以下にサンプルコードを示す。
global _main section .data msg db 'Hello, World!', 0x0A msg_len equ $ - msg section .text _main: ; write system call (macOS / x86-64) mov rax, 0x2000004 mov rdi, 1 mov rsi, msg mov rdx, msg_len syscall ; exit system call (macOS / x86-64) mov rax, 0x2000001 mov rdi, 0 syscall
ビルドする。
$ nasm -f macho64 greet.asm
$ ld -macosx_version_min 10.14 -lsystem -o greet greet.o
実行する。
$ ./greet Hello, World! $ echo $? 0
うまく動いているようだ。
いじょう。
- 作者:もみじあめ
- 発売日: 2020/02/29
- メディア: Kindle版