转自: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. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 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(path, O_RDWR|O_CREAT, 777);
  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_SIZE, PROT_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_SIZE, PROT_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_SIZE, PROT_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, &act, NULL) == -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_addr, PAGE_SIZE);
  60.  
    my_backtrace();
  61.  
    }
  62.  
    }
  63.  
    static void my_backtrace()
  64.  
    {
  65.  
    const int N = 100;
  66.  
    void* array[100];
  67.  
    size_t size = backtrace(array, N);
  68.  
    backtrace_symbols_fd(array, size, fd_);
  69.  
    }
  70.  
    static uint64_t address_;
  71.  
    static int len_;
  72.  
    static int fd_;
  73.  
    };
  74.  
     

这个封装还存在一些问题,比如缺少错误处理,待保护内存必须在一页内等。读者诸君可以根据需要自行完善。

实战

来个例子,实战一下吧

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 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 *p = NULL;
  9.  
    void g()
  10.  
    {
  11.  
    usleep(2000000);
  12.  
    char *q = reinterpret_cast<char *>(p);
  13.  
    *(q+2) = 111;//非法篡改!!!
  14.  
    }
  15.  
    void f()
  16.  
    {
  17.  
    p = new int(1);
  18.  
    MemoryDetector::protect(p, 4);
  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.  
     

用如下方式编译链接以上程序:

  1. 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命令:

  1. addr2line -e test 0x405909

获得非法篡改的代码位置:

/home/yebangyu/test.cpp:13

真相大白了。

多线程内存问题分析之mprotect方法【转】的更多相关文章

  1. Android 内存泄漏分析与解决方法

    在分析Android内存泄漏之前,先了解一下JAVA的一些知识 1. JAVA中的对象的创建 使用new指令生成对象时,堆内存将会为此开辟一份空间存放该对象 垃圾回收器回收非存活的对象,并释放对应的内 ...

  2. MFC多线程内存泄漏问题&amp;解决方法

    在用visual studio进行界面编程时(如MFC),前台UI我们能够通过MFC的消息循环机制实现.而对于后台的数据处理.我们可能会用到多线程来处理. 那么对于大多数人(尤其是我这样的菜鸟),一个 ...

  3. Android handler 内存泄露分析及解决方法

    1. 什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引 ...

  4. Java内存溢出分析方法(Eclipse Memory Analyzer 使用简单入门)

    转载至:http://outofmemory.cn/java/jvm/OutOfMemoryError-analysis 工具 安装Memory Analyse Tools(MAT) 工具, 可以直接 ...

  5. 学会用Clang来进行内存泄露分析

    最近项目出现了内存泄露的问题,对于PC x86平台来说,一点点的内存泄露往往不会出错,很难进行debug调试.这个时候我们可以用到苹果给我们带来的神器--Clang编译器来进行内存泄露分析检测,开关打 ...

  6. nginx 内存池分析

    最近nginx的源码刚好研究到内存池,这儿就看下nginx内存池的相关的东西. 一,为什么要使用内存池 大多数的解释不外乎提升程序的处理性能及减小内存中的碎片,对于性能优化这点主要体现在: (1)系统 ...

  7. 《深入理解Java虚拟机》(六)堆内存使用分析,垃圾收集器 GC 日志解读

    堆内存使用分析,GC 日志解读 重要的东东 在Java中,对象实例都是在堆上创建.一些类信息,常量,静态变量等存储在方法区.堆和方法区都是线程共享的. GC机制是由JVM提供,用来清理需要清除的对象, ...

  8. Oracle内存全面分析

    Oracle内存全面分析 Oracle的内存配置与oracle性能息息相关.而且关于内存的错误(如4030.4031错误)都是十分令人头疼的问题.可以说,关于内存的配置,是最影响Oracle性能的配置 ...

  9. 【java基础 7】java内存区域分析及常见异常

    本篇博客,主要是读书笔记总结,还有就是结合培训分享的总结,没有太多的技术含量! java 的自动内存管理机制,使得程序员不用为每一个new惭怍的对象写配对的delete/ free代码(回想起C++的 ...

随机推荐

  1. 腾讯云ping wget yum 常用命令设置问题

    遇到ping wget yum 命令不能正常使用的情况是因为腾讯云有些配置: root执行如下即可: wget -q http://mirrors.tencentyun.com/install/sof ...

  2. java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)

    前面学习ByteArrayInputStream,了解了“输入流”.接下来,我们学习与ByteArrayInputStream相对应的输出流,即ByteArrayOutputStream.本章,我们会 ...

  3. vue基础篇---watch监听

    watch可以让我们监控一个值的变化.从而做出相应的反应. 示例: <div id="app"> <input type="text" v-m ...

  4. Web API中的路由(一)——约定路由

    一.Web API中的路由概念 路由的作用用一句话说明:通过request的uri找到处理该请求的Controller,Action,以及给Action的参数赋值. 一些路由的基本概念: route: ...

  5. [NIO-4]选择器

    选择器 最后,我们探索一下选择器.由于选择器内容比较多,所以本篇先偏理论地讲一下,后一篇讲代码,文章也没有什么概括.总结的,写到哪儿算哪儿了,只求能将选择器写明白,并且将一些相对重要的内容加粗标红. ...

  6. VMware Workstation Pro 14 虚拟机安装教程

    准备学习一下linxus,可不想双系统,那么只有虚拟机这条路了.故在此记录一下安装VMware的过程. 一.下载VMware 我用的是VMware Workstation Pro 14(其他版本也可以 ...

  7. 我的长大app开发教程第二弹:完成ContentFragment底部按钮

    在开始之前,先上一张效果图 突然发现有点知乎的味道...的确..知乎灰#989898,知乎蓝15,136,235(逃.... 1.学P图 想我大一的时候也用过不少Adobe的软件,昨天重新打开我的Ph ...

  8. WEUI控件JS用法

    /* dialog */ document.querySelector('#alertBtn').addEventListener('click', function () { _weui2.defa ...

  9. lua 设置文件运行的环境

    背景 在一个lua文件中书写的代码, 使用的变量, 需要设置其运行环境. 目的: 1. 不破坏全局环境. 2. 限定文件所使用的环境, 作为沙箱功能. 解法 限定运行空间环境的文件: local m ...

  10. 【python小练】0014题 和 0015 题

    第 0014 题: 纯文本文件 student.txt为学生信息, 里面的内容(包括花括号)如下所示: { ":["张三",150,120,100], ":[& ...