如何处理 linux 中多次出现的 SIGSEGV?
How to handle more than one SIGSEGV occurrence in linux?
我编写了一个程序来扫描内核内存以查找来自用户 space 的模式。我 运行 它来自 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 这样的函数来避免直接对匹配部分进行编程(我最初是这样做的),但后来我想确保从故障中恢复的最细粒度控制,这样我就可以准确地看到发生了什么.
我在 Internet 上搜索有关此行为的信息,但一无所获。 linux 我在 运行 下使用的系统是 arm 3.12.30。
如果我尝试做的事情在 linux 下是不可能的,有什么方法可以从用户 space 获取内核页面的当前状态(这将允许我避免尝试搜索那些页面无法访问。)我搜索了可能提供此类信息的电话,但也没有找到。
感谢您的帮助!
虽然 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
我编写了一个程序来扫描内核内存以查找来自用户 space 的模式。我 运行 它来自 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 这样的函数来避免直接对匹配部分进行编程(我最初是这样做的),但后来我想确保从故障中恢复的最细粒度控制,这样我就可以准确地看到发生了什么. 我在 Internet 上搜索有关此行为的信息,但一无所获。 linux 我在 运行 下使用的系统是 arm 3.12.30。 如果我尝试做的事情在 linux 下是不可能的,有什么方法可以从用户 space 获取内核页面的当前状态(这将允许我避免尝试搜索那些页面无法访问。)我搜索了可能提供此类信息的电话,但也没有找到。 感谢您的帮助!
虽然 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