Mac
GCC
assembly

MacにおけるSIGSEGV/SIGBUS/SIGILL

はじめに

どうにもMacのSIGBUSやSIGILLを出すポリシーがよくわからないので、特徴的なコードをまとめておく。環境依存の可能性大。あとで何か見つけたら追加するかも。

環境はこんな感じ。

  • macOS High Sierra 10.13.3
  • g++ (Homebrew GCC 7.2.0) 7.2.0

結果

文字列領域への書き込み

文字列領域に何か書き込もうとするとSIGBUSが出ます。

test_const_char.cpp
int main(void){
  const char *str = "test";
  char *s = (char*)str;
  s[0] = 'a';
}

不正なジャンプ命令 その1

不正なジャンプ命令を発行するとSIGBUSが出ます。

test_jump_sigbus.cpp
int main(void){
  __asm__("jmp -6");
}

不正なジャンプ命令 その2

不正なジャンプ命令で、飛ぶアドレスを少し変えるとSIGILLが出ます。

test_jump_sigill.cpp
int main(void){
  __asm__("jmp -8");
}

不正なmov命令 その1

不正なアドレスを指定した間接ストアをすると、普通はSIGSEGVが出ます。

test_mov_sigsegv.cpp
int main(void) {
  __asm__("movq  $10000, %rax");
  __asm__("movl  $0, 0(%rbp,%rax,1)");
}

不正なmov命令 その2

しかし、レジスタの値を大きくすると、movでSIGILLが出ます。

test_mov_sigill.cpp
int main(void) {
  __asm__("movq  $10000000000, %rax"); // raxの値を大きくした
  __asm__("movl  $0, 0(%rbp,%rax,1)");
}

不正なmov命令 その3

デフォルトでraxの指すアドレスに何か書き込もうとするとSIGBUSが出ます。

test_mov_rax.cpp
int
main(void){
  __asm__("movq $0x100000f94, %rbx");
  __asm__("movq $0, (%rax)");
}
$ g++ test_mov_rax.cpp -o test_mov_rax.out
$ ./test_mov_rax.out
zsh: bus error  ./test_mov_rax.out

gdbを使うと、raxの値は0x100000f94であり、これがプログラムの先頭アドレスと一致していることがわかります。

$ objdump -S test_mov_rax.out

test_mov_rax.out:   file format Mach-O 64-bit x86-64

Disassembly of section __TEXT,__text:
__text:
100000f94:  55  pushq   %rbp
100000f95:  48 89 e5    movq    %rsp, %rbp
100000f98:  48 bb 98 0f 00 00 01 00 00 00   movabsq $4294971288, %rbx
100000fa2:  48 c7 00 00 00 00 00    movq    $0, (%rax)
100000fa9:  b8 00 00 00 00  movl    $0, %eax
100000fae:  5d  popq    %rbp
100000faf:  c3  retq

_main:
100000f94:  55  pushq   %rbp
100000f95:  48 89 e5    movq    %rsp, %rbp
100000f98:  48 bb 98 0f 00 00 01 00 00 00   movabsq $4294971288, %rbx
100000fa2:  48 c7 00 00 00 00 00    movq    $0, (%rax)
100000fa9:  b8 00 00 00 00  movl    $0, %eax
100000fae:  5d  popq    %rbp
100000faf:  c3  retq

そして、その値と同じ値をrbxに入れてあります。

不正なmov命令 その4

先程、raxの値は0x100000f94であり、そこに書き込もうとするとSIGBUSが出ました。rbxにはraxと同じ値が入っているので、(%rbx)にアクセスしてもSIGBUSが出ることが予想されます。

試してみましょう。

test_mov_rbx.cpp
int
main(void){
  __asm__("movq $0x100000f94, %rbx");
  __asm__("movq $0, (%rbx)");
}

さっきのコードの(%rax)(%rbx)に変えただけです。

実行してみましょう。

$ g++ test_mov_rbx.cpp -o test_mov_rbx.out
$ ./test_mov_rbx.out
zsh: segmentation fault  ./test_mov_rbx.out

SIGSEGVが出ました。全く同じ場所を触っているのに謎です。

gdbで追いかけてみましょう。

$ gdb ./test_mov_rbx.out
(gdb) b main
Breakpoint 1 at 0x100000f98
(gdb) r
Thread 2 hit Breakpoint 1, 0x0000000100000f98 in main ()
(gdb) stepi   # rbxに0x100000f94   を代入
0x0000000100000fa2 in main ()
(gdb) i r rax rbx
rax            0x100000f94  4294971284
rbx            0x100000f94  4294971284  # この時点でrax=rbx
(gdb) stepi

Thread 2 received signal SIGBUS, Bus error.  # ←SIGBUSが出てる
0x0000000100000fa2 in main ()

gdbで追いかけると、予想どおりSIGBUSが出ていますが、なぜかプログラム単体で実行するとSIGSEGVが出ます。シグナルが上書きされているのでしょうか・・・?

不正なmov命令 その5

raxと同じ内容をrbxに代入した上で(%rbx)に何か書き込むとSIGSEGVが出ますが、movでraxの内容をrbxにコピーしてから(%rbx)に何か書き込むとSIGBUSが出ます。

test_mov_raxcopy.cpp
int
main(void){
  __asm__("movq %rax, %rbx");
  __asm__("movq $0, (%rbx)");
}
$ g++ test_mov_raxcopy.cpp -o test_mov_raxcopy.out
$ ./test_mov_raxcopy.out
zsh: bus error  ./test_mov_raxcopy.out

まとめ

MacのSIGNALのポリシーは謎です。ちなみにLinuxではtest_mov_sigill.cppがSIGBUSで、それ以外は全てSIGSEGVでした。

関連記事