尝试使用mmap导致分段错误

时间:2016-12-04 07:09:29

标签: c++ linux segmentation-fault mmap

为了理解工作原理,我试图用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);
}

2 个答案:

答案 0 :(得分:0)

this answer。它解释了SIGSEGV信号处理程序应更改机器状态,否则重新启动相同的机器指令并提供内核转换为发送相同的信号(在相同的&#34;上下文&#34;),因此循环。

BTW,在信号处理程序中使用C ++ I / O(甚至是<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 curiousmprotect failed错误,由{{3}给出}})。

您可以使用perror(3)更准确地了解正在发生的事情。

最后,您的EINVAL应声明为strace(1)

countercounter声明为易变的全局变量:

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()