为了理解工作原理,我试图用mmap
从内核中分配一些内存然后设置保护位,以便任何内存访问都会导致分段错误,之后我想尝试设置保护位使得分段故障不再发生。
对mprotect的调用失败,si_addr
中的地址错误,即使sigaction
的linux手册页说siginfo
结构的si_addr
函数包含地址导致错误。地址不是main()
函数中分配的地址。 代码在mac
#define _XOPEN_SOURCE
#include <iostream>
#include <signal.h>
#include <ucontext.h>
#include <sys/mman.h>
#include <string.h>
#include <cstdlib>
using std::cout;
using std::cerr;
using std::endl;
void handle_signal(int signal_number, siginfo_t* signal_info, void* context);
void register_signal_handler();
int counter = 0;
int main() {
register_signal_handler();
int* page_mapped = (int*) mmap(nullptr, 100, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (page_mapped == MAP_FAILED) {
cerr << "mmap failed" << endl;
}
cout << "page mapped is " << reinterpret_cast<uintptr_t>(page_mapped)
<< endl;
// cause the segmentation fault
cout << *page_mapped << endl;
return 0;
}
void handle_signal(int, siginfo_t* siginfo, void*) {
cout << "Handled a segmentation fault" << endl;
cout << "The segmentation fault was caused by the address "
<< reinterpret_cast<uintptr_t>(siginfo->si_addr) << endl;
if (mprotect(siginfo->si_addr, 100, PROT_READ | PROT_WRITE) == -1) {
cerr << "mprotect failed" << endl;
exit(1);
}
// stop an infinite loop
++counter;
if (counter == 3) {
cerr << "Counter got to 3, probably going into an infinite loop.. "
"stopping" << endl;
exit(1);
}
}
void register_signal_handler() {
struct sigaction sigaction_information;
memset(&sigaction_information, 0, sizeof(struct sigaction));
sigaction_information.sa_sigaction = &handle_signal;
sigaction(SIGSEGV, &sigaction_information, nullptr);
}
答案 0 :(得分:0)
见this answer。它解释了SIGSEGV
信号处理程序应更改机器状态,否则重新启动相同的机器指令并提供内核转换为发送相同的信号(在相同的&#34;上下文&#34;),因此循环。
<stdio.h>
)是错误的(因为您正在使用非异步信号安全函数)。仔细阅读signal(7)。请注意,禁止信号处理程序调用许多函数(那些不是异步信号安全函数的函数)。
您对mprotect(2)的来电是错误的(并且失败了)。大小应该是页面大小的倍数(通常为4K),地址也应该是它的倍数(您可能应该使用page_mapped
,而不是siginfo->si_addr
作为{{1的地址参数或者,您可以将向下 mprotect
舍入到4K页面大小的前一个倍数。当我运行你的程序(由siginfo->si_addr
在Debian / x86-64上使用GCC 6&amp; linux内核4.8编译时)它抱怨:g++ -O -Wall curious.cc -o curious
(mprotect failed
错误,由{{3}给出}})。
您可以使用perror(3)更准确地了解正在发生的事情。
最后,您的EINVAL
应声明为strace(1)。
将counter
和counter
声明为易变的全局变量:
page_mapped
并在内部volatile int counter;
int*volatile page_mapped;
以下代码(在我的系统上,页面大小为4K):
handle_signal
它的行为有所不同(并且更像你想要的),因为if (mprotect(page_mapped, 4096, PROT_READ | PROT_WRITE) == -1) {
/// this is still wrong in theory,
/// .... since we are using non-async signal safe functions
perror("mprotect");
exit(EXIT_FAILURE);
/// but in practice mprotect is successful
}
没有失败,mprotect
的最终值(counter
的末尾)是1(如你所愿)它是)。
答案 1 :(得分:0)
你只是从man sigaction
错过了这个:
如果在 sa_flags 中指定了 SA_SIGINFO ,那么 sa_sigaction (而不是 sa_handler )指定 signum 的信号处理功能。
换句话说,如果要指定 sa_sigaction 而不是 sa_handler ,你必须设置那个标志,所以
sigaction_information.sa_flags = SA_SIGINFO;
将添加到register_signal_handler()
。