揮発性のメモ2

http://d.hatena.ne.jp/iww/

gccで、スレッド間で同じメモリを同時にアクセスしてる人を探してぶっ殺したい

gccで、スレッド間で同じメモリを同時にアクセスしてる人を探してぶっ殺したい。

そのためにはgccの TSAN(Thread Sanitizer)機能を使う

-fsanitize=thread

ソース
#include <pthread.h>
#include <stdio.h>

volatile long long A=0;

void *gettidtest( void *arg )
{
    int number = (int)(long) arg;
    long long i;

    printf("thread=%d start\n", number);
    for(i=0;i<1000000LL;i++){
        A++;     // 排他制御なし
//      __atomic_add_fetch( &A, 1, __ATOMIC_RELAXED );   // アトミック加算
    }
    printf("thread=%d end\n", number);

    return NULL;
}

int main( int argc, char **argv )
{
    int i;
    pthread_t p[10]={};

    for(i=0;i<10;i++){
        pthread_create( &p[i], NULL, gettidtest, (void*)(long)i );
    }
    for(i=0;i<10;i++){
        pthread_join( p[i], NULL );
    }
    printf("A=%lld\n",A);

    return 0;
}
コンパイル
$ gcc -g -fsanitize=thread thread.c
実行結果

競合したところを教えてくれる

問題点
  • 競合を発見しても終了しない
    -fno-sanitize-recover=all が効かず、検知後もプログラムは続行する
  • -fsanitize=address と一緒には使えない
    どちらか一方しか使えない
  • x86_64 でしかサポートしてない
    Cygwinもダメ、32bitもダメ、ARMもダメ
続きを読む

gccで、スタックを破壊している人を探してぶっ殺したい

gccで、スタックを破壊している人を探してぶっ殺したい。

#include <stdio.h>
#include <string.h>

void fn1()
{
    int c=0;

    // intは4バイト。5~12で静かに破壊。13以上でSegv
    memset(&c, 0x55, 16);
    // memsetが最適化で消えないように
    printf("c=0x%x\n", c);
}

int main()
{
    fn1();
    return 0;
}
$ gcc -g -fstack-protector-all stack.c

$ ./a.out
c=0x55555555
*** stack smashing detected ***: terminated
Aborted (core dumped)

スタックを破壊するとAbortしてくれる。 ulimitを設定しておけばcoreも吐く。

ただし、「マジックナンバーを敷き詰めておいて、誰かが破壊したあとに照合して検知する」という方法らしいので、派手に破壊されたときはcoreを見ても意味がない。

-fsanitize=address でもスタックオーバーフローは検知してくれるしこれで検知できたときはスタックの中身がピンピンしてるので、 -fstack-protector は使わないかも

依存ライブラリが無いので、ASANが使えない化石みたいに古い環境なら利用価値があるかも

gccで、構造体の中の配列の範囲を超えてアクセスしてる人を探してぶっ殺したい

gccで、配列の範囲を超えてアクセスしてる人を探してぶっ殺したい - 揮発性のメモ2

gccで、構造体の中の配列の範囲を超えてアクセスしてる人を探してぶっ殺したい。
しかし、 -fsanitize=address で検知できるのは「構造体をはみ出たかどうか」のみである。
配列の範囲は -fsanitize=bounds で検知する

サンプルコード

#include <stdio.h>

struct hoge {
    int i;
    char a[15];
};


int main()
{
    struct hoge hoge = {};
    printf("sizeof(hoge)=%ld\n", sizeof(struct hoge));
    
    
    hoge.a[15] = 1; // ★A
    printf("%d\n", hoge.a[15]);
    hoge.a[16] = 1; // ★B
    printf("%d\n", hoge.a[16]);
    
    return 0;
}
gcc -g -fsanitize=address sani.c

構造体が配列にぴっちりしているとき

struct hoge {
    char a[15];
};

struct hoge のサイズは 15バイト(char 15バイト)となる。
よって、hoge.a[15] は配列を超えているし構造体もはみ出ているので検知される。

構造体がアライメントに合わされてパディングがついてるとき

struct hoge {
    int i;
    char a[15];
};

struct hoge のサイズは 20バイト(int 4バイト + char 15バイト + パディング 1バイト)となる。
よって、hoge.a[15] は配列を超えてはいるが構造体の中なので 検知されない。
hoge.a[16] は構造体をはみ出ているので検知される。

構造体で配列の定義が先になっているとき

struct hoge {
    char a[15];
    int i;
};

struct hoge のサイズは 同じく20バイトとなる。
しかし、配列を超えても超えても次の変数があるので
hoge.a[15] も hoge.a[16] も構造体の中であるため検知されない。

-fsanitize=bounds

sanitize=bounds ならどこの配列だろうと範囲越えをチェックできる

gcc -g -fsanitize=address -fsanitize=bounds sani.c

やったぜ

gccで、どんな最適化がかかっているかを見る

gcc -Q --help=optimizers

このコマンドで、どんなオプティマイズオプションがあるか、どれが有効になっているか を確認できる

