Jak wyśledzić błąd „podwójne zwolnienie lub korupcja”

92

Kiedy uruchamiam mój program (C ++), zawiesza się i wyświetla ten błąd.

* wykryto glibc * ./load: podwójne zwolnienie lub uszkodzenie (! prev): 0x0000000000c6ed50 ***

Jak mogę wyśledzić błąd?

Próbowałem użyć std::coutinstrukcji print ( ), ale bez powodzenia. Czy mogłoby gdbto ułatwić?

neuromancer
źródło
5
Zastanawiam się, dlaczego wszyscy sugerują NULLwskaźniki (co maskuje błędy, które w przeciwnym razie zostałyby wyłapane, jak ładnie pokazuje to pytanie), ale nikt nie sugeruje, aby w ogóle nie robić ręcznego zarządzania pamięcią, co jest bardzo możliwe w C ++. Nie pisałem deleteod lat. (I tak, mój kod jest krytyczny dla wydajności. W przeciwnym razie nie zostałby napisany w C ++.)
sbi
2
@sbi: Zepsucie sterty i tym podobne są rzadko wykrywane, a przynajmniej nie tam, gdzie się zdarzają. NULLwprowadzenie wskaźników może spowodować wcześniejszą awarię programu.
Hasturkun
@Hasturkun: Zdecydowanie się nie zgadzam. Główną zachętą do NULLwskazywania wskaźników jest zapobieganie wybuchowi sekundy delete ptr;- co maskuje błąd, ponieważ ta sekunda deletenigdy nie powinna była mieć miejsca. (Służy również do sprawdzania, czy wskaźnik nadal wskazuje prawidłowy obiekt. Ale to rodzi pytanie, dlaczego masz wskaźnik w zakresie, który nie ma obiektu, na który można by wskazać.)
sbi

Odpowiedzi:

64

Jeśli używasz glibc, możesz ustawić MALLOC_CHECK_zmienną środowiskową na 2, spowoduje to, że glibc użyje wersji tolerującej błędy malloc, co spowoduje przerwanie programu w miejscu, w którym podwójne zwolnienie zostanie wykonane.

Możesz to ustawić z gdb, używając set environment MALLOC_CHECK_ 2polecenia przed uruchomieniem programu; program powinien przerwać działanie, z free()wywołaniem widocznym w śladzie wstecznym.

zobacz stronęmalloc() podręcznika, aby uzyskać więcej informacji

Hasturkun
źródło
2
Ustawienie MALLOC_CHECK_2faktycznie naprawiło mój podwójny problem z wolnością (chociaż nie jest naprawiany, jeśli jest tylko w trybie debugowania)
puk
4
@puk Mam ten sam problem, ustawienie MALLOC_CHECK_ na 2 pozwala uniknąć mojego podwójnego problemu. Jakie są inne opcje, aby wstrzyknąć mniej niż kod w celu odtworzenia problemu i zapewnienia śledzenia wstecznego?
Wei Zhong
Miej go również tam, gdzie ustawienie MALLOC_CHECK_ pozwala uniknąć problemu. Koledzy komentujący / ktokolwiek ... czy odkryliście inny sposób pokazania problemu?
pellucidcoder
"Gdy MALLOC_CHECK_ jest ustawiony na wartość niezerową, używana jest specjalna (mniej wydajna) implementacja, która jest zaprojektowana tak, aby była odporna na proste błędy, takie jak podwójne wywołania free z tym samym argumentem lub przekroczenia jednego bajtu (wyłączone o jeden błąd). ” gnu.org/software/libc/manual/html_node/… Więc wygląda na to, że MALLOC_CHECK_ jest używany tylko do unikania prostych błędów pamięci, a nie do ich wykrywania.
pellucidcoder
Właściwie ... support.microfocus.com/kb/doc.php?id=3113982 wydaje się, że ustawienie MALLOC_CHECK_ na 3 jest najbardziej przydatne i może być używane do wykrywania błędów.
pellucidcoder
32

Istnieją co najmniej dwie możliwe sytuacje:

  1. usuwasz ten sam podmiot dwukrotnie
  2. usuwasz coś, co nie zostało przydzielone

W przypadku pierwszego zdecydowanie sugeruję ZEROWANIE wszystkich usuniętych wskaźników.

