まず最初に、これは絶対的な答えのある完璧なQ&Aスタイルの質問ではないことを理解していますが、それを改善するための表現は考えられません。これに対する絶対的な解決策はないと思います。これが、Stack Overflowではなくここに投稿する理由の1つです。
先月、私はかなり古いサーバーコード(mmorpg)をより近代的で拡張/修正しやすいものに書き換えました。私はネットワーク部分から始め、サードパーティのライブラリ(libevent)を実装して、自分のものを処理しました。すべてのリファクタリングとコードの変更により、どこかでメモリ破損が発生し、どこで発生するかを見つけるのに苦労しています。
原始的なボットを実装して負荷をシミュレートしても、クラッシュが発生しない場合でも、開発/テスト環境で確実に再現することはできません(何らかの原因で発生したlibeventの問題を修正しました)
私はこれまで試しました:
地獄を破壊する-ものがクラッシュするまで無効な書き込みはありません(実稼働では1日以上かかる場合があります。1時間かかる場合があります)。これは本当に私を困惑させます。チャンス?(アドレス範囲を「広げる」方法はありますか?)
コード分析ツール、すなわちコベリティとcppcheck。彼らはいくつかの..を指摘しましたが、コードの厄介さとエッジケースは深刻なものではありませんでした。
(undodbを介して)gdbでクラッシュするまでプロセスを記録し、逆方向に作業します。この/ sounds /は実行可能であるはずですが、オートコンプリート機能を使用してgdbをクラッシュさせるか、可能性のあるブランチが多すぎるために失われる内部libevent構造になります(1つの破損が原因でに)。ポインターが元々どの場所に/どこに割り当てられていたのかを見ることができれば、ブランチの問題のほとんどを解消できると思います。ただし、undodbを使用してvalgrindを実行することはできません。通常のgdbレコードは、使用できないほど遅くなります(valgrindと組み合わせても機能する場合)。
コードレビュー!自分で(完全に)そして何人かの友人に私のコードを見てもらうことで、それが十分に徹底的だったとは思いませんが。コードレビュー/デバッグを行うために開発者を雇うことを考えていましたが、多額の資金を投入する余裕はありません。彼が問題を見つけられなかったり、資格を持っている人がいなければ、お金はありません。
また、注意する必要があります。通常、一貫したバックトレースが取得されます。クラッシュが発生する場所はいくつかありますが、大部分は何らかの理由でソケットクラスが破損することに関連しています。ソケットではない何かを指している無効なポインタか、ソケットクラス自体が(部分的に)意味不明なもので上書きされています。よく使用される部品の1つであるため、クラッシュが最も多いと思われますが、使用されるのは最初に破損したメモリです。
全体として、この問題はほぼ2か月間(忙しく、趣味のプロジェクトが多い)忙しく、不機嫌なIRLになり、あきらめることを考えるほどイライラしています。私は問題を見つけるために他に何をすべきかについて考えることができません。
見逃した便利なテクニックはありますか?どのように対処しますか?(これについてはあまり情報がないため、それほど一般的ではありません..または私は本当に盲目ですか?)
編集:
重要な場合の仕様:
gcc 4.7を介したc ++(11)の使用(debian wheezyが提供するバージョン)
コードベースは約15万行です
david.pfx投稿への応答で編集:(応答が遅くなって申し訳ありません)
パターンを探すために、クラッシュの注意深い記録を保持していますか?
はい、私はまだ周りの最近のクラッシュのダンプを持っています
いくつかの場所は本当に似ていますか?どのように?
さて、最新バージョン(コードを追加/削除したり、関連する構造を変更するたびに変更されるようです)では、常にアイテムタイマーメソッドでキャッチされます。基本的に、アイテムには期限が切れる特定の時間があり、更新された情報をクライアントに送信します。無効なソケットポインタは、主にそれに関連するPlayerクラス(私の知る限り有効)にあります。また、クリーンアップフェーズで、明示的に破棄されていないすべての静的クラスを(__run_exit_handlers
バックトレースで)破棄する通常のシャットダウンの後、クラッシュの負荷が発生しています。ほとんどの場合std::map
、1つのクラスが関与しますが、それが最初に現れるのは単なる推測です。
破損したデータはどのように見えますか?ゼロ?アスキー?パターン?
まだパターンが見つかりませんでした。破損がどこから始まったのかわからないので、わかりにくいです。
ヒープ関連ですか?
それは完全にヒープ関連です(gccのスタックガードを有効にしましたが、何もキャッチしませんでした)。
破損は後に発生し
free()
ますか?
少し詳しく説明する必要があります。すでに解放されたオブジェクトのポインターが横になっているということですか?オブジェクトが破棄されると、すべての参照をnullに設定するので、どこかで見逃さない限り、いいえ。valgrindには表示されますが、表示されませんでした。
ネットワークトラフィック(バッファサイズ、リカバリサイクル)に特有のものはありますか?
ネットワークトラフィックは生データで構成されます。したがって、char配列、(u)intX_tまたはより複雑なもののための(パディングを削除するための)パックされた構造体には、各パケットに、予想されるサイズに対して検証されるidおよびパケットサイズ自体で構成されるヘッダーがあります。サイズは10〜60バイトで、最大の(内部「起動」パケット、起動時に1回起動される)サイズは数Mbです。
多くの生産が主張します。損傷が伝播する前に、早期かつ予想どおりにクラッシュします。
私はかつてstd::map
破損に関連したクラッシュを経験しました。各エンティティには「ビュー」のマップがあり、それを見ることができる各エンティティはその中にあります。前後に200バイトのバッファーを追加し、0x33で埋め、各アクセスの前にチェックしました。腐敗は魔法のように消え去りました。私は何かを動かして、他の何かを腐敗させたに違いありません。
戦略的なロギング。これにより、直前に何が起こっていたかを正確に把握できます。回答に近づいたら、ログに追加してください。
それは機能します。
必死になって、状態を保存して自動再起動できますか?私はそれを行う生産ソフトウェアのいくつかの部分を考えることができます。
やややる。このソフトウェアは、メインの「キャッシュ」プロセスと、すべてのものを取得して保存するためにすべてキャッシュにアクセスする他のワーカープロセスで構成されています。そのため、クラッシュごとに大きな進歩を失うことはありません。それでもすべてのユーザーが切断されるなど、間違いなく解決策ではありません。
並行性:スレッド化、競合状態など
「非同期」クエリを実行するmysqlスレッドがありますが、これはすべてそのままで、すべてのロックを備えた関数を介してデータベースクラスと情報を共有するだけです。
割り込み
30秒間のサイクルを完了しなかった場合に停止するロックを防ぐための割り込みタイマーがありますが、そのコードは安全なはずです:
if (!tics) {
abort();
} else
tics = 0;
ティックはvolatile int tics = 0;
、サイクルが完了するたびに増加します。古いコードも。
イベント/コールバック/例外:状態またはスタックが予期せず破損する
多くのコールバックが使用されています(非同期ネットワークI / O、タイマー)が、悪いことは何もすべきではありません。
異常なデータ:異常な入力データ/タイミング/状態
それに関連したいくつかのエッジケースがありました。パケットの処理中にソケットを切断するとnullptrなどにアクセスすることになりますが、クラス自体に完了を通知した直後にすべての参照がクリーンアップされるため、これまでのところ簡単に見つけることができました。(破壊自体は、サイクルごとに破壊されたオブジェクトをすべて削除するループによって処理されます)
非同期外部プロセスへの依存。
手入れをしますか?これは、上記のキャッシュプロセスの場合です。頭上で想像できるのは、十分な速さで完了せず、ガベージデータを使用することだけですが、それもそうではありません。ネットワークを使用しているからです。同じパケットモデル。
/analyze
)、AppleのMallocおよびScribbleガードも追加します。また、コンパイラの警告は診断であり、時間とともに改善されるため、できるだけ多くの標準を使用して、できるだけ多くのコンパイラを使用する必要があります。特効薬はなく、1つのサイズがすべてに適合するわけではありません。使用するツールとコンパイラが多いほど、各ツールには長所と短所があるため、カバレッジはより完全になります。