ブラックボックスデバッグ
はじめに
デバッグというとデバッガを使ったりprint文を挿入するのが一般的です。しかし、現実にはそういった手法を取れない環境でデバッグする必要があることもあります。
例えば私の仕事はLSIの設計ですが、製造されたLSIの動作中に内部を見ることは当然できません。もし何らかの不具合が発生した場合、内部を観測することなくデバッグする必要があります。
こういったデバッグ手法をここではブラックボックステストにならって「ブラックボックスデバッグ」と呼ぶことにします。ブラックボックスデバッグはLSI固有の技法ではありません。例えばソフトウェアでもデバッガのアタッチやprint文の挿入で状態が変わってバグが再現しなくなることはあります。また大規模なネットワークインフラのデバッグでは対象が大きすぎて、実質的に詳細を観測できないこともあるかもしれません。
このようなブラックボックスデバッグは(おそらくドメイン固有の知識を強く要求される関係もあって)特定の部署で伝統芸的に伝承されたり、個々人が長年の経験から編み出したパターンが多いような気がします。
とはいえ、話を一般化して伝えられる部分もあると思うので、ブラックボックスデバッグの方法について私の考えをまとめてみました。
ブラックボックスデバッグの方法
ブラックボックスデバッグでは、対象に入力を与え、その出力が期待値と異なる原因をその入出力ペアだけから推定します。
以下では、私の思う典型的なデバッグの流れを説明します。ただ、実際にはこれらを何度も繰り返しながらデバッグ範囲を絞っていく感じになります。
情報を集める
「ある一組の入出力ペアでバグを発見した」という情報でわかることは非常に少ないので、まずは情報を増やす必要があります。
例えば、その入出力ペアに再現性があるかどうかは非常に重要です。何度繰り返してもその入力だけで発生するのか、それとも任意の入力に対して確率的に発生するのかでは大きく違います。
また、我々コンピュータエンジニアはどうしてもデジタル的な入力値に注目しがちですが、アナログ的な入力も振る必要があります。
LSIの場合に典型的なのは電源電圧や動作周波数ですが、ソフトウェアであっても、入力を与える速度や間隔、時刻などが関係する場合もあります。
パターンを見いだす
次に収集した情報を眺めて何らかの法則性があるかどうかを考えます。例えば特定の入力値でのみ発生するならかなり分かりやすいですが、確率的に発生する場合はたくさんのデータを取って発生確率の違いを見出したりする場合もあります。
時系列的なパターンも考えられます。例えば、同じ入力を与え続けた時に定期的に発生する場合や、ある特定の入力列を連続的に与えたときにだけ発生するような場合です。
仮説を立てる
パターンを見出したら、それを説明できる仮説を立てます。つまり「もし内部でこのような現象が発生していれば、このような入出力ペアになりうる」というような仮説です。
ここは対象やその周辺知識を最も必要とされる部分です。LSIであれば半導体や基板伝送路のアナログ的な挙動、ソフトウェアならOS、コンパイラ、プロセッサやネットワークの挙動など、知識や経験があるほど多くの仮説が立てられると思います。
このとき仮説はシンプルなものから検討する方が良いと思います。対象の品質がある程度確保されていることを仮定するなら、実際のバグの数は少ないはずです。なので例え入出力ペアのパターンが複雑な現象を示していたとしても、複数のバグが絡み合っているより、単一のバグが回り回って複雑な現象を引き起こしている可能性のほうが高いと考えられます。
仮説を検証する
立てた仮説は仮説でしかないので検証する必要があります。具体的にはその仮説からバグの発生するであろう入力を生成し、意図したとおりにバグを発生させられるかどうかを確認します。
単にひとつのパターンでバグの再現に成功しただけでは、別の原因でたまたまその結果になった可能性を否定できないので、いろいろなパターンを試してその仮説を補強していきます。
結局のところ、内部を直接観測できない以上、100%の確定はさせられないのですが、検証によって確度を上げていくことは可能です。
補足: 情報の確度
これは特にブラックボックスデバッグの手法というわけでもありませんが、関連する話題として触れておきます。
ブラックボックスデバッグでは出力しか観測できないですが、その観測値は本当に信頼できるのか、という問題があります。
LSIなどでは特に電源起因の問題で入出力が信頼できないことがありますが、ソフトウェアにおいても低レイヤ・組み込みではprint文の出力が信頼できないような場合もあるでしょう。
こちらも仮説検証の話と同じで100%確実というのは難しいですが、例えば複数の出力系統の結果を比較したり、バグが発生していない正常状態での出力が妥当であることをもって確度を上げていくことが可能です。
もちろんこれはバグ発生時のみ同時に出力にも異常が発生する可能性を否定はしませんし、どうしても原因不明なときにそこまで疑う必要が出てくるケースもあります。
ただそうは言っても観測できる値が信頼できないとどうしようもないので、最初にある程度確度を上げたらそれ以降は信頼できるものとして扱うしかない、というのが現実的なところだと思います。
具体例
一つ具体的な例を挙げて説明します。あるテレビ向けの映像信号処理LSIをデバッグしていて以下のような現象に遭遇したとします。
LSIからの出力映像をテレビに映してみていたところ、ときどき画面表示がおかしくなる
おそらく一般には知られていませんが、このようなとき映像信号処理LSIを作っているエンジニアは「画面表示がおかしくなる」の発生間隔をストップウォッチで計測したりします。
(こういったものはまさに特定部署に伝わる伝統技法ですね…)
測定してみると、発生間隔が463.2秒ごとであったとします。1回の測定で1秒以下まで精度よく測定できないので何度も測定して平均を取るなど精度を上げることも必要です。
今、LSIに供給しているクロックの周波数は 148.35MHz (これは映像信号でよく出てくる数値です)なので、463.2秒をクロック数に換算すると
463.2 * 148.35 * 10^6 = 68715720000
となります。ところで 2^36 = 68719476736 ですね。つまりおよそ 2^36 クロックごとに問題が起きているということになります。
LSI内にはソフトウェアにおけるループ変数のように、今何クロック目かを数えているカウンタがいることがありますが、36bitのカウンタがオーバーフローして0に戻る周期はまさに 2^36 クロックです。ということはLSI全体から36bitのカウンタがある場所を探せば、バグの原因に近づけるかもしれません。
もちろん実際にはこんなにすんなりと絞り込めるような簡単なバグが残っていることはまれで、こういったデバッグを1週間~数カ月続けてようやくバグの原因にたどり着ける(場合もある)、といった感じです。
おわりに
私の思うブラックボックスデバッグについて書いてみました。ここでは特に対象が完全なブラックボックスである場合を想定しましたが、実際には普通にソフトウェアデバッグにも流用できる部分があると思います。
例えばprintデバッグするとしても、全ソースコードにランダムにprint文を挿入したりはせず、ある程度あたりを付けるはずです。この「あたりを付ける」部分はまさに上で説明した仮説を立てる部分に相当すると思います。
LSI設計のようにブラックボックスデバッグが当たり前の環境では半ば強制的に訓練させられますが、普通のデバッグにおいてもこういったことを意識しておくとデバッグの効率が上がったりするかもしれません。
Discussion