Cara melacak kesalahan "bebas ganda atau korupsi"

92

Ketika saya menjalankan program (C ++) saya itu macet dengan kesalahan ini.

* glibc terdeteksi * ./load: bebas ganda atau rusak (! prev): 0x0000000000c6ed50 ***

Bagaimana cara melacak kesalahan tersebut?

Saya mencoba menggunakan std::coutpernyataan print ( ), tidak berhasil. Bisakah gdbmembuat ini lebih mudah?

neuromancer
sumber
5
Saya bertanya-tanya mengapa semua orang menyarankan untuk NULLpointer (yang menutupi kesalahan yang jika tidak tertangkap, seperti yang ditunjukkan pertanyaan ini dengan baik), tetapi tidak ada yang menyarankan untuk tidak melakukan manajemen memori manual sama sekali, yang sangat mungkin dilakukan di C ++. Saya sudah deletebertahun-tahun tidak menulis . (Dan, ya, kode saya sangat penting untuk kinerja. Jika tidak, kode tidak akan ditulis dalam C ++.)
sbi
2
@sbi: Korupsi tumpukan dan sejenisnya jarang tertangkap, setidaknya tidak di tempat yang terjadi. NULLpetunjuk mungkin membuat program Anda crash lebih awal.
Hasturkun
@Hasturkun: Saya sangat tidak setuju. Insentif utama untuk NULLpetunjuk adalah untuk mencegah ledakan kedua delete ptr;- yang menutupi kesalahan, karena detik itu deleteseharusnya tidak pernah terjadi. (Ini juga digunakan untuk memeriksa apakah sebuah pointer masih menunjuk ke objek yang valid. Tapi itu hanya menimbulkan pertanyaan mengapa Anda memiliki pointer dalam lingkup yang tidak memiliki objek untuk
dituju

Jawaban:

64

Jika Anda menggunakan glibc, Anda dapat menyetel MALLOC_CHECK_variabel lingkungan ke 2, ini akan menyebabkan glibc menggunakan versi toleran kesalahan malloc, yang akan menyebabkan program Anda dibatalkan pada titik di mana free ganda selesai.

Anda dapat mengatur ini dari gdb dengan menggunakan set environment MALLOC_CHECK_ 2perintah sebelum menjalankan program Anda; program harus dibatalkan, dengan free()panggilan terlihat di backtrace.

lihat halaman manualmalloc() untuk informasi lebih lanjut

Hasturkun
sumber
2
Pengaturan MALLOC_CHECK_2sebenarnya memperbaiki masalah bebas ganda saya (meskipun tidak memperbaiki jika hanya dalam mode debug)
puk
4
@puk Saya memiliki masalah yang sama, mengatur MALLOC_CHECK_ ke 2 akan menghindari masalah bebas ganda saya. Apa pilihan lain di sana untuk memasukkan kurang dari kode untuk mereproduksi masalah dan menyediakan pelacakan balik?
Wei Zhong
Juga miliki di mana pengaturan MALLOC_CHECK_ menghindari masalah. Sesama pemberi komentar / siapa saja ... apakah Anda menemukan cara lain untuk menunjukkan masalah tersebut?
pellucidcoder
"Ketika MALLOC_CHECK_ disetel ke nilai bukan nol, implementasi khusus (kurang efisien) digunakan yang dirancang agar toleran terhadap kesalahan sederhana, seperti panggilan ganda gratis dengan argumen yang sama, atau kelebihan byte tunggal (nonaktif) bug -oleh-satu). " gnu.org/software/libc/manual/html_node/… Jadi sepertinya MALLOC_CHECK_ hanya digunakan untuk menghindari kesalahan memori sederhana, bukan mendeteksinya.
pellucidcoder
Sebenarnya .... support.microfocus.com/kb/doc.php?id=3113982 sepertinya mengatur MALLOC_CHECK_ ke 3 adalah yang paling berguna dan dapat digunakan untuk mendeteksi kesalahan.
pellucidcoder
32

Setidaknya ada dua kemungkinan situasi:

  1. Anda menghapus entitas yang sama dua kali
  2. Anda menghapus sesuatu yang tidak dialokasikan

Untuk yang pertama saya sangat menyarankan NULL-ing semua pointer yang dihapus.

Anda memiliki tiga opsi:

  1. membebani yang baru dan menghapus serta melacak alokasi
  2. ya, gunakan gdb - maka Anda akan mendapatkan lacak balik dari kerusakan Anda, dan itu mungkin akan sangat membantu
  3. seperti yang disarankan - gunakan Valgrind - tidak mudah untuk memasukinya, tetapi ini akan menghemat waktu Anda ribuan kali lipat di masa depan ...
Kornel Kisielewicz
sumber
2. akan menyebabkan korupsi, tetapi menurut saya pesan ini tidak akan muncul secara umum, karena pemeriksaan kewarasan hanya dilakukan di heap. Namun, menurut saya 3. heap buffer overflow adalah mungkin.
Matthew Flaschen
Bagus. Benar saya melewatkan untuk membuat pointer NULL dan menghadapi kesalahan ini. Pelajaran yang dipetik!
hrushi
26

Anda dapat menggunakan gdb, tetapi saya akan mencoba Valgrind terlebih dahulu . Lihat panduan memulai cepat .

Singkatnya, Valgrind memperlengkapi program Anda sehingga dapat mendeteksi beberapa jenis kesalahan dalam menggunakan memori yang dialokasikan secara dinamis, seperti kebebasan ganda dan menulis melewati akhir blok memori yang dialokasikan (yang dapat merusak heap). Ini mendeteksi dan melaporkan kesalahan segera setelah terjadi , sehingga mengarahkan Anda langsung ke penyebab masalah.

Matthew Flaschen
sumber
1
@SMR, dalam hal ini bagian penting dari jawabannya adalah keseluruhan halaman yang besar dan tertaut. Jadi memasukkan hanya tautan dalam jawaban tidak masalah. Beberapa kata tentang mengapa penulis lebih memilih Valgrind daripada gdb dan bagaimana dia akan menangani masalah spesifik adalah IMHO apa yang sebenarnya hilang dari jawabannya.
ndemou
20

Tiga aturan dasar:

  1. Setel penunjuk ke NULLsetelah bebas
  2. Periksa NULLsebelum membebaskan.
  3. Inisialisasi pointer ke NULLawal.

Kombinasi ketiganya bekerja dengan cukup baik.

Mendongkrak
sumber
1
Saya bukan ahli C, tapi saya biasanya bisa menjaga kepala saya tetap di atas air. Mengapa # 1? Apakah hanya begitu program Anda benar-benar crash ketika Anda mencoba dan mengakses pointer gratis, dan bukan hanya kesalahan diam-diam?
Daniel Harms
1
@ Presisi: Ya, itulah intinya. Ini adalah praktik yang baik: memiliki penunjuk ke memori yang dihapus adalah sebuah risiko.
sebelum
10
Sebenarnya saya pikir # 2 tidak diperlukan karena sebagian besar kompiler akan memungkinkan Anda untuk mencoba menghapus pointer nol tanpa ini menyebabkan masalah. Saya yakin seseorang akan mengoreksi saya jika saya salah. :)
Komponen 10
11
@ Component10 Saya berpikir bahwa membebaskan NULL diperlukan oleh standar C untuk tidak melakukan apa-apa.
Demi
2
@ Demetri: Ya, Anda benar "jika nilai operan delete adalah penunjuk null, operasi tidak berpengaruh." (ISO / IEC 14882: 2003 (E) 5.3.5.2)
Komponen 10
15

Anda dapat menggunakan valgrinduntuk men-debugnya.

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

Satu kemungkinan perbaikan:

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

Lihat blog di menggunakan Valgrind Link

Sandipan Karmakar
sumber
Program saya membutuhkan waktu sekitar 30 menit untuk dijalankan, di Valgrind dapat memakan waktu 18 hingga 20 jam untuk menyelesaikannya.
Kemin Zhou
12

Dengan kompiler C ++ modern, Anda dapat menggunakan pembersih untuk melacak.

Contoh contoh:

Program saya:

$cat d_free.cxx 
#include<iostream>

using namespace std;

int main()

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

Kompilasi dengan pembersih alamat:

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

Jalankan:

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

Untuk mempelajari lebih lanjut tentang pembersih Anda dapat memeriksa ini atau ini atau semua kompiler c ++ modern (mis. Gcc, clang dll.) Dokumentasi.

Sitesh
sumber
5

Apakah Anda menggunakan petunjuk cerdas seperti Boost shared_ptr? Jika demikian, periksa apakah Anda langsung menggunakan pointer mentah di mana saja dengan memanggil get(). Saya telah menemukan ini sebagai masalah yang cukup umum.

Misalnya, bayangkan skenario di mana pointer mentah diteruskan (mungkin sebagai penangan panggilan balik, katakanlah) ke kode Anda. Anda mungkin memutuskan untuk menetapkan ini ke penunjuk cerdas untuk mengatasi penghitungan referensi dll. Kesalahan besar: kode Anda tidak memiliki penunjuk ini kecuali Anda mengambil salinan yang dalam. Ketika kode Anda selesai dengan penunjuk pintar, itu akan menghancurkannya dan mencoba untuk menghancurkan memori yang ditunjuknya karena ia berpikir bahwa tidak ada orang lain yang membutuhkannya, tetapi kode panggilan kemudian akan mencoba untuk menghapusnya dan Anda akan mendapatkan ganda masalah gratis.

Tentu saja, itu mungkin bukan masalah Anda di sini. Yang paling sederhana berikut ini adalah contoh yang menunjukkan bagaimana hal itu bisa terjadi. Penghapusan pertama baik-baik saja tetapi kompilator merasakan bahwa itu telah menghapus memori itu dan menyebabkan masalah. Itulah mengapa menetapkan 0 ke penunjuk segera setelah penghapusan adalah ide yang bagus.

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

Edit: diubah deletemenjadi delete[], karena ptr adalah array dari char.

Komponen 10
sumber
Saya tidak menggunakan perintah hapus apa pun dalam program saya. Mungkinkah ini masih menjadi masalah?
neuromancer
1
@Phenom: Mengapa Anda tidak menggunakan penghapusan? Apakah karena Anda menggunakan petunjuk cerdas? Mungkin Anda menggunakan new di kode Anda untuk membuat objek di heap? Jika jawaban untuk keduanya adalah ya, apakah Anda menggunakan get / set pada smart pointer untuk menyalin di sekitar pointer mentah? Jika demikian, jangan! Anda akan melanggar penghitungan referensi. Atau Anda bisa menetapkan penunjuk dari kode perpustakaan yang Anda panggil ke penunjuk cerdas. Jika Anda tidak 'memiliki' memori yang ditunjuk maka jangan lakukan itu, karena perpustakaan dan penunjuk cerdas akan mencoba untuk menghapusnya.
Komponen 10
-2

Saya tahu ini adalah utas yang sangat lama, tetapi ini adalah pencarian google teratas untuk kesalahan ini, dan tidak ada tanggapan yang menyebutkan penyebab umum kesalahan tersebut.

Yang menutup file yang sudah Anda tutup.

Jika Anda tidak memperhatikan dan memiliki dua fungsi berbeda menutup file yang sama, maka yang kedua akan menghasilkan kesalahan ini.

Jason
sumber
Anda salah, kesalahan ini dilemparkan karena bebas ganda, persis seperti status kesalahan. Fakta bahwa Anda menutup file dua kali menyebabkan double free karena jelas metode close mencoba membebaskan data yang sama dua kali.
Geoffrey