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)");
}
```

```shell-session
$ g++ test_mov_rax.cpp -o test_mov_rax.out
$ ./test_mov_rax.out
zsh: bus error  ./test_mov_rax.out
```

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

```shell-session
$ 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)`に変えただけです。

実行してみましょう。

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

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

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

```shell-session
$ 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)");
}
```

```shell-session
$ 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でした。

# 関連記事

* [SIGSEGVとSIGILLのあいだ](https://qiita.com/kaityo256/items/99cd0c3ce2e7e38a0924)
* [x86でbus errorを起こす](https://qiita.com/kaityo256/items/fadffae00e517efbc8aa)