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.