来自tcmalloc的意外行为

Unexpected Behaviour from tcmalloc

本文关键字:意外 tcmalloc 来自      更新时间:2023-10-16

我已经在一个大型项目中使用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不顾这种非常危险(如果不是完全错误的话)的对象释放而保持应用程序运行,以及当object_on_stack在AllocatingFunction结束时超出范围时的双重释放。事实上,那些令人讨厌的代码可以被反复调用,而没有任何潜在的令人讨厌的暗示。

我知道内存释放在使用不当时是那些"未定义的行为"之一,但我惊讶的是"标准"libc和tcmalloc之间的行为如此不同。

有没有人对为什么tcmalloc保持应用程序运行有一些解释?

Thanks in advance:)

祝你有美好的一天

非常危险(如果不是完全错误的话)的对象回收

好吧,我不同意,它完全错误的,并且由于您调用了UB,任何事情都可能发生。

这在很大程度上取决于tcmalloc代码在释放时实际做了什么,以及它如何使用该位置堆栈周围的(可能是垃圾的)数据。

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

首先,您的案例中没有双重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调用崩溃