$ gcc -Q --help=optimizers
The following options control optimizations:
  -O<number>
  -Ofast
  -Og
  -Os
  -Oz
  -faggressive-loop-optimizations       [enabled]
  -falign-functions                     [disabled]
  -falign-functions=
  -falign-jumps                         [disabled]
(後略)
-fno-omit-frame-pointer

レジスタEBPを汎用レジスタに使わせないように指示するオプション。
レジスタEBPは本来はスタック用のポインタだけど、-O2とかの最適化オプションを指定すると勝手に使いまわされちゃって、死んだときcore見てもうまくバックトレースできないことがあって嫌なので、それを抑止する

なにも指定しないとdisabled

$ gcc -Q --help=optimizers | grep omit
  -fomit-frame-pointer                  [disabled]

-O2をつけると自動でenabledになる

$ gcc -Q --help=optimizers -O2 | grep omit
  -fomit-frame-pointer                  [enabled]

-fno-omit-frame-pointer でdisabledにおさえつける

$ gcc -Q --help=optimizers -O2 -fno-omit-frame-pointer | grep omit
  -fomit-frame-pointer                  [disabled]

gccで、配列の範囲を超えてアクセスしてる人を探してぶっ殺したい

gccで、配列の範囲を超えてアクセスしてる人を探してぶっ殺したい。
そのためにはgccの ASAN(Address Sanitizer)機能を使う

-fsanitize=address

ソース
#include <stdio.h>
int main()
{
    char buf[15];

    printf("MAIN\n");
    for(int i=0; i<16; i++){
        printf("%08X:buf[%d]\n", &buf[i], i);
        buf[i] = i;
    }

    return 0;
}

配列が15個しかないのに16回まわるのではみ出る、というサンプルプログラム

コンパイル
gcc -g -fsanitize=address main.c

-gオプションは無くてもよいが、つけると検知時に行番号が表示されるのでつける

実行結果
MAIN
621FF520:buf[0]
621FF521:buf[1]
(中略)
621FF52E:buf[14]
621FF52F:buf[15]
=================================================================
==1594509==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd621ff52f at pc 0x55660474d2aa bp 0x7ffd621ff4e0 sp 0x7ffd621ff4d8
WRITE of size 1 at 0x7ffd621ff52f thread T0
    #0 0x55660474d2a9 in main /tmp/main.c:9
    #1 0x7f68a29f8189 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #2 0x7f68a29f8244 in __libc_start_main_impl ../csu/libc-start.c:381
    #3 0x55660474d0e0 in _start (/tmp/a.out+0x10e0)

Address 0x7ffd621ff52f is located in stack of thread T0 at offset 47 in frame
    #0 0x55660474d1b8 in main /tmp/main.c:3

  This frame has 1 object(s):
    [32, 47) 'buf' (line 4) <== Memory access at offset 47 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /tmp/main.c:9 in main

はみ出た時点でABORTする。 ちゃんと何行目ではみ出たかを検知している。
(はみ出なければ何も起きない)

-fsanitize=bounds

配列をはみ出たことだけを検知したいときは bounds を使う。
またこのときは ABORTせずに適当にごまかして先に進めてしまうので、それを回避したいときは
-fno-sanitize-recover=all オプションも指定すると、そこでプログラムが終了する。

コンパイル
gcc -g -fno-sanitize-recover=all -fsanitize=bounds main.c
実行結果
MAIN
9ED8575D:buf[0]
9ED8575E:buf[1]
(中略)
9ED8576B:buf[14]
9ED8576C:buf[15]
main.c:9:12: runtime error: index 15 out of bounds for type 'char [15]'

情報量が無いしcoreも吐けないので これはまあ使わない

コアダンプの設定

ABORTしたときにcoreを吐かせる方法。
まずそもそもulimitでコアが出るようにした上で、環境変数ASAN_OPTIONSでcoreを吐くように設定する

実行結果
$ ulimit -c unlimited
$ export ASAN_OPTIONS=abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1

$ ./a.out
(中略)
==1594730==ABORTING
Aborted (core dumped)

吐かれたcoreには、はみ出しを検知してから実際にABORTするまでの間に ASANの関数がいっぱい挟まるので 解析利用時には最後の8~9層くらいは読み飛ばす

処理能力

3倍くらい遅くなる気がする

Googleドライブで、「エラーが発生しました。」と出て同期されないとき

Googleドライブで、「エラーが発生しました。」と出て同期されないとき

エラーの原因はだいたいファイルがロックされているとかの場合。
このエラーが出ると、ロックしていたアプリが終了し問題が解消しても同期は再開されない。

解消する方法は

エラーリスト画面から再試行する

タスクトレイの右クリックメニューから 歯車⇒「エラーリスト」をクリック
ファイルの一覧が表示されるので、右の ・・・ でアップロードさせる。
全部解消したら、同期が再開する

Googleドライブの再起動

エラーリストの数が多いときはやってられないので、
Googleドライブをいったん終了し、再起動する。
一時停止⇒再開 ではエラーは解消されず再開しない