2011-01-28 11 views
11

私は誰かがシグナルハンドラから呼んでいることを知りました。私が書いた非同期シグナル安全な機能ではありません。そして、もちろん、私は責任がある(私の文書の警告にもかかわらず)。 (同じコーダーが、シグナルハンドラから非同期シグナルセーフな機能をすべて呼び出そうとしています。)シグナルハンドラコンテキストでコードが実行されているかどうかを調べるには?

私は今、この状況を回避する方法を再現していますか?私は(言語はCですが、解決策は、任意の言語に適用されないのでしょうか?)私のコードは、シグナルハンドラのコンテキストで実行されている場合は容易に判断できるようにしたいと思います。これは、Linuxの下で

int myfunc(void) { 
    if(in_signal_handler_context()) { return(-1) } 
    // rest of function goes here 
    return(0); 
} 

です。 これは簡単な答えではない、そうでなければ私は馬鹿のように感じるでしょう。

+0

。しかし、プログラマーが最初にシグナルの概念に遭遇すると、この種のことは常に起こります。 C++オブジェクトのデストラクタを強制的にシグナルハンドラから呼び出すとどうなるでしょうか? – smcdow

答えて

7

新しいLinux/x86(おそらく2.6.xカーネル以降)は、vdsoからシグナルハンドラを呼び出します。この事実を使って、疑いのない世界に次のひどいハックを起こすことができます:

#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <string.h> 
#include <signal.h> 

#include <unistd.h> 

uintmax_t vdso_start = 0; 
uintmax_t vdso_end = 0;    /* actually, next byte */ 

int check_stack_for_vdso(uint32_t *esp, size_t len) 
{ 
    size_t i; 

    for (i = 0; i < len; i++, esp++) 
      if (*esp >= vdso_start && *esp < vdso_end) 
        return 1; 

    return 0; 
} 

void handler(int signo) 
{ 
    uint32_t *esp; 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    /* XXX only for demonstration, don't call printf from a signal handler */ 
    printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 
} 

void parse_maps() 
{ 
    FILE *maps; 
    char buf[256]; 
    char path[7]; 
    uintmax_t start, end, offset, inode; 
    char r, w, x, p; 
    unsigned major, minor; 

    maps = fopen("/proc/self/maps", "rt"); 
    if (maps == NULL) 
      return; 

    while (!feof(maps) && !ferror(maps)) { 
      if (fgets(buf, 256, maps) != NULL) { 
        if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s", 
            &start, &end, &r, &w, &x, &p, &offset, 
            &major, &minor, &inode, path) == 11) { 
          if (!strcmp(path, "[vdso]")) { 
            vdso_start = start; 
            vdso_end = end; 
            break; 
          } 
        } 
      } 
    } 

    fclose(maps); 

    printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end); 
} 

int main() 
{ 
    struct sigaction sa; 
    uint32_t *esp; 

    parse_maps(); 
    memset(&sa, 0, sizeof(struct sigaction)); 
    sa.sa_handler = handler; 
    sa.sa_flags = SA_RESTART; 

    if (sigaction(SIGUSR1, &sa, NULL) < 0) { 
      perror("sigaction"); 
      exit(1); 
    } 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 

    kill(getpid(), SIGUSR1); 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 

    return 0; 
} 

SCNR。私たちはあなたを取ることができる場合は-fno-omit-frame-pointerオプションを追加する必要

他のgccがスタックコンテキスト情報

+0

私はシグナルハンドラがvdsoから呼び出されたことに気づいていませんでした。あなたは参照を指すことができますか?とにかく、私はこのハックが好きです。たくさん。これを不透明なライブラリにロールオーバーするのは簡単です。このトリックは、シグナルハンドラの前にparse_maps()が呼び出されたことを確認することです。 – smcdow

+0

最高のリファレンスはhttp://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v=2.6.37#L310 – ninjalj

+0

ですが、320行のコメントは面白そうです:http ://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v = 2.6.37#L320 – ninjalj

0

これに対処するには、2つの適切な方法があります。

  • は同僚が間違ったことをやって停止してもらいます。しかし、上司と一緒にこれを引っ張って幸運を祈る。

  • あなたの機能を再入力し、非同期で安全にする。必要に応じて、状態保存に必要な追加の引数を使用して、異なる署名(たとえば、広く使用されている*_r命名規則を使用)を持つ関数を提供します。あなたの関数の呼び出し元の一覧を通過するbacktrace()や友人を使用することができるのlibc GNUとLinux上で、これを行うに非適切方法については

。それは、右の安全やポータブル取得するない簡単だが、それはしばらくの間行う可能性があります:私の意見では

