知人と与太話をしているときに、「自分が経験した、最も恐ろしいバグ」の話になった。
僕がこういうときにする話は決まっている。
プログラムを知らなくても、技術話がわかる人には鉄板でウケる。
生々しい話でもあるし、企業秘密でもある。
ゆえにネット上で公開などはしていなかったのだが、もう時効だと思うので(20年も前の話だ)ちょっと書いてみよう。
自分が経験した、と言っても、自分がいた部署の、別チームのプログラムの話。
だから、僕は直接的にはかかわっていない。
まず、「一番恐ろしいバグ」というのは、完成間近のプログラムを一晩エージングテスト(動かし続けて問題が出ないか調べる)しておいて、朝来たら止まっている、というものだ。
画面がブラックアウトしていたら完璧。
どこで、どんな原因で止まったかわからない。
しかも、状況確認の仕組みを備えてもう1度エージングをしたら、…今度はいつまでたっても止まらない。
確実に止まる、ということがわかっているが、その条件がわからず、再現できないのだ。
大抵、エージングテストと言うのは完成間近にならないとやらない。
完成間近なのに、原因不明で、致命的な状況が生じていることだけはわかる。
そして、原因究明がいったいいつになるのかわからない。タイムスケジュールが見通せない。
さぁ、行軍ラッパの音は高らかに鳴った。デスマーチの始まりだ。
しかし、これより恐ろしいバグ、と言うのが存在したのだ。
さんざんエージングも行って、停止しないことが確認された。
少なくともそのつもりだった。
開発部隊の手を離れ、品質保証部隊がチェックする。
ここでも、エージングテストや、想像もつかないような操作でバグが出ないかがチェックされる。
それらの多くの関門を潜り抜け、出荷されたと言うのに…市場に出回ってから「止まりました」報告が次々あがってくる。
これは、悪夢以外の何者でもない。
すでに開発機材がつなげられていない機械なので、何が起きているかは、本当に、まったくわからない。
この状況に比べれば、「朝来たらブラックアウト」のほうが、山のように情報があった感じがする。
使用者に平謝り、当面はリセットで凌いでもらうことにして、すぐに原因を究明、修正バージョンを作る、という作業になる。
まずは停止条件の再現をしたいのだが、この作業を「ノーヒント」で行わなくてはならない。
エージングなら散々したし、思いつく限りの過酷な操作もやったはずなのに、これ以上どんな条件があるのか?
とりあえず、最初の行動は頭を抱え込むことだ。
そして、これから記すのが、自分の経験で最大のバグ。
使用者に平謝りして、リセットを試みてもらうも…再起動しない!
「当面しのぐ」ことができなくなる。いったんは全商品を回収して、返金に応じなくてはならない。
数千万の金を掛けて開発しているのに、評判はがた落ち、修正して再発売しても、買ってもらえるかわからない。
プログラムは ROM に焼かれているし、いったい「再起動しない」っていうのはどういう状況なんだ?
上司から、原因の徹底究明が言い渡される。
まず、原因は割込みルーチン内にあった。
とあるスイッチが何回押されたか、と言うカウントは、「絶対に」取りこぼしてはいけないことになっている。
そこで、このスイッチは割込み信号を起こすようになっており、カウントは割込みルーチン内で行われる。
割込みルーチン、というのがどういうものか、プログラマでない人にもわかるように解説する必要があるだろう。
通常、サブルーチン(プログラムのひとまとまり)と言うのはプログラムの中から呼び出される。
しかし、外部からの操作に対して、現在実行中のプログラムがどのような状況であっても、必ず処理しないといけないような場合がある。
このときに、「割込み」が発生し、実行中だったプログラムは「ひとまず」停止、サブルーチンが呼び出される。
割込みルーチン内では、実行中だったプログラムが、あとで問題なく作業を続けられるように、「割込み時の状態」を保存することからはじめる。
割込みルーチン内で「破壊」してしまうレジスタ(変数みたいなもの)などを保存し、作業終了後に元に戻してから「割込み終了」するのだ。
この作法を守らないと、プログラムはおかしな動作をすることになる。
このとき使用していた CPU は NEC の V60 だった。
この CPU は非常に…信じられないほどの高機能で、「指定アドレスから始まる、指定された終端文字をもった文字列が、別に指定されたアドレスから、指定されたサイズまでのメモリ内の、どこに存在しているか検索する」などの動作が、1命令で完了した。
#と思う。少なくとも文字列操作命令はあったが、記憶によるものだから、少し違うかも。
さて、V60 の文字列操作命令、非常に高機能だが、「暗黙のうちに、2つのレジスタを破壊する」という問題があった。文字列だから、ソース側とディスとネーション側で、2つのポインタが必要になるためだ。
CPU インストラクションには書いてあったが、問題となった割込みルーチンでは、割り込み冒頭の「状態保存」で、暗黙に破壊されるレジスタを保存していなかった。
にもかかわらず、割込み内で文字列操作命令を使用した。
そのため、割込み終了後にプログラムの動作がおかしくなり、「暴走」したのだった。
暴走したのはよいとして、なぜリセットまで効かなくなったのか?
このプログラムを動作させていた基盤には、プログラムのデッドコピーを防ぐ仕組みがあった。
この基盤では、プログラムの ROM は、CPU から直接呼び出されるようにはなっていない。
間に、スクランブル回路が入っているのだ。
スクランブル回路は、入ってきたデータに対して一定のビット演算を施し、出力する。
この回路が、データバスに挟まっていた。
スクランブル回路の中には、小さな PROM が入っていた。
PROM とは、一度だけ書き込める ROM だ。ここにキーが書き込まれていて、ビット演算の内容を制御している。
つまり、基盤から ROM を抜き取ってコピーしても、スクランブルが掛かっているので他の基盤で動作させることができない。
スクランブル回路は、一般には市販されないカスタム IC だった上、プログラムごとにキーが変更されるので、動作が推定できない。
しかも、ここからがすごい。
スクランブル回路だけ取り出されて、散々実験されて動作を推定されるのを避けるために、スクランブル回路には「正しい手順でアクセスしないと自爆する」仕組みが付けられていた。
#スクランブルは、状況によって解除したりもできた。
(ROM からのデータ読み込みでスクランブルが解除されるのはよいが、メモリマップド I/O の読み込みでビット演算されてしまうのは余計なお世話)
そのため、スクランブル回路自体にアクセスして、動作を変更できた。
自爆、といっても本当に爆発するわけではない。PROM の内容を破壊してしまうのだ。
こうなると、正しいキーが失われるので、外部からの動作推定は不可能になる。
まとめると、つまりはこういうことなのだ。
V60 の命令インストラクションには癖があり、「暗黙の」レジスタ破壊が起こることがある。
しかし、それに気づかなかったプログラマが、割込みルーチン内で破壊されるレジスタを保存しなかった。
該当レジスタは「たまたま」あまり使用されておらず、割込み時の破壊が問題とならなかった。
しかし、絶妙なタイミングで割り込みが起きた場合だけ、割込み後にプログラムがうまく復帰できず、暴走してしまった。
そして、暴走したプログラムは、スクランブル回路に「おかしな方法で」アクセスしてしまった。
スクランブル回路は異常を感じ、自爆。基盤は2度と起動しないようになった…
原因はわかった。
報告を聞いて、原因究明を命じた上司は、責任者を探すように命じた。
責任者は、破壊されるレジスタを保存しないような割込みルーチンを書いた奴だろう。
実際、返金で億単位の損害が出てしまったのだ。誰かが責任を取らなくてはならない。
この割込みルーチンは「絶対に取りこぼしてはいけない」重要信号を扱うものだった。
信頼性が重要なので、はるか以前に書かれたルーチンが使い続けられていた。
(いくつものプログラムで使われていたにもかかわらず、いままで「たまたま」問題が出なかったのだ)
最初に使われたプログラムはどれか?
古いプロジェクトの資料が引っ張り出され、調査されたところ…
プログラムを書いたのは、原因究明を命じた上司だった。
このことが上司に伝えられると、それまでこの件に対して厳しかった上司の態度は一変。
「まぁ、人間だから間違えることもあるよな。ははは…」
…そして、問題は有耶無耶のうちに葬り去られたのでした。
#製品は再発売されたが、悪評がついてしまったためほとんど売れませんでした。
2014.9.27追記
僕が過去にかかわった仕事の裏話なんかを、今後少しづつ出していこうと考えています。
そのために必要なので、笑い話をつまらなくしてしまうのですが、お断りを追記しておきます。
この記事書いたときには、ウケを狙う意味もあって、嘘は書いていませんが事実も伝えていません。
まるで上司が犯人で、問題を握りつぶしたように書いていますが、事実はそうではありません。
確かに上司が過去に書いたプログラムで問題が出たわけですが、書かれた時点では「暗黙の破壊」は承知の上です。
暗黙に破壊されるレジスタは、いつうっかりミスが起きるかわかりません。
「使わない」と言うルールでコーディングを行っており、一番確実な対処法でした。
ただ、上司がそのプログラムを書いた時点では、そのプログラムが「重要部分だから手を加えないで使いまわす」ようになるとは考えていません。
ただ、必要だったから書いただけのプログラムです。
だから、使い方の説明を詳細に残したりもしていません。
後に、別のプロジェクトでこのプログラムが流用され、それでも当初は「暗黙に破壊されるレジスタは使わない」作法が守られていたようです。
やがて、いつの間にか重要ルーチンだから手を加えてもならない、となっていき、手を加えてはならないから中身を詳細に調べることもなく、「暗黙に破壊されるレジスタを使ってはならない」という作法も忘れ去られます。
そして、問題のバグが生じたわけです。
当初は小さなプログラムだったため、十分に少人数でプログラムを作っていた、というのも、「暗黙に破壊されるレジスタは使わない」という作法が守られる理由となっていたでしょう。
しかし、プログラムが肥大化し、多人数で作るようになると、お互いの分担部分ごとで、守らねばならない作法があることが徹底できなくなっています。
過去のプログラムを使いまわしただけで、誰も把握していないプログラムが入り込んでいるとなるとなおさらです。
ここまで説明するとわかっていただけるかもしれませんが、上司が自分の責任と判ったので有耶無耶にした、というよりは、「誰の責任でもないとわかったので、誰も処分しなかった」と言うのが正確な表現です。
大問題でしたから、会社からは責任の明確化を求められていたでしょう。
誰かに責任をかぶせ、クビにしてしまえば上司の立場は守られます。でも、そうはしなかった。
笑い話としては上司の責任にした方が面白いのでそう書きましたが、本当は全く逆。
部下を守るために、上司はあえて自分の立場を難しくする判断を選んでいるのです。
この上司、この件に限らず、自分の立場が多少悪くなっても、必要なことだと判断したら実行する方でした。
その点、良い上司だったと思っています。
以上、僕が今後過去の仕事を明らかにすることで、上司が誰か推察されて彼にあらぬ疑いがかかる前に、釈明しておきます。
ついでになってしまうけど、上の話書いて公表した後で、やっぱりというか案の定というか「ココに書かれている製品は何だろう」と調べている方がいました。
で、その方が調べていた痕跡を公開しているので追いかけて、自分の記憶の曖昧さも判りました (^^;;
大体の話の筋としては正しいのですが、ハードウェアの仕組みとか多少間違って書いております。
却って煙に巻く効果があり、製品が何であるかは特定されておりませんでしたが、今後いろいろ書くうちに、わかってしまうとは思います。
その際に「ハードウェアの記述が間違えている」と思われるかもしれませんが、記した通り自分が経験したバグではありませんし、自分が後に扱ったハードウェアとの混同もありました。この点についてはお詫びいたします。
同じテーマの日記(最近の一覧)
別年同日の日記
申し訳ありませんが、現在意見投稿をできない状態にしています。 【SP】 組み込み系でエージングテストのバグが出ると大変ですよね。同じ苦労をした事があります。 (2012-03-07 06:04:04) |