2013-05-16 4 views
0

J'ai utilisé tcmalloc pendant quelques mois dans un grand projet, et jusqu'à présent, je dois dire que j'en suis assez content, surtout pour ses fonctionnalités HeapProfiling qui permettent de suivre les fuites de mémoire et de supprimer leur.Comportement inattendu de tcmalloc

Au cours des deux dernières semaines, cependant, nous avons rencontré des plantages aléatoires dans notre application, et nous n'avons pas pu trouver la source du crash aléatoire. Dans une situation très particulière, lorsque l'application s'est plantée, nous nous sommes retrouvés avec une pile complètement corrompue pour l'un des threads de l'application. Plusieurs fois, j'ai trouvé que les threads étaient bloqués dans tcmalloc :: PageHeap :: AllocLarge(), mais comme je n'ai pas de symboles de débogage de tcmalloc, je ne pouvais pas comprendre le problème.

Après presque une semaine d'enquête, aujourd'hui j'ai essayé la chose la plus simple: enlever tcmalloc du linkage pour éviter de l'utiliser, juste pour voir ce qui s'est passé. Eh bien ... J'ai finalement découvert quel était le problème, et le code incriminé ressemble beaucoup à ceci:

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

    } 

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

En utilisant l'application libc encore écrasé mais j'ai finalement vu que je l'appelais supprimer sur un objet qui était alloué sur la pile. Ce que je n'arrive toujours pas à comprendre, c'est pourquoi tcmalloc a gardé l'application en cours d'exécution malgré ce deallocation d'objet très risqué (sinon complètement faux) et la double désallocation quand object_on_stack est hors de portée quand AllocatingFunction se termine. Le fait est que le code incriminé pourrait être appelé à plusieurs reprises sans aucun indice de l'abomination sous-jacente.

Je sais que la désallocation de mémoire est un de ces "comportements non définis" lorsqu'elle n'est pas utilisée correctement, mais ma surprise est un comportement si différent entre la libc "standard" et tcmalloc.

Est-ce que quelqu'un a une sorte d'explication de la perspicacité, pourquoi tcmalloc maintient-il l'application?

Merci à l'avance :)

Have a Nice Day

+0

Ils ont choisi leur "comportement indéfini" pour * ne pas planter *. Est-ce que je me fierais à cela? *Sûrement pas*. – WhozCraig

Répondre

2

très risqué (sinon tout à fait faux) objet désallocation

bien, je suis en désaccord ici, il est tout à fait mal, et puisque vous invoquez UB, tout peut arriver.

Cela dépend beaucoup de ce que fait le code tcmalloc sur deallocation, et comment il utilise les données (éventuellement inutiles) autour de la pile à cet endroit.

J'ai vu tcmalloc tomber en panne dans de telles occasions, ainsi que glibc dans une boucle infinie. Ce que vous voyez est juste une coïncidence.

+0

Eh bien ... Je n'ai pas osé l'appeler "complètement faux" parce que vous ne savez jamais quel genre de trucs sales un programmeur essaie d'accomplir. Peut-être qu'il y a des gars quelque part dans le monde qui prétendent qu'ils devaient faire une telle chose. Dans mon cas, c'était définitivement complètement faux, et la réparation du code incriminé a tout arrangé. Encore il m'a fallu 7 jours d'enquête inutile à d'autres gars. - – BaroneAshura

+0

@BaroneAshura: Même dans de tels cas, je dirais personnellement tout à fait faux. Btw. courir votre code à travers valgrind aurait révélé cela sur place. – PlasmaHH

+0

Cela avait été une option il y a quelques mois lorsque nous étions à la recherche de fuites de mémoire ... malheureusement, nous n'avons pas réussi à exécuter notre application dans un environnement valgrind :(De toute façon, je suis d'accord avec vous en l'appelant "complètement faux". Je ne voulais pas que quelqu'un me saute dessus en disant que ce n'est PAS "complètement faux": P – BaroneAshura

0

Premièrement, il n'y avait pas de double free dans votre cas. Quand object_on_stack est hors de portée, il n'y a pas d'appel free, juste le pointeur de pile est diminué (ou plutôt augmenté, quand la pile se développe ...).

Deuxièmement, lors de la suppression, TcMalloc devrait être capable de reconnaître que l'adresse d'une pile n'appartient pas au segment de programme. Voici une partie de la mise en œuvre 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; 
    } 

Appel à l'invalid_free_fn se bloque.