我编写了一个程序来扫描内核内存中的用户空间模式。我从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下做的事情是不可能的,那么我是否可以通过某种方式从用户空间获取内核页面的当前状态(这将允许我避免尝试搜索无法访问的页面。)我搜索了调用那可能会提供这样的信息,但也是空洞的。 谢谢你的帮助!
答案 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