如何编写一个信号处理程序来捕捉 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);

并继续正常运行我的代码。我不想退出函数。 在以后对同一内存的写操作中,我希望再次捕获信号并修改写权限,然后记录该事件。

以下是 密码:

#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);
}

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

93361 次浏览

你已经掉进了所有人第一次尝试处理信号时都会做的陷阱。陷阱?认为您实际上可以使用信号处理程序执行任何 很有用操作。从信号处理程序中,只允许调用异步和可重入安全的库调用。

关于原因,请参阅 这个 CERT 的建议以及安全的 POSIX 函数列表。

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

我的保护也不是。你不能从信号处理器打电话。它的 也许吧工作,但我可以保证,你会遇到问题的道路上。小心使用信号处理程序,它们很难处理好!

剪辑

既然我现在已经是一个便携式的混蛋,我会指出你 也不应该写入共享(即全局)变量没有采取适当的预防措施。

当您的信号处理程序返回时(假设它没有调用 exit 或 longjmp 或其他阻止它实际返回的东西) ,代码将在信号发生时继续执行,重新执行相同的指令。因为在这一点上,内存保护没有被改变,它只是再次抛出信号,您将在一个无限循环中回到您的信号处理程序中。

因此,为了使其工作,您必须在信号处理程序中调用 mprotected。不幸的是,正如 Steven Schansker 指出的那样,mprotected 不是异步安全的,因此不能从信号处理程序安全地调用它。所以,就 POSIX 而言,你完蛋了。

幸运的是,在大多数实现中(据我所知,都是现代的 UNIX 和 Linux 变体) ,mprotected 是一个系统调用,从信号处理器内部呼叫是安全的也是,所以您可以做您想做的大部分事情。问题是,如果您想在读取之后修改保护,那么您必须在主程序中在读取之后修改保护。

另一种可能性是对信号处理程序执行第三个参数,它指向一个操作系统和包含信号发生地点信息的拱形特定结构。在 Linux 上,这是一个 上下文结构,其中包含有关 $PC 地址和发生信号的其他寄存器内容的机器特定信息。如果您修改了它,您就可以更改信号处理程序将返回的位置,这样您就可以将 $PC 更改为刚好在错误指令之后,这样在处理程序返回之后它就不会重新执行了。这是非常棘手的权利(和不可移植的)。

编辑

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

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

将给你的电脑发生异常。你可以读取它,以找出什么指令它 就是那个错误,然后做一些不同的事情。

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

您可以在 Linux 上从 SIGSEGV 恢复。还可以从 Windows 上的分段故障恢复(您将看到一个结构化异常,而不是信号)。但是 POSIX 标准不能保证恢复,所以您的代码将是非常不可移植的。

看看 Libsigsev

使用 ucontext_t或 struct ucontext(在 /usr/include/sys/ucontext.h中出现)时存在编译问题

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

您不应该从信号处理程序返回,因为那样行为就是未定义的。

只有在异步信号安全函数中生成信号时才可以这样做。否则,如果程序调用另一个异步信号不安全的函数,则行为是未定义的。因此,信号处理器只能在必要之前立即建立,并尽快解除。

事实上,我知道 SIGSEGV 处理程序的用法很少:

  • 使用异步信号安全的回溯库记录回溯,然后终止。
  • 在诸如 JVM 或 CLR 之类的 VM 中: 检查 SIGSEGV 是否出现在 JIT 编译的代码中。如果没有,那么死亡; 如果是这样,那么抛出一个特定于语言的异常(没有一个 C + + 异常) ,这个异常可以工作,因为 JIT 编译器知道陷阱可能会发生,并生成适当的帧展开数据。
  • Clone ()和 exec ()调试器(do 没有 use fork ()-调用由 pthread _ atfork ()注册的回调函数)。

最后,请注意触发 SIGSEGV 的任何操作都可能是 UB,因为它正在访问无效内存。但是,如果信号是 SIGFPE,则不会出现这种情况。