多線程內存問題分析之mprotect方法【轉】


轉自:https://blog.csdn.net/agwtpcbox/article/details/53230664

http://www.yebangyu.org/blog/2016/02/01/detectmemoryghostinmultithread/

多線程中的內存問題,一直被認為是噩夢般的存在,幾乎只有高手、大仙才能解決。除了大量的打log、gdb調試、code review以及依靠多年的經驗和直覺之外,有沒有一些分析的手段和工具呢?答案是肯定的。本文首先介紹其中的一種:mprotect大法。通過mprotect,保護特定的感興趣的內存,當有線程改寫該區域時,會產生一個中斷,我們在中斷處理函數中把調用棧等信息打印出來。這是大概的思路,不過其中的問題很多,我們慢慢道來。

原理

mprotect函數

mprotect函數的原型如下:

int mprotect(const void *addr, size_t len, int prot);

其中addr是待保護的內存首地址,必須按頁對齊;len是待保護內存的大小,必須是頁的整數倍,prot代表模式,可能的取值有PROT_READ(表示可讀)、PROT_WRITE(可寫)等。

不同體系結構和操作系統,一頁的大小不盡相同。如何獲得頁大小呢?通過PAGE_SIZE宏或者getpagesize()系統調用即可。

定制中斷處理函數

當線程試圖對我們已保護(成只讀)的內存進行篡改時,默認情況下程序會收到SIGSEGV錯誤而退出。能不能不退出並且把相應的調用棧打印出來分析?當然可以。通過如下代碼注冊你定制的中斷處理函數即可:

  1.  
    struct sigaction act;
  2.  
    act.sa_sigaction = your_handler;
  3.  
    sigemptyset(&act.sa_mask);
  4.  
    act.sa_flags = SA_SIGINFO;
  5.  
    if(sigaction(SIGSEGV, &act, NULL) == -1) {
  6.  
    perror( "Register hanlder failed");
  7.  
    exit(EXIT_FAILURE);
  8.  
    }

這樣,控制流就會到達你編寫的your_handler函數上。而your_handler的函數原型是:

void your_handler(int sig, siginfo_t *si, void *unused);

編寫your_handler函數即可?是的,不過這里面有兩個注意事項:

1,中斷處理函數里不應該調用內存分配函數,否則可能會引起double fault。因此,不適合調用backtrace_symbols(內部會動態分配內存),而是通過backtrace_symbols_fd直接將調用棧信息直接刷到文件中。

2,中斷處理函數中應該恢復被保護內存為可寫,否則會引起死循環。(再次中斷並進入咱們編寫的函數)

封裝