Masz trzy możliwości:

  1. przeciążać nowe oraz usuwać i śledzić alokacje
  2. tak, użyj gdb - wtedy uzyskasz ślad po awarii, który prawdopodobnie będzie bardzo pomocny
  3. zgodnie z sugestią - użyj Valgrind - nie jest łatwo się do niego dostać, ale zaoszczędzi to tysiąckrotnie czasu w przyszłości ...
Kornel Kisielewicz
źródło
2. spowodowałoby korupcję, ale nie sądzę, aby ten komunikat był generalnie wyświetlany, ponieważ sprawdzanie poprawności odbywa się tylko na stercie. Jednak myślę, że 3. przepełnienie buforu sterty jest możliwe.
Matthew Flaschen
Dobry. To prawda, że ​​nie udało mi się ustawić wskaźnika NULL i napotkałem ten błąd. Wyciągnięte wnioski!
hrushi
26

Możesz użyć gdb, ale najpierw spróbuję Valgrind . Zobacz skróconą instrukcję obsługi .

Krótko mówiąc, Valgrind instrumentuje twój program, aby mógł wykryć kilka rodzajów błędów w używaniu dynamicznie przydzielonej pamięci, takich jak podwójne zwolnienia i zapisy poza koniec przydzielonych bloków pamięci (co może uszkodzić stertę). Wykrywa i zgłasza błędy, gdy tylko się pojawią , wskazując w ten sposób bezpośrednio przyczynę problemu.

Matthew Flaschen
źródło
1
@SMR, w tym przypadku zasadniczą częścią odpowiedzi jest cała, duża, połączona strona. Zatem uwzględnienie samego linku w odpowiedzi jest w porządku. Kilka słów o tym, dlaczego autor woli Valgrinda od gdb i jak poradziłby sobie z konkretnym problemem, to IMHO, czego naprawdę brakuje w odpowiedzi.
ndemou
20

Trzy podstawowe zasady:

  1. Ustaw wskaźnik na NULLpo zwolnieniu
  2. Sprawdź NULLprzed zwolnieniem.
  3. Zainicjuj wskaźnik do NULLna początku.

Połączenie tych trzech działa całkiem nieźle.

Jacek
źródło
1
Nie jestem ekspertem od C, ale zwykle potrafię trzymać głowę nad wodą. Dlaczego nr 1? Czy to tylko po to, aby twój program się zawiesił, gdy próbujesz uzyskać dostęp do darmowego wskaźnika, a nie tylko cichy błąd?
Daniel Harms
1
@Precision: Tak, o to chodzi. Jest to dobra praktyka: posiadanie wskaźnika do usuniętej pamięci stanowi ryzyko.
ere
10
Ściśle mówiąc, myślę, że # 2 jest niepotrzebny, ponieważ większość kompilatorów pozwoli ci spróbować usunąć pusty wskaźnik bez powodowania problemu. Jestem pewien, że ktoś mnie poprawi, jeśli się mylę. :)
Komponent 10
11
@ Component10 Myślę, że zwolnienie NULL jest wymagane przez standard C, aby nic nie robić.
Demi
2
@Demetri: Tak, masz rację „jeśli wartość argumentu delete jest pustym wskaźnikiem, operacja nie ma żadnego efektu”. (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Komponent 10
15

Możesz użyć valgrinddo debugowania.

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
*** glibc detected *** ./t1: double free or corruption (top): 0x00000000058f7010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3a3127245f]
/lib64/libc.so.6(cfree+0x4b)[0x3a312728bb]
./t1[0x400500]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3a3121d994]
./t1[0x400429]
======= Memory map: ========
00400000-00401000 r-xp 00000000 68:02 30246184                           /home/sand/testbox/t1
00600000-00601000 rw-p 00000000 68:02 30246184                           /home/sand/testbox/t1
058f7000-05918000 rw-p 058f7000 00:00 0                                  [heap]
3a30e00000-3a30e1c000 r-xp 00000000 68:03 5308733                        /lib64/ld-2.5.so
3a3101b000-3a3101c000 r--p 0001b000 68:03 5308733                        /lib64/ld-2.5.so
3a3101c000-3a3101d000 rw-p 0001c000 68:03 5308733                        /lib64/ld-2.5.so
3a31200000-3a3134e000 r-xp 00000000 68:03 5310248                        /lib64/libc-2.5.so
3a3134e000-3a3154e000 ---p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a3154e000-3a31552000 r--p 0014e000 68:03 5310248                        /lib64/libc-2.5.so
3a31552000-3a31553000 rw-p 00152000 68:03 5310248                        /lib64/libc-2.5.so
3a31553000-3a31558000 rw-p 3a31553000 00:00 0
3a41c00000-3a41c0d000 r-xp 00000000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41c0d000-3a41e0d000 ---p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
3a41e0d000-3a41e0e000 rw-p 0000d000 68:03 5310264                        /lib64/libgcc_s-4.1.2-20080825.so.1
2b1912300000-2b1912302000 rw-p 2b1912300000 00:00 0
2b191231c000-2b191231d000 rw-p 2b191231c000 00:00 0
7ffffe214000-7ffffe229000 rw-p 7ffffffe9000 00:00 0                      [stack]
7ffffe2b0000-7ffffe2b4000 r-xp 7ffffe2b0000 00:00 0                      [vdso]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0                  [vsyscall]
Aborted
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck ./t1
==20859== Memcheck, a memory error detector
==20859== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20859== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20859== Command: ./t1
==20859==
==20859== Invalid free() / delete / delete[]
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004FF: main (t1.c:8)
==20859==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20859==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20859==    by 0x4004F6: main (t1.c:7)
==20859==
==20859==
==20859== HEAP SUMMARY:
==20859==     in use at exit: 0 bytes in 0 blocks
==20859==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20859==
==20859== All heap blocks were freed -- no leaks are possible
==20859==
==20859== For counts of detected and suppressed errors, rerun with: -v
==20859== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$


