Home > programming > Debug Heap Corrupt

Debug Heap Corrupt

December 12th, 2014 Leave a comment Go to 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: ,
  1. No comments yet.
  1. No trackbacks yet.