Cpp Tutorials-04-Debug and Perf

排查指针/内存问题 & 解决方案

指针引起的内存问题:

  • 野指针读写: 野指针指的是未经初始化的指针(似乎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

手动异常测试请求:

  1. 异常的tcp连接。即在客户端 tcp connent系统调用时,10%概率直接close这个socket。
  2. 异常的ssl连接。考虑两种情况,full handshake第一阶段时,即发送 client hello时,客户端10%概率直接close连接。full handshake第二阶段时,即发送 clientKeyExchange时,客户端 10%概率直接直接关闭 TCP连接。
  3. 异常的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取代

@ref: linux性能分析工具oprofile的安装与使用 » reille blog

gprof

gprof主要是针对应用层程序的性能分析工具,缺点是需要重新编译程序,而且对程序性能有一些影响。不支持内核层面的一些统计,优点就是应用层的函数性能统计比较精细,接近我们对日常性能的理解,比如各个函数时间的运行时间,,函数的调用次数等,很人性易读。
原理是 编译期前在每个函数增加一个mcount函数调用, 用来记录函数耗时和调用次数。

systemtap

systemtap 其实是一个运行时程序或者系统信息采集框架,主要用于动态追踪,当然也能用做性能分析,功能最强大,同时使用也相对复杂。不是一个简单的工具,可以说是一门动态追踪语言。如果程序出现非常麻烦的性能问题时,推荐使用 systemtap。