[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20899== Memcheck, a memory error detector
==20899== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20899== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20899== Command: ./t1
==20899==
==20899== Invalid free() / delete / delete[]
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004FF: main (t1.c:8)
==20899==  Address 0x4c26040 is 0 bytes inside a block of size 100 free'd
==20899==    at 0x4A05A31: free (vg_replace_malloc.c:325)
==20899==    by 0x4004F6: main (t1.c:7)
==20899==
==20899==
==20899== HEAP SUMMARY:
==20899==     in use at exit: 0 bytes in 0 blocks
==20899==   total heap usage: 1 allocs, 2 frees, 100 bytes allocated
==20899==
==20899== All heap blocks were freed -- no leaks are possible
==20899==
==20899== For counts of detected and suppressed errors, rerun with: -v
==20899== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Jedna możliwa poprawka:

#include<stdio.h>
#include<stdlib.h>

int main()
{
 char *x = malloc(100);
 free(x);
 x=NULL;
 free(x);
 return 0;
}

[sand@PS-CNTOS-64-S11 testbox]$ vim t1.c
[sand@PS-CNTOS-64-S11 testbox]$ cc -g t1.c -o t1
[sand@PS-CNTOS-64-S11 testbox]$ ./t1
[sand@PS-CNTOS-64-S11 testbox]$

[sand@PS-CNTOS-64-S11 testbox]$ valgrind --tool=memcheck --leak-check=full ./t1
==20958== Memcheck, a memory error detector
==20958== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==20958== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==20958== Command: ./t1
==20958==
==20958==
==20958== HEAP SUMMARY:
==20958==     in use at exit: 0 bytes in 0 blocks
==20958==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==20958==
==20958== All heap blocks were freed -- no leaks are possible
==20958==
==20958== For counts of detected and suppressed errors, rerun with: -v
==20958== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)
[sand@PS-CNTOS-64-S11 testbox]$

Zajrzyj na blog dotyczący korzystania z Valgrind Link

Sandipan Karmakar
źródło
Mój program trwa około 30 minut, na Valgrind może zająć od 18 do 20 godzin.
Kemin Zhou
11

Dzięki nowoczesnym kompilatorom C ++ możesz używać środków dezynfekujących do śledzenia.

Przykładowy przykład:

Mój program:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

{
   int * i = new int();
   delete i;
   //i = NULL;
   delete i;
}

Skompiluj ze środkami dezynfekującymi adres:

# g++-7.1 d_free.cxx -Wall -Werror -fsanitize=address -g

Wykonać :

# ./a.out 
=================================================================
==4836==ERROR: AddressSanitizer: attempting double-free on 0x602000000010 in thread T0:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b2c in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:11
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)
    #3 0x400a08  (/media/sf_shared/jkr/cpp/d_free/a.out+0x400a08)

