2013-05-16 5 views
0

Я использую tcmalloc в течение нескольких месяцев в большом проекте, и до сих пор я должен сказать, что я очень доволен этим, прежде всего его функциями HeapProfiling, которые позволяют отслеживать утечки памяти и удалять их.Неожиданное поведение от tcmalloc

За последние пару недель, хотя мы испытали случайные сбои в нашем приложении, и мы не смогли найти источник случайного сбоя. В очень конкретной ситуации, когда приложение разбилось, мы оказались с полностью поврежденным стеком для одного из потоков приложений. Несколько раз вместо этого я обнаружил, что потоки застряли в tcmalloc :: PageHeap :: AllocLarge(), но поскольку у меня нет отладочных символов tcmalloc, я не мог понять, в чем проблема.

После почти одной недели расследования, сегодня я попробовал самые простые вещи: удалил tcmalloc из уст новки, чтобы не использовать его, просто чтобы узнать, что произошло. Ну ... я, наконец, узнал, что проблема была, и код обижая очень похож на это:

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

    } 

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

Использование Libc приложение по-прежнему разбился, но я, наконец, увидел, что я звоню удалить на объект, который был выделенных в стеке.

Что я до сих пор не могу понять, почему tcmalloc вместо этого сохранил приложение, несмотря на это очень рискованное (если не совсем ошибочное) освобождение объекта, и двойное освобождение, когда object_on_stack выходит из области действия при завершении AllocatingFunction. Дело в том, что оскорбительный код можно было бы называть неоднократно без какого-либо намека на лежащую в основе мерзость.

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

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

Заранее спасибо :)

имеют хороший день

+0

Они выбрали свое «неопределенное поведение» для * не сбой *. Я полагаюсь на это? *Конечно нет*. – WhozCraig

ответ

2

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

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

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

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

+0

Ну ... Я не посмел назвать это «совершенно неправильно», потому что вы никогда не знаете, какие грязные трюки пытается сделать программист. Возможно, в мире есть ребята, которые утверждают, что им это нужно. В моем случае это было совершенно неправильно, и исправление оскорбительного кода все фиксировало. Тем не менее мне потребовались и другие парни 7 дней бесцельного расследования. - – BaroneAshura

+0

@BaroneAshura: Даже в таких случаях я лично назвал бы это совершенно неправильным. Btw. запуск вашего кода через valgrind показал бы это на месте. – PlasmaHH

+0

Это был вариант несколько месяцев назад, когда мы охотились на утечку памяти ... к сожалению, мы не смогли успешно запустить наше приложение в среде valgrind: (В любом случае, я согласен с вами в названии его «совершенно неправильно» .. Я просто не хотел, чтобы кто-то прыгал на меня, говоря, что это НЕ «совершенно неправильно»: P – BaroneAshura

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 аварий.