批判我的非侵入式堆调试器

时间:2010-05-14 15:34:53

标签: c++ debugging heap

这是昨天Critique my heap debugger的后续行动。正如bitc所建议的那样,我现在将分配的块的元数据保存在一个单独的手写哈希表中。

堆调试器现在检测到以下类型的错误:

  1. 内存泄漏(现在有更详细的调试输出)
  2. 传递给delete的非法指针(也负责双删除)
  3. 错误的删除形式(数组与非数组)
  4. 缓冲区溢出
  5. 缓冲区下溢
  6. 欢迎提前讨论和感谢!

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <new>
    
    namespace
    {
        // I don't want to #include <algorithm> for a single function template :)
        template <typename T>
        void my_swap(T& x, T& y)
        {
            T z(x);
            x = y;
            y = z;
        }
    
        typedef unsigned char byte;
    
        const byte CANARY[] = {0x5A, 0xFE, 0x6A, 0x8D,
                               0x5A, 0xFE, 0x6A, 0x8D,
                               0x5A, 0xFE, 0x6A, 0x8D,
                               0x5A, 0xFE, 0x6A, 0x8D};
    
        bool canary_dead(const byte* cage)
        {
            bool dead = memcmp(cage, CANARY, sizeof CANARY);
            if (dead)
            {
                for (size_t i = 0; i < sizeof CANARY; ++i)
                {
                    byte b = cage[i];
                    printf(b == CANARY[i] ? "__ " : "%2X ", b);
                }
                putchar('\n');
            }
            return dead;
        }
    
        enum kind_of_memory {AVAILABLE, TOMBSTONE, NON_ARRAY_MEMORY, ARRAY_MEMORY};
    
        const char* kind_string[] = {0, 0, "non-array memory", "    array memory"};
    
        struct metadata
        {
            byte* address;
            size_t size;
            kind_of_memory kind;
    
            bool in_use() const
            {
                return kind & 2;
            }
    
            void print() const
            {
                printf("%s at %p (%d bytes)\n", kind_string[kind], address, size);
            }
    
            bool must_keep_searching_for(void* address)
            {
                return kind == TOMBSTONE || (in_use() && address != this->address);
            }
    
            bool canaries_alive() const
            {
                bool alive = true;
                if (canary_dead(address - sizeof CANARY))
                {
                    printf("ERROR:    buffer underflow at %p\n", address);
                    alive = false;
                }
                if (canary_dead(address + size))
                {
                    printf("ERROR:     buffer overflow at %p\n", address);
                    alive = false;
                }
                return alive;
            }
        };
    
        const size_t MINIMUM_CAPACITY = 11;
    
        class hashtable
        {
            metadata* data;
            size_t used;
            size_t capacity;
            size_t tombstones;
    
        public:
    
            size_t size() const
            {
                return used - tombstones;
            }
    
            void print() const
            {
                for (size_t i = 0; i < capacity; ++i)
                {
                    if (data[i].in_use())
                    {
                        printf(":( leaked ");
                        data[i].print();
                    }
                }
            }
    
            hashtable()
            {
                used = 0;
                capacity = MINIMUM_CAPACITY;
                data = static_cast<metadata*>(calloc(capacity, sizeof(metadata)));
                tombstones = 0;
            }
    
            ~hashtable()
            {
                free(data);
            }
    
            hashtable(const hashtable& that)
            {
                used = 0;
                capacity = 3 * that.size() | 1;
                if (capacity < MINIMUM_CAPACITY) capacity = MINIMUM_CAPACITY;
                data = static_cast<metadata*>(calloc(capacity, sizeof(metadata)));
                tombstones = 0;
    
                for (size_t i = 0; i < that.capacity; ++i)
                {
                    if (that.data[i].in_use())
                    {
                        insert_unsafe(that.data[i]);
                    }
                }
            }
    
            hashtable& operator=(hashtable copy)
            {
                swap(copy);
                return *this;
            }
    
            void swap(hashtable& that)
            {
                my_swap(data, that.data);
                my_swap(used, that.used);
                my_swap(capacity, that.capacity);
                my_swap(tombstones, that.tombstones);
            }
    
            void insert_unsafe(const metadata& x)
            {
                *find(x.address) = x;
                ++used;
            }
    
            void insert(const metadata& x)
            {
                if (2 * used >= capacity)
                {
                    hashtable copy(*this);
                    swap(copy);
                }
                insert_unsafe(x);
            }
    
            metadata* find(void* address)
            {
                size_t index = reinterpret_cast<size_t>(address) % capacity;
                while (data[index].must_keep_searching_for(address))
                {
                    ++index;
                    if (index == capacity) index = 0;
                }
                return &data[index];
            }
    
            void erase(metadata* it)
            {
                it->kind = TOMBSTONE;
                ++tombstones;
            }
        } the_hashset;
    
        struct heap_debugger
        {
            heap_debugger()
            {
                puts("heap debugger started");
            }
    
            ~heap_debugger()
            {
                the_hashset.print();
                puts("heap debugger shutting down");
            }
        } the_heap_debugger;
    
        void* allocate(size_t size, kind_of_memory kind) throw (std::bad_alloc)
        {
            byte* raw = static_cast<byte*>(malloc(size + 2 * sizeof CANARY));
            if (raw == 0) throw std::bad_alloc();
    
            memcpy(raw, CANARY, sizeof CANARY);
            byte* payload = raw + sizeof CANARY;
            memcpy(payload + size, CANARY, sizeof CANARY);
    
            metadata md = {payload, size, kind};
            the_hashset.insert(md);
            printf("allocated ");
            md.print();
            return payload;
        }
    
        void release(void* payload, kind_of_memory kind) throw ()
        {
            if (payload == 0) return;
    
            metadata* p = the_hashset.find(payload);
    
            if (!p->in_use())
            {
                printf("ERROR:   no dynamic memory at %p\n", payload);
            }
            else if (p->kind != kind)
            {
                printf("ERROR:wrong form of delete at %p\n", payload);
            }
            else if (p->canaries_alive())
            {
                printf("releasing ");
                p->print();
                free(static_cast<byte*>(payload) - sizeof CANARY);
                the_hashset.erase(p);
            }
        }
    }
    
    void* operator new(size_t size) throw (std::bad_alloc)
    {
        return allocate(size, NON_ARRAY_MEMORY);
    }
    
    void* operator new[](size_t size) throw (std::bad_alloc)
    {
        return allocate(size, ARRAY_MEMORY);
    }
    
    void operator delete(void* payload) throw ()
    {
        release(payload, NON_ARRAY_MEMORY);
    }
    
    void operator delete[](void* payload) throw ()
    {
        release(payload, ARRAY_MEMORY);
    }
    
    int main()
    {
        int* p = new int[1];
        delete p;   // wrong form of delete
        delete[] p; // ok
        delete p;   // no dynamic memory (double delete)
    
        p = new int[1];
        p[-1] = 0xcafebabe;
        p[+1] = 0x12345678;
        delete[] p; // underflow and overflow prevent release
                    // p is not released, hence leak
    }
    

