Archive

Posts Tagged ‘debug’

Debug Heap Corrupt

December 12th, 2014 No comments

c/c++代码对内存操作有着极强的控制力, 但是也很容易造成问题。 特别是内存访问越界这类问, 一旦出现了,在很多情况下是很难查找的。 如果软件中的某个模块因为单元测试没有完全覆盖(这个通常也很难)然后集成起来以后出来的这类问题就更加难以查找。 很多时候出现问题的地方根本就不是你看到的地方。 我们来举个例子, 这个也是我们遇到的问题的一个简化版本。 直接上代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vector>

void corrupt( ) {
    char* p = (char*)malloc(1);
    memset(p,0x47,32);//fill some garbage to corrupt the heap
}

void victim( ) {
    std::vector<int> arr;
    for(int i = 0 ; i < 100; i ++ ) {
        arr.push_back(i);
    }
}

int main(int argc, char* argv[]){
    corrupt();
    victim();
    return 0;
}

上面的代码在corrupt函数中分配了一个字节的空间,接着调用memset向分配的地址写入32个0x47. 那么很明显,写越界了。 这样的操作会把heap的维护链表弄坏,导致以后再次进行内存分配释放的时候出现错误,也可能写入另外一块分配的内存区域。
在vc(visual studio 2005)下面编译运行以后在

        arr.push_back(i);

出现错误。 output窗口(调试模式下面)输出

First-chance exception at 0x7c922bb3 (ntdll.dll) in heap_corrupt.exe: 0xC0000005: Access violation reading location 0x003b6000.

很明显出现错误的地方并不是造成问题的地方, 这个简单的程序你可能很容易就可以看出问题出在什么位置。但是如果是一个很复杂的大型工程,那么很可能就没有那么容易了。如果没有其他的帮助工具的话估计要花很大的时间和精力来逐步收缩出问题的范围。
上面的代码在g++下面(需要增加一些include的头文件)编译运行以后也会显示是arr.push_back(i)这一行除了问题。
为了能有效的快速定位此类问题, 我们通常需要借助其他的工具。 在windows上面我们可以使用gflags来做到这一点。
使用如下命令,monitor heap的操作

gflags /p /enable heap_corrupt.exe /full

然后再来调试改程序,就会在memset(p,0x47,32);这一行发生异常。 那么我们就可以很快的检查出来是因为对p的写越界造成了这个问题。 但并不是有这个工具就万事OK了。如果我们把memset那一句改成memset(p,0x47,2);的话。那么程序在整个调试过程中是不会出错的,当然了,这个很可能是因为malloc(1)的时候得到的内存块的真是大小不是1而是某个最小的bucket的大小.
在linux上面呢, 我们可以使用AddressSanitizer这种神器来快速定位这个问题。因为我们的gcc版本是4.4.7的,还没有AddressSanitizer的支持,所以可以使用clang.

clang++ -g -fsanitize=address -o t test.cpp

然后直接运行程序就可以在输出上看到一堆东西

[hongquan.zhang@devbox Download]$ ./t
=================================================================
==1216==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000f00f at pc 0x48aaab bp 0x7fff4a1687d0 sp 0x7fff4a1687c8
WRITE of size 32 at 0x60200000f00f thread T0
    #0 0x48aaaa in corrupt() /home/hongquan.zhang/Download/test.cpp:10
    #1 0x48b0d0 in main /home/hongquan.zhang/Download/test.cpp:21
    #2 0x3f7201ecdc in __libc_start_main (/lib64/libc.so.6+0x3f7201ecdc)
    #3 0x48a84c in _start (/home/hongquan.zhang/Download/t+0x48a84c)

0x60200000f00f is located 30 bytes to the right of 1-byte region [0x60200000eff0,0x60200000eff1)
allocated by thread T0 here:
    #0 0x473131 in malloc /home/hongquan.zhang/Work/tools/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74
    #1 0x48a9cb in corrupt() /home/hongquan.zhang/Download/test.cpp:9
    #2 0x48b0d0 in main /home/hongquan.zhang/Download/test.cpp:21
    #3 0x3f7201ecdc in __libc_start_main (/lib64/libc.so.6+0x3f7201ecdc)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/hongquan.zhang/Download/test.cpp:10 corrupt()
Shadow bytes around the buggy address:
  0x0c047fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa 01 fa
