如何在 free() 中检查 diabolical wild free?
How check diabolical wild free in free()?
这个情况我有一个测试:
// Diabolical wild free #2.
int main() {
char* a = (char*) my_malloc(200);
char* b = (char*) my_malloc(50);
char* c = (char*) my_malloc(200);
char* p = (char*) my_malloc(3000);
(void) a, (void) c;
memcpy(p, b - 200, 450);
my_free(b);
memcpy(b - 200, p, 450);
my_free(b);
m61_printstatistics();
}
预期结果:
//! MEMORY BUG: invalid free of pointer
我现在可以使用空闲列表来检查第二个 free()
调用,但是
1) 第二个 free()
调用,释放另一个对象,因为 memcpy(b - 200, p, 450)
2) 在这种情况下我们也对同一个对象多次调用free()
,但这是正确的执行
//正确执行应该不会报错
int main() {
for (int i = 0; i < 10; ++i)
{
int* ptr = (int*) my_malloc(sizeof(int) * 10);
for (int j = 0; j < 10; ++j)
{
ptr[i] = i;
}
my_free(ptr);
}
m61_printstatistics();
}
那么如何在free()
实现中检查无效指针?
您可以使用 xfree
宏进行测试(假设您的程序还没有 free(NULL)
:
#if MY_DEBUG
#define xfree(p) ((void) ((p) ? (free(p), (p) = 0) : (fprintf(stderr, "double free\n"), exit(1), (void *) 0)))
#else
#define xfree(p) free(p)
#endif
实际上,检查此类事情的最佳方法是使用 valgrind 运行 它。您可以尝试创建自己的 framework/macros,但通常最好将 运行 包含在 valgrind 下作为定期测试过程的一部分。
让我们假设我们忽略了由 b
指向的对象边界之外的读取和写入导致的明显未定义行为,并且您的预期目的是编写或测试应该是的 C 库能够诊断在 b
.
上对 free()
的两次调用的错误
这是错误的原因是 malloc
和 free
的典型实现会将 free()
的内存作为链表放入空闲内存列表中。作为一种节省内存的方法,刚刚 free()
的内存本身被重新用作空闲列表数据结构的节点对象。因此,在第一个 free()
调用之后,空闲列表中的某些其他节点可能指向由 b
表示的内存。在第二次调用 free()
时,b
的节点将再次插入到列表中,可能导致另一个节点指向它。空闲列表将被视为已损坏。由于 malloc()
从空闲列表中删除数据,因此在空闲列表中出现两次相同的节点将是一个严重的问题。
有几种方法可以 "catch" 这个问题。一种可能性是将空闲列表实现为查找树,该查找树将自己的内存用于指向返回到 free()
的内存的节点。这允许对 free()
的第二次调用注意到 b
已经在树中。因此,检测到对 free()
的双重调用,并可以相应地进行标记。
这个情况我有一个测试:
// Diabolical wild free #2.
int main() {
char* a = (char*) my_malloc(200);
char* b = (char*) my_malloc(50);
char* c = (char*) my_malloc(200);
char* p = (char*) my_malloc(3000);
(void) a, (void) c;
memcpy(p, b - 200, 450);
my_free(b);
memcpy(b - 200, p, 450);
my_free(b);
m61_printstatistics();
}
预期结果:
//! MEMORY BUG: invalid free of pointer
我现在可以使用空闲列表来检查第二个 free()
调用,但是
1) 第二个 free()
调用,释放另一个对象,因为 memcpy(b - 200, p, 450)
2) 在这种情况下我们也对同一个对象多次调用free()
,但这是正确的执行
//正确执行应该不会报错
int main() {
for (int i = 0; i < 10; ++i)
{
int* ptr = (int*) my_malloc(sizeof(int) * 10);
for (int j = 0; j < 10; ++j)
{
ptr[i] = i;
}
my_free(ptr);
}
m61_printstatistics();
}
那么如何在free()
实现中检查无效指针?
您可以使用 xfree
宏进行测试(假设您的程序还没有 free(NULL)
:
#if MY_DEBUG
#define xfree(p) ((void) ((p) ? (free(p), (p) = 0) : (fprintf(stderr, "double free\n"), exit(1), (void *) 0)))
#else
#define xfree(p) free(p)
#endif
实际上,检查此类事情的最佳方法是使用 valgrind 运行 它。您可以尝试创建自己的 framework/macros,但通常最好将 运行 包含在 valgrind 下作为定期测试过程的一部分。
让我们假设我们忽略了由 b
指向的对象边界之外的读取和写入导致的明显未定义行为,并且您的预期目的是编写或测试应该是的 C 库能够诊断在 b
.
free()
的两次调用的错误
这是错误的原因是 malloc
和 free
的典型实现会将 free()
的内存作为链表放入空闲内存列表中。作为一种节省内存的方法,刚刚 free()
的内存本身被重新用作空闲列表数据结构的节点对象。因此,在第一个 free()
调用之后,空闲列表中的某些其他节点可能指向由 b
表示的内存。在第二次调用 free()
时,b
的节点将再次插入到列表中,可能导致另一个节点指向它。空闲列表将被视为已损坏。由于 malloc()
从空闲列表中删除数据,因此在空闲列表中出现两次相同的节点将是一个严重的问题。
有几种方法可以 "catch" 这个问题。一种可能性是将空闲列表实现为查找树,该查找树将自己的内存用于指向返回到 free()
的内存的节点。这允许对 free()
的第二次调用注意到 b
已经在树中。因此,检测到对 free()
的双重调用,并可以相应地进行标记。