BugStories

Shiro(2011/09/16 02:28:53 PDT):ハードなバグに関する話って読んでてとてもおもしろいのだけれど、 いざ事例を探そうとすると思うように探せないので、何となくまとめておこう。 好きなように足してください。自分で書いてリンクするのも歓迎。 (2017/05/06 17:40:34 UTC: 新しい記事を前に足してゆくように変更)


How we found and fixed a rare race condition in our session handling

githubで起きた、セッションIDが別ユーザのものに入れ替わってしまうバグ。 シングルスレッドで使われることが想定されていたコンポーネントと、 新たに追加したスレッドとが予想外の干渉。

種明かしを読むと、そう特殊でもないというかいかにもうっかりやっちゃいそうな話だけど、 レポートは良く書かれてるし、追っていった経緯は参考になる。ログ大事。

Finding a CPU Design Bug in the Xbox 360

ハードウェアバグ。 抽象化が漏れる事例として非常に興味深い。

ゲームで性能を出すために、L2キャッシュをバイパスするプリフェッチ命令を追加した。 しかしL2キャッシュにはメモリのコヒーレンシを保つ機能もあり、 バイパスすると一貫性が保証できなくなる。 実際、うっかり他のプロセッサが書き込みを行うアドレスと同じキャッシュラインに乗るデータを プリフェッチしてしまうと、その部分がCPU間で食い違いクラッシュした。

ところが、そのプリフェッチ命令を使うコードをスキップするようにランタイムフラグを変えても、 同様のクラッシュが起きる。メモリダンプをいくら眺めても原因がわからない。

犯人は分岐予測による投機実行だった。分岐予測ミスで 本来実行されないはずのプリフェッチ命令が実行されてしまうことがある。 実行がキャンセルされても、キャッシュの状態は巻き戻されないので、 そのインストラクションがたとえ実行されないパスであってもコード中に存在するだけで、 効果を発揮してしまっていたのだ。

投機実行の正当性の前提は、投機実行されるコードが副作用を持たないことだ。 しかしこの「副作用」の範囲はどこまでか。 通常考えるのはCPUのISAレベルで扱う、レジスタやメモリの状態だ。 キャッシュはその状態に対して透過的に動作するはずで、論理的な動作モデルとは分離されているはずだった。 その抽象化の壁が壊れる事例のひとつ。

オーバーフローが引き起こした面白いバグの話

本人の体験ではないけれど、おもしろいので。オーバーフローに起因するバグ2題。 初代CivilizationのNuclear Gandhiと、Ariane 5ロケット。

The Horror in the Standard Library

メモリリークを追いかける話。新しいバージョンのサーバをデプロイしたら数百MBしか 消費しないはずのプロセスがGB単位でがんがんメモリを増やしてこける。

最適化ってのはその時点での制約条件に対する特殊化でもあるんで、 条件が変わると逆にお荷物になり得るんだよな。でもそれを見直すのは難しい。 広く使われて概ね動いている実績があるほど、動いてるコードには触るな、になりがちだし。

それはそれとして原因を追ってく過程が詳細でおもしろかった。こっちも行き止まり、 あっちも行き止まり、途方に暮れた時にふと壁の隙間から光が漏れてるのに気づく、って展開、 よくあるよね。

A tale of an impossible bug: big.LITTLE and caching

特定のアーキテクチャでMonoを使ってるソフトがランダムにSIGILLで落ちる。 でもデバッガで見ても該当アドレスのインストラクションは正しい。 インストラクションキャッシュのフラッシュのコードを疑うが、 調べてみても変なことはやっていない。何故?

非対称マルチコアプロセッサでは、ランタイムにプロセッサ特性を問い合わせても それが実行されるコアによって帰ってくるパラメータが違う、というのが犯人なんだけど、 この可能性を思いつくこと自体が難しいだろうなあ。

バグの症状からパターンを見つけてそれを解決につなげる過程が見事。

This is strictly a violation of the TCP specification

