はじめに
x86システムでは、あまりbus errorは見かけない。どういう場合にbus error (SIGBUS)が発生するか調べた。タイトルにはx86と書いたが、一応SPARCも調べた。
Bus errorについて
Bus error (SIGBUS)は、その名の通りバスで発生したエラー。CPUが物理メモリにアクセス要求を出した時にエラーが起きると発生する(ということになっている)。普通、ユーザプログラムから見えているのは論理アドレスであり、物理アドレスを直接触ることは無いことから、ユーザプログラムからBus errorを起こすことは難しい。また、アラインメントエラーなどでも起きることが知られているが、x86ではアライメントのチェックが通常はオフになっているため、これは起きない。とりあえず調べた限りにおいては、「SIGBUSを発生させるシステムコールを呼ぶ」「アラインメントチェックを有効にした上でアラインメントエラーを起こす」の二通りでBus errorを起こせることがわかった。
mmapを使う
StackOverflowにあった例。mmapで確保した領域外を触るとSIGBUSが出る。
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
int main() {
FILE *f = tmpfile();
int *m = (int*)mmap(0, 4, PROT_WRITE, MAP_PRIVATE, fileno(f), 0);
*m = 0;
return 0;
}
$ gcc bus.c
$ ./a.out
[1] 16871 bus error ./bus
man mmapにもその旨の記述がある。
The system shall always zero-fill any partial page at the end of an object. Further, the system shall never write out any modified portions of the last page of an object which are beyond its end. References within the address range starting at pa and continuing for len bytes to whole pages following the end of an object shall result in delivery of a SIGBUS signal.
また、mmapのソースでも明示的にVM_FAULT_SIGBUSを返している。
アラインメント不整合の例
ここで見つけた、charで確保しておいてintでアクセスする例。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char *p = (char*)malloc(sizeof(int) + 1);
memset(p, 0, sizeof(int) + 1);
p++;
printf("%d\n", *(int *)p);
return 0;
}
これはアラインメント不整合があるにもかかわらず、正常終了する。これはx86のEFLAGSのAlignment Check (AC)フラグがオフになっているから。これをオンにするとBus errorが発生する。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
asm( "pushf\n\torl $0x40000,(%rsp)\n\tpopf");
char *p = (char*)malloc(sizeof(int) + 1);
memset(p, 0, sizeof(int) + 1);
p++;
printf("%d\n", *(int *)p);
return 0;
}
なお、ACフラグをオンにしていても、Mac OS Xではbus errorは起きず、正常終了する。
文字列領域への不正アクセス
文字列領域にアクセスしてみる。
#include <stdio.h>
#include <stdlib.h>
int
main(void){
char *s = "test";
s[0] = '0';
printf("%s\n",s);
}
これは、LinuxではSegmentation Fault (SIGSEGV)になるが、MacではBus errorを出す。
$ clang bus3.c
$ ./a.out
Bus error: 10
まとめ
まとめるとこんな感じ。Cygwinの結果も入れてみた。あとSPARCのLinuxシステムがあったので、そこでも調べてみた(インラインアセンブリ使ってるbus2p.cは除く)。
bus.c | bus2.c | bus2p.c | bus3.c | |
---|---|---|---|---|
Linux | SIGBUS | 正常終了 | SIGBUS | SIGSEGV |
Mac OS X | SIGBUS | 正常終了 | 正常終了 | SIGBUS |
Cygwin | SIGBUS | 正常終了 | 正常終了 | SIGSEGV |
SPARC | SIGSEGV | SIGBUS | ---- | SIGSEGV |
今時のx86システムにおいて、明示的にアラインメントチェックを有効にしてアラインメント不整合アクセスをするか、SIGBUSを出すシステムコールを使わない限り、Bus errorはユーザプログラムからは出てこない感じ。例外はMacで、SIGBUSとSIGSEGVの区別が曖昧という指摘もあり、文字列にアクセスするだけでbus errorが出る。同じLinuxでもSPARCだと、動作がかなり異なる。アラインメント不正でSIGBUSを出すのはそうだと思うけど、mmapの不正でSIGSEGVを出すのはなぜかよくわからない(POSIXで決まってることじゃないのかな?)。
っていうか調べたシステム全てで動作が全部違うって嫌だなぁ。