So finden Sie einen Fehler "Double Free oder Korruption"

92

Wenn ich mein (C ++) Programm ausführe, stürzt es mit diesem Fehler ab.

* glibc erkannt * ./load: doppelt frei oder beschädigt (! prev): 0x0000000000c6ed50 ***

Wie kann ich den Fehler aufspüren?

Ich habe versucht, print ( std::cout) -Anweisungen zu verwenden, ohne Erfolg. Könnte gdbdas einfacher machen?

Neuromant
quelle
5
Ich frage mich, warum jeder NULLZeiger vorschlägt (die Fehler maskieren, die sonst abgefangen werden, wie diese Frage gut zeigt), aber niemand schlägt vor, einfach überhaupt keine manuelle Speicherverwaltung durchzuführen, was in C ++ sehr gut möglich ist. Ich habe seit Jahren nicht mehr geschrieben delete. (Und ja, mein Code ist leistungskritisch. Sonst wäre er nicht in C ++ geschrieben worden.)
sbi
2
@sbi: Haufen Korruption und dergleichen werden selten gefangen, zumindest nicht dort, wo sie passieren. NULLZeiger können dazu führen, dass Ihr Programm früher abstürzt.
Hasturkun
@ Hasturkun: Ich bin absolut anderer Meinung. Ein Hauptanreiz für NULLZeiger besteht darin, zu verhindern, dass eine Sekunde delete ptr;explodiert - was einen Fehler maskiert, da diese Sekunde deleteniemals hätte passieren dürfen. (Es wird auch verwendet, um zu überprüfen, ob ein Zeiger noch auf ein gültiges Objekt zeigt. Dies wirft jedoch nur die Frage auf, warum Sie einen Zeiger im Gültigkeitsbereich haben, auf den kein Objekt verweisen kann.)
sbi

Antworten:

64

Wenn Sie glibc verwenden, können Sie die MALLOC_CHECK_Umgebungsvariable auf setzen 2. Dies führt dazu, dass glibc eine fehlertolerante Version von verwendet malloc, wodurch Ihr Programm an dem Punkt abgebrochen wird, an dem das Double Free ausgeführt wird.

Sie können dies über gdb festlegen, indem Sie den set environment MALLOC_CHECK_ 2Befehl verwenden, bevor Sie Ihr Programm ausführen. Das Programm sollte abgebrochen werden, wobei der free()Aufruf im Backtrace sichtbar ist.

Weitere Informationen finden Sie in der Manpagemalloc()

Hasturkun
quelle
2
Die Einstellung behebt MALLOC_CHECK_2tatsächlich mein doppeltes freies Problem (obwohl es nicht behoben wird, wenn es nur im Debug-Modus ist)
puk
4
@puk Ich habe das gleiche Problem. Wenn Sie MALLOC_CHECK_ auf 2 setzen, wird mein doppeltes Problem vermieden. Welche anderen Optionen gibt es, um weniger als Code einzufügen, um das Problem zu reproduzieren und eine Rückverfolgung bereitzustellen?
Wei Zhong
Haben Sie es auch, wo das Setzen von MALLOC_CHECK_ das Problem vermeidet. Mitkommentatoren / irgendjemand ... haben Sie einen anderen Weg gefunden, um das Problem aufzuzeigen?
Pellucidcoder
"Wenn MALLOC_CHECK_ auf einen Wert ungleich Null gesetzt wird, wird eine spezielle (weniger effiziente) Implementierung verwendet, die so ausgelegt ist, dass sie gegenüber einfachen Fehlern tolerant ist, z. B. doppelte Aufrufe von free mit demselben Argument oder Überläufe eines einzelnen Bytes (off) -by-one Bugs). " gnu.org/software/libc/manual/html_node/… Es scheint also, dass MALLOC_CHECK_ nur verwendet wird, um einfache Speicherfehler zu vermeiden, nicht um sie zu erkennen.
Pellucidcoder
Eigentlich ... support.microfocus.com/kb/doc.php?id=3113982 scheint das Setzen von MALLOC_CHECK_ auf 3 am nützlichsten zu sein und kann zum Erkennen von Fehlern verwendet werden.
Pellucidcoder
32

