Неожиданное поведение от 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 поддерживает работу приложения независимо от этого очень рискованного (если не совершенно неправильного) освобождения объекта и двойного освобождения, когда object_on_stack выходит из области действия, когда завершается AllocatingFunction. Дело в том, что оскорбительный код можно было вызывать повторно без какого-либо намека на основную мерзость.

Я знаю, что освобождение памяти является одним из тех «неопределенных действий», когда они не используются должным образом, но мое удивление заключается в том, что между «стандартными» libc и tcmalloc такое поведение отличается.

У кого-нибудь есть какое-то объяснение понимания того, почему tcmalloc поддерживает работу приложения?

Заранее спасибо 🙂

Хорошего дня

0

Решение

очень рискованное (если не совершенно неправильное) освобождение объекта

хорошо, я не согласен здесь, это является совершенно неверно, и поскольку вы вызываете UB, может произойти все что угодно.

Это очень сильно зависит от того, что код tcmalloc делает с освобождением, и как он использует (возможно, мусор) данные в стеке в этом месте.

Я также видел сбой tcmalloc в таких случаях, а также glibc, входящий в бесконечный цикл. То, что вы видите, это просто совпадение.

2

Другие решения

Во-первых, не было двойного 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 вылетает.

0

По вопросам рекламы [email protected]