增强mcheck的内存检查方法:打印指针名

243 阅读3分钟

背景

最近刚刚转到C技术栈,直接就碰到了C语言的常见问题:内存越界。 检查内存越界的工具有很多,这里笔者采用mcheck,因为这是glibc原生的工具,不需要额外下载。但是,深入使用后发现mcheck有个坑:

  1. 功能过于简单,mcheck原生方法只会在遇到内存问题时上报,具体是哪个指针被踩了,哪个指针没有回收,哪个指针double free了没有提示出来,不利于问题定位。

为了方便实用,花几分钟写了一个新的结构体+单链表封装mcheck,以增强其检查函数。实现比较简单,仅供学习使用。

源码

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

#define VARIABLE_NAME(var) #var

class TestStruct {
public:
    int id;
};

struct SmartPtr {
    void* ptr;
    const char* ptr_name;
    SmartPtr* next = NULL;
public:
    SmartPtr(void* ptr, const char* ptr_name) {
        this->ptr = ptr;
        this->ptr_name = ptr_name;
    }

    static void create_block(void* ptr, const char* ptr_name);

    static void checkAllPtr();
};

static SmartPtr* root = NULL;

void SmartPtr::create_block(void* ptr, const char* ptr_name) {
    if (ptr == NULL) {
        printf("[var_name = %s]Failed to create smart ptr: ptr is NULL.\n");
        return;
    }
    SmartPtr* new_block = new SmartPtr(ptr, ptr_name);
    if (root == NULL) {
        root = new_block;
    } else {
        SmartPtr* tail = root;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        tail->next = new_block;
    }
};

void SmartPtr::checkAllPtr() {
    // 遍历指针
    SmartPtr* tmp = root;
    while(tmp != NULL) {
        printf("[var_name = %s]check ptr.\n", tmp->ptr_name);
        mprobe(tmp->ptr);
        tmp = tmp->next;
    }
}
void print_mstatus(enum mcheck_status mstatus){
    switch(mstatus){
        case MCHECK_DISABLED:
            printf("consistency checking is not turned on\n");
            break;
        case MCHECK_OK:
            printf("block is fine\n");
            break;
        case MCHECK_FREE:
            printf("block freed twice\n");
            break;
        case MCHECK_HEAD:
            printf("memory before the block was clobbered\n");
            break;
        case MCHECK_TAIL:
            printf("memory after the block was clobbered\n");
            break;
        default:
            printf("Memory is OK.\n");
            break;
    }
}

void abortfun(enum mcheck_status mstatus) {
    fprintf(stderr, "abortfun called with %d\n", mstatus);
    print_mstatus(mstatus);
}

int main(int argc, char *argv[]) {
    TestStruct *p1, *p2, *tmpp = NULL;
    enum mcheck_status mstatus;
    int result = mcheck(abortfun);
    if( result != 0) {
        fprintf(stderr, "mcheck:%d\n", result);
        return -1;
    }
    
    p1 = (TestStruct*)malloc(sizeof(TestStruct) * 10);
    p2 = (TestStruct*)malloc(sizeof(TestStruct) * 10);
    SmartPtr::create_block(p1, VARIABLE_NAME(p1));
    SmartPtr::create_block(p2,VARIABLE_NAME(p2));
    SmartPtr::create_block(tmpp,VARIABLE_NAME(tmpp));

    tmpp = p1 + 10;
    tmpp[0].id = 10; // 内存块后越界

    tmpp = p2 - 1;
    tmpp[0].id = 10; // 内存块前越界

    // 检测内存是否越界
    printf("checkAllPtr starts...\n");
    SmartPtr::checkAllPtr();
    printf("checkAllPtr ends...\n");
    
// mcheck 原生方法,检查所有堆内的内存
//    mcheck_check_all(); 
//    printf("mcheck_check_all finished.\n");

    free(p1);
    free(p2);

    return 0;
}

样例输出

以上实例的输出为:

[var_name = tmpp]Failed to create smart ptr: ptr is NULL.
checkAllPtr starts...
[var_name = p1]check ptr.
abortfun called with 3
memory after the block was clobbered
[var_name = p2]check ptr.
abortfun called with 2
memory before the block was clobbered
checkAllPtr ends...
abortfun called with 3
memory after the block was clobbered
abortfun called with 2
memory before the block was clobbered

总结

由于mcheck的报告信息过于简单,不利于问题定位,笔者扩展了mcheck,使其能保持内存错误的具体位置。 但是笔者不建议直接使用mcheck,原因如下:

  1. 相关资料不多,深入使用时,经常要自己去看代码。
  2. mcheck经常莫名奇妙就被disable了,显式调用mcheck()、启动参数加上-lmcheck也没用,当然这点可能是笔者功底不足。

建议使用valgrind,这是一个封装得更好的工具,功能更强大,相关资料、文档也更丰富。