Es gibt mindestens zwei mögliche Situationen:

  1. Sie löschen dieselbe Entität zweimal
  2. Sie löschen etwas, das nicht zugewiesen wurde

Für den ersten empfehle ich dringend, alle gelöschten Zeiger auf NULL zu setzen.

Sie haben drei Möglichkeiten:

  1. Überladen Sie neu und löschen und verfolgen Sie die Zuordnungen
  2. Ja, verwenden Sie GDB - dann erhalten Sie eine Rückverfolgung von Ihrem Absturz, und das wird wahrscheinlich sehr hilfreich sein
  3. Wie vorgeschlagen - verwenden Sie Valgrind - es ist nicht einfach, sich darauf einzulassen, aber es wird Ihnen in Zukunft tausendfach Zeit sparen ...
Kornel Kisielewicz
quelle
2. würde Korruption verursachen, aber ich glaube nicht, dass diese Meldung im Allgemeinen angezeigt wird, da die Überprüfung der Integrität nur auf dem Heap durchgeführt wird. Ich denke jedoch, dass 3. Heap Buffer Overflow möglich ist.
Matthew Flaschen
Gut. Richtig, ich habe den Zeiger nicht auf NULL gesetzt und bin mit diesem Fehler konfrontiert. Stunden gelernt!
Hrushi
26

Sie können gdb verwenden, aber ich würde zuerst Valgrind versuchen . Siehe die Kurzanleitung .

Kurz gesagt, Valgrind instrumentiert Ihr Programm so, dass es verschiedene Arten von Fehlern bei der Verwendung von dynamisch zugewiesenem Speicher erkennen kann, z. B. doppelte Freigaben und Schreibvorgänge nach dem Ende der zugewiesenen Speicherblöcke (die den Heap beschädigen können). Es erkennt und meldet die Fehler , sobald sie auftreten , und weist Sie so direkt auf die Ursache des Problems hin.

Matthew Flaschen
quelle
1
@ SMR, in diesem Fall ist der wesentliche Teil der Antwort die gesamte große, verknüpfte Seite. Es ist also vollkommen in Ordnung, nur den Link in die Antwort aufzunehmen. Ein paar Worte darüber, warum der Autor Valgrind gegenüber GDB bevorzugt und wie er das spezifische Problem angehen würde, sind meiner Meinung nach das, was in der Antwort wirklich fehlt.
ndemou
20

Drei Grundregeln:

  1. Zeiger auf NULLnach frei setzen
  2. Überprüfen Sie , NULLbevor Sie zu befreien.
  3. Initialisieren Sie den Zeiger zu NULLBeginn.

Die Kombination dieser drei funktioniert ganz gut.