3 个答案:

答案 0 :(得分:5)

非常好,的确如此。你的金丝雀实际上可以揭示一些溢出/下溢的真实情况(尽管不是Matthieu指出的所有情况)。

还有什么。您可能会遇到多线程应用程序的一些问题。也许保护哈希表不会出现并发访问?

现在您记录了每个分配和释放,您可以(如果您愿意)提供有关正在测试的程序的更多信息。了解任何给定时间的总分配和平均分配数量可能会很有趣吗?分配的总字节数,最大值,最小值和平均字节数,以及分配的平均寿命。

如果要比较不同的线程,至少使用Pthreads,可以使用pthread_self()来识别它们。这个堆调试器可能会成为一个非常有用的分析工具。

答案 1 :(得分:2)

你是否正在使用一个非常弱的malloc,它还没有内置这种东西?因为如果它在那里,你将增加一倍的开销。此外,这种系统在进行小对象分配时确实很痛,或者对它们无效,因为人们会自己分配和管理内存。

就代码而言,它看起来会像你说的那样做,它看起来设计得很好并且易于阅读。但是,如果你要经历这样做的麻烦,为什么不通过使用托管容器/指针/运算符[]东西在源上捕获缓冲区上/下流。这样,你就可以在故障现场进行调试,而不是免费发现邪恶事件。

我确信其他人会找到效率,但在查看代码几分钟之后,这些只是我头脑中的一些想法。

答案 2 :(得分:2)

我想知道检测到下溢/溢出。

我的意思是,如果我有一个10个元素的数组,那么你似乎会检测我是在-110写的,但如果我在20写一下该怎么办?下溢或溢出不一定是缓冲区溢出(连续)的一部分。

此外,防止块的释放有什么意义?这个区块(相对)很好,这是你(不幸)腐败的邻居。

无论如何,对我来说似乎相当不错,尽管我可能每个功能都有多个返回,因为单一退出没有任何意义。你似乎更像是C程序员而不是C ++程序员:)