/* 
* *** Warning *** 
* 
* Black, fragile and unportable magic ahead 
* 
* Do not use this, lest the daemons of hell be unleashed upon you 
*/ 
int in_signal_handler_context() { 
     int i, n; 
     void *bt[1000]; 
     char **bts = NULL; 

     n = backtrace(bt, 1000); 
     bts = backtrace_symbols(bt, n); 

     for (i = 0; i < n; ++i) 
       printf("%i - %s\n", i, bts[i]); 

     /* Have a look at the caller chain */ 
     for (i = 0; i < n; ++i) { 
       /* Far more checks are needed here to avoid misfires */ 
       if (strstr(bts[i], "(__libc_start_main+") != NULL) 
         return 0; 
       if (strstr(bts[i], "libc.so.6(+") != NULL) 
         return 1; 
     } 

     return 0; 
} 


void unsafe() { 
     if (in_signal_handler_context()) 
       printf("John, you know you are an idiot, right?\n"); 
} 

、それだけでに良いかもしれないを終了するのではなくのようなコードを書くことを余儀なくされますこの

+0

私はちょうど 'backtrace()'を試しました。ちょうどうまくいきません。 '__libc_start_main'はシグナル処理コンテキスト内外のトレースにあります。 –

+0

私が言及したように、それは簡単ではありません。あなたは、2つのケースの間のバックトレースの違いを見つけ、それを使用しなければなりません。例えば、私のテストでは、シグナルハンドリングコードでない限り、 'main()'に到達する前にバックトレースにlibc関数がないと仮定しました。あなたのバックトレースはどのように見えますか? – thkala

+0

2つのコメント:(1)私はこれがこのようなものかもしれないと恐れていました。 (2)snotではなく、printf(3)は非同期シグナルセーフな関数ではありません。あなたはwrite(2)を使わなければなりません。 - async-signal-safe関数のリスト(少なくとも私が通常参照しているリスト)はhttp://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html – smcdow

0

sigaltstackを使用して解決することができます。代わりのシグナルスタックを設定し、代替スタック内にある場合は非同期で安全な方法でスタックポインタを取得し、そうでない場合は中止します。

+0

私はそのようなことについて考えましたが、関数がシグナルハンドラから呼び出される前に代替スタックが設定されていることを保証できないと思います。また、私の関数がシグナルハンドラから呼び出されることは決してないと繰り返し述べなければなりません。 – smcdow

+0

簡単な方法があります。 'sigaltstack'は、既に代替スタック上で実行していて、それを変更しようとするとエラーを返すために必要なので、単に呼び出すと、呼び出しが失敗するかどうかを調べることができます。 –

0

次のようにする必要があります。これは、コーディングだけでなくソフトウェアエンジニアリングのベストプラクティスを組み合わせた複雑なソリューションです。

  1. シグナルハンドラの命名規則が良いことを上司に説得してください。たとえば、Hungarian notationを提案し、それがMicrosoftで大きな成功を収めたことを伝えます。 したがって、すべてのシグナルハンドラは、sighndInterruptのように、sighndで始まります。次の操作を行います信号処理コンテキストを検出し
  2. あなたの関数は:
    1. backtrace()を取得します。
    2. それに含まれる関数のいずれかがsighnd...で始まるかどうか調べます。それができたら、おめでとう、あなたはシグナルハンドラの中にいます!
    3. そうでなければ、そうではありません。
  3. Jimmyと同じ会社で働かないようにしてください。 「1つしかない」と知っている。
+0

私たちは、管理者にコードベースを渡し、シグナルハンドラから呼び出されているすべての機能を報告します。私はすでに泣き叫んでいる。 – smcdow

+0

@smcdow、ところで、静的解析ツールを使用することができます。しかし、もう一度、各シグナルハンドラに注釈を付ける必要があります。これにより、ソリューションはもはや複雑ではありません。 :-) –

+0

しばらく前に、シグナルハンドラの問題を探すValgrindツールがありました。それはクロッカスと呼ばれていました。 – ninjalj

0

を発見しましたアプリケーションがsigprocmask()またはpthread_sigmask()を使用して手動で信号をブロックしない場合、これは非常に簡単です。現在のスレッドID(tid)を取得します。 /proc/tid/statusを開き、SigBlkSigCgtの値を取得します。 ANDこれらの2つの値。そのANDの結果がゼロでない場合、そのスレッドは現在シグナルハンドラの内部から実行されています。私はこれを自分でテストしています。

3

を最適化する-O2以上(ISTR)に最適化されたコードのための

+0

スレッドIDではなくプロセスID(PID)が必要です。これを行うには、非同期シグナル安全な関数を呼び出す必要があります。 – mgarey

関連する問題