如何在linux中处理多个SIGSEGV?

时间:2018-02-15 21:35:32

标签: linux memory-management kernel sigsegv

我编写了一个程序来扫描内核内存中的用户空间模式。我从root运行它。我希望它会在遇到无法访问的页面时生成SIGSEGV;我想忽略这些错误,然后跳转到下一页继续搜索。我已经设置了一个信号处理程序,它在第一次出现时工作正常,并且按预期继续向前。但是,当第二个SIGSEGV发生时,忽略处理程序(它在第一次出现后重新注册)并且程序终止。代码的相关部分是:

jmp_buf restore_point;
void segv_handler(int sig, siginfo_t* info, void* ucontext)
{
    longjmp(restore_point, SIGSEGV);
}
void setup_segv_handler()
{
  struct sigaction sa;
  sa.sa_flags = SA_SIGINFO|SA_RESTART|SA_RESETHAND;
  sigemptyset (&sa.sa_mask);
  sa.sa_sigaction = &segv_handler;
  if (sigaction(SIGSEGV, &sa, NULL) == -1) {
    fprintf(stderr, "failed to setup SIGSEGV handler\n");
  }
}
unsigned long search_kernel_memory_area(unsigned long start_address, size_t area_len, const void* pattern, size_t pattern_len)
{
    int fd;
    char* kernel_mem;

    fd = open("/dev/kmem", O_RDONLY);

    if (fd < 0)
    {
        perror("open /dev/kmem failed");
        return -1;
    }

    unsigned long page_size = sysconf(_SC_PAGESIZE);

    unsigned long page_aligned_offset = (start_address/page_size)*page_size;
    unsigned long area_pages = area_len/page_size + (area_len%page_size ? 1 : 0);

    kernel_mem =
        mmap(0, area_pages,
           PROT_READ, MAP_SHARED,
           fd, page_aligned_offset);

    if (kernel_mem == MAP_FAILED)
    {
        perror("mmap failed");

        return -1;
    }

    if (!mlock((const void*)kernel_mem,area_len))
    {
        perror("mlock failed");

        return -1;
    }

    unsigned long offset_into_page = start_address-page_aligned_offset;
    unsigned long start_area_address = (unsigned long)kernel_mem + offset_into_page;
    unsigned long end_area_address = start_area_address+area_len-pattern_len+1;

    unsigned long addr;

    setup_segv_handler();

    for (addr = start_area_address; addr < end_area_address;addr++)
    {
        unsigned char* kmp = (unsigned char*)addr;
        unsigned char* pmp = (unsigned char*)pattern;
        size_t index = 0;
        for (index = 0; index < pattern_len; index++)
        {
            if (setjmp(restore_point) == 0)
            {
                unsigned char p = *pmp;
                unsigned char k = *kmp;

                if (k != p)
                {
                    break;
                }

                pmp++;
                kmp++;
            }
            else
            {
                addr += page_size -1;

                setup_segv_handler();
                break;
            }
        }

        if (index >= pattern_len)
        {
            return addr;
        }
    }

    munmap(kernel_mem,area_pages);

    close(fd);

    return 0;
 }

我意识到我可以使用像memcmp这样的函数来避免直接编写匹配的部分(我最初这样做了),但我后来想要保证最好的粒度控制以便从故障中恢复,这样我就可以确切地看到发生了什么。 我搜索了互联网以查找有关此行为的信息,然后空了。我运行的linux系统是arm 3.12.30。 如果我想在linux下做的事情是不可能的,那么我是否可以通过某种方式从用户空间获取内核页面的当前状态(这将允许我避免尝试搜索无法访问的页面。)我搜索了调用那可能会提供这样的信息,但也是空洞的。 谢谢你的帮助!

1 个答案:

答案 0 :(得分:0)

尽管longjmp完全允许在信号处理程序中使用(该函数称为 async-signal-safe ,请参阅man signal-safety)并有效退出信号处理,它不会恢复信号掩码。调用信号处理程序时,将自动修改掩码,以阻止新的 SIGSEGV 信号中断处理程序。

虽然可以手动恢复信号掩码,但使用siglongjmp函数会更好(也更简单):除longjmp的效果外,它还会恢复信号掩码。当然,在这种情况下,应使用sigsetjmp函数代替setjmp

// ... in main() function
if(sigsetjmp(restore_point, 1)) // Aside from other things, store signal mask
// ...

// ... in the signal handler
siglongjmp(restore_point); // Also restore signal mask as it was at sigsetjmp() call