2007-05-03-01-ハイゼンバグの思い出話 - project-enigma

2007-05-03-01-ハイゼンバグの思い出話

>> Site top >> weblog >> 月別アーカイブ >> 2007年05月のlog >> 2007-05-03-01-ハイゼンバグの思い出話

最終更新日付:2014/01/02 00:00:00


ハイゼンバグの思い出話

2007 年 05 月 03 日

陰郎は、本格的なプログラミングを C 言語から始めた人間なので、アセンブラのことは良く知らない。しかし、長年この業界で仕事をしていると高級言語の水準では説明のつかない事態にでくわすことがある。以下、長い上に後味の悪い、そしていささか怪談じみた内容ではあるが、陰郎が経験した話を紹介しよう。

かれこれ2年近く前の話であるが、当時面倒を見ていた UNIX システムで原因不明のバグが問題になっていた。業務アプリケーションがクラッシュするのだが、とにかく再現性が低い。24時間365日稼働のシステムで夜間に動作するバッチジョブであり、発生すれば呼び出しを受ける。しかし、発生条件もはっきりせず頻度も低いため、コストや他の条件から『無事を祈る』ことしかできなかった。しかもこのバグ、デバッグバージョンのビルドでは一切発生しないというのが悩みのが種だった。このように再現条件がはっきりせず、追跡のために環境を変えると姿を消すようなバグはハイゼンバグ ( heisenbug : 量子物理学の Heisenburg の不確定性原理から。「ハッカーズ大辞典」参照)と呼ばれる。一般的にハイゼンバグはメモリ管理の脆弱性などが原因であることが多い。

そんな状況で陰郎に調査が命じられたわけだ。さて、なにができるか。最初にやったのは、発生時に作成されたコアファイルをデバッガで調査することだった。コアファイルというのは UNIX システムでアプリケーションがクラッシュした時に作成されるファイルで、アプリケーションのメモリ空間の状態がダンプされている。これをデバッガで開くことでクラッシュした時点の状況が再現できる。

コアファイルをデバッガで開くと、ソースコードの具体的な位置やそこに至るまでの道筋、そして各種の変数に格納されている値を知ることができる...と言いたいところだが、それはアプリケーションがデバッグオプション付きでビルドされている場合だけだ。通常、リリース向けのビルドではデバッグオプションを外し、最適化オプションを指定している。だからソースコードに現れるような名前は登場せず、全てアドレスで表示される。当然このままではどこで落ちているのかわからない。正直どうしていいのかわからなかったが、詰まるところソースコード上のそれぞれの関数がどのアドレスにマップされたかがわかればいい。結局、コンパイルオプションを変更して関数アドレスのマッピング情報をファイルに出力し、それと照らし合せることで呼び出されている関数を特定することができた。

しかし、呼び出し関数の追跡は最後のところで行き詰まってしまった。関数アドレスがマップファイル上に見当たらないのだ。ということは、これは実行時にリンクされる外部ライブラリの関数...例えば標準ライブラリの関数、ということになる。しかし、それをどのようにして特定すれば良いのだろう? ...デバッガでコアファイルを開いて得られる関数の呼び出し経路情報には、呼出元のコードのアドレスも含まれている。ということは、最後の関数を呼び出した位置だけはわかるわけだ。ならばということでコンパイルオプションを更に変え、今度はアセンブルリストを出す。これと突き合わせればソースコードの位置がわかる。

...突き合わせの結果、やはり動的リンクしている標準ライブラリ関数の内部でオチていることがわかった。これで再現性が低いということは、普通ならばやはりメモリ管理関連のバグが疑われるところだが、残念ながらそうでもない。メモリ管理とは無関係なコードだったのだ。全く同じ条件でも正常に動作する場合もあればコアを吐くこともある。再現性を確認するためのテストプログラムを作成し、同じ条件で1時間に数千回も実行を繰り返す耐久試験をしてみたが、発生率は1%に満たない。しかし、0%ではないのだ。

この時点で上に状況を報告し、「どうしても無理ならいい」と言われたものの、個人的に面白くない。ソースレベルで究明できない原因なんて本当はあっちゃいけないはずだ。C 言語は他の「高級」言語やスクリプト言語とは違うはずだ。そうだろう? ...というこだわりがあったわけでは必ずしもないものの、陰郎も意地になっていたというのが実際のところである。

で、最終的にどうなったか。これがなんと直せた(というか直った)のだ。しかし、どう考えても不本意な内容だった。とはいえ、事象の理不尽さを考えるならば致し方なかったかもしれない。陰郎は、問題を起こしているコードブロックの周辺を眺めた。大まかに、以下のようになっていた。

