2011-12-10 18 views
13

這裏是我的代碼,爲什麼我不能忽略SIGSEGV信號?

#include<signal.h> 
#include<stdio.h> 

int main(int argc,char ** argv) 
    { 
    char *p=NULL; 
    signal(SIGSEGV,SIG_IGN); //Ignoring the Signal 
    printf("%d",*p); 
    printf("Stack Overflow"); //This has to be printed. Right? 
    return 0; 
    } 

在執行的代碼中,我得到段錯誤。我忽略了使用SIG_IGN的信號。所以我不應該得到分段錯誤。對?然後,打印'* p'值後的printf()語句也必須執行。對?

+11

將會有一段時間,編寫代碼段會導致segfaults被認爲足以讓程序員入獄。 – 6502

回答

18

您的代碼忽略了SIGSEGV而不是捕獲它。回想一下處理信號後,觸發信號的指令重新開始。在你的情況下,處理信號並沒有改變任何東西,所以在下一次嘗試違規指令時,它會以同樣的方式失敗。

如果您打算趕上信號改變這種

signal(SIGSEGV, SIG_IGN); 

這個

signal(SIGSEGV, sighandler); 

你或許應該還可以使用sigaction()代替signal()。請參閱相關手冊頁。

在你的情況下,有問題的指令是試圖取消引用NULL指針的指令。

printf("%d", *p); 

以下內容完全取決於您的平臺。

您可以使用gdb來確定什麼特定的彙編指令觸發信號。如果您的平臺是像我這樣的話,你會發現指令

movl (%rax), %esi 

與RAX寄存器,保持0值,即NULL。一種(不可移植的)方法可以在你的信號處理程序中解決這個問題,就是使用你的處理程序得到的第三個參數信號,即用戶上下文。下面是一個例子:

#include <signal.h> 
#include <stdio.h> 

#define __USE_GNU 
#include <ucontext.h> 

int *p = NULL; 
int n = 100; 

void sighandler(int signo, siginfo_t *si, ucontext_t* context) 
{ 
    printf("Handler executed for signal %d\n", signo); 
    context->uc_mcontext.gregs[REG_RAX] = &n; 
} 

int main(int argc,char ** argv) 
{ 
    signal(SIGSEGV, sighandler); 
    printf("%d\n", *p); // ... movl (%rax), %esi ... 
    return 0; 
} 

此程序將顯示:

Handler executed for signal 11 
100 

它首先導致通過嘗試取消引用NULL地址來執行該處理程序。然後處理程序通過設置rax到變量n的地址來解決問題。一旦處理程序返回系統重試有問題的指令並且這次成功。 printf()收到100作爲第二個參數。

雖然我強烈建議您不要在您的程序中使用這種非便攜式解決方案。

+0

感謝您的詳細解釋亞當:) – Dinesh

13

你可以忽略信號,但你必須做一些事情。我相信你在發佈的代碼中所做的事情(忽略SIGSEGV通過SIG_IGN根本不會工作由於在閱讀大膽的項目符號後會變得明顯的原因。

當你做一些事情,導致內核送你一個SIGSEGV:

  • 如果沒有信號處理程序,內核終止進程,就是這樣
  • 如果你有一個信號處理
    • 你的處理程序被調用
    • 內核重新開始違規操作

所以如果你不做任何事情,它會連續循環。如果您SIGSEGV,你不退出,從而與正常流量的干擾,你必須:

  • 解決的事情,使得違規操作不重新啓動或
  • 解決內存佈局, 下次運行
+0

好的,那我該怎麼做呢? – Dinesh

+0

@Dinesh,你試圖完成什麼? – ibid

+0

@ibid,在代碼中,我試圖訪問一個內存空內存。所以它會導致創建SIGSEGV信號。但我做了一個處理器,只打印「捕捉信號」。之後,語句 「return 0」 其中駐留在main()中的語句必須執行。對? – Dinesh

9

另一種選擇是用括號的setjmp/longjmp的有風險的操作,即

#include <setjmp.h> 
#include <signal.h> 

static jmp_buf jbuf; 
static void catch_segv() 
{ 
    longjmp(jbuf, 1); 
} 

int main() 
{ 
    int *p = NULL; 

    signal(SIGSEGV, catch_segv); 
    if (setjmp(jbuf) == 0) { 
     printf("%d\n", *p); 
    } else { 
     printf("Ouch! I crashed!\n"); 
    } 
    return 0; 
} 

這裏的setjmp/longjmp的圖案是類似於try/catch塊。但是如果你的風險函數超過了堆棧,或者分配了資源但是在它們被釋放之前崩潰,那麼它是非常危險的,並且不會拯救你。更好地檢查你的指針,而不是通過壞指針間接。

+1

據我可以告訴這是行不通的,如果你多次碰到段錯誤(第二次過程仍然segfaults)。 AFAIU'longjmp' /'setjmp'不能正確處理信號上下文,而應該使用'sigsetjmp' /'siglongjmp'。請參閱https://linux.die.net/man/2/setcontext中的「註釋」 – mortenpi

0

ANS: 您分配NULL爲P指針

char *p = NULL; 

然後打印* p值:

printf("%d",*p); 

這意味着你要打印位於空地址值(即; 0地址地方)這是不可能的。這就是爲什麼它給分段錯誤。在訪問存儲器的情況下會出現分段故障,這種情況現在對程序不可用。

嘗試:

char *p = (char*)malloc(sizeof(char)); 
*p = '\0'; 
printf("%d",*p); 

**注:**這種類型的錯誤被稱爲懸空指針誤差。懸掛指針是語法正確的錯誤/異常情況,但用戶嘗試訪問已指向NULL的指針的值。