如何编写一个信号处理程序来捕获SIGSEGV?

我想写一个信号处理程序来捕获SIGSEGV。 我保护一块内存以供读取或写入使用

char *buffer; char *p; char a; int pagesize = 4096; mprotect(buffer,pagesize,PROT_NONE) 

这可以保护从缓冲区开始的页面缓冲区的任何读写操作。

其次,我尝试阅读记忆:

 p = buffer; a = *p 

这将生成一个SIGSEGV,我的处理程序将被调用。 到现在为止还挺好。 我的问题是,一旦处理程序被调用,我想通过做改变访问写入的内存

 mprotect(buffer,pagesize,PROT_READ); 

并继续我的代码正常运作。 我不想退出这个function。 在未来写入相同的内存,我想再次捕捉信号,并修改写权限,然后logging该事件。

这里是代码 :

 #include <signal.h> #include <stdio.h> #include <malloc.h> #include <stdlib.h> #include <errno.h> #include <sys/mman.h> #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) char *buffer; int flag=0; static void handler(int sig, siginfo_t *si, void *unused) { printf("Got SIGSEGV at address: 0x%lx\n",(long) si->si_addr); printf("Implements the handler only\n"); flag=1; //exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { char *p; char a; int pagesize; struct sigaction sa; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) handle_error("sigaction"); pagesize=4096; /* Allocate a buffer aligned on a page boundary; initial protection is PROT_READ | PROT_WRITE */ buffer = memalign(pagesize, 4 * pagesize); if (buffer == NULL) handle_error("memalign"); printf("Start of region: 0x%lx\n", (long) buffer); printf("Start of region: 0x%lx\n", (long) buffer+pagesize); printf("Start of region: 0x%lx\n", (long) buffer+2*pagesize); printf("Start of region: 0x%lx\n", (long) buffer+3*pagesize); //if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) if (mprotect(buffer + pagesize * 0, pagesize,PROT_NONE) == -1) handle_error("mprotect"); //for (p = buffer ; ; ) if(flag==0) { p = buffer+pagesize/2; printf("It comes here before reading memory\n"); a = *p; //trying to read the memory printf("It comes here after reading memory\n"); } else { if (mprotect(buffer + pagesize * 0, pagesize,PROT_READ) == -1) handle_error("mprotect"); a = *p; printf("Now i can read the memory\n"); } /* for (p = buffer;p<=buffer+4*pagesize ;p++ ) { //a = *(p); *(p) = 'a'; printf("Writing at address %p\n",p); }*/ printf("Loop completed\n"); /* Should never happen */ exit(EXIT_SUCCESS); } 

问题是只有信号处理程序运行,捕捉信号后我不能返回到主函数。

当你的信号处理程序返回时(假设它没有调用exit或longjmp或阻止它实际返回),代码将在信号发生的地方继续,重新执行相同的指令。 由于在这一点上,内存保护没有改变,它只会再次发出信号,你将无限循环回到你的信号处理程序。

所以要使它工作,你必须在信号处理程序中调用mprotect。 不幸的是,正如Steven Schansker指出的那样,mprotect不是asynchronous安全的,所以你不能从信号处理器中安全地调用它。 所以,就POSIX而言,你被搞砸了。

幸运的是,在大多数实现中(据我所知,所有的现代UNIX和Linux版本),mprotect是一个系统调用,所以在信号处理程序中调用是安全的 ,所以你可以做大部分你想要的。 问题是,如果您想在读取之后更改保护,您必须在读取之后在主程序中执行此操作。

另一个可能性是对信号处理程序的第三个参数做一些处理,它指向一个操作系统和特定的结构,包含有关信号发生的位置的信息。 在Linux上,这是一个ucontext结构,其中包含有关$ PC地址的机器特定信息以及发生信号的其他寄存器内容。 如果你修改了这个,你可以改变信号处理程序返回的位置,所以你可以把$ PC改成错误指令之后,所以在处理程序返回后它不会重新执行。 这是非常棘手的(也是非可移植的)。

编辑

ucontext结构在<ucontext.h>定义。 在ucontext字段包含机器上下文, 其中的数组gregs包含通用寄存器上下文。 所以在你的信号处理器中:

 ucontext *u = (ucontext *)unused; unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP]; 

会给你的电脑发生exception。 你可以阅读它来弄清楚是什么指令是错误的,做一些不同的事情。

就在信号处理程序中调用mprotect的可移植性而言,遵循SVID规范或BSD4规范的任何系统都应该是安全的 – 它们允许在信号中调用任何系统调用(手册的第2节中的任何内容)处理程序。

你陷入了所有人在第一次尝试处理信号时所做的陷阱。 陷阱? 认为你实际上可以做任何有用的信号处理程序。 从信号处理程序中,只允许调用asynchronous和重入安全的库调用。

请参阅此CERT咨询 ,了解为什么以及安全的POSIXfunction列表。

请注意,您已经调用的printf()不在该列表中。

保护也不是。 你不能从信号处理程序中调用它。 这可能会奏效,但我可以保证你会遇到问题。 对信号处理程序要非常小心,它们很难得到正确的!

编辑

由于我现在已经是一个可移植的包,我会指出,你也不应该写没有采取适当的预防措施共享(即全球)的variables 。

你可以在Linux上从SIGSEGV恢复。 你也可以从Windows上的分段错误中恢复(你会看到一个结构化的exception,而不是一个信号)。 但POSIX标准并不保证恢复 ,所以你的代码将是非常不便携的。

看看libsigsegv 。

你不应该从信号处理程序返回,因为行为是不确定的。 相反,用longjmp跳出来。

如果信号是在asynchronous信号安全function中生成的,那么这只是一个好的方法。 否则,如果程序调用另一个asynchronous信号不安全的函数,行为是不确定的。 因此,信号处理程序只能在需要之前立即build立,并尽快解除。

实际上,我知道SIGSEGV处理程序的使用非常less:

  • 使用asynchronous信号安全的回溯库来logging回溯,然后死亡。
  • 在诸如JVM或CLR之类的虚拟机中:检查是否在JIT编译的代码中出现了SIGSEGV。 如果没有,就死吧。 如果是,则抛出一个特定于语言的exception( 不是 C ++exception),这是因为JIT编译器知道陷阱可能发生并生成适当的帧展开数据。
  • clone()和exec()debugging器(不要使用fork() – 调用pthread_atfork()注册的callback函数)。

最后请注意,任何触发SIGSEGV的动作都可能是UB,因为这是访问无效的内存。 但是,如果信号是SIGFPE,情况就不会如此。

有一个使用ucontext_t或结构ucontext编译问题(存在于/usr/include/sys/ucontext.h

http://www.mail-archive.com/arch-general@archlinux.org/msg13853.html