誰にも隠れていないバグの3つの例





CPUバグ、カーネルバグ、中間の4 GBメモリ割り当てなど、トリッキーなバグの調査について多くのことを書いていますが、ほとんどのバグはそれほどエキゾチックではありません。バグを見つけるには、サーバーダッシュボードを確認するか、プロファイラーで数分を費やすか、コンパイラの警告を読む必要がある場合があります。



この記事では、私が見つけて修正した3つの重大なバグについて説明します。それらのすべてはまったく隠れず、誰かがそれらに気付くのをただ待っていました。



サーバープロセッサの驚き







数年前、私はライブゲームサーバーでのメモリの動作を数週間研究しました。サーバーはリモートデータセンターでLinuxを実行していたため、ほとんどの時間は、サーバーにトンネリングできるように必要なアクセス許可を取得することと、perfやその他のLinux診断ツールを効果的に使用する方法を学ぶことに費やされましたメモリ消費量が必要量の3倍になる一連のバグを発見し、それらを修正しました。



  • マップIDに不一致が見つかりました。これにより、各ゲームは約20MBのデータの同じコピーを使用せず、新しいコピーをロードしました。
  • ゼロmemset(!!!)が設定された未使用(!)の50 MBグローバル変数(!!)が見つかりました。これにより、すべてのプロセスで物理RAMが消費されました。
  • それほど深刻ではないさまざまなバグ。


しかし、私たちの話はそれについてではありません。



時間をかけてゲームサーバーのプロファイルを作成する方法を学んだ後、これをもう少し深く調査できることに気付きました。したがって、私はゲームの1つのサーバーでperfを実行しました。私がプロファイリングした最初のサーバープロセスは...奇妙でした。サンプリングされたプロセッサデータを「ライブ」で見ると、1つの関数がCPU時間の100%を消費していることがわかりました。ただし、この関数では14個の命令しか実行されませんでした。それは意味がありませんでした。



最初は、perfを間違って使用していると思いましたまたはデータの誤解。他のサーバープロセスのいくつかを調べたところ、それらの約半分が奇妙な状態にあることがわかりました。後半は、より通常のCPUプロファイルでした。



私たちが関心を持っている機能は、ナビゲーションノードのリンクされたリストを通過しました。私は同僚に尋ねたところ、浮動小数点の精度の問題によりゲームがループナビゲーションリストを生成する可能性があると言ったプログラマーを見つけました。彼らは常に歩くことができるノードの最大数を制限したいと思っていましたが、それを実現することはできませんでした。



それで、パズルは解決されましたか?浮動小数点計算の不安定性により、ナビゲーションリストにループが発生し、ゲームがそれらを際限なくバイパスします。これで、動作が説明されます。



しかし...そのような説明は、これが発生すると、サーバープロセスが無限ループに入り、すべてのプレーヤーがループから切断する必要があり、サーバープロセスがプロセッサコア全体を際限なく消費することを意味します。もしそうなら、私たちは最終的にサーバー上のリソースを使い果たしてしまうのではないでしょうか?誰もこれに気づいていませんか?



サーバー監視データを探したところ、次のようなものが見つかりました。







監視期間全体(1〜2年)で、サーバーの負荷の日次および週次の変動を観察し、月次のパターンを重ね合わせました。プロセッサの使用率レベルは徐々に増加し、その後ゼロに低下しました。もう少し聞いてみると、月に一度サーバーが再起動していることがわかりました。そして最後に、ロジックはこのすべてに現れました:



  • , .
  • , , .
  • CPU , 50%.
  • .


このバグは、20のナビゲーションノードの後でリストのトラバースを停止する数行のコードを追加することで修正されました。おそらく、サーバーと電力のコストを数百万ドル節約できます。モニタリンググラフを見てもこのバグは見つかりませんでしたが、見た人なら誰でも見つけることができました。



バグの頻度がコストの最大化と完全に一致したという事実が好きです。同時に、彼は発見されるのに十分な深刻な問題を引き起こしたことはありませんでしたこれは、人々を殺すのではなく、くしゃくしゃにするために進化するウイルスの作用に似ています。



読み込みが遅い







ソフトウェア開発者の生産性は、編集/コンパイル/リンク/デバッグサイクルの速度に密接に関係しています。つまり、ソースファイルに変更を加えてから、変更を加えて新しいバイナリを実行するのにかかる時間によって異なります。私は何年にもわたってコンパイル/リンク時間を短縮するために素晴らしい仕事をしてきましたが、ロード時間も重要です。一部のゲームは、開始するたびに膨大な量の作業を行います。私はせっかちなので、ゲームの読み込みを数秒速くするために最初に数時間または数日を費やすことがよくあります。



この場合、お気に入りのプロファイラーを実行し、ゲームの初期ロードフェーズでのCPU使用率グラフを確認しました。1つのステップが最も有望に見えました。いくつかの照明データを初期化するのに約10秒かかりました。起動フェーズで5秒節約することで、これらの計算を高速化する方法が見つかることを期待していました。研究に飛び込む前に、私はグラフィックの専門家に相談しました。彼は次のように



述べています「ゲームではこの照明データを使用しません。この課題を取り除くだけです。」



それはいい。簡単でした。



30分かけてプロファイリングと1行の変更を行うことで、メインメニューの読み込み時間を半分にすることができ、特別な労力はかかりませんでした。



早すぎる出発



フォーマット には任意の数の引数があるprintfため、型の不一致エラーが発生するの非常に簡単です。実際には、結果は大きく異なる可能性があります。



  1. printf(“ 0x%08lx”、p); //ポインタをintとして出力します-64ビットで切り捨て以下
  2. printf(“%d、%f”、f、i); // floatとintの場所を変更すると、意味がないか、機能する可能性があります(!)
  3. printf(“%s%d”、i、s); // stringとintの順序を変更すると、クラッシュする可能性が高くなります


標準では、このような型の不一致は未定義の動作であり、一部のコンパイラはこれらの不一致のいずれかで意図的にクラッシュするコードを生成すると述べていますが、上記は最も可能性の高い結果を示しています(注:2番目の段落がしばしば望ましい結果を出力する理由の質問は良いですABIナレッジパズル)。



このようなエラーは非常に簡単に発生するため、最近のすべてのコンパイラには、不一致が発生したことを開発者に警告する機能があります。 gccとclangの両方に関数のprintfスタイルの注釈があり、不一致について警告できます(ただし、残念ながら、注釈はwprintfスタイルの関数では機能しません)。 VC ++には、/ analysisが不一致について警告するために使用できる注釈(残念ながら他のもの)がありますが、/ analysisを使用しない場合は、printf / wprintfスタイルのCRTスタイルの関数についてのみ警告し、カスタム関数については警告しません。 ..。



私が働いていた会社は、gcc / clangが警告を発行するように、printfスタイルで関数に注釈を付けましたが、後で警告を無視することにしました。このような警告はバグの絶対的に正確な指標であるため、これは奇妙な決定です。信号とノイズの比率は無限大です。



VC ++でこれらのバグのクリーンアップを開始するか、アノテーションを分析してすべてのバグを正確に見つけることにしました。私はほとんどのバグを処理し、コードを送信する前にコードがチェックされるのを待って1つの大きな変更を加えました。







その週末にデータセンターで停電が発生し、すべてのサーバーがダウンしました(おそらく電源設定のエラーが原因です)。非常に多くのお金が失われる前に、救急隊員は急いですべてを修復して修理しました。



printfバグの面白い点は、100%の確率で誤動作することです。つまり、誤ったデータを表示したり、プログラムをクラッシュさせたりする場合、これは毎回発生します。したがって、それらが読み取られることのないロギングコード、またはめったに実行されないエラー処理コードにある場合にのみ、プログラムにとどまることができます。



「すべてのサーバーの同時再起動」イベントにより、通常は実行されないパスに沿ってコードが移動することが判明しました。起動サーバーは他のサーバーを探し始めましたが、それらを見つけることができず、次のようなメッセージを表示しました。



fprintf(log、 "サーバーが見つかりません%s。エラーコード%d。\ n"、err、server_name);


おっと。任意の数の引数のタイプの不一致。そして出発。



緊急対応要員には追加の問題があります。サーバーを再起動する必要がありましたが、クラッシュダンプが調べられ、バグが発見され、サーバーバイナリが再構築されず、新しいビルドがリリースされるまで、再起動できませんでした。それはかなり迅速なプロセスでした-数時間以内のようですが、回避できたはずです。



この話は、これらの警告の原因のトラブルシューティングに時間を費やす必要がある理由を完全に示していると思いました-実行時にコードが確実にクラッシュしたり、動作が悪くなることを示す警告を無視するのはなぜですか?ただし、このクラスの警告を排除することで、数時間のダウンタイムを節約できることを誰も気にしませんでした。実際、会社の文化はこれらの修正のいずれにも興味を持っていないようでしたしかし、この最後のバグで、別の会社に引っ越す時が来たと気づきました。



これからどのような教訓を学ぶことができますか?



関係者全員が製品の機能に一生懸命取り組んでいて、よく知られているバグを修正している場合は、おそらく非常に単純なバグが公開されています。ログの調査、コンパイラの警告のクリーンアップに少し時間をかけます(ただし、実際、コンパイラの警告がある場合は、人生で行った決定を再考する価値があります)。プロファイラを数分間実行します。独自のロギングシステムを追加したり、新しい警告を有効にしたり、自分以外の誰も使用していないプロファイラーを使用したりすると、追加のポイントが得られます。



メモリ/ cpuの使用量または安定性を向上させる優れた修正を行っていて、誰も気にしない場合は、それを高く評価している会社を見つけてください。



ハッカーニュースのディスカッションはこちら、Redditのディスカッションはこちら、Twitterのディスカッションはこちら






広告



信頼性の高い賃貸サーバーと料金プランの正しい選択により、不快な監視通知に気を取られることが少なくなります。すべてがスムーズに、非常に長い稼働時間で機能します。









All Articles