Jack
quelle
1
Ich bin kein C-Experte, aber normalerweise kann ich meinen Kopf über Wasser halten. Warum # 1? Ist es nur so, dass Ihr Programm abstürzt, wenn Sie versuchen, auf einen freien Zeiger zuzugreifen, und nicht nur auf einen stillen Fehler?
Daniel Harms
1
@ Präzision: Ja, das ist der Punkt. Es ist eine gute Praxis: Ein Zeiger auf gelöschten Speicher ist ein Risiko.
Am
10
Genau genommen halte ich # 2 für unnötig, da die meisten Compiler es Ihnen ermöglichen, einen Nullzeiger zu löschen, ohne dass dies zu Problemen führt. Ich bin sicher, jemand wird mich korrigieren, wenn ich falsch liege. :)
Komponente 10
11
@ Component10 Ich denke, dass das Freigeben von NULL vom C-Standard verlangt wird, um nichts zu tun.
Demi
2
@ Demetri: Ja, Sie haben Recht. "Wenn der Wert des Operanden" Löschen "der Nullzeiger ist, hat die Operation keine Auswirkung." (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Komponente 10.
15

Sie können es valgrindzum Debuggen verwenden.

#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]$

Eine mögliche Lösung:

#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]$

Lesen Sie den Blog über die Verwendung von Valgrind Link

Sandipan Karmakar
quelle
Mein Programm dauert ungefähr 30 Minuten, auf Valgrind kann es 18 bis 20 Stunden dauern, bis es fertig ist.
Kemin Zhou
11

Mit modernen C ++ - Compilern können Sie Desinfektionsmittel verwenden, um zu verfolgen.

Beispielbeispiel:

Mein Programm:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

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

Kompilieren Sie mit Adressdesinfektionsmitteln:

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

Ausführen :

# ./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

Um mehr über Desinfektionsmittel zu erfahren, können Sie diese oder jene oder alle modernen C ++ - Compiler-Dokumentationen (z. B. gcc, clang usw.) überprüfen .

Sitesh
quelle
5

Verwenden Sie intelligente Zeiger wie Boost shared_ptr? Wenn ja, überprüfen Sie durch Aufrufen, ob Sie den Rohzeiger irgendwo direkt verwenden get(). Ich habe festgestellt, dass dies ein recht häufiges Problem ist.

Stellen Sie sich beispielsweise ein Szenario vor, in dem ein Rohzeiger (z. B. als Callback-Handler) an Ihren Code übergeben wird. Sie können dies einem intelligenten Zeiger zuweisen, um mit der Referenzzählung usw. fertig zu werden. Großer Fehler: Ihr Code besitzt diesen Zeiger nur, wenn Sie eine tiefe Kopie erstellen. Wenn Ihr Code mit dem Smart Pointer fertig ist, wird er zerstört und versucht, den Speicher zu zerstören, auf den er verweist, da er denkt, dass niemand anderes ihn benötigt, aber der aufrufende Code versucht dann, ihn zu löschen, und Sie erhalten ein Double freies Problem.

Das könnte hier natürlich nicht Ihr Problem sein. Am einfachsten ist hier ein Beispiel, das zeigt, wie es passieren kann. Das erste Löschen ist in Ordnung, aber der Compiler erkennt, dass dieser Speicher bereits gelöscht wurde, und verursacht ein Problem. Aus diesem Grund ist es eine gute Idee, einem Zeiger unmittelbar nach dem Löschen 0 zuzuweisen.

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;
}

Bearbeiten: geändert deletein delete[], da ptr ein Array von Zeichen ist.

Komponente 10
quelle
Ich habe in meinem Programm keine Löschbefehle verwendet. Könnte dies immer noch das Problem sein?
Neuromancer
1
@Phenom: Warum hast du keine Löschvorgänge verwendet? Liegt es daran, dass Sie intelligente Zeiger verwenden? Vermutlich verwenden Sie new in Ihrem Code, um Objekte auf dem Heap zu erstellen? Wenn die Antwort auf beide Fragen Ja lautet, verwenden Sie dann get / set für die Smart-Zeiger, um Rohzeiger zu kopieren? Wenn ja, nicht! Sie würden die Referenzzählung unterbrechen. Alternativ können Sie einen Zeiger aus dem Bibliothekscode, den Sie aufrufen, einem intelligenten Zeiger zuweisen. Wenn Sie den Speicher, auf den verwiesen wird, nicht "besitzen", tun Sie dies nicht, da sowohl die Bibliothek als auch der intelligente Zeiger versuchen, ihn zu löschen.
Komponente 10
-2

Ich weiß, dass dies ein sehr alter Thread ist, aber es ist die häufigste Google-Suche nach diesem Fehler, und in keiner der Antworten wird eine häufige Fehlerursache erwähnt.

Welches ist das Schließen einer Datei, die Sie bereits geschlossen haben.

Wenn Sie nicht aufpassen und zwei verschiedene Funktionen dieselbe Datei schließen, generiert die zweite diesen Fehler.

Jason
quelle
Sie sind falsch, dieser Fehler wird aufgrund eines Double Free ausgelöst, genau wie der Fehler angibt. Die Tatsache, dass Sie eine Datei zweimal schließen, führt zu einer doppelten Freigabe, da die Methode zum Schließen eindeutig versucht, dieselben Daten zweimal freizugeben.
Geoffrey