为啥我不能忽略 SIGSEGV 信号?
Posted
技术标签:
【中文标题】为啥我不能忽略 SIGSEGV 信号?【英文标题】:Why can't I ignore SIGSEGV signal?为什么我不能忽略 SIGSEGV 信号? 【发布时间】:2011-12-10 11:09:04 【问题描述】:这是我的代码,
#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()
语句也必须执行。对?
【问题讨论】:
会有一段时间,编写吞下段错误的代码会被认为足以让程序员入狱。 【参考方案1】:您的代码忽略了 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 作为其第二个参数。
不过,我强烈建议不要在您的程序中使用此类不可移植的解决方案。
【讨论】:
感谢您的详细解释亚当 :)【参考方案2】:你可以忽略这个信号,但你必须对它做点什么。我相信您在发布的代码中所做的事情(通过 SIG_IGN
忽略 SIGSEGV
)根本不起作用,原因在阅读粗体项目符号后将变得显而易见。
当你做了一些导致内核向你发送 SIGSEGV 的事情时:
如果您没有信号处理程序,内核会终止进程,仅此而已 如果您有信号处理程序 您的处理程序被调用 内核重新启动有问题的操作所以如果你不做任何事情,它就会不断循环。如果你确实捕获了SIGSEGV
,但你没有退出,从而干扰了正常流程,你必须:
【讨论】:
好的,那我到底该怎么做呢? @Dinesh,你想要完成什么? @ibid,在代码中,我正在尝试访问内存空内存。所以它会导致产生 SIGSEGV 信号。但我为它制作了一个处理程序,它只会打印“Catching the signal”。然后必须执行位于 main() 中的语句“return 0”。对吗? @Dinesh 目前,您发布的代码只是忽略了信号,它没有为它建立信号处理程序 (sighandler
)。即使是这样,我怀疑它会不断打印“捕捉信号”。
@cnicutar,哦,那你能告诉我如何为此添加 sighandler 吗?你能给我任何可以了解的链接吗【参考方案3】:
另一种选择是用 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 块。虽然它非常冒险,如果您的风险函数超出堆栈,或者分配资源但在它们被释放之前崩溃,则不会拯救您。最好检查你的指针,而不是通过坏指针间接检查。
【讨论】:
据我所知,如果您多次遇到段错误(第二次进程仍然段错误),这将不起作用。 AFAIUlongjmp
/setjmp
没有正确处理信号上下文,应该使用 sigsetjmp
/siglongjmp
代替。参见linux.die.net/man/2/setcontext中的“注释”【参考方案4】:
试图忽略或处理 SIGSEGV 是错误的方法。由您的程序总是触发的 SIGSEGV 表示存在错误。在您的代码或您委托的代码中。一旦触发了错误,任何事情都可能发生。信号处理程序无法执行合理的“清理”或修复操作,因为它不知道信号在哪里触发或执行什么操作。你能做的最好的事情就是让程序快速失败,这样程序员就有机会在它仍然处于立即失败状态时对其进行调试,而不是在失败的原因已经确定后让它(可能)失败模糊不清。如果不尝试忽略或处理信号,您可能会导致程序快速失败。
【讨论】:
这不是 总是 一个错误。一些 JVM 或 javascript 引擎有时会将数组的末尾放在一个页面的末尾,然后是一个未映射的页面,从而将边界检查卸载到硬件。 SIGSEGV 意味着来宾 Java 或 Javascript 代码发生了数组越界异常,而不是 JVM 本身有问题。但是,是的,在这样的计划案例之外,这是一个错误,不应被忽视。 程序还可以专门 mprotect() 一个内存范围,并使用 SIGSEGV 处理程序来了解该范围内的地址被访问过。以上是关于为啥我不能忽略 SIGSEGV 信号?的主要内容,如果未能解决你的问题,请参考以下文章
当我尝试从双向链表中删除最后一个元素时,为啥会收到“信号 SIGSEGV,分段错误”?
为啥启用 NEON SIMDization 时 Android 会崩溃?信号 11 (SIGSEGV),代码 1 (SEGV_MAPERR)
Django 1.6 + RabbitMQ 3.2.3 + Celery 3.1.9 - 为啥我的芹菜工人死于:WorkerLostError:工人过早退出:信号11(SIGSEGV)
如何在tid xxxxx(Thread-X)中解决Android致命信号11(SIGSEGV),代码1,故障地址0x0?