来自tcmalloc的意外行为

时间:2013-05-16 09:47:06

标签: c++ linux libc tcmalloc

我在一个大项目中使用tcmalloc已经有几个月了,到目前为止我必须说我很高兴,最重要的是它的HeapProfiling功能允许跟踪内存泄漏并删除它们。

在过去的几周里,虽然我们的应用程序遇到了随机崩溃,但我们无法找到随机崩溃的来源。在一个非常特殊的情况下,当应用程序崩溃时,我们发现自己的应用程序线程之一的堆栈已完全损坏。好几次,我发现线程卡在tcmalloc :: PageHeap :: AllocLarge()中,但由于我没有连接tcmalloc的调试符号,我无法理解问题是什么。

经过近一周的调查,今天我尝试了最简单的事情:从联动中删除tcmalloc以避免使用它,只是为了看看发生了什么。嗯......我终于找到了问题所在,而且违规代码看起来非常像这样:

   void AllocatingFunction()
   {
       Object object_on_stack;
       ProcessObject(&object_on_stack);

   }

   void ProcessObject(Object* object)
   {
       ...
       // Do Whatever
       ...
       delete object;
   }

使用libc,应用程序仍然崩溃,但我终于看到我在堆栈上分配的对象上调用了delete。

我仍然无法弄清楚为什么tcmalloc会保持应用程序运行而不管这种非常危险(如果不是完全错误的)对象释放,以及当AllocatingFunction结束时object_on_stack超出范围时的双重释放。事实是,可以反复调用违规代码而不暗示潜在的憎恶。

我知道内存释放是未正确使用时的“未定义行为”之一,但令我惊讶的是“标准”libc和tcmalloc之间存在这种不同的行为。

有没有人对tcmalloc保持应用程序运行的原因有什么见解?

提前致谢:)

度过美好的一天

2 个答案:

答案 0 :(得分:2)

  

非常危险(如果不是完全错误的)对象释放

嗯,我在这里不同意, 是完全错误的,因为你调用UB,任何事情都可能发生。

这在很大程度上取决于tcmalloc代码在解除分配时的实际作用,以及它如何在该位置使用堆栈周围的(可能是垃圾)数据。

我也看到过tcmalloc在这种情况下崩溃,以及glibc进入无限循环。你看到的只是巧合。

答案 1 :(得分:0)

首先,你的案例中没有双free。当object_on_stack超出范围时,没有free调用,只是堆栈指针减少(或者更确切地说,随着堆栈增长......)。

其次,在删除期间,TcMalloc应该能够识别堆栈中的地址不属于程序堆。以下是free(ptr)实施的一部分:

const PageID p = reinterpret_cast<uintptr_t>(ptr) >> kPageShift;
Span* span = NULL;
size_t cl = Static::pageheap()->GetSizeClassIfCached(p);

if (cl == 0) {
    span = Static::pageheap()->GetDescriptor(p);
    if (!span) {
        // span can be NULL because the pointer passed in is invalid
        // (not something returned by malloc or friends), or because the
        // pointer was allocated with some other allocator besides
        // tcmalloc.  The latter can happen if tcmalloc is linked in via
        // a dynamic library, but is not listed last on the link line.
        // In that case, libraries after it on the link line will
        // allocate with libc malloc, but free with tcmalloc's free.
        (*invalid_free_fn)(ptr);  // Decide how to handle the bad free request
        return;
    }

调用invalid_free_fn崩溃。