Bagaimana cara debug tumpukan kesalahan korupsi?

165

Saya men-debug aplikasi C ++ multi-threaded (asli) di bawah Visual Studio 2008. Pada kesempatan yang tampaknya acak, saya mendapatkan kesalahan "Windows telah memicu break point ..." dengan catatan bahwa ini mungkin disebabkan oleh korupsi di tumpukan. Kesalahan-kesalahan ini tidak akan selalu membuat aplikasi crash segera, meskipun kemungkinan akan crash segera setelah itu.

Masalah besar dengan kesalahan ini adalah mereka muncul hanya setelah korupsi benar-benar terjadi, yang membuatnya sangat sulit untuk dilacak dan didebug, terutama pada aplikasi multi-threaded.

  • Hal-hal macam apa yang dapat menyebabkan kesalahan ini?

  • Bagaimana cara men-debug mereka?

Tips, alat, metode, pencerahan ... dipersilakan.

Peter Mortensen
sumber

Jawaban:

128

Aplikasi Verifier dikombinasikan dengan Alat Debugging untuk Windows adalah pengaturan yang luar biasa. Anda bisa mendapatkan keduanya sebagai bagian dari Kit Pengandar Windows atau Windows SDK yang lebih ringan . (Mengetahui tentang Aplikasi Verifier ketika meneliti pertanyaan sebelumnya tentang masalah korupsi tumpukan .) Saya telah menggunakan BoundsChecker dan Insure ++ (disebutkan dalam jawaban lain) di masa lalu juga, meskipun saya terkejut betapa banyak fungsionalitas dalam Aplikasi Verifier.

Pagar Listrik (alias "efence"), dmalloc , valgrind , dan sebagainya semuanya layak disebutkan, tetapi sebagian besar lebih mudah dijalankan di bawah * nix daripada Windows. Valgrind sangat fleksibel: Saya telah men-debug perangkat lunak server besar dengan banyak masalah tumpukan yang menggunakannya.

Ketika semuanya gagal, Anda dapat memberikan overloads global baru / delete dan malloc / calloc / realloc Anda - cara melakukannya akan sedikit berbeda tergantung pada kompiler dan platform - dan ini akan menjadi investasi yang sedikit - tetapi mungkin terbayar dalam jangka panjang. Daftar fitur yang diinginkan harus terlihat familier dari dmalloc dan electricfence, dan buku yang sangat bagus, Writing Solid Code :

  • nilai penjaga : memberikan sedikit lebih banyak ruang sebelum dan setelah setiap alokasi, dengan menghormati persyaratan perataan maksimum; isi dengan angka ajaib (membantu menangkap buffer overflows dan underflow, dan sesekali pointer "liar")
  • alokasi isi : isi alokasi baru dengan nilai non-0 ajaib - Visual C ++ sudah akan melakukan ini untuk Anda di Debug build (membantu menangkap penggunaan vars tidak diinisialisasi)
  • free fill : isi memori yang dibebaskan dengan nilai non-0 ajaib, yang dirancang untuk memicu segfault jika dalam beberapa kasus dereferenced (membantu menangkap pointer menggantung)
  • tertunda gratis : jangan mengembalikan memori yang dibebaskan ke heap untuk sementara waktu, tetap bebaskan isi tetapi tidak tersedia (membantu menangkap lebih banyak petunjuk yang menggantung, menangkap proximate double-frees)
  • pelacakan : bisa merekam di mana alokasi dilakukan terkadang dapat bermanfaat

Perhatikan bahwa dalam sistem homebrew lokal kami (untuk target tertanam) kami menjaga pelacakan terpisah dari sebagian besar hal lain, karena overhead run-time jauh lebih tinggi.


Jika Anda tertarik pada lebih banyak alasan untuk membebani fungsi / operator alokasi ini, lihat jawaban saya untuk "Ada alasan untuk membebani operator global yang baru dan hapus?" ; Selain promosi diri yang tak tahu malu, daftar teknik lain yang membantu melacak kesalahan korupsi tumpukan, serta alat-alat lain yang berlaku.


Karena saya terus menemukan jawaban saya sendiri di sini ketika mencari nilai alokasi / gratis / pagar yang digunakan MS, inilah jawaban lain yang mencakup nilai pengisian Microsoft dbgheap .

