36
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

x86でbus errorを起こす

Posted at

はじめに

x86システムでは、あまりbus errorは見かけない。どういう場合にbus error (SIGBUS)が発生するか調べた。タイトルにはx86と書いたが、一応SPARCも調べた。

Bus errorについて

Bus error (SIGBUS)は、その名の通りバスで発生したエラー。CPUが物理メモリにアクセス要求を出した時にエラーが起きると発生する(ということになっている)。普通、ユーザプログラムから見えているのは論理アドレスであり、物理アドレスを直接触ることは無いことから、ユーザプログラムからBus errorを起こすことは難しい。また、アラインメントエラーなどでも起きることが知られているが、x86ではアライメントのチェックが通常はオフになっているため、これは起きない。とりあえず調べた限りにおいては、「SIGBUSを発生させるシステムコールを呼ぶ」「アラインメントチェックを有効にした上でアラインメントエラーを起こす」の二通りでBus errorを起こせることがわかった。

mmapを使う

StackOverflowにあった例。mmapで確保した領域外を触るとSIGBUSが出る。

bus.c
#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でアクセスする例。

bus2.c
#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が発生する。

bus2p.c
#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は起きず、正常終了する。

文字列領域への不正アクセス

文字列領域にアクセスしてみる。

bus3.c
#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で決まってることじゃないのかな?)。

っていうか調べたシステム全てで動作が全部違うって嫌だなぁ。

36
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?