localhostへのtcp接続がたまにtimeoutする。サーバのrecv queueは空なので サーバが取りこぼしてるわけではない。lo経由だからどこかでパケットが落ちてる わけでもない。なのにSYNパケットの返事が来ない。何故?

原因を突き止める経緯が参考になる。種明かしをすると、サーバがソケットの後処理を 忘れてて、クライアントがいなくなった後でもソケットがCLOSE_WAITのままになっていた。 そこで新たなクライアントがたまたま同じポート番号を使って接続要求を出すと、 サーバのカーネルは以前のクライアントから場違いなSYNパケットが送られてきたと思って 無視してた、というわけ。

How we found that the Linux nios2 memset() implementation had a bug!

めっちゃ基本的な関数にもバグが残ってることがある。この場合、特殊プラットフォームのみ 使われるインラインアセンブラのバグだけれど、gccのレジスタ指定をミスってて まだ使うレジスタが上書きされてたというもの このミスは自分も経験あるけど、ソースだけをいくら眺めてもわからないんだよね。

The curious case of the disappearing Polish S

medium.comのオンラインエディタで、ポーランド語のŚだけが何故か入力できない、というバグ。

技術的に複雑な話ではなく、むしろ原因は歴史的あるいは文化的なものだ。

バグは技術と文化の界面に生じるものなのかもしれない。 コードが変わらなくても、コードを取り巻く環境が変わればかつての仕様は新たなバグになり得る。

Corrupted initramfs during boot

組み込みLinuxデバイスが時々死ぬ問題を追いかける話。現象にも結論にも派手さはないけれど、 追いかける過程が丁寧に書かれているのが参考になる。

Cryptic genetic variation in software: hunting a buffered 41 year old bug

ガウス分布を生成するコードが非常に稀にクラッシュするというバグレポートを受け取った。 しかしその部分のコードは世界中で使われているRANLIBから持ってきたもの。 稀とはいえこれまで問題になっていないのは何故?

調べてみると、サンプリング部分のコードは共通だけど、乱数発生器だけ独自のものに 差し替えていた。乱数発生器は0から1の間に一様分布する実数を生成するものだが、 これが正確な0を返した場合にクラッシュする、という問題がRANLIBの元のコードにあることを発見。 しかし、そもそも閉区間の発生器を使ったのは、RANLIBのコードの元になった1973年の 論文でそう書いてあるからだ。

更なる調査によって判明した事実: 元論文に閉区間と開区間を取り違えるバグがあった。しかしその実装でたまたま他のみんなが(論文に反して)開区間の発生器を使っていたため、そのバグは41年間表面化しなかった

あるバグが他のバグによって隠されるということは時々あるが、 これだけ使い込まれたコードにも潜んでいることがあるというのは興味深い。著者はこれを 隠された遺伝的変異に喩えている。

Packets of Death

LinuxでSIPアプライアンスを作っている人。 顧客のところで「インタフェースが死んで、物理的に電源をon/offしないと回復しない」という トラブルが散発。手元ではどうしても再現しない。壊れたアプライアンスを送ってもらっても、 手元で火を入れてテストするとちゃんと動く。

結局NICのファームウェアのバグとわかるんだけれど、 まず問題を手元で再現する手がかりを見つけるまで数ヶ月。 そこからさらに絞り込んで、最終的な再現条件を見つけるまでがまた大変。 デバッグに銀色の弾丸なし、地道な調査あるのみだなあ。

The little ssh that (sometimes) couldn't

ロンドンからモントリオールへのssh転送が時々失敗する。 パケットダンプから原因を追いかけ、失敗しているノードの特定まで。

見どころは、インターネット上の自分では触れないノードのどこかの機器が失敗している という状況で、自分から触れる道具を駆使して原因を追い詰めてゆくところ。

向井さんのG+ポストより。

valgrindの思い出

valgrindがリークをレポートした。でもデストラクタで開放してるのに…コードを良く見たらデストラクタに至る前にSIGTERMで終了してた。落着? いや、でも他にもデストラクタが呼ばれてないデータはたくさんあるのにvalgrindはレポートしてないよ?

手がかりが見つかったと思ったら違ってて、また新たな手がかりが見つかって…と二転三転するミステリのようなバグ譚。

コンパイラのバグ

なぜか無関係なパッチでMacのビルドだけが壊れる。はじめはMacのビルド環境がflakyなだけかと疑ったが……。

Chromium Notes: Tracking down a mysterious Windows crash

(高林さんのポスト経由)。 Windows上での謎のクラッシュを追い詰める。デバッガから取れる情報はあまり役に立たず、 上のOSXのケースと違ってOSの中身もいじれない、という状況で、 instrumentationを仕込んで原因を絞って行く話。

Debugging Mach Ports

(鵜飼さんのGoogle+のポスト経由)。 OSXのChromeのリソースリーク (MachカーネルがIPCに使うポートを食いつぶす) を追い詰める。 ストーリーは淡々と語られてるけど、技術的なディテイルが詳しく語られてて参考になる。 既存のカーネルインタフェースでは犯人をつきとめるのに必要な情報が得られないので、 即席のカーネル拡張を書いてしまうあたり、著者の並ならぬ体力を感じさせてくれる話である。

バグから学ぶ計算機科学 Scalaのハッシュテーブルにおいて並列コレクションのためのコード変更が大量の衝突を引き起こした事例

効率化のためのちょっとした変更で、テーブル拡張の度に頻繁に衝突が起きるようになり 大幅に遅くなったという話。局所的だと思ってやった変更が思わぬ干渉を起こすって 経験はあるなあ。機能テストにはひっかからないのが厄介。

Beeping Robots and Bug Stories

ハードウェアのバグ。バグ自体は特異なものじゃないけれど、printfさえ使えない状況で どうやってバグを絞り込んだかが面白い。via Radium Software

In 2005 at my job we had a pretty severe problem just as unexplainable....

データベースサーバが謎のトラブル。しかし、 隣の倉庫に配送車が到着したときだけ、魔法のようにサーバが正常に戻るのだ…

The case of the 500-mile email

物理的に500マイル以上離れたサイトにメールが届かない、というユーザからのクレーム。 emailの配送に物理的距離は関係ないだろう、何かの間違いでは、と思ったが事実だった。

g新部、バグを追う旅 2010 (FSIJ月例会)

Kernel, gcc, glibcが複雑に絡み合ったバグの追跡。

Island life - 因果律を否定するバグ

テストを足したら、別のテストがメモリを食いまくって落ちる。 だが、落ちる時点では、足したテストはまだ実行されていないのだ。

教訓: 隠された状態に注意せよ

Just fixed 20-year-old bug

I just fixed a 20-year old bug… that I wrote, and is in daily use by a few million people…

JavaのHotSpotコンパイラで、非常に稀に起きるクラッシュの原因を探ると、 レジスタアロケーションで生存解析がおかしいことがわかり、 20年前に自分で書いたハッシュテーブルのコードに行き当たった。 一見何の変哲もないごく普通のコード。しかも20年の間、ヘヴィに使われてきたコードのどこに問題が?

教訓: 枯れたコードでも、暗黙の前提が変わればバグは出る。

One App's Poison

ソースの無いアプリケーションがハングする。デバッガで見てみるとmain threadが 無くなってる!? 調査のために、main threadから呼ばれるライブラリ関数に わざとクラッシュするコードを仕込んでみた。 そうしたらアプリが動くようになった! ハングもクラッシュもせずに。どういうこと?

教訓: 安全ネットは時として真の問題を隠す

Joost's Dev Blog: The lamest bug we ever encountered

プレステ3でデバッグ中に原因不明の一時的フリーズが起きる。非常にレアで、 チーム総出で連続デバッグプレイしている時しか起きない。 だが、起きるときは複数のマシンで同時に起きる。通信の問題か? ログを仕込んでみるが、フリーズ時には通信は行われていない。 フリーズ時のメモリ状態を解析するも、フリーズ箇所はばらばら。 まるでプレステ同士がテレパシーで通信してるかのようなこのフリーズの原因は?

教訓は、つながってるもの全てを疑え、かな?

More ...