Leander
sumber
3
Satu hal kecil yang perlu diperhatikan tentang Aplikasi Verifier: Anda harus mendaftarkan simbol Aplikasi Verifier di depan simbol server simbol microsoft di jalur pencarian simbol Anda, jika Anda menggunakannya ... Butuh sedikit pencarian untuk mencari tahu mengapa! Avrf tidak menemukan simbol yang dibutuhkan.
leander
Aplikasi Verifier sangat membantu, dan dikombinasikan dengan beberapa tebakan, saya dapat menyelesaikan masalah! Terima kasih banyak, dan untuk semua orang juga, karena telah menyampaikan poin-poin yang bermanfaat.
Apakah Aplikasi Verifier harus digunakan dengan WinDbg, atau haruskah itu bekerja dengan Visual Studio debugger? Saya sudah mencoba menggunakannya, tetapi tidak menimbulkan kesalahan atau tampaknya melakukan apa pun ketika saya men-debug di VS2012.
Nathan Reed
@NathanReed: Saya percaya ini bekerja dengan VS juga - lihat msdn.microsoft.com/en-us/library/ms220944(v=vs.90).aspx - meskipun perhatikan tautan ini untuk VS2008, saya tidak yakin tentang versi yang lebih baru. Memori agak kabur, tetapi saya percaya ketika saya memiliki masalah pada tautan "pertanyaan sebelumnya", saya hanya menjalankan Aplikasi Verifier dan menyimpan opsi, menjalankan program, dan ketika macet saya memilih VS untuk debug dengan. AV baru saja membuatnya crash / menegaskan sebelumnya. Perintah! Avrf khusus untuk WinDbg sejauh yang saya tahu. Semoga orang lain dapat memberikan info lebih lanjut!
Leander
Terima kasih. Saya benar-benar menyelesaikan masalah awal saya dan ternyata bukan menumpuk korupsi, tetapi sesuatu yang lain, sehingga mungkin menjelaskan mengapa App Verifier tidak menemukan apa pun. :)
Nathan Reed
35

Anda dapat mendeteksi banyak masalah korupsi tumpukan dengan mengaktifkan Page Heap untuk aplikasi Anda. Untuk melakukan ini, Anda perlu menggunakan gflags.exe yang datang sebagai bagian dari Alat Debugging Untuk Windows

Jalankan Gflags.exe dan dalam opsi file gambar untuk dieksekusi Anda, centang opsi "Aktifkan Penumpukan Halaman".

Sekarang restart exe Anda dan lampirkan ke debugger. Dengan Halaman Tumpukan diaktifkan, aplikasi akan masuk ke debugger setiap kali terjadi tumpukan korupsi.

Canopus
sumber
ya tapi begitu saya mendapatkan panggilan fungsi ini di tempat sampah saya (setelah kerusakan memori rusak): wow64! Wow64NotifyDebugger, apa yang bisa saya lakukan? Saya masih tidak tahu apa yang salah dalam aplikasi saya
Guillaume07
Baru saja mencoba gflag untuk men-debug tumpukan korupsi di sini, alat kecil SANGAT berguna, sangat dianjurkan. Ternyata saya sedang mengakses memori bebas, yang, ketika diinstrumentasi dengan gflags akan segera masuk ke debugger ... Handy!
Dave F
Alat Hebat! Baru saja menemukan bug, yang saya buru selama berhari-hari, karena Windows tidak mengatakan alamat korupsi, hanya bahwa "sesuatu" salah, yang sebenarnya tidak terlalu membantu.
Devolus
Agak terlambat ke pesta, tapi saya perhatikan peningkatan signifikan penggunaan memori saya aplikasi saya debug ketika saya mengaktifkan Page Heap. Sayangnya sampai pada titik aplikasi (32bit) kehabisan memori sebelum deteksi korupsi tumpukan dipicu. Ada ide bagaimana mengatasi masalah itu?
uceumern
13

Untuk benar-benar memperlambat segalanya dan melakukan banyak pengecekan runtime, coba tambahkan berikut ini di bagian atas main()atau yang setara di Microsoft Visual Studio C ++

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );
Dave Van Wagner
sumber
8

Hal-hal macam apa yang dapat menyebabkan kesalahan ini?

Melakukan hal-hal nakal dengan ingatan, misalnya menulis setelah ujung buffer, atau menulis ke buffer setelah itu dibebaskan kembali ke tumpukan.

Bagaimana cara men-debug mereka?

Gunakan instrumen yang menambahkan pemeriksaan batas otomatis ke executable Anda: mis. Valgrind di Unix, atau alat seperti BoundsChecker (Wikipedia menyarankan juga Purify and Insure ++) pada Windows.

Berhati-hatilah karena ini akan memperlambat aplikasi Anda, jadi aplikasi itu mungkin tidak dapat digunakan jika aplikasi Anda adalah soft-real-time.

Alat / alat debugging lain yang mungkin adalah HeapAgent dari MicroQuill.

ChrisW
sumber
1
Membangun kembali aplikasi dengan debugging runtime (/ MDd atau / MTd flag) akan menjadi langkah pertama saya. Ini melakukan pemeriksaan tambahan di malloc dan gratis, dan sering kali berhenti efektif untuk mempersempit lokasi bug.
Dipekerjakan Rusia
Heapagent MicroQuill: Tidak banyak yang ditulis atau mendengar tentang hal itu, tetapi untuk korupsi tumpukan, itu harus ada dalam daftar Anda.
Samrat Patil
1
BoundsChecker berfungsi dengan baik sebagai tes asap, tetapi bahkan tidak berpikir untuk menjalankan program di bawahnya ketika mencoba menjalankan program itu dalam produksi juga. Perlambatan bisa di mana saja dari 60x hingga 300x, tergantung pada opsi mana yang Anda gunakan, dan apakah Anda menggunakan fitur instrumentasi kompilasi atau tidak. Penafian: Saya adalah salah satu dari orang-orang yang mengelola produk untuk Micro Focus.
Rick Papo
8

Satu tip cepat, yang saya dapatkan dari Mendeteksi akses ke memori yang dibebaskan adalah ini:

Jika Anda ingin mencari kesalahan dengan cepat, tanpa memeriksa setiap pernyataan yang mengakses blok memori, Anda dapat mengatur penunjuk memori ke nilai yang tidak valid setelah membebaskan blok:

#ifdef _DEBUG // detect the access to freed memory
#undef free
#define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666;
#endif
StackedCrooked
sumber
5

Alat terbaik yang saya temukan bermanfaat dan berfungsi setiap kali adalah ulasan kode (dengan peninjau kode yang baik).

Selain ulasan kode, saya terlebih dahulu akan mencoba Page Heap . Penumpukan Halaman membutuhkan beberapa detik untuk disiapkan dan jika beruntung mungkin akan menunjukkan masalah Anda.

Jika tidak beruntung dengan Page Heap, unduh Debugging Tools untuk Windows dari Microsoft dan pelajari cara menggunakan WinDbg. Maaf tidak bisa memberi Anda bantuan yang lebih spesifik, tetapi men-debug korupsi tumpukan multi-ulir lebih merupakan seni daripada sains. Google untuk "WinDbg tumpukan korupsi" dan Anda harus menemukan banyak artikel tentang hal ini.

Shing Yip
sumber
4

Anda mungkin juga ingin memeriksa untuk melihat apakah Anda menautkan ke perpustakaan runtime C dinamis atau statis. Jika file DLL Anda terhubung dengan perpustakaan runtime C statis, maka file DLL memiliki tumpukan yang terpisah.

Oleh karena itu, jika Anda membuat objek dalam satu DLL dan mencoba untuk membebaskannya di DLL lain, Anda akan mendapatkan pesan yang sama dengan yang Anda lihat di atas. Masalah ini dirujuk dalam pertanyaan Stack Overflow lain, Membebaskan memori yang dialokasikan dalam DLL yang berbeda .

dreadpirateryan
sumber
3

Apa jenis fungsi alokasi yang Anda gunakan? Baru-baru ini saya menemukan kesalahan serupa menggunakan fungsi alokasi gaya Heap *.

Ternyata saya keliru membuat tumpukan dengan HEAP_NO_SERIALIZEopsi. Ini pada dasarnya membuat fungsi Heap berjalan tanpa pengaman thread. Ini merupakan peningkatan kinerja jika digunakan dengan benar tetapi jangan pernah digunakan jika Anda menggunakan HeapAlloc dalam program multi-utas [1]. Saya hanya menyebutkan ini karena posting Anda menyebutkan Anda memiliki aplikasi multi-utas. Jika Anda menggunakan HEAP_NO_SERIALIZE di mana saja, hapus itu dan itu kemungkinan akan memperbaiki masalah Anda.

[1] Ada situasi tertentu di mana ini sah, tetapi mengharuskan Anda untuk membuat serialisasi panggilan ke Heap * dan biasanya tidak berlaku untuk program multi-utas.

JaredPar
sumber
Ya: lihat opsi kompiler / build aplikasi, dan pastikan itu dibangun untuk menghubungkan dengan versi "multi-threaded" dari perpustakaan run-time C.
ChrisW
@ ChrisW untuk API gaya HeapAlloc ini berbeda. Ini sebenarnya adalah parameter yang dapat diubah pada waktu pembuatan tumpukan, bukan waktu tautan.
JaredPar
Oh Tidak terpikir oleh saya bahwa OP mungkin berbicara tentang tumpukan itu, dan bukan tentang tumpukan di CRT.
ChrisW
@ ChrisW, pertanyaannya agak kabur tapi saya baru saja menemukan masalah yang saya perincikan ~ 1 minggu yang lalu jadi masih segar di pikiran saya.
JaredPar
3

Jika kesalahan ini terjadi secara acak, ada kemungkinan besar Anda mengalami balapan data. Silakan, periksa: apakah Anda memodifikasi pointer memori bersama dari utas yang berbeda? Intel Thread Checker dapat membantu mendeteksi masalah tersebut dalam program multithreaded.

Vladimir Obrizan
sumber
1

Selain mencari alat, pertimbangkan untuk mencari pelakunya. Apakah ada komponen yang Anda gunakan, mungkin tidak ditulis oleh Anda, yang mungkin tidak dirancang dan diuji untuk berjalan di lingkungan multithread? Atau hanya satu yang Anda tidak tahu telah berjalan di lingkungan seperti itu.

Terakhir kali itu terjadi pada saya, itu adalah paket asli yang telah berhasil digunakan dari pekerjaan batch selama bertahun-tahun. Tetapi ini adalah pertama kalinya di perusahaan ini telah digunakan dari layanan .NET web (yang multithreaded). Itu dia - mereka berbohong tentang kode yang aman.

John Saunders
sumber
1

Anda dapat menggunakan VC CRT Heap-Check macro untuk _CrtSetDbgFlag : _CRTDBG_CHECK_ALWAYS_DF atau _CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF .

KindDragon
sumber
0

Saya ingin menambahkan pengalaman saya. Dalam beberapa hari terakhir, saya memecahkan contoh kesalahan ini dalam aplikasi saya. Dalam kasus khusus saya, kesalahan dalam kode adalah:

  • Menghapus elemen dari koleksi STL saat iterasi di atasnya (saya percaya ada bendera debug di Visual Studio untuk menangkap hal-hal ini; Saya menangkapnya selama tinjauan kode)
  • Yang ini lebih kompleks, saya akan membaginya dalam langkah-langkah:
    • Dari utas C ++ asli, panggil kembali ke kode terkelola
    • Di tanah yang dikelola, panggil Control.Invokedan buang benda yang dikelola yang membungkus objek asli yang menjadi milik panggilan balik itu.
    • Karena objek masih hidup di dalam utas asli (itu akan tetap diblokir dalam panggilan panggil sampai Control.Invokeberakhir). Saya harus mengklarifikasi yang saya gunakan boost::thread, jadi saya menggunakan fungsi anggota sebagai fungsi utas.
    • Solusi : Gunakan Control.BeginInvoke(GUI saya dibuat dengan Winforms) sebagai gantinya sehingga utas asli dapat berakhir sebelum objek dihancurkan (tujuan panggilan balik ini persis memberitahukan bahwa utas berakhir dan objek dapat dihancurkan).
dario_ramos
sumber
0

Saya memiliki masalah yang sama - dan itu muncul secara acak. Mungkin ada yang korup dalam membangun file, tetapi saya akhirnya memperbaikinya dengan membersihkan proyek terlebih dahulu kemudian membangun kembali.

Jadi selain tanggapan lain yang diberikan:

Hal-hal macam apa yang dapat menyebabkan kesalahan ini? Sesuatu yang rusak dalam file build.

Bagaimana cara men-debug mereka? Membersihkan proyek dan membangun kembali. Jika sudah diperbaiki, ini kemungkinan masalahnya.

Marty
sumber