2016-12-04 54 views
0

分段故障只是爲了瞭解事情是如何工作的,我想分配從內核的某些內存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); 
} 

回答

0

this answer。它解釋說,SIGSEGV信號處理程序應該變化機器狀態,否則同樣機器指令重新啓動並給出了一些例外,內核會轉化爲同一發送信號(在相同的「上下文」),因此,循環。

BTW,使用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的海灣合作委員會6 & Linux內核4.8編譯)就抱怨:mprotect failed(與EINVAL錯誤,通過perror(3)給出)。

你可以使用strace(1)瞭解更多到底發生了什麼。

最後,您counter應該聲明爲volatile

通過聲明都counterpage_mapped揮發性全球變量:

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(因爲你希望它是)。

+0

* SIGSEGV信號處理程序應該改變機器狀態*爲什麼如果信號處理程序從頁面中刪除導致段錯誤的保護(因此重新啓動相同的機器指令不應該重新發出相同的信號),這是必要的? – Leon

+0

更改保護正在改變機器狀態(例如,在MMU中)。 –

+0

那麼如何獲得mprotect來正確保護導致分段錯誤的地址? – Curious