c - 如何编写信号处理程序来捕获 SIGSEGV?

标签 c linux system-calls signal-handling mprotect

我想编写一个信号处理程序来捕获 SIGSEGV。 我使用

保护一 block 内存以供读取或写入
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);
}

问题是只有信号处理程序在运行,捕获到信号后我无法返回到主函数。

最佳答案

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

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

幸运的是,在大多数实现中(据我所知,所有现代 UNIX 和 Linux 变体),mprotect 是一个系统调用,safe to call from within a signal handler 也是。 ,所以你可以做你想做的大部分事情。问题是,如果您想在读取后更改保护,则必须在读取后在主程序中执行此操作。

另一种可能性是对信号处理程序的第三个参数做一些事情,它指向一个操作系统和架构特定的结构,其中包含有关信号发生位置的信息。在 Linux 上,这是一个 ucontext 结构,其中包含有关 $PC 地址和发生信号的其他寄存器内容的机器特定信息。如果修改它,就会更改信号处理程序将返回的位置,因此您可以将 $PC 更改为紧跟在错误指令之后,这样它就不会在处理程序返回后重新执行。要做到这一点非常棘手(而且也不可移植)。

编辑

ucontext结构在 <ucontext.h> 中定义. ucontext内领域uc_mcontext包含机器上下文,在 that 中,数组 gregs包含通用寄存器上下文。所以在你的信号处理程序中:

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

将为您提供发生异常的电脑。你可以阅读它来弄清楚它是什么指令 那是错的,做一些不同的事情。

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

关于c - 如何编写信号处理程序来捕获 SIGSEGV?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48960314/

相关文章:

c - 在c上使用getchar()在输入后得到 'Enter'

从主端更改伪 tty 回显模式

linux - 程序是从命令行执行还是通过系统调用调用

c - 函数中的malloc()结构,结束程序之前为free()

c - 在c中操作字符串

linux - 从 vcard (*.vcf) 文件中提取姓名和电子邮件信息

linux - postgresql 什么时候需要写入文件 "pg_logical/replorigin_checkpoint.tmp"?

operating-system - 从内核模式返回到用户模式

c - 无需重新编译 Debian 内核即可调试新的系统调用

python - Python 和 C 之间 ntohl 和 htonl 的不同行为