【问题标题】:How to write a signal handler to catch SIGSEGV?如何编写信号处理程序来捕获 SIGSEGV?
【发布时间】:2017-08-02 05:16:55
【问题描述】:

我想编写一个信号处理程序来捕获 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);
}

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

【问题讨论】:

  • 感谢 nos 的编辑……我很感激。我需要花一些时间来学习编辑我的问题..
  • 编译时,始终启用所有警告,然后修复这些警告。 (对于gcc,至少使用:-Wall -Wextra -pedantic我也使用:-Wconversion -std=gnu99)编译器会告诉你:1)参数argc未使用2)参数argv未使用(建议使用main()签名: int main( void ) 3) 局部变量 pelse 代码块中使用,未初始化。 4)参数unused未使用,建议:添加语句:(void)unused;作为该函数的第一行。 5) 局部变量a 已设置但未使用。
  • 永远不要在信号处理程序中使用printf()!函数write() 可以使用,但最好不要在信号处理程序中执行任何 I/O,只需设置一个标志并让主代码行检查该标志
  • 变量pagesize被声明为int,但它应该被声明为size_t
  • sig 参数应该与 SIGSEGV 进行比较,因为还有其他信号,这样的比较将删除有关未使用的 sig 参数的编译器消息

标签: c linux system-calls signal-handling mprotect


【解决方案1】:

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

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

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

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

编辑

ucontext 结构在&lt;ucontext.h&gt; 中定义。在ucontext 中,字段uc_mcontext 包含机器上下文,而在that 中,数组gregs 包含通用寄存器上下文。所以在你的信号处理程序中:

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

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

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

【讨论】:

  • 对,您可以代表程序(如VM)执行内存访问,然后更新指令指针。拨打mprotect 肯定更容易。
  • 嗨,克里斯,你给了我一些有用的信息。谢谢你..你能告诉我如何阅读ucontext结构中的信息(第三个参数并更改$ PC)。我很想知道。
  • @Ben Voigt,我没听明白你在说什么,请你说得详细一点。
  • @chris,看起来我可以在信号处理程序中执行 mprotect,然后安全返回执行我的正常执行。我不确定你们提到的可移植性,但我希望在我的情况下没问题。谢谢大家的帮助..
  • @chris 感谢您的解释,我会看到使用您的技术的 PC。
【解决方案2】:

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

请参阅this CERT advisory 了解原因以及安全的 POSIX 函数列表。

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

mprotect 也不是。您不能从信号处理程序中调用它。它可能工作,但我可以保证你会在路上遇到问题。对信号处理程序要非常小心,它们很难正确处理!

编辑

由于我现在已经是一个便携混蛋,我会指出你also shouldn't write to shared (i.e. global) variables 没有采取适当的预防措施。

【讨论】:

  • 嗨史蒂文,如果我不能在信号处理程序中做任何有用的事情,如果我可以更新其中的一些计数器并返回到主程序并正常运行我的代码,我会没事的,是否可以?
  • 引用 CERT 公告,“他们可以调用其他函数,前提是代码移植到的所有实现都保证这些函数是异步的——安全的”。在包含更多功能的 linux 上。
  • 当然可以,但是您必须意识到问题所在!我无法说出哪些功能是信号安全的,哪些不是信号安全的,我怀疑很多人都可以!
  • CERT 安全编码是一个很棒的网站,我不知道。看来我有一段时间读了一些新书:)
  • "您不能从信号处理程序中调用 [mprotect]。"仅当您需要严格遵守 POSIX 时。 Glibc 今天的 mprotect 是异步信号安全的:gnu.org/software/libc/manual/html_node/…
【解决方案3】:

您可以在 linux 上从 SIGSEGV 恢复。您还可以从 Windows 上的分段错误中恢复(您将看到结构化异常而不是信号)。但是the POSIX standard doesn't guarantee recovery,所以您的代码将非常不可移植。

看看libsigsegv

【讨论】:

    【解决方案4】:

    您不应该从信号处理程序返回,因为行为未定义。而是用 longjmp 跳出来。

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

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

    • 使用异步信号安全回溯库来记录回溯,然后死掉。
    • 在 JVM 或 CLR 等 VM 中:检查 SIGSEGV 是否出现在 JIT 编译的代码中。如果没有,就死;如果是这样,则抛出特定于语言的异常(不是 C++ 异常),因为 JIT 编译器知道可能发生陷阱并生成适当的帧展开数据,所以该异常有效。
    • clone() 和 exec() 调试器(使用 fork() - 调用 pthread_atfork() 注册的回调)。

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

    【讨论】:

    • mmap() 和 mprotect() 通常与 SIGSEGV 处理程序结合使用,以捕获对某些区域的内存访问,在这种情况下,行为被定义为内存访问不是无效的,而是受保护的.
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-16
      相关资源
      最近更新 更多