if( foo ) {
    /* ・・・ */
    /* 問題を起こす部分 */
    /* ・・・ */
}
 
if( bar ) {
    /* ・・・ */
}

ここで、foo と bar の2つの条件が決して同時に成立しないことに気付いた。つまり、同時に通過しない以上、これら2つのブロックの順序を入れ替えてもまったく問題なかったのだ。そして、それをやってみたところ、バグは発生しなくなった...完全に。

陰郎はこのとき、正直なところ非常に悩んだ。先の耐久試験をどれだけやっても再現しない。それはいいのだが、まったくもって説明に苦しむのだ。どうして2つのブロックを入れ替えたら直るのか、そもそもなぜそんなことを試そうと思ったのか? ...自分でもわからない。しかし、実際に(微々たる発生率とはいえ)確実に再現させられる試験で再現しなくなっている。そして、2つのブロックを入れ替えても実処理に影響がないことは論証できる。しかし、バグの原因と、直った理由については何も言えないのだ。これでは直った(あるいは直した)とは言えない。より再現率が低くなっているだけかもしれないのだから...

陰郎の良心はほぼ完全に負けを認めていたが、同時に上に全てを委ねようという気にもなっていた。有体に説明し、判断を求めた。結果としては、その修正を受け入れ、稼動させてみようということになった...それ以来、ほぼ2年間にわたって実稼動システムではこのバグは発生していない。顧客は喜んでくれた。深夜の不意の呼び出しに悩まされていた担当SEからも感謝された。敗北に打ちのめされていたのは陰郎一人だった。今でもこの案件の話が出るたび、「なぜあんなことを思いついたのか」という話になる。格好をつければ「直感が働いた」とでも言うのだろうが、実際のところは「他に何も思いつかなかったから」でしかないし、それで直るとも思っていなかった。今でも陰郎にはわからない。

しかし、ひとつだけ実感として学べたことがある。それは、コンピュータはソースコードのことなど何も知らないということだ。アセンブラやマシン語を肌で知らない開発者は、自分の書いたソースコードがコンピュータに指示を与えていると思いがちだ(自分もその1人だ)。しかし、そこにはコンパイラが介在しており、コンピュータはコンパイラが作ったオブジェクトコードしか見ていない。本当に機械語のレベルで考えたり追跡したりできる開発者なら、この案件の原因や対処についてきちっと論証できたかもしれない。しかし、陰郎には最後までできなかった。ただ必死でマップファイルやアセンブルリストで「位置」を追っていただけだ。最後までコンピュータの目線まで降りることはできなかった。

...以上が、2年前に陰郎が経験した案件の顛末である。これは陰郎が覚えている限り、この10年間の仕事で「解決」できなかった唯一のバグであり、こてんぱんに敗れた上に「結果オーライ」という皮肉な結末が添えられた案件だ。陰郎はこの件をずっと忘れないだろう。

 

コメント

鶴丸 - 05/04/2007 02:01:19 AM

(*´θ`)すごいですね。この話

hiro1999 - 05/04/2007 01:40:21 PM

陰朗さんは本当に大変だったのだと思いますが,

僕もわくわくしながら読んでしまいました ^^;

陰郎 - 05/07/2007 05:58:21 AM

コメントありがとうございます。

Palm 界でも、天上人と見なされている方々はほぼ例外なく機械語のレベルまで降りていけるようです。やはりハッキングの世界まで行けばそれが必須なんでしょうね。そのへんは陰郎にとっては明らかに専門外です。陰郎にできるのは DA と ARMlet までですね。

いなあも - 05/07/2007 12:43:51 PM

うんうん。

プログラミング技術ってのはすぐに覚えられるものなんだけど、いくつもこのような経験をする事で、センスが磨かれていくんだよね。

Cは他の言語と比べて確かに小回りは利くし便利なんだけれど、やはり実行コードとの間には薄いベールがあるのも事実。原因を突き止めるよりも「やばそうな所」を嗅ぎ分けるための経験を積んでいくほうが大事かと。

kagelow - 05/09/2007 12:30:21 PM

> いなあもさん

そうですよね。

知識だけじゃダメだし、経験とセンスもちゃんと培っていかないとですよね。

現実的に考えて、C/C++ が効率と切れ味でベストチョイスだよな...と思いつつもこのエントリのような経験をするとうぅむ、と考え込んでしまいます。

このページにコメントする

 

このページのタグ

Page tag : 開発

 

 


Copyright(C) 2005-2017 project-enigma.
Generated by CL-PREFAB.