2013-04-25 21 views
2

我想知道在多線程環境中是否有可能/推薦捕獲SIGSEGV信號。我特別感興趣的是處理由'*((int *)0)= 0'之類的東西引發的SIGSEGV。關於在多線程環境中捕獲SIGSEGV

關於此主題的一些閱讀讓我看到了signal()和sigaction(),它們安裝了一個信號處理程序。雖然在多線程環境中看起來並不樂觀。然後我嘗試了sigwaitinfo(),它在一個線程中接收到信號,而之前的pthread_sigmask()調用阻止了其他信號。它在SIGSEGV信號被引發的程度上使用raise(),在線程內部或當它通過類似'kill -SIGSEGV'的方式發送到進程時。然而,*((int *)0)= 0'仍然會殺死進程。我的測試程序如下

void block_signal() 
{ 
     sigset_t set; 

     sigemptyset(&set); 
     sigaddset(&set, SIGSEGV); 
     sigprocmask(SIG_BLOCK, &set, NULL); 

     if (pthread_sigmask(SIG_BLOCK, &set, NULL)) { 
       fprintf(stderr, "pthread_sigmask failed\n"); 
       exit(EXIT_FAILURE); 
     } 
    } 

void *buggy_thread(void *param) 
{ 
     char *ptr = NULL; 
     block_signal();                         

     printf("Thread %lu created\n", pthread_self()); 

     // Sleep for some random time 
     { ... } 

     printf("About to raise from %lu\n", pthread_self()); 

     // Raise a SIGSEGV 
     *ptr = 0; 

     pthread_exit(NULL); 
} 

void *dispatcher(void *param) 
{ 
     sigset_t set; 
     siginfo_t info; 
     int sig; 

     sigemptyset(&set); 
     sigaddset(&set, SIGSEGV); 

     for (;;) { 
       sig = sigwaitinfo(&set, &info); 
       if (sig == -1) 
         fprintf(stderr, "sigwaitinfo failed\n"); 
       else 
         printf("Received signal SIGSEGV from %u\n", info.si_pid); 
     } 
} 