0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:
    #0 0x7f35b2d7b3c8 in operator delete(void*, unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140
    #1 0x400b1b in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:9
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

previously allocated by thread T0 here:
    #0 0x7f35b2d7a040 in operator new(unsigned long) /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:80
    #1 0x400ac9 in main /media/sf_shared/jkr/cpp/d_free/d_free.cxx:8
    #2 0x7f35b2050c04 in __libc_start_main (/lib64/libc.so.6+0x21c04)

SUMMARY: AddressSanitizer: double-free /media/sf_shared/gcc-7.1.0/libsanitizer/asan/asan_new_delete.cc:140 in operator delete(void*, unsigned long)
==4836==ABORTING

Aby dowiedzieć się więcej na temat środków odkażających można sprawdzić to czy ten lub wszelkie nowoczesne kompilatory C ++ (np gcc, dzyń itd.) Dokumentacje.

Sitesh
źródło
5

Czy używasz inteligentnych wskaźników, takich jak Boost shared_ptr? Jeśli tak, sprawdź, czy w dowolnym miejscu bezpośrednio używasz surowego wskaźnika, wywołując get(). Uważam, że jest to dość powszechny problem.

Na przykład wyobraź sobie scenariusz, w którym surowy wskaźnik jest przekazywany (na przykład jako funkcja obsługi wywołania zwrotnego) do twojego kodu. Możesz zdecydować się przypisać to do inteligentnego wskaźnika, aby poradzić sobie z liczeniem referencji itp. Duży błąd: Twój kod nie jest właścicielem tego wskaźnika, chyba że wykonasz głęboką kopię. Kiedy twój kod zostanie wykonany za pomocą inteligentnego wskaźnika, zniszczy go i spróbuje zniszczyć pamięć, na którą wskazuje, ponieważ uważa, że nikt inny go nie potrzebuje, ale kod wywołujący spróbuje go usunąć, a otrzymasz podwójną wolny problem.

Oczywiście może to nie być twój problem. Najprościej jest to przykład, który pokazuje, jak to się może stać. Pierwsze usunięcie jest w porządku, ale kompilator wyczuwa, że ​​usunął już tę pamięć i powoduje problem. Dlatego przypisanie 0 do wskaźnika bezpośrednio po usunięciu jest dobrym pomysłem.

int main(int argc, char* argv[])
{
    char* ptr = new char[20];

    delete[] ptr;
    ptr = 0;  // Comment me out and watch me crash and burn.
    delete[] ptr;
}

Edycja: zmieniono deletena delete[], ponieważ ptr jest tablicą znaków.

Komponent 10
źródło
Nie użyłem żadnych poleceń usuwania w moim programie. Czy to nadal może być problem?
neuromancer
1
@Phenom: Dlaczego nie użyłeś usuwania? Czy to dlatego, że używasz inteligentnych wskaźników? Prawdopodobnie używasz nowego w swoim kodzie do tworzenia obiektów na stercie? Jeśli odpowiedź na oba te pytania brzmi tak, to czy używasz get / set na inteligentnych wskaźnikach do kopiowania wokół surowych wskaźników? Jeśli tak, nie rób tego! Przerwałbyś liczenie referencji. Alternatywnie możesz przypisać wskaźnik z kodu biblioteki, który wywołujesz, do inteligentnego wskaźnika. Jeśli nie jesteś „właścicielem” wskazanej pamięci, nie rób tego, ponieważ zarówno biblioteka, jak i inteligentny wskaźnik będą próbowały ją usunąć.
Komponent 10
-2

Wiem, że to bardzo stary wątek, ale jest to najpopularniejsza wyszukiwarka Google dla tego błędu i żadna z odpowiedzi nie wspomina o częstej przyczynie błędu.

Który zamyka plik, który już zamknąłeś.

Jeśli nie zwracasz uwagi i masz dwie różne funkcje zamykające ten sam plik, druga z nich wygeneruje ten błąd.

Jason
źródło
Nie masz racji, ten błąd jest generowany z powodu podwójnego wolnego, dokładnie tak, jak wskazuje błąd. Fakt, że zamykasz plik dwa razy, powoduje podwójne zwolnienie, co wyraźnie widać, że metoda close próbuje dwukrotnie zwolnić te same dane.
Geoffrey