=>0x0c047fff9e00: fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9e50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Contiguous container OOB:fc
  ASan internal:           fe
==1216==ABORTING

输出的最开始就表明了出错的地方是corrupt函数,可以非常方便的让我们在第一时间发现真正制造问题的地方。而且即便你把memset调用改成memset(p,0x47,1)也依然有效。

上面的方法也许对你在遇到问题的时候有一些帮助,当然了最好还是在操作内存的时候小心一些。如果可以的话,尽量使用c++已经封装好的class。 如果不得已需要直接操作内存需要慎之又慎。

Categories: programming Tags: ,

一个与字节对齐有关的错误(member align)

February 16th, 2012 No comments

   在C/C++里面字节对齐在大部分时候都不需要被考虑进来,编译器会使用缺省的对齐大小对代码进行编译。但是如果遇到某些特殊的地方,就需要指定编译器用什么大小进行字节对齐。例如,以一个字节大小进行对齐。  

  而最近在一个项目中则遇到了与字节对齐有点关系的一个很奇怪的bug。故事大致如下:

在项目赶工的过程中,对一个半成品的程序大致的调试了一下,看看大致的流程是否可以跑通。不过这一试不要紧,却发现了一个很严重的问题。程序一定会crash, 调试的时候发现一个class instance的this居然是0x00000001,这个也太妖了。这个instance被new出来以后没有做过什么特别的操作。我实在是没有搞懂为何会出现这种诡异的问题。后来检查代码的时候发现有一个消息解析类的头文件里面使用了

#pragma pack(1),但是很郁闷的发现该头文件里面并没有使用#pragma pack()。我猜想大概程序出错和这个问题有关。在头文件尾部添加了#pragma pack()以后测试程序就一切正常了。

再后来,我自己做了一个小的测试程序,想模拟一下这种#pragma pack()没有配对的情况对程序的影响,去发现无法正常模拟出这个情况。但是在模拟过程中却发现在VS里面编译时会出现warning:

1>.align.cpp(10) : warning C4103: ‘e:worktestalignalign.cpp’ : alignment changed after including header, may be due to missing #pragma pack(pop)

不过为何在项目程序的编译的时候不会出现这个warning呢(setting里面没有什么特殊的配置禁掉某些warning或者error)

Categories: programming Tags:

到底是哪个线程导致了crash呢?

December 15th, 2011 No comments

  在linux下调试确实不熟悉,这不?又上当了。

   以前同事告知,当程序crash以后,用gdb打开core文件,第一个引入眼帘线程就是出现异常的那个线程。通常情况下一个where指令就可以让你知道到底是什么位置引起的crash。

  但是今天遇到的问题却有点奇怪。事情是这样的,一个程序在测试环境中crash了。拿到了core文件用gdb打开以后,直接where,出现的call stack如下。(下面的callstack是我随意写的,只是问了说明一下大概情形)

0 memcpy() ...
1 std::string::string()
2 xmlbuild::buildnode()
....

也就是说看起来是在Buildnode函数中调用一个std::string的构造函数的时候出错了。我满以为找到问题了,但是当我看代码的时候就郁闷了。buildnode中调用string的构造函数的传入传输也是一个std::string.用gdb的p命令查看这个std::string instance发现没有异常。奇怪了,要抓狂了。我开始怀疑这个call stack是否准确了,难道gdb坏了。

  经过较长时间的瞎想乱猜,我觉得会不会是我们的程序因为catch了SIGSEGV并且在这个信号的处理函数中做了一些事情(打印一些日志说当前的进程光荣就义了之类的,然后flush在内存中的log消息到磁盘)影响了线程在gdb里面的顺序。于是我修改程序去掉了信号的处理的函数。等待再次crash的时候发现这个时候gdb中的第一线程的callstack中所显示的代码确实有问题,有个地方没有判断是否是NULL就开始操作了。

  上面导致crash的代码逻辑很容易修改,但是这个事情也告诉我。在多核的环境下,如果在信号的处理函数中还做了一些事情(我估计特别是这个事情需要再次进入内核)那么用gdb打开core文件得到的第一个线程不一定是真正导致程序crash的那个线程。现在的解决方案很简单,如果你觉得gdb打开的core文件的第一个线程怎么看都不太像是有问题的线程,那就看看其他线程吧。没准在其他的线程里面可以找到真正引起错误的元凶。

Categories: programming Tags: , ,