int main() 
{ 
     int i; 
     pthread_t tid; 
     pthread_t disp_tid; 

     block_signal(); 

     if (pthread_create(&disp_tid, NULL, dispatcher, NULL)) { 
       fprintf(stderr, "Cannot create dispatcher\n"); 
       exit(EXIT_FAILURE); 
     } 

     for (i = 0; i < 10; ++i) { 
       if (pthread_create(&tid, NULL, buggy_thread, NULL) { 
         fprintf(stderr, "Cannot create thread\n"); 
         exit(EXIT_FAILURE); 
       } 
     } 

     pause(); 
} 

不料,程序分段錯誤而不是打印的專業戶的線程ID死亡。

+0

爲什麼要捕獲'SIGSEGV'?抓到它後你會做什麼? – 2013-04-25 05:40:15

回答

5

您的代碼不會調用sigaction(2),我相信它應該調用它。另請參閱signal(7)。和信號作用(直通sa_sigaction場應該做的事(機專用),其siginfo_t跳過有問題的機器指令,或mmap違規地址,或撥打siglongjmp,否則從信號處理程序返回的時候,你會再次得到SIGSEGV因爲有問題的機器指令重新啓動。

您不能處理其他線程的SIGSEGV,因爲異步信號是線程特定的(見this answer),你試試用sigwaitinfo達到無法工作,所以什麼。特別SIGSEGV針對有問題的線程

請也閱讀all about Linux signals

+0

信號處置不是線程局部的。只有信號掩碼,並且在某些情況下,信號傳送是線程本地的。 – 2013-04-25 01:46:55

+0

感謝您的資源豐富的參考。但是當我用'raise(SIGSEGV)'替換了有問題的行'* ptr = 0'時,上面的代碼就工作了。不知道爲什麼'* ptr = 0'不會產生相同的結果。 – 2013-04-25 04:01:24

+0

因爲像'* ptr = 0;'這樣的segentation違規是線程特定的,所以'SIGSEGV'被髮送到違規線程。這與'raise(SIGSEGV)' – 2013-04-25 05:34:41

2

由斷層存儲器訪問引起的SIGSEGV的信號傳遞是執行無效訪問的線程。每個POSIX(XSH 2.4.1):

在生成時,應確定是否爲進程或進程內的特定線程生成了信號。應該爲引起信號產生的線程產生由可歸因於特定線程的某些動作產生的信號,例如硬件故障。應爲過程生成與進程ID或進程組ID或異步事件(如終端活動)關聯生成的信號。

的試圖在一個多線程程序來處理SIGSEGV有問題的方面是,雖然遞送和信號掩碼是線程局部,信號處置(即什麼處理程序調用)是過程全局。換句話說,sigaction爲整個過程設置了一個信號處理程序,而不僅僅是調用線程。這意味着每個嘗試設置自己的處理程序的多個線程都會破壞對方的設置。

我可以提出最好的解決辦法是設置一個全球性的信號處理程序使用sigactionSIGSEGV,最好用SA_SIGINFO讓您獲得有關故障的附加信息,然後有針對特定線程處理一個線程局部變量。然後,實際的信號處理程序是:

_Thread_local void (*thread_local_sigsegv_handler)(int, siginfo_t *, void *); 
static void sigsegv_handler(int sig, siginfo_t *si, void *ctx) 
{ 
    thread_local_sigsegv_handler(sig, si, ctx); 
} 

注意,這是利用C11線程本地存儲。如果您沒有可用的版本,則可以使用「GNU C」__thread線程本地存儲或POSIX線程特定數據(使用pthread_key_createpthread_setspecific/pthread_getspecific)。嚴格地說,後者不是異步信號安全的,因此如果非法訪問發生在標準庫中的非異步信號安全函數內,則從信號處理程序調用它們將調用UB。但是,如果它發生在你自己的代碼中,你可以確定沒有非異步信號安全函數被信號處理程序中斷,因此這些函數具有定義良好的行爲(模塊化的事實是你的整個程序可能已經有UB,無論它做什麼來產生SIGSEGV ...)。

+0

我不太明白從thread_local_sigsegv_handler獲得的利潤是多少。如果我理解正確,這是一個全局函數指針(專門分配給每個線程)。但是,您如何獲得導致信號被調用以調用該函數的線程? – user1708860 2014-05-24 15:18:26

+0

這總是發生在同步生成的信號上。請參閱XSH 2.4.1 *信號生成和傳送*:http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_01:*在生成時,應確定信號是否有爲進程或進程內的特定線程生成。通過可歸因於特定線程的某些動作(例如硬件故障)生成的信號必須爲導致信號生成的線程生成。* – 2014-05-24 15:26:27

+1

根據https://www.gnu.org/software/libc /manual/html_node/Thread_002dspecific-Data.html pthread_getspecific是異步信號安全的(pthread_setspecific不是),所以使用POSIX線程特定的數據存儲應該沒問題(至少在glibc中)。另一方面,我找不到任何有關變量是否聲明__thread(或C++ 11中的thread_local)實際上是異步信號安全的信息。任何有關此問題的文檔鏈接將不勝感激。 – 2016-06-30 10:18:08

1

「爲什麼要抓住SIGSEGV?抓到它後你會做什麼?」

最常見的答案是:quit/abort。但是,那麼甚至將這個信號傳遞給一個過程而不是任意終止它的原因是什麼呢?

答案是:因爲包括SIGSEGV在內的信號只是例外 - 對於某些應用程序f.e來說非常重要。將硬件輸出設置爲「安全模式」或確保一些重要數據在終止過程之前處於一致狀態。

通常有2種段錯誤:由寫或讀操作引起的。

引起的段錯誤讀取在某些情況下(1),操作對於捕獲甚至忽略是完全安全的。失敗編寫操作需要更多的關注和努力來安全地處理(數據/內存損壞的風險),但這也是可能的(例如通過避免在段錯誤後動態分配內存)。

「關鍵信號」(傳遞給特定線程,如SIGFPE或SIGSEGV)的問題是通常程序不會「知道」信號的上下文是什麼 - 也就是說,哪種操作或功能已觸發信號。

至少有幾個可能的方式來獲得這些信息,例如:

  1. 每個線程只能執行單一類的小操作的 - 所以,如果它得到一個信號,那麼很容易知道是什麼發生 - >終止線程,驗證處理的數據等 - >安全終止。
  2. 使用Ç例外 - 有幾個準備使用的解決方案,我的是:libcxc

(1)F.E. ESRCH和pthread_kill()發出的一個已經退出的線程的着名問題)