只是为了了解它是如何工作的,我尝试使用 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);
}
最佳答案
参见 this answer .它解释了 SIGSEGV
信号处理程序应该改变机器状态,否则重新启动相同的机器指令并给出一些异常,内核将其转换为发送的相同信号(在相同的“上下文”中),因此循环。
顺便说一句,在信号处理程序中使用 C++ I/O(甚至是 <stdio.h>
)是错误的(因为您正在使用非异步信号安全函数)。仔细阅读signal(7) .请注意,禁止信号处理程序调用许多函数(那些不是异步信号安全的函数)。
然后您调用 mprotect(2)是错误的(并且失败了)。大小应该是页面大小的倍数(通常为 4K),地址也应该是页面大小的倍数(您可能应该使用 page_mapped
而不是 siginfo->si_addr
作为 mprotect
的地址参数;或者您可以向下 siginfo->si_addr
舍入到 4K 页面大小的前一个倍数)。当我运行你的程序时(由 g++ -O -Wall curious.cc -o curious
在 Debian/x86-64 上使用 GCC 6 和 linux 内核 4.8 编译)它提示:mprotect failed
(有 EINVAL
错误,由 perror(3) 给出)。
你可以使用 strace(1)更准确地了解正在发生的事情。
最后,你的 counter
应声明为 volatile
.
通过同时声明 counter
和 page_mapped
作为 volatile 全局变量:
volatile int counter;
int*volatile page_mapped;
并通过内部 handle_signal
以下代码(在我的系统上,页面大小为 4K):
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
(在 main
末尾)为 1(如您所愿)。
关于c++ - 试图用 mmap 引起段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40956319/