我想写一个信号处理程序来捕获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);
并继续正常运行我的代码。我不想退出该功能。 在将来写入相同内存时,我想再次捕获信号并修改写入权限,然后记录该事件。
以下是the code:
#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);
}
问题是只有信号处理程序运行,并且在捕获信号后我无法返回主函数。
答案 0 :(得分:64)
当你的信号处理程序返回时(假设它没有调用exit或longjmp或阻止它实际返回的东西),代码将在信号发生时继续,重新执行相同的指令。由于此时内存保护尚未更改,它只会再次抛出信号,并且您将在无限循环中返回信号处理程序。
为了使其工作,你必须在信号处理程序中调用mprotect。不幸的是,正如Steven Schansker所说,mprotect不是异步安全的,所以你不能安全地从信号处理程序中调用它。所以,就POSIX而言,你已经搞砸了。
幸运的是,在大多数实现中(据我所知,所有现代UNIX和Linux版本都是),mprotect是一个系统调用,safe to call from within a signal handler也是如此,因此您可以完成所需的大部分工作。问题是,如果你想在阅读后更改保护,你必须在阅读后在主程序中这样做。
另一种可能性是对信号处理程序的第三个参数执行某些操作,该参数指向OS和特定于结构的结构,该结构包含有关信号发生位置的信息。在Linux上,这是一个 ucontext 结构,其中包含有关$ PC地址和信号发生的其他寄存器内容的机器特定信息。如果你修改它,你可以改变信号处理程序返回的位置,这样你就可以将$ PC更改为故障指令之后,这样在处理程序返回后它就不会重新执行。这样做是非常棘手的(也是非便携式的)。
修改强>
ucontext
结构在<ucontext.h>
中定义。在ucontext
字段uc_mcontext
中包含机器上下文,在 中,数组gregs
包含通用寄存器上下文。所以在你的信号处理程序中:
ucontext *u = (ucontext *)unused;
unsigned char *pc = (unsigned char *)u->uc_mcontext.gregs[REG_RIP];
会给你发生异常的电脑。你可以阅读它来弄清楚它是什么指令 那是故障,并做了不同的事情。
就信号处理程序中调用mprotect的可移植性而言,遵循SVID规范或BSD4规范的任何系统都应该是安全的 - 它们允许调用任何系统调用(本手册第2部分中的任何内容)在信号处理程序中。
答案 1 :(得分:24)
你陷入了所有人第一次尝试处理信号时所做的陷阱。陷阱?认为你可以用信号处理程序实际做任何有用的。从信号处理程序,您只能调用异步和可重入安全的库调用。
请参阅this CERT advisory关于为什么以及安全的POSIX函数列表。
请注意,您已经在调用的printf()不在该列表中。
也不是mprotect。你不能从信号处理程序中调用它。 可能工作,但我保证你会遇到问题。对信号处理程序要非常小心,它们很难做到正确!
修改强>
由于我现在已经是一个便携式douchebag,我会指出你also shouldn't write to shared (i.e. global) variables没有采取适当的预防措施。
答案 2 :(得分:12)
您可以从linux上的SIGSEGV恢复。您还可以从Windows上的分段错误中恢复(您将看到结构化异常而不是信号)。但是the POSIX standard doesn't guarantee recovery,因此您的代码将非常不便携。
看看libsigsegv。
答案 3 :(得分:4)
您不应该从信号处理程序返回,因为行为未定义。相反,用longjmp跳出它。
只有在异步信号安全功能中生成信号时才可以。否则,如果程序曾调用另一个异步信号不安全函数,则行为未定义。因此,信号处理程序只应在必要之前立即建立,并尽快解除。
事实上,我知道很少使用SIGSEGV处理程序:
最后,请注意,触发SIGSEGV的任何操作都可能是UB,因为这是访问无效内存。但是,如果信号是SIGFPE,则不会出现这种情况。
答案 4 :(得分:0)
使用ucontext_t
或结构ucontext
存在编译问题(存在于/usr/include/sys/ucontext.h
)
http://www.mail-archive.com/arch-general@archlinux.org/msg13853.html