これは「知識を共有する」質問です。私はあなたの成功や失敗から学ぶことに興味があります。
役に立つかもしれない情報...
背景:
ハンティング
殺害
事後分析
これらの例は一般的なものであり、すべての状況に適用できるわけではなく、おそらく役に立たないものです。必要に応じて味付けしてください。
実際には、アプリケーションのサードパーティの画像ビューアサブコンポーネントにありました。
アプリケーションのユーザーのうち2〜3人が、画像ビューアコンポーネントで例外をスローし、ひどく死ぬことがよくあることがわかりました。ただし、他の何十人ものユーザーが、ほとんどの業務で同じタスクのアプリケーションを使用しているにもかかわらず、この問題を見たことがないユーザーがいました。また、他のユーザーよりも頻繁にアクセスするユーザーが1人いました。
通常の手順を試しました。
(1)コンピュータ/構成を除外する問題がなかった別のユーザーとコンピュータを切り替えた場合。 -問題は彼らに続いた。
(2)ユーザーがアプリケーションにログインして、問題を確認したことのないユーザーとして作業した場合。 -問題はまだ続きました。
(3)ユーザーがどの画像を表示しているかをレポートし、テストハーネスをセットアップして、その画像の表示を何千回も連続して繰り返し繰り返すようにしました。問題はハーネス自体には現れませんでした。
(4)開発者がユーザーと一緒に座り、一日中彼らを監視していました。彼らはエラーを見ましたが、彼らがそれらを引き起こすために異常なことをしていることに気づきませんでした。
「エラーユーザー」の共通点を他のユーザーが共有していないものと理解しようと、数週間苦労しました。どのようにすればよいかはわかりませんが、ステップ(4)の開発者は、ある日、百科事典ブラウンにふさわしい仕事をするために、ドライブ中にエウレカの瞬間を過ごしました。
彼はすべての「エラーユーザー」が左利きであることを認識し、この事実を確認しました。左利きのユーザーのみがエラーを受け取り、Rightiesは受け取りませんでした。しかし、どのように左利きになるとバグが発生するのでしょうか?
私たちは彼に座り、左利きの人たちが再び彼らが違ったことをしていることに特に注意を向けているのを見てもらいました、そしてそれが私たちがそれを見つけた方法です。
新しい画像を読み込んでいるときに画像ビューアーのピクセルの右端の列にマウスを移動した場合にのみバグが発生することが判明しました(ベンダーがマウスオーバーイベントに対して1回限りの計算を行ったため、オーバーフローエラー)。
どうやら、次の画像が読み込まれるのを待っている間、ユーザーは自然に手(つまりマウス)をキーボードに向かって動かしました。
最も頻繁にエラーが発生した1人のユーザーは、次のページが読み込まれるのを待っている間、マウスを強制的に非常にいらいらさせたADDタイプの1つでした。タイミングがちょうどいいので、ロードイベントが発生したときに彼女はそれを行いました。ベンダーから修正を受け取るまでは、(次のドキュメント)をクリックした後にマウスを離し、読み込まれるまで触れないように指示しました。
以降、開発チームの伝説では"The Left Handed Bug"と呼ばれていました。
これはlong昔(1980年代後半)のものです。
私が働いていた会社は、さまざまなUnixワークステーション(HP、Sun、Silcon Graphicsなど)で実行されるCADパッケージ(FORTRAN))を作成しました。データを保存するために独自のファイル形式を使用し、パッケージが開始されたとき、ディスクスペースが不足していたため、エンティティヘッダーに複数のフラグを格納するために使用される多くのビットシフトがありました。
エンティティーのタイプ(線、円弧、テキストなど)は、保管時に4096(多分)倍になりました。さらに、この値は削除されたアイテムを示すために否定されました。したがって、型を取得するには、次のようなコードを使用しました。
type = record[1] MOD 4096
1つを除くすべてのマシンで、これは±1(線の場合)、±2(円弧の場合)などを示し、その後、サインをチェックして、削除されたかどうかを確認できます。
1台のマシン(HPだと思います)では、削除済みアイテムの処理が台無しになるという奇妙な問題がありました。
これはIDEとビジュアルデバッガの前の日だったので、問題を追跡するためにトレースステートメントとログを挿入する必要がありました。
他のすべてのメーカーがMOD
を実装しているので-4096 MOD 4096
をもたらしました -1
HPは数学的に正しく実装したので、-4096 MOD 4096
をもたらしました -4097
。
MOD
を実行する前にコードベース全体を調べて値の符号を保存し、それを正にして、結果に符号値を乗算する必要がありました。
これには数日かかりました。
うわー、ここで良い読書!
私の最も辛かったのは、Turbo Pascalが大きくなった数年前のことですが、当時の初期のC++ IDEの1つだったかもしれません。唯一の開発者として(そしてこのスタートアップで3人目の人間として)、私はシンプルな営業担当者にやさしいCADプログラムのようなものを書きました。それは当時は素晴らしいですが、厄介なランダムクラッシュを開発しました。再現することは不可能ですが、頻繁に発生してバグハントを始めました。
私の最善の戦略は、デバッガーでシングルステップを実行することでした。このバグは、ユーザーが十分な数の図面を入力し、おそらく特定のモードまたはズーム状態でなければならない場合にのみ発生したため、面倒な設定とブレークポイントのクリアが多く、通常通り1分間実行して図面を入力しました。大量のコードをステップ実行します。特に役立つのは、調整可能な回数スキップしてブレークするブレークポイントでした。この運動全体を数回繰り返す必要がありました。
やがて、サブルーチンが呼び出される場所に絞り込み、2が与えられましたが、内部から意味不明な数字が表示されました。私はこれをもっと早くキャッチできたかもしれませんが、与えられたものを得たと想定して、このサブルーチンに踏み込んでいませんでした。最も単純なものは大丈夫だと仮定して盲目になりました!
スタックに16ビットの整数を詰め込んでいることがわかりましたが、サブルーチンは32ビットを想定しています。またはそのようなもの。コンパイラーはすべての値を32ビットに自動的に埋め込んだり、十分な型チェックを行ったりしませんでした。修正するのは簡単で、1行の一部であり、必要な考えはほとんどありませんでした。しかし、そこにたどり着くまでには、3日間の狩猟と自明な質問が必要でした。
だから私は、高価なコンサルタントがやって来て、しばらくしてどこかを1回タップして$ 2000を請求するという逸話についての個人的な経験を持っています。幹部は内訳を要求し、タップ1ドル、タップする場所を知るために1999年です。私の場合を除いて、それはお金ではありません。
得られた教訓:1)「最良」とは、コンピューターサイエンスが確認する方法と同じくらい多くの問題を確認することを含むと定義されている最高のコンパイラーを使用し、2)単純で明白なものに質問するか、少なくともそれらの適切な機能を確認します。
それ以来、必要と思われるよりも簡単なことを徹底的にチェックすることを知っているので、すべての難しいバグは本当に難しいものになりました。
レッスン2は、私がこれまでに修正した最も困難な電子機器のバグにも当てはまり、些細な修正も含まれていますが、いくつかのスマートEEが数か月間行き詰っていました。しかし、これは電子フォーラムではないので、これ以上は言いません。
地獄からのネットワークデータの競合状態
私は、別の開発者が書いた非常に古い(Encore 32/77)ワークステーションで同様のアプリケーションを使用できるように、ネットワーククライアント/サーバー(Windows XP/C#)を作成していました。
アプリケーションが本質的に行ったことは、ホスト上の特定のデータを共有/操作して、ファンシーなPCベースのマルチモニタータッチスクリーンUIでシステムを実行するホストプロセスを制御することでした。
3層構造でこれを行いました。通信プロセスは、ホストとの間でデータを読み書きし、必要なすべてのフォーマット変換(エンディアン、浮動小数点形式など)を実行し、データベースとの間で値を読み書きしました。データベースは、コミュニケーションとタッチスクリーンUI間のデータ仲介として機能しました。タッチスクリーンUIのアプリは、PCに接続されているモニターの数に基づいてタッチスクリーンインターフェイスを生成しました(これは自動的に検出されました)。
ホストとPCの間の値のパケットが与えられた時間枠では、一度に最大128の値をワイヤ経由で送信でき、最大遅延は往復あたり最大110ミリ秒でした(UDPは、直接のx-overイーサネット接続で使用されました)コンピューター)。したがって、接続されているタッチスクリーンの可変数に基づいて許可される変数の数は、厳密に制御されていました。また、ホスト(リアルタイムコンピューティングに使用される共有メモリバスを備えたかなり複雑なマルチプロセッサアーキテクチャを備えていますが)は、私の携帯電話の処理能力の約100分の1であるため、可能な限り少ない処理とサーバーを実行する必要がありました。/clientは、これを保証するためにアセンブリで作成する必要がありました(ホストは、プログラムの影響を受けないフルリアルタイムシミュレーションを実行していました)。
問題でした。一部の値は、タッチスクリーンで変更されると、新しく入力された値だけではなく、その値と前の値の間でランダムに循環します。これは、特定のページの特定の組み合わせを持ついくつかの特定のページのいくつかの特定の値でのみ、症状を示しました。最初の顧客承認プロセスを実行するまで、問題をほぼ完全に見逃していた
問題を特定するために、変動する値の1つを選びました。
次に、wiresharkを解除して、手動でパケットキャプチャのデコードを開始しました。結果:
通信コードの細部を100度調べたところ、欠陥やエラーは見つかりませんでした。
最後に、私は他の開発者に電子メールを送り始め、欠けているものがないかどうか彼の目的がどのように機能したかを詳細に尋ねました。それから私はそれを見つけました。
どうやら、彼がデータを送信したとき、彼は送信前にデータの配列をフラッシュしなかったので、本質的に、新しい値で使用された最後のバッファを上書きして古い値を上書きしていましたが、上書きされていない古い値はまだ送信されています。
したがって、値がデータ配列の80の位置にあり、要求された値のリストが80未満に変更されたが、同じ値が新しいリストに含まれている場合、両方の値がその特定のバッファーのデータバッファーに存在します。与えられた時間。
データベースから読み取られる値は、UIが値を要求したときのタイムスライスによって異なります。
修正は非常に簡単でした。データバッファー(実際にはパケットプロトコルの一部として含まれていました)に着信するアイテムの数を読み取り、その数を超えるバッファーを読み取らないでください。
学んだ教訓:
当然のことながら、現代のコンピューティング能力を使用しないでください。コンピュータがイーサネットをサポートしておらず、アレイのフラッシュが高価であると考えられる時期がありました。私たちがどこまで進んだのかを本当に知りたいのなら、動的メモリ割り当ての形式が実質的にないシステムを想像してみてください。 IE、実行プロセスはすべてのプログラムにすべてのメモリを順番に事前に割り当てる必要があり、プログラムはその境界を超えて成長できませんでした。 IE、システム全体を再コンパイルせずにプログラムにより多くのメモリを割り当てると、大規模なクラッシュを引き起こす可能性があります。ガベージコレクションの前の日はいつの日か同じように語られるのではないでしょうか。
カスタムプロトコルを使用してネットワーキングを行う場合(または一般にバイナリデータ表現を処理する場合)は、パイプを介して送信されるすべての値のすべての機能を理解するまで、仕様を読んでください。つまり、目が痛くなるまで読んでください。個々のビットまたはバイトを操作することによってデータを処理する人々は、物事を行うための非常に巧妙で効率的な方法を持っています。非常に細かい詳細がないと、システムが壊れる可能性があります。
修正までの全体的な時間は2〜3日で、ほとんどの時間は他の作業に費やして、私がこれに不満を感じるようになりました。
補足:問題のホストコンピューターは、デフォルトではイーサネットをサポートしていませんでした。それを駆動するカードはカスタムメイドで改造されており、プロトコルスタックは事実上存在しませんでした。私が一緒に働いていた開発者はプログラマーの地獄でした、彼はUDPのストリップされたバージョンと最小限の偽のイーサネットスタック(プロセッサは完全なイーサネットスタックを処理するのに十分強力ではなかった)をこのプロジェクトのシステムに実装しただけではありませんでも一週間もかからなかった。彼はまた、そもそもOSを設計およびプログラミングした元のプロジェクトチームリーダーの1人でもありました。簡単に言えば、彼がコンピュータ/プログラミング/アーキテクチャについてこれまでに共有しなければならなかったものはどれほど長くても、私がすでにどれだけ新しいものでも、私はすべての言葉を聞きます。自分の仕事に真の情熱を持っている善良な人々と協力することほど価値のあることはありません。
背景
不具合
どうやって見つけたの
最初はこれが通常のパフォーマンスの問題であると確信していたため、精巧なログを作成しました。使用率についてデータベースの担当者に問い合わせるたびに、パフォーマンスをチェックして、サーバーの問題を監視しました。 1週間
次に、スレッドの競合の問題があると確信しました。デッドロックがシチュエーション作成ツールを作成しようとしていることを確認して、デバッグでシチュエーションを作成しようとしました。管理の不満が高まるにつれ、プロジェクトをゼロから再開することからサーバーを1つのスレッドに制限することまで、どのように提案されているかを同僚に尋ねました。 1.5週間
次に Tess Ferrandez のブログを見て、ユーザーダンプファイルを作成し、次にサーバーがダンプを取得したときにwindebugでそれを分析しました。私のスレッドはすべて、dictionary.add関数でスタックしていることがわかりました。
X個のスレッドエラーを書き込むログを追跡するだけの短い1つの小さな辞書が同期されていません。
ハードウェアデバイスと通信するアプリケーションがあり、場合によっては、デバイスが物理的に接続されていないと、2回プラグインしてソフトリセットするまで正しく動作しません。
問題は、起動時に実行されているアプリケーションが、まだマウントされていないファイルシステムから読み取ろうとすると、segfaultを実行することがあるということでした(たとえば、ユーザーがNFSボリュームから読み取るように構成した場合)。起動時に、アプリケーションはいくつかのioctlをドライバーに送信してデバイスを初期化し、構成設定を読み取り、さらにioctlを送信してデバイスを正しい状態にします。
ドライバーのバグにより、初期化呼び出しが行われたときに無効な値がデバイスに書き込まれていましたが、呼び出しが行われてデバイスが特定の状態になると、値は有効なデータで上書きされました。
デバイス自体にバッテリーがあり、マザーボードから電力が失われたかどうかを検出し、揮発性メモリにフラグを書き込んで電力が失われたことを示し、次に電源がオンになったときに特定の状態に入り、特定のフラグをクリアするには命令を送信する必要がありました。
問題は、デバイスを初期化するためにioctlが送信された後(そしてデバイスに無効な値が書き込まれた後)、有効なデータが送信される前に電源が切断された場合でした。デバイスの電源が再びオンになると、フラグが設定されていることがわかり、初期化が不完全なためにドライバーから送信された無効なデータを読み取ろうとします。これにより、デバイスは電源オフのフラグがクリアされた無効な状態になりますが、デバイスは、ドライバーによって再初期化されるまで、それ以上の指示を受け取りません。 2番目のリセットは、デバイスに保存されている無効なデータをデバイスが読み取ろうとしていないことを意味し、正しい構成指示を受け取って、デバイスを正しい状態にすることができます(ioctlを送信するアプリケーションがsegfaultを実行しなかった場合) )。
最終的に、問題を引き起こしている正確な一連の状況を把握するのに約2週間かかりました。
これを別の質問に投稿しました。 ここに投稿を参照
メインフレームに新しいバージョンのコンパイラをインストールしたことが原因です。
アップデート06/11/13:(元の回答はOPによって削除されました)
このメインフレームアプリケーションを継承しました。ある日、青く澄んでいたので動作しなくなりました。それだけです...止まっただけです。
私の仕事はそれをできるだけ速く動かすことでした。ソースコードは2年間変更されていませんでしたが、突然停止しました。コードをコンパイルしようとしたところ、XX行で壊れました。行XXを見たところ、行XXが壊れる原因がわかりませんでした。このアプリケーションの詳細な仕様を尋ねたところ、何もありませんでした。行XXは原因ではありませんでした。
コードを印刷して、上から下にレビューを始めました。私は何が起こっているかのフローチャートを作成し始めました。コードが複雑すぎて、意味がわかりません。私はそれをフローチャートにしようとするのをあきらめました。特にアプリケーションが何を行っているか詳細がわからなかったので、その変更が残りのプロセスにどのように影響するかを知らずに変更を加えることを恐れました。
そこで、ソースコードの先頭から始めて、コードを読みやすくするためにwhitespceとラインブレーキを追加することにしました。場合によっては、ANDとORを組み合わせた条件があり、どのデータがANDで処理されているか、どのデータがORで処理されているかを明確に区別できない場合がありました。 ANDとOR条件を括弧で囲んで読みやすくしました。
ゆっくりと下に移動して掃除しながら、定期的に作業内容を保存していました。ある時点でコードをコンパイルしようとしたところ、奇妙なことが起こりました。エラーはジャンプして元のコード行を通過し、現在はさらに下にありました。 ANDとOR条件をかっこで分離しました。クリーンアップが完了すると、機能しました。図に移動してください。
次に、オペレーションショップに行って、メインフレームに新しいコンポーネントを最近インストールしたかどうかを尋ねることにしました。はい、私たちは最近コンパイラをアップグレードしました。うーん。
古いコンパイラは式に関係なく左から右に式を評価したことがわかりました。新しいバージョンのコンパイラは、式を左から右に評価しましたが、あいまいなコードであるため、ANDとORの明確な組み合わせを解決できませんでした。
これから学んだ教訓...常に、常に、常に、括弧を使用してAND条件を分離し、OR条件を互いに組み合わせて使用する場合)。
最後のセミセッターでいくつかの混乱する並行処理の問題を修正しなければなりませんでしたが、私にとって最も目立つバグは、宿題のためにPDP-11アセンブリで書いていたテキストベースのゲームにありました。これはConwayのGame of Lifeに基づいており、奇妙な理由により、グリッドの横にある情報の大部分が、本来あるべきではない情報で常に上書きされていました。ロジックもかなり単純だったので、非常に混乱しました。何度も繰り返して、すべてのロジックが正しいことを再発見しましたが、私は突然、何が問題であるかに気付きました。このこと: .
PDP-11では、数値の隣にあるこの小さなドットは、8ではなく10を基数にします。これは、グリッドに制限されるはずのループの境界となる数値の隣にあり、サイズは同じ数値で定義されていますが、基数は8。
このような小さな4ピクセルサイズの追加によって引き起こされたダメージの量が原因で、それは私にとってまだ目立ちます。それで結論は何ですか? PDP-11アセンブリでコーディングしないでください。
私はまだ私の最も難しいバグハント中です。それは時々そこにあり、時にはバグではありません。それが、私が翌日の午前6時10分にここにいる理由です。
背景:
ハンティング
殺害
事後分析
大学のプロジェクトでは、ファイルを共有する分散P2Pノードシステムを作成していました。これにより、お互いを検出するためのマルチキャスト、ノードの複数のリング、およびノードがクライアントに割り当てられるネームサーバーがサポートされました。
C++で記述された [〜#〜] poco [〜#〜] を使用すると、Nice IO、ソケット、スレッドのプログラミングが可能になります。
私たちを苛立たせ、多くの時間を失う原因となった2つのバグがありました。それは本当に論理的なものです。
ランダムに、コンピュータがリモートIPではなくローカルホストIPを共有していました。
これにより、クライアントは同じPC上のノードに接続するか、ノードが自分自身に接続します。
これをどのように特定しましたか?ネームサーバーの出力を改善すると、後でコンピューターを再起動したときに、与えるIPを決定するスクリプトが間違っていることがわかりました。ランダムに、eth0デバイスの代わりにloデバイスが最初にリストされました...本当に愚かです。これがeth0から要求されるようにハードコーディングされました。これはすべての大学のコンピューターで共有されているためです...
そして今、より迷惑なもの:
ランダムに、パケットフローはランダムに一時停止します。
次のクライアントが接続すると、続行されます...
これは非常にランダムに発生し、複数のコンピューターが関与しているため、この問題をデバッグするのが面倒になりました。大学のコンピューターでは、Wiresharkを実行できないため、問題が送信側と受信側のどちらにあるのかを推測できます。側。
コードに多くの出力があるので、コマンドの送信がうまくいくと仮定しました。
これにより、実際の問題はどこにあるのか疑問に思いました... POCOのポーリング方法が間違っているようで、代わりに着信ソケットで使用可能な文字を確認する必要があるようです。
これは、パケットが少ないプロトタイプではより簡単なテストとして機能するため、この問題は発生しないと想定しました。そのため、pollステートメントが機能していると想定するだけでしたが、そうではありませんでした。 :-(
教訓:
ネットワークデバイスの順序のような愚かな仮定をしないでください。
フレームワークは常にその仕事(実装またはドキュメント)を正しく行うとは限りません。
コードに十分な出力を提供します。許可されていない場合は、拡張された詳細をファイルに記録してください。
コードが単体テストされていない場合(難しすぎるため)、動作することを前提にしないでください。
これは非常に単純なバグで、どういうわけか私にとって悪夢になりました。
背景:私は自分のオペレーティングシステムの作成に取り組んでいました。デバッグは非常に困難です(トレースステートメントだけで十分ですが、それさえできない場合もあります)。
バグ:ユーザーモードで2つのスレッド切り替えを行う代わりに、一般保護違反が発生します。
バグハント:この問題を修正するために、おそらく1〜2週間を費やしました。どこにでもtraceステートメントを挿入します。生成されたアセンブリコードを調べる(GCCから)。私ができるすべての価値を印刷する。
問題:バグハントの早い段階で、hlt
命令をcrt0に配置しました。基本的にcrt0は、オペレーティングシステムで使用するユーザープログラムをブートストラップするものです。このhlt
命令は、ユーザーモードから実行されるとGPFを引き起こします。そこに置いて、基本的に忘れてしまいました。 (元々は、バッファオーバーフローまたはメモリ割り当てエラーの問題でした)
修正:hlt
命令を削除します:)削除後、すべてがスムーズに動作しました。
私が学んだこと:問題をデバッグしようとするとき、あなたが試みた修正を見失わないでください。最新の安定したソース管理バージョンに対して定期的に差分を作成し、他に何も機能しないときに最近変更した内容を確認します
背景:
ハンティング
殺害
事後分析
gdb
+モニタリング!ディスクを疑うのに時間がかかり、監視グラフでアクティビティのスパイクの原因を特定しただけです...工場が稼働している完全な本番環境以外では再現できなかったため、最も困難なものは殺されませんでした。
私が殺した最もクレイジーなもの:
図面が意味不明に印刷されています!
コードを見て、何も見えません。プリンタキューからジョブを引き出して調べたところ、問題はありません。 (これは、DOSG時代のHPGl/2が埋め込まれたPCL5です-実際には、図面をプロットするのに非常に優れており、限られたメモリでラスターイメージを構築するのに問題はありません)。 。
コードをロールバックします。問題はまだ残っています。
最後に、手動で簡単なファイルを作成し、それをプリンターに送信します。それはまったく私のバグではなく、プリンター自体であることがわかりました。保守会社は、何かを修正しているときに最新バージョンにフラッシュし、その最新バージョンにバグがありました。重要な機能を削除し、それを以前のバージョンにフラッシュバックする必要があることを理解させることは、バグ自体を見つけるよりも困難でした。
さらに厄介なものですが、私の箱にしかなかったので、最初に配置することはしませんでした。
Borland Pascal、サポートされていないAPIを処理するDPMIコード。それを実行し、時々それはうまくいきました、通常、それは無効なポインターを処理しようとしてブームに行きました。ただし、ポインタを踏みつけて期待するように、誤った結果が生成されることはありません。
デバッグ-私がコードをシングルステップ実行した場合、それは常に正しく機能しますが、そうでなければ以前と同じように不安定でした。検査は常に正しい値を示しました。
犯人:2つありました。
1)ボーランドのライブラリコードに大きなバグがありました:リアルモードポインターがプロテクトモードでポインター変数に格納されていました。問題は、プロテクトモードでほとんどのリアルモードポインターに無効なセグメントアドレスがあり、ポインターをコピーしようとすると、ポインターをレジスタペアにロードして保存したことです。
2)デバッガーは、シングルステップモードでのこのような無効なロードについては何も言わないでしょう。内部で何が行われたかはわかりませんが、ユーザーに提示された内容は完全に正しいように見えました。実際に命令を実行するのではなく、シミュレーションするのではないかと思います。