為了方便使用,我封裝了一個類,供參考:

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 
  1.  
    #include <fcntl.h>
  2.  
    #include <signal.h>
  3.  
    #include <stdio.h>
  4.  
    #include <stdlib.h>
  5.  
    #include <string.h>
  6.  
    #include <stdint.h>
  7.  
    #include <sys/mman.h>
  8.  
    #include <sys/stat.h>
  9.  
    #include <unistd.h>
  10.  
    #include <sys/user.h>
  11.  
    #include <execinfo.h>
  12.  
    class MemoryDetector
  13.  
    {
  14.  
    public:
  15.  
    typedef void (*segv_handler) (int sig, siginfo_t *si, void *unused);
  16.  
    static void init(const char *path)
  17.  
    {
  18.  
    register_handler(handler);
  19.  
    fd_ open(pathO_RDWR|O_CREAT777);
  20.  
    }
  21.  
    static int protect(void *p, int len)
  22.  
    {
  23.  
    address_ reinterpret_cast<uint64_t>(p);
  24.  
    len_ len;
  25.  
    uint64_t start_address (address_ >> PAGE_SHIFT<< PAGE_SHIFT;
  26.  
    return mprotect(reinterpret_cast<void *>(start_address), PAGE_SIZEPROT_READ);
  27.  
    }
  28.  
    static int umprotect(void *p, int len)
  29.  
    {
  30.  
    uint64_t tmp_address_ reinterpret_cast<uint64_t>(p);
  31.  
    uint64_t start_address (tmp_address_ >> PAGE_SHIFT<< PAGE_SHIFT;
  32.  
    return mprotect(reinterpret_cast<void *>(start_address), PAGE_SIZEPROT_READ PROT_WRITE);
  33.  
    }
  34.  
    static int umprotect()
  35.  
    {
  36.  
    uint64_t start_address (address_ >> PAGE_SHIFT<< PAGE_SHIFT;
  37.  
    return mprotect(reinterpret_cast<void *>(start_address), PAGE_SIZEPROT_READ PROT_WRITE);
  38.  
    }
  39.  
    static void finish()
  40.  
    {
  41.  
    close(fd_);
  42.  
    }
  43.  
    private:
  44.  
    static void register_handler(segv_handler sh)
  45.  
    {
  46.  
    struct sigaction act;
  47.  
    act.sa_sigaction sh;
  48.  
    sigemptyset(&act.sa_mask);
  49.  
    act.sa_flags SA_SIGINFO;
  50.  
    if(sigaction(SIGSEGV&actNULL== -1){
  51.  
    perror("Register hanlder failed");
  52.  
    exit(EXIT_FAILURE);
  53.  
    }
  54.  
    }
  55.  
    static void handler(int sig, siginfo_t *si, void *unused)
  56.  
    {
  57.  
    uint64_t address reinterpret_cast<uint64_t>(si->si_addr);
  58.  
    if (address >= address_ && address address_ len_{
  59.  
    umprotect(si->si_addrPAGE_SIZE);
  60.  
    my_backtrace();
  61.  
    }
  62.  
    }
  63.  
    static void my_backtrace()
  64.  
    {
  65.  
    const int 100;
  66.  
    voidarray[100];
  67.  
    size_t size backtrace(arrayN);
  68.  
    backtrace_symbols_fd(arraysizefd_);
  69.  
    }
  70.  
    static uint64_t address_;
  71.  
    static int len_;
  72.  
    static int fd_;
  73.  
    };
  74.  
     

這個封裝還存在一些問題,比如缺少錯誤處理,待保護內存必須在一頁內等。讀者諸君可以根據需要自行完善。

實戰

來個例子,實戰一下吧

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 
  1.  
    #include "test.h" //就是上面封裝的MemoryDetector類
  2.  
    #include <thread>
  3.  
    using namespace std;
  4.  
    uint64_t MemoryDetector::address_ 0;
  5.  
    int MemoryDetector::len_ 0;
  6.  
    int MemoryDetector::fd_ 0;
  7.  
    ///////////////////////////////////////
  8.  
    int *NULL;
  9.  
    void g()
  10.  
    {
  11.  
    usleep(2000000);
  12.  
    char *reinterpret_cast<char *>(p);
  13.  
    *(q+2111;//非法篡改!!!
  14.  
    }
  15.  
    void f()
  16.  
    {
  17.  
    new int(1);
  18.  
    MemoryDetector::protect(p4);
  19.  
    }
  20.  
    int main()
  21.  
    {
  22.  
    const char *path "result.tmp";//調用棧信息存放路徑
  23.  
    MemoryDetector::init(path);
  24.  
    std::thread t1(f);
  25.  
    std::thread t2(g);
  26.  
    t1.join();
  27.  
    t2.join();
  28.  
    MemoryDetector::finish();
  29.  
    return 0;
  30.  
    }
  31.  
     

用如下方式編譯鏈接以上程序:

g++ -g -rdynamic -std=c++11 -pthread test.cpp -o test 

程序運行結束后,打開result.tmp文件,看到如下內容:

  1.  
    ./ test(_ZN14MemoryDetector12my_backtraceEv+0x26)[0x405ce8]
  2.  
    ./ test(_ZN14MemoryDetector7handlerEiP7siginfoPv+0x60)[0x405cc0]
  3.  
    /lib64/libpthread.so.0[0x339a80f500]
  4.  
    ./ test(_Z1gv+0x25)[0x405909]
  5.  
    ./ test(_ZNSt6thread5_ImplIPFvvEE6_M_runEv+0x16)[0x406e2c]
  6.  
    /usr/lib64/libstdc++.so.6[0x3a6f6b6490]
  7.  
    /lib64/libpthread.so.0[0x339a807851]
  8.  
    /lib64/libc.so.6( clone+0x6d)[0x339a4e767d]

注意其中的第四行:./test(_Z1gv+0x25)[0x405909]。使用addr2line命令:

addr2line -e test 0x405909 

獲得非法篡改的代碼位置:

/home/yebangyu/test.cpp:13

真相大白了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM