多线程内存问题分析之mprotect方法

Posted 小象

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程内存问题分析之mprotect方法相关的知识,希望对你有一定的参考价值。

多线程中的内存问题,一直被认为是噩梦般的存在,几乎只有高手、大仙才能解决。除了大量的打log、gdb调试、code review以及依靠多年的经验和直觉之外,有没有一些分析的手段和工具呢?答案是肯定的。本文首先介绍其中的一种:mprotect大法。通过mprotect,保护特定的感兴趣的内存,当有线程改写该区域时,会产生一个中断,我们在中断处理函数中把调用栈等信息打印出来。这是大概的思路,不过其中的问题很多,我们慢慢道来。


原理

mprotect函数

mprotect函数的原型如下:

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



定制中断处理函数

当线程试图对我们已保护(成只读)的内存进行篡改时,默认情况下程序会收到SIGSEGV错误而退出。能不能不退出并且把相应的调用栈打印出来分析?当然可以。通过如下代码注册你定制的中断处理函数即可:


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

这样,控制流就会到达你编写的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,中断处理函数中应该恢复被保护内存为可写,否则会引起死循环。(再次中断并进入咱们编写的函数)


封装

为了方便使用,我封装了一个类,供参考:



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


实战

来个例子,实战一下吧。


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


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

程序运行结束后,打开result.tmp文件,看到如下内容:


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

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


addr2line -e test 0x405909

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


/home/yebangyu/test.cpp:13


真相大白了。


来源:yebangyu's blog


以上是关于多线程内存问题分析之mprotect方法的主要内容,如果未能解决你的问题,请参考以下文章

高并发多线程安全之信号量线程组守护线程线程栅栏等的分析

内存越界定位mprotect

多线程之synchronized实现原理

mprotect 函数用法

多线程

Linux 内核 内存管理内存管理系统调用 ① ( mmap 创建内存映射 | munmap 删除内存映射 | mprotect 设置虚拟内存区域访问权限 )