Pertama, saya menyadari ini bukan pertanyaan gaya Q&A yang sempurna dengan jawaban absolut, tapi saya tidak bisa memikirkan kata-kata untuk membuatnya bekerja lebih baik. Saya tidak berpikir ada solusi mutlak untuk ini dan ini adalah salah satu alasan mengapa saya mempostingnya di sini daripada Stack Overflow.
Selama bulan lalu saya telah menulis ulang sepotong kode server (mmorpg) yang cukup lama menjadi lebih modern dan lebih mudah untuk diperluas / mod. Saya mulai dengan bagian jaringan dan menerapkan perpustakaan pihak ke-3 (libevent) untuk menangani hal-hal untuk saya. Dengan semua perubahan faktor dan perubahan kode saya memperkenalkan kerusakan memori di suatu tempat dan saya telah berjuang untuk mencari tahu di mana itu terjadi.
Saya sepertinya tidak dapat mereproduksi dengan andal pada lingkungan dev / test saya, bahkan ketika menerapkan bot primitif untuk mensimulasikan beberapa beban, saya tidak mendapatkan crash lagi (saya memperbaiki masalah libevent yang menyebabkan beberapa hal)
Saya sudah mencoba sejauh ini:
Memurnikan neraka itu keluar - Tidak ada penulisan yang tidak valid sampai hal itu crash (yang mungkin membutuhkan 1+ hari dalam produksi .. atau hanya satu jam) yang benar-benar membingungkan saya, pasti pada titik tertentu itu akan mengakses memori yang tidak valid dan tidak menimpa barang oleh kesempatan? (Apakah ada cara untuk "menyebar" kisaran alamat?)
Alat Analisis Kode, yaitu cakupan dan cppcheck. Sementara mereka menunjukkan beberapa .. kasus-kasus buruk dan tepi dalam kode tidak ada yang serius.
Merekam proses sampai crash dengan gdb (via undodb) dan kemudian bekerja dengan cara saya mundur. Ini / terdengar / seperti itu seharusnya bisa dilakukan, tapi saya akhirnya menabrak gdb dengan menggunakan fitur lengkapi-otomatis atau saya berakhir di beberapa struktur libevent internal di mana saya tersesat karena ada terlalu banyak cabang yang mungkin (satu korupsi menyebabkan yang lain dan sebagainya di). Saya kira akan lebih baik jika saya bisa melihat apa pointer awalnya milik / di mana dialokasikan, yang akan menghilangkan sebagian besar masalah percabangan. Saya tidak bisa menjalankan valgrind dengan undodb, dan saya catatan gdb normal sangat lambat (jika itu bahkan bekerja dalam kombinasi dengan valgrind).
Ulasan kode! Sendiri (menyeluruh) dan memiliki beberapa teman memeriksa kode saya, meskipun saya ragu itu cukup menyeluruh. Saya sedang berpikir tentang mungkin mempekerjakan seorang dev untuk melakukan review kode / debugging dengan saya, tetapi saya tidak mampu untuk memasukkan terlalu banyak uang di dalamnya dan saya tidak akan tahu di mana mencari seseorang yang bersedia bekerja untuk sedikit- ke-tidak ada uang jika dia tidak menemukan masalah atau siapa pun yang memenuhi syarat sama sekali.
Saya juga harus mencatat: Saya biasanya mendapatkan backtraces yang konsisten. Ada beberapa tempat di mana crash terjadi, sebagian besar terkait dengan kelas soket entah bagaimana menjadi rusak. Baik itu pointer yang tidak valid yang menunjuk ke sesuatu yang bukan soket atau kelas soket itu sendiri menjadi ditimpa (sebagian?) Dengan omong kosong. Meskipun saya curiga itu paling banyak menabrak karena itu salah satu bagian yang paling banyak digunakan, jadi itu adalah memori rusak pertama yang digunakan.
Semua dalam semua masalah ini telah membuat saya sibuk selama hampir 2 bulan (hidup dan mati, lebih dari proyek hobi) dan benar-benar membuat saya frustasi ke titik di mana saya menjadi IRL pemarah dan berpikir tentang menyerah. Saya tidak bisa memikirkan apa lagi yang harus saya lakukan untuk menemukan masalah ini.
Apakah ada teknik berguna yang saya lewatkan? Bagaimana Anda menghadapinya? (Tidak mungkin itu biasa karena tidak ada banyak informasi tentang ini .. atau aku benar-benar buta?)
Edit:
Beberapa spesifikasi jika itu penting:
Menggunakan c ++ (11) via gcc 4.7 (versi disediakan oleh debian wheezy)
Basis kode adalah sekitar 150 ribu baris
Edit sebagai respons terhadap posting david.pfx: (maaf atas respons yang lambat)
Apakah Anda menyimpan catatan kerusakan dengan cermat, untuk mencari pola?
Ya, saya masih memiliki kesedihan dari kecelakaan baru-baru ini yang tergeletak di sekitar
Apakah beberapa tempat benar-benar mirip? Dengan cara apa?
Nah, dalam versi terbaru (mereka tampaknya berubah setiap kali saya menambah / menghapus kode atau mengubah struktur terkait) itu akan selalu terjebak dalam metode item timer. Pada dasarnya suatu item memiliki waktu tertentu setelah itu kedaluwarsa dan mengirimkan info yang diperbarui kepada klien. Pointer soket yang tidak valid akan berada di kelas pemain (masih valid sejauh yang saya tahu), sebagian besar terkait dengan itu. Saya juga mengalami banyak crash dalam fase pembersihan, setelah shutdown normal di mana ia menghancurkan semua kelas statis yang belum dihancurkan secara eksplisit ( __run_exit_handlers
di backtrace). Sebagian besar melibatkan std::map
satu kelas, menebak itu hanya hal pertama yang muncul.
Seperti apa data korup itu? Nol? Ascii? Pola?
Saya belum menemukan pola apa pun, tampaknya agak acak bagi saya. Sulit dikatakan karena saya tidak tahu dari mana korupsi dimulai.
Apakah ini terkait tumpukan?
Ini sepenuhnya terkait dengan tumpukan (saya mengaktifkan stack guard gcc dan tidak menangkap apa pun).
Apakah korupsi terjadi setelah a
free()
?
Anda harus sedikit menguraikan yang itu. Apakah maksud Anda memiliki pointer benda yang sudah bebas tergeletak di sekitar? Saya mengatur setiap referensi ke null setelah objek dihancurkan, jadi kecuali saya melewatkan sesuatu di suatu tempat, tidak. Itu harus muncul di valgrind meskipun yang tidak.
Apakah ada sesuatu yang khas tentang lalu lintas jaringan (ukuran buffer, siklus pemulihan)?
Lalu lintas jaringan terdiri dari data mentah. Jadi char array, (u) intX_t atau packed (untuk menghapus padding) struct untuk hal-hal yang lebih kompleks, setiap paket memiliki header yang terdiri dari id dan ukuran paket itu sendiri yang divalidasi terhadap ukuran yang diharapkan. Mereka adalah sekitar 10-60bytes dengan paket (boot 'internal' terbesar, dipecat sekali saat startup) memiliki ukuran beberapa Mb.
Banyak dan banyak pernyataan produksi. Hancurkan lebih awal dan dapat diprediksi sebelum kerusakan menyebar.
Saya pernah mengalami crash terkait std::map
korupsi, setiap entitas memiliki peta "view" -nya, setiap entitas yang dapat melihatnya dan sebaliknya di dalamnya. Saya menambahkan buffer 200byte di depan dan sesudahnya, mengisinya dengan 0x33 dan memeriksanya sebelum setiap akses. Korupsi yang baru saja lenyap secara ajaib, saya pasti telah memindahkan sesuatu yang membuatnya merusak sesuatu yang lain.
Pencatatan strategis, sehingga Anda tahu secara akurat apa yang terjadi sebelumnya. Tambahkan ke logging ketika Anda mendekati jawaban.
Ini berfungsi .. sampai batas tertentu.
Dalam keputusasaan, dapatkah Anda menyimpan status dan memulai kembali secara otomatis? Saya dapat memikirkan beberapa perangkat lunak produksi yang melakukan itu.
Saya agak melakukan itu. Perangkat lunak ini terdiri dari proses "cache" utama dan beberapa pekerja lain yang semuanya mengakses cache untuk mendapatkan dan menyimpan barang. Jadi per crash, saya tidak kehilangan banyak kemajuan, itu masih memutuskan semua pengguna dan sebagainya, itu jelas bukan solusi.
Konkurensi: threading, kondisi balapan, dll
Ada utas mysql untuk melakukan kueri "async", itu semua belum tersentuh dan hanya membagikan informasi ke kelas basis data melalui fungsi dengan semua kunci.
Terganggu
Ada penghenti waktu untuk mencegah penguncian yang hanya dibatalkan jika tidak menyelesaikan siklus selama 30 detik, kode itu harus aman:
if (!tics) {
abort();
} else
tics = 0;
Tics adalah volatile int tics = 0;
yang meningkat setiap kali siklus selesai. Kode lama juga.
events / callbacks / exception: kondisi rusak atau tumpukan tidak terduga
Banyak panggilan balik digunakan (jaringan I / O async, timer), tetapi mereka seharusnya tidak melakukan hal buruk.
Data yang tidak biasa: data input / timing / state yang tidak biasa
Saya punya beberapa kasus tepi yang terkait dengan itu. Melepaskan soket saat paket masih diproses menghasilkan akses nullptr dan semacamnya, tetapi hal itu mudah dikenali sejauh ini karena setiap referensi dibersihkan segera setelah memberi tahu kelas sendiri bahwa hal itu dilakukan. (Penghancuran itu sendiri ditangani oleh loop menghapus semua benda yang hancur setiap siklus)
Ketergantungan pada proses eksternal asinkron.
Mau menguraikan? Ini agak terjadi, proses cache yang disebutkan di atas. Satu-satunya hal yang dapat saya bayangkan dari atas kepala saya adalah penyelesaiannya tidak cukup cepat dan menggunakan data sampah, tetapi itu tidak terjadi karena itu menggunakan jaringan juga. Model paket yang sama.
/analyze
) dan Apple Malloc and Scribble guards juga. Anda juga harus menggunakan banyak kompiler sebanyak mungkin menggunakan standar sebanyak mungkin karena peringatan kompiler adalah diagnostik dan mereka menjadi lebih baik dari waktu ke waktu. Tidak ada peluru perak, dan satu ukuran tidak cocok untuk semua. Semakin banyak alat dan kompiler yang Anda gunakan, semakin lengkap cakupannya karena setiap alat memiliki kelebihan dan kekurangannya.Jawaban:
Ini masalah yang menantang, tetapi saya curiga ada banyak petunjuk yang bisa ditemukan dalam tabrakan yang sudah Anda lihat.
Hal-hal yang kami gunakan dalam situasi serupa.
Dalam keputusasaan, dapatkah Anda menyimpan status dan memulai kembali secara otomatis? Saya dapat memikirkan beberapa perangkat lunak produksi yang melakukan itu.
Jangan ragu untuk menambahkan detail jika kami dapat membantu sama sekali.
Bisakah saya menambahkan bahwa bug yang tak tentu serius seperti ini tidak terlalu umum, dan tidak ada banyak hal yang (biasanya) dapat menyebabkannya. Mereka termasuk:
Ini adalah bagian dari kode untuk fokus.
sumber
Gunakan versi debug dari malloc / gratis. Bungkus mereka dan tulis sendiri jika perlu. Banyak bersenang-senang!
Versi yang saya gunakan menambahkan byte penjaga sebelum dan sesudah setiap alokasi, dan memelihara daftar "yang dialokasikan" yang memeriksa bebas membebaskan potongan. Ini menangkap sebagian besar buffer overrun dan banyak kesalahan "bebas".
Salah satu sumber korupsi yang paling berbahaya adalah terus menggunakan bongkahan setelah dibebaskan. Bebas harus mengisi memori yang dibebaskan dengan pola yang diketahui (secara tradisional, 0xDEADBEEF) Ini membantu jika struktur yang dialokasikan menyertakan elemen "angka ajaib", dan secara bebas menyertakan pemeriksaan untuk angka ajaib yang sesuai sebelum menggunakan struktur.
sumber
Mengutip apa yang Anda katakan dalam pertanyaan Anda, tidak mungkin memberi Anda jawaban yang pasti. Yang terbaik yang bisa kita lakukan adalah memberikan saran tentang hal-hal yang harus dicari dan alat serta teknik.
Beberapa saran akan tampak naif, yang lain mungkin lebih cocok, tetapi mudah-mudahan seseorang memicu pemikiran yang dapat Anda tindak lanjuti. Saya harus mengatakan bahwa jawabannya oleh david.pfx memiliki saran dan saran yang bagus.
Dari gejalanya
bagi saya itu terdengar seperti buffer overrun.
masalah terkait adalah menggunakan data soket yang tidak divalidasi sebagai subskrip atau kunci, dll.
mungkinkah Anda menggunakan variabel global di suatu tempat, atau memiliki global dan lokal dengan nama yang sama, atau entah bagaimana data satu pemain mengganggu yang lain?
Seperti halnya banyak bug, Anda mungkin membuat asumsi yang tidak valid di suatu tempat. Atau mungkin lebih dari satu. Banyak kesalahan yang berinteraksi sulit dideteksi.
Apakah setiap variabel memiliki deskripsi? Dan bisakah Anda mendefinisikan pernyataan validitas?
Jika tidak menambahkannya, pindai kode untuk melihat bahwa masing-masing variabel tampaknya digunakan dengan benar. Tambahkan pernyataan itu di mana pun hal itu masuk akal.
Saran untuk menambahkan pernyataan banyak adalah saran yang bagus: tempat pertama untuk menempatkannya adalah pada setiap titik masuk fungsi. Validasi argumen dan keadaan global yang relevan.
Saya menggunakan banyak logging untuk debug kode lama-berjalan / asinkron / real-time.
Sekali lagi, masukkan menulis log pada setiap panggilan fungsi.
Jika file log menjadi terlalu besar, fungsi logging dapat membungkus / mengganti file / dll.
Ini sangat berguna jika pesan log indent dengan kedalaman panggilan fungsi.
File log dapat menunjukkan bagaimana bug menyebar. Berguna ketika salah satu bagian dari kode melakukan sesuatu yang tidak benar yang bertindak sebagai bom aksi yang tertunda.
Banyak orang memiliki kode logging sendiri yang ditanam di rumah. Saya memiliki sistem log makro C lama di suatu tempat, dan mungkin versi C ++ ...
sumber
Segala sesuatu yang dikatakan dalam jawaban lain sangat relevan. Satu hal penting yang sebagian disebutkan oleh ddyer adalah bahwa pembungkus malloc / gratis memiliki manfaat. Dia menyebutkan beberapa tetapi saya ingin menambahkan alat debugging yang sangat penting untuk itu: Anda dapat login setiap malloc / gratis ke file eksternal bersama dengan beberapa baris callstack (atau callstack lengkap jika Anda peduli). Jika Anda berhati-hati, Anda dapat dengan mudah membuat ini dengan cepat dan menggunakannya dalam produksi jika itu yang terjadi.
Dari apa yang Anda jelaskan, tebakan pribadi saya adalah bahwa Anda mungkin menyimpan referensi ke pointer di suatu tempat untuk membebaskan memori dan mungkin akhirnya membebaskan pointer yang tidak lagi milik Anda atau menulisnya. Jika Anda dapat menyimpulkan kisaran ukuran untuk dipantau dengan teknik di atas, Anda harus dapat mempersempit logging. Jika tidak, setelah Anda menemukan memori apa yang sudah rusak, Anda dapat mengetahui malloc / pola bebas yang menyebabkannya cukup mudah dari log.
Catatan penting adalah bahwa seperti yang Anda sebutkan, mengubah tata letak memori mungkin menyembunyikan masalah. Karena itu sangat penting bahwa logging Anda tidak mengalokasikan (jika Anda bisa!) Atau sesedikit mungkin. Ini akan membantu reproduksibilitas jika terkait dengan memori. Ini juga akan membantu jika secepat mungkin jika masalah terkait multi-threading.
Penting juga bahwa Anda menjebak alokasi dari perpustakaan pihak ke-3 sehingga Anda dapat mencatatnya dengan benar. Anda tidak pernah tahu dari mana asalnya.
Sebagai alternatif terakhir, Anda juga dapat membuat pengalokasi khusus tempat Anda mengalokasikan setidaknya 2 halaman untuk setiap alokasi dan membatalkan pemetaannya saat Anda membebaskan (menyelaraskan alokasi ke batas halaman, mengalokasikan halaman sebelumnya dan menandainya sebagai tidak dapat diakses atau menyelaraskan alokasikan di akhir halaman dan alokasikan halaman setelah dan tandai tidak dapat diakses). Pastikan untuk tidak menggunakan kembali alamat memori virtual tersebut untuk alokasi baru setidaknya untuk beberapa saat. Ini berarti Anda harus mengelola sendiri memori virtual Anda (cadangan dan gunakan sesuka Anda). Perhatikan bahwa ini akan menurunkan kinerja Anda dan mungkin berakhir menggunakan sejumlah besar memori virtual tergantung pada berapa banyak alokasi yang Anda berikan. Untuk mengurangi ini akan membantu jika Anda dapat berjalan dalam 64bit dan / atau mengurangi rentang alokasi yang membutuhkan ini (berdasarkan ukuran). Valgrind mungkin sudah melakukan ini tetapi mungkin terlalu lambat bagi Anda untuk mengetahui masalahnya. Melakukan ini hanya untuk beberapa ukuran atau objek (jika Anda tahu yang mana, Anda dapat menggunakan pengalokasi khusus hanya untuk objek-objek itu) akan memastikan kinerja terkena dampak minimal.
sumber
Coba atur titik tontonan pada alamat memori tempat crash. GDB akan rusak pada instruksi yang menyebabkan memori tidak valid. Kemudian dengan jejak belakang Anda dapat melihat kode Anda yang menyebabkan korupsi. Ini mungkin bukan sumber korupsi tetapi mengulang titik pengawasan pada setiap korupsi dapat mengarah pada sumber masalahnya.
Ngomong-ngomong, karena pertanyaan diberi tag C ++, pertimbangkan untuk menggunakan pointer bersama yang menjaga kepemilikan dengan mempertahankan jumlah referensi dan menghapus memori dengan aman setelah pointer keluar dari ruang lingkup. Tetapi gunakan dengan hati-hati karena mereka dapat menyebabkan kebuntuan dalam penggunaan ketergantungan sirkular yang langka.
sumber