FacebookのエンジニアであるSlobodan Predolac,Nicolas Spielberg両氏は先日,"モバイルアプリ長期的なデバッグ問題を解決し,クラッシュ率を50%以上低減した"方法について解説した。その中で氏らは,大規模で急速に進化するコードベースを支援することが可能な,汎用性のある技術といくつかのFacebookのツールを紹介している。
バグの所在が明らかになったのは,PredolacとSpielbergの再集計時,Core Dataのクラッシュとしてだった。氏らは最初のステップとして,Facebook自身のクラッシュレポートのデータの照会と集計するツールであるHipalとScubaを使用した。分析の結果,"6つ程の異なる徴候を示す"Core Dataのエラーが確認された。
問題の原因特定を複雑にしたのは,Facebookのソフトウェア開発方法にあった。同社のリリースサイクルは1ヶ月で,"リリース毎に数百人の開発者がコミット"していた。それについて氏らは,"タイムフレームを限定することは可能でしたが,数千のコミットから絞り込むことができませんでした"と書いている。さらに,リリース毎に実施されるA / Bテストが,"変更がコードとコンフィギュレーションのどちらに関係したものなのか"の特定を難しくしていた。
最後の手段として氏らは,別の仮説を作る作業を始めた。そして,それらの多くを除外した後に,Core Dataに問題の根本原因があるという考えの検討に取り掛かったのだ。結果として氏らは,"[自分たちの]仮説をテストするために,Core DataからSQLiteに簡単に切り替え可能だったコードが影響を受けていた"ことの特定にたどり着いた。
その後しばらくして氏らは,"不正なスレッドあるいはプロセスによって"上書きされた,いくつかのファイルを示したクラッシュレポートを受け取った。それは正しい方向への一歩だったが,"大規模なコードベース"の中から不正なスレッドあるいはプロセスを特定するのは,簡単な作業ではないように思われた。そこで氏らが選択した次のアプローチは,SQLiteファイルを開く前におとりのファイルを開いておいて,そのファイルに書き込みを行うスレッドを捕らえられるようにしておいた上で,破損したファイルを調査する,という方法だった。この方法で氏らは,すべてのアタッチメントに共通するプレフィックス17 03 03 00 28
を発見した。そこで次に示すコマンドをlldb
で使用してブレークポイントを設定し,POSIXのwrite()
コマンドにこのコンテントを送ろうとしているのが誰かを特定することにした。
breakpoint set -n write -c "(*(char**) ($esp + 8))[0]==0x17 && (*(char**) ($esp + 8))[1]==0x03 && (*(char**) ($esp + 8))[2]==0x03 && (*(char**) ($esp + 8))[3]==0x00 && (*(char**) ($esp + 8))[4]==0x28"
同社のSPDYネットワークスタックが犯人らしいということがすぐに判明したので,この仮説を確かめるため,それをオフにすることにした。そのために,Facebookで開発されたオープンソースツールであるFishhookを使って,wtite
システムコールを再バインドした。
// ハニーポットファイルの設定 int trap_fd = open(…); // ハニーポットへの書き込みを検出する関数の新規作成 static WRITE_FUNC_T original_write = dlsym(RTLD_DEFAULT, "write"); ssize_t corruption_write(int fd, const void *buf, size_t size) { FBFatal(fd != trap_fd, @"Writing to the honeypot file"); return original_write(fd, buf, size); } // システムのwriteを”チェック付きバージョン”で置き換え rebind_symbols((struct rebinding[1]){{(char *)"write", (void *)corruption_write}}, 1);
翌日,氏らの手元には,SSLレイヤがクローズ済みのソケットに書き込みを行って,それがデータベースファイルに再割り当てされていたという事実を示す,新たなクラッシュレポートがあった。
クラッシュの原因が分かってしまえば,その修正は数時間で終わる作業だった。