zoukankan      html  css  js  c++  java
  • 多线程内存问题分析之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

    真相大白了。

  • 相关阅读:
    第十一周学习总结
    个人冲刺——(六)
    第二阶段冲刺—第二天
    软件工程第十四周总结
    第二阶段冲刺—第一天
    大道至简阅读笔记02
    输入法用户体验评价
    软件工程第十三周总结
    人机交互-水王
    大道至简阅读笔记01
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/9950639.html
Copyright © 2011-2022 走看看