gpt4 book ai didi

c++ - 批评我的非侵入式堆调试器

转载 作者:IT老高 更新时间:2023-10-28 23:01:25 28 4
gpt4 key购买 nike

这是昨天 Critique my heap debugger 的后续。正如 bitc 所建议的,我现在将有关已分配 block 的元数据保存在单独的手写哈希表中。

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

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

欢迎讨论,提前致谢!

#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
}

最佳答案

确实很好。您的金丝雀实际上可以揭示一些真实的上溢/下溢情况(尽管并非所有这些情况都像 Matthieu 指出的那样)。

还有什么。您可能会在使用多线程应用程序时遇到一些问题。也许保护哈希表免受并发访问?

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

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

关于c++ - 批评我的非侵入式堆调试器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2835416/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com