您的位置:首页 > 其它

调试 Segmentation fault 问题

2016-06-24 20:40 375 查看
        今天上班的时候,这个 segmentation fault 困扰了我半天,调试了半天就是不知道问题在哪,于是愤愤的下班了,晚饭都没吃好

。不过饭后坐下来,仔细想了想,想到以下办法快速定位出错的语句

。方法如下:

        在介绍这个方法之前,先让我扯会蛋

。首先先简单说一下这个 segmentation fault 产生的过程 (个人理解)。segmentation fault 一般是指内存的非法访问。是由CPU硬件首先产生缺页异常,然后转到linux内核事先安装好的异常处理程序,在这里内核会检查下缺页异常产生的原因。如果是由于内核只是已经答应给应用程序分配该地址指向的内存,但是还没有实际分配(也就是还没有创建页表项),那么内核会说“不好意思哈,我现在给你分配这块内存”。然后分配完这块内存之后,应用程序返回用户空间之后,会继续执行刚刚产生缺页异常的那条语句,由于这时候内核已经创建了相应的页表项,这次就不会再产生缺页异常了。整个过程在应用程序看来,好像就没有缺页异常这回事。这样并不奇怪,内核总是在最后迫不得已的时候才肯真正将内存分配出来的(懒内核

)。但是如果在内核的缺页异常处理程序中,内核发现你的应用程序访问了本来不属于你的内存地址(或者内存的读写属性不合适),那么内核就该呵呵了,就把你的应用程序干掉了。不过话说回来,内核是如何把你的程序干掉的呢?其实内核是通过向你的应用程序发送一个 SIGSEGV
的signal,而这个signal的默认处理函数就是让该程序退出。关于Linux的signal是何时被执行的,简单的说,就是当应用程序从内核空间返回用户空间的时候,会检查是否有未被处理的 signal,如果有,就执行相应的 signal handler。也就是说,你的程序进入缺页异常后,在即将返回产生缺页异常的那句话之前,会先进入 SIGSEGV 的 signal handler,而这个handler会导致程序退出,并打印“segmentation fault”给你看。看起来就好像是程序执行到那个非法地址访问的语句时,程序就突然退出了一样。不过关于Linux下signal
handler的执行时机,有更详细的解释(随便抄了一段,实在是不好找,不过看了下面一段话,突然明白了为什么有些进程死活也杀不掉,原来它们躲在内核空间里面转悠呢。。。):

       

-------------------------------------------------------------------------

1) The signal handler is executed the next time the target process returns from kernel mode to user mode.

This occurs either when the process is scheduled to run again after a hardware interrupt (and it wasn't already running in kernel mode), or when the process returns from a system call (on some architectures, these are the same thing).

In normal operation, when leaving kernel mode, your process will simply return to the next instruction after the point where it originally left user mode.

However if a signal is pending for your process, the kernel will re-write your processes context such that the return to user mode will instead go to the first instruction of your signal handler and your stack will have been modified to look like you had made
a "special" subroutine call to the signal handler at the point where you originally left user mode (the return from this "special" subroutine call involves making a system call to restore the original state).

For details read this, this and this.

So the 'target' process may receive its execution token from the Scheduler any number of times before the signal handler is finally executed (if it happens to stay in kernel mode for some reason).

2) No - the signal handler will only ever execute in the user mode context of your process.

3) The aren't really any execution priorities in a time-shared system such as Linux, unless you count the nice value of a process, so you can't sweep away something that isn't there.

Things are complicated by threads and so-called real time scheduling policies, so the comments above are only valid for single-threaded processes running with non-real-time scheduling policies (the only sort of process that existed in the good old days :-).

----------------------------------------------------------------------------

        好了,闲话扯完了,介绍一下怎么定位 产生 segmentation fault 的语句吧。通过上面的分析,其实定位产生 segmentation fault 的语句,也就是定位产生那个缺页异常的语句(好像是废话)。而那个缺页异常的直接后果是内核向应用程序发送了一个 SIGSEGV的signal,而这个signa的直接后果就是使程序退出了。不过好心的linux内核给了我们机会让我们可以捕捉到这个消息,安装自己的消息处理函数,替换掉那个导致程序退出的默认的消息处理函数。而如果我们在消息处理函数里面什么都不做,直接返回,会怎样呢?哈哈,程序继续执行刚才产生缺页异常的语句,结果内核又向程序发送消息,使得程序进入消息处理函数,然后又继续执行产生缺页异常的语句,周而复始。。。

        虽然这时程序进入了死循环,但总比直接退出好吧,至少给了我们机会,定位产生缺页异常的语句。当程序进入这个死循环之后,定位缺页异常就非常简单啦,打开gdb,然后attach这个程序的id号就好啦,hoho (实验证明,这时候gdb必须以root权限运行才行,不知为何。。。



        给个简单的测试程序吧


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

static void handler(int signum)
{
/* We do nothing in this signal handler */
}

int main(int argc, char* argv[])
{
struct sigaction sa;
int *a = NULL;

/* Install signal handler for SIGSEGV */
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGSEGV, &sa, NULL)==-1)
perror("sigaction");

/* Paging exception occurs here... */
*a = 100;

return 0;
}


不过这个程序非常简单,不用gdb也知道错在哪里,不知道今天遇见的那个问题能不能顺利找到出错的语句,但愿吧
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息