排查指针/内存问题 & 解决方案
指针引起的内存问题:
- 野指针读写: 野指针指的是未经初始化的指针(似乎
int *p;
定义的指针没有自动置为Null) - 悬垂指针读写: 被free释放但是没有置为Null的指针
- 数组等类型读写越界
- 内存释放两次(DF,Double Free),第二次释放导致coredump
- 内存泄漏, 通常是不匹配地使用
malloc/new/new[]
和free/delete/delete[]
Core Down问题排查
引起core down的原因可能有:
- 数组访问越界, 读到错误的数据, 这种情况一般直接Core down?
- 数组/指针写越界, 破坏了其他的数据, 这种情况可能当时不引起Core down
- Double Free, 第二次
free()
直接Core down
不使用第三方工具
重载new/ malloc, 申请的内存添加头部/尾部特殊字节(线程id), 并用magic number填充, core down时可以分析是被哪个线程写入了
使用第三方工具/库解决方案:
一些满足特殊现象的分析方法:
- 对于固定会越界的代码位置来说,计算好数据位置,使得越界后第一个字节的内存起始的内存页
mprotect
写保护中就可以了。随后像man文档的例子一样注册SIGSEGV信号的处理函数即可,这里可以用backtrace(3)和backtrace_symbols(3)等函数来打出调用栈,轻松找过越界的罪魁祸首 gdb
调试支持对内存位置设置修改断点,而且gdb的内存断点不像直接用mprotect()
有那么多限制
静态分析工具
代码静态分析工具, google有很多, 可以检查疑似写内存的问题
分析coredump文件
一般方法仍然是分析coredump文件, coredump文件里有哪些有用的信息?
- glibc的MALLOC_CHECK环境变量, 适用于“double free”, “free(invalid )”
- 实现: 实际上malloc()分配的内存会比用户实际申请的长度大一点,在返回给用户代码的指针位置的前面有一个固定大小的结构,放置着该块内存的长度、属性和管理的数据结构。
- 每当在程序运行过程free内存给glibc时,glibc会检查其隐藏的元数据的完整性,如果发现错误就会立即abort。
- electric-fence内存调试库: 适用于内存被写坏, 延后引发的core down。
- 原理是采用Linux的虚拟内存机制来保护动态分配的内存,在申请的内存的位置放置只读的哨兵页,在程序越界读写时直接coredump退出。
- 因为对内存做保护使用了mprotect(2)等API,这个API对内存设置只读等属性要求内存页必须是4K对齐的(本质上是Intel CPU的页属性设置的要求),所以内存使用率较低的程序可以用该库进行检查,但是内存使用率很高的程序在使用过程中会造成内存暴涨而不可用。
- Valgrind仿真工具(最常用的是Memcheck) 可以检查: 使用未初始化的内存,使用已经释放了的内存,内存访问越界等。
- 以上两种工具都很明显影响性能, 新版本的gcc(gcc49)提供了很好的内存访问检查机制
命令行参数 -fsanitize=address -fno-omit-frame-pointer
- 检查内存越界的实现是..?
- 另外, Google的 address sanitizer(简称asan)是一个用来检测c/c++程序的快速内存检测工具。相比valgrind的优点就是速度快,官方文档介绍对程序性能的降低只有2倍。
内存泄漏排查
- 代码静态检查工具
- Valgrind仿真
- 重载全局的malloc / free函数,申请和释放内存的时候打印函数和返回地址(用异步日志库)
C++考虑使用shared_ptr, RAII机制来避免内存泄漏
多线程 & 高并发情况下
在增加debug log/ efence动态库 / 都会严重影响qps导致Core down无法重现, 另外特殊网络环境(高延迟, 丢包)下才会重现的问题
- 弱网络环境模拟
traffic control
: 能够控制网络速率、丢包率、延时等网络环境,作为iproute工具集中的一个工具,由linux系统自带 - Http压测工具
wrk
, 类似ab
手动异常测试请求:
- 异常的tcp连接。即在客户端 tcp connent系统调用时,10%概率直接close这个socket。
- 异常的ssl连接。考虑两种情况,full handshake第一阶段时,即发送 client hello时,客户端10%概率直接close连接。full handshake第二阶段时,即发送 clientKeyExchange时,客户端 10%概率直接直接关闭 TCP连接。
- 异常的HTTPS请求,客户端10%的请求使用错误的公钥加密数据,这样nginx解密时肯定会失败。
使用 tcpcopy等工具在线上引流到测试机器进行压测,如果常规流量达不到重现标准,可以对流量进行放大。若线上搭建环境测试有困难,可以对线上流量抓包,然后在线下重放(tcpdump、tcpreplay和tcprewrite等工具)。
这一步之后,一般情况下都能增大重现的概率。如果还难以重现,往往都是一些代码本身的竞态条件(Race Condition)造成的,一般需要在引流测试的同时对CPU或者IO加压,以增大资源竞争的概率来增加问题复现的概率。甚至有些问题是出现网络抖动等情况下,需要模拟弱网络的环境(Linux 2.6内核以上有netem模块,可以模拟低带宽、传输延迟、丢包等情况,使用tc这个工具就可以设置netem的工作模式)。
参考
程序性能分析
@ref:: Perf – Linux下的系统性能调优工具,第 1 部分
perf
perf应该是最全面最方便的一个性能检测工具。由 linux内核携带并且同步更新,基本能满足日常使用。
- 使用 perf,您可以分析程序运行期间发生的硬件事件,比如 instructions retired ,processor clock cycles 等;您也可以分析软件事件,比如 Page Fault 和进程切换。
- 使用 Perf 可以计算每个时钟周期内的指令数,称为 IPC,IPC 偏低表明代码没有很好地利用 CPU。
- Perf 还可以对程序进行函数级别的采样,从而了解程序的性能瓶颈究竟在哪里等等。
通过perf top
就能列举出当前系统或者进程的热点事件,函数的排序。 perf record能够纪录和保存系统或者进程的性能事件,用于后面的分析,比如火焰图。
oprofile
基本被perf取代
gprof
gprof主要是针对应用层程序的性能分析工具,缺点是需要重新编译程序,而且对程序性能有一些影响。不支持内核层面的一些统计,优点就是应用层的函数性能统计比较精细,接近我们对日常性能的理解,比如各个函数时间的运行时间,,函数的调用次数等,很人性易读。
原理是 编译期前在每个函数增加一个mcount函数调用, 用来记录函数耗时和调用次数。
systemtap
systemtap 其实是一个运行时程序或者系统信息采集框架,主要用于动态追踪,当然也能用做性能分析,功能最强大,同时使用也相对复杂。不是一个简单的工具,可以说是一门动态追踪语言。如果程序出现非常麻烦的性能问题时,推荐使用 systemtap。