tech
ハッカーの遺言状──竹内郁雄の徒然苔
第26回:秘技デバグ、遊戯デバグ
元祖ハッカー、竹内郁雄先生による書き下ろし連載の第26回。今回のお題は「秘技デバグ、遊戯デバグ」。
ハッカーは、今際の際(いまわのきわ)に何を思うのか──。ハッカーが、ハッカー人生を振り返って思うことは、これからハッカーに少しでも近づこうとする人にとって、貴重な「道しるべ」になるはずです(これまでの連載一覧)。
文:竹内 郁雄
カバー写真: Goto Aki
デバグ? ここではバグ取りのことを「デバグ」と書く。通常、「デバッグ」と書くが、これが私には気持ち悪い。「バッグ」ではなく「バグ」を取るのだからやはりデバグではなくてはいかんでしょ(※1)。超弩級のバグを「ドバッグ」と言うだろうか? 「ドケッチ」と言わずに「ドケチ」と言うがごとく、やはり「ドバグ」だ。
「パターン」と「パタン」は、Google検索すると圧倒的に「パターン」が多いが、私がNTT研究所にいたころ、音声認識や画像認識の人たちがあるときから突然「パタン」と書き始めたことを記憶している。確かに英語の発音はこちらのほうが近い。これを言い出すと、いまの和製英語的カタカナ表記は随分と直さないといけないだろう。学会ではもう「コンピュータ」だが、新聞ではいまだに「コンピューター」がのさばっている。
要するにどちらでもいいのだが、私は普段から自分のためにはデバグと書いている。と、長い口上になってしまったが、今回はデバグの話である。
原理主義的に言うと、最初から正しいプログラムを書くべきであって、そもそもデバグなどしてはいかんのである。最初から正しいプログラムを書くためには、最初から正しい仕様を書き、それを自動的にプログラムに変換すればいい。しかし、正しい仕様を書くこと自体が人間には無理だということを忘れてはいけない。とはいえ、仕様のデバグのほうが、プログラムのデバグより楽そうだということに異論はない。
つい最近、200行程度のプログラムを書いた。遺言状第23回の最後のほうでちょっと紹介した「賢者が別の賢者を完全にシミュレートできることをシミュレートするプログラム」を書いたのである。つい、学会でこの話をすると約束してしまったので、久々に真面目にプログラミングした。できたと思って、いろいろな場合を試してみると、やっぱりうまく行かない場合が出てくる。そのうまく行かない場合をつぶすと、別のところでうまく行かない場合が出てくる。まさにモグラ叩きである。
こんな短いプログラムなのに、ロジックのややこしいこと限りなく、すべての場合に間違っていない結果が出てくると信じられるようになるまで、延べ40時間以上はかかったと思う。この問題が難しいためなのか、私がもうロートルだからかについては議論があろう。
モグラ叩きが割りと大詰めに近づいたころ、どうにも理解できない現象が起こった。いくら考えても理解できないときは、あっちゃこっちゃにプリント文を入れるという野蛮なデバグになる。小さな関数にまさか間違いはないだろうと思って、環境をお膳立てして、単体テストをしたらちゃんと動く。まさにキツネにつままれた気分。さらに多数のプリント文を入れて、初めて、あっと気がついたのは「lost.」という文字列を「lost」と打ち間違えていたこと。なんと単純なミス(※2)。つまり、文法的にはなにも間違っていず、意味的に一見それっぽいといった単純なミスほど見つかりにくい。逆に一見難しそうな論理ミスのほうが、実は見つかりやすい。
さらにその先に進んでも、やはり「これは変だなぁ」という現象が起こる。計算がエラーを吐いて止まってくれると逆に楽である。多くの場合、エラーを起こしたプログラムの怪しい部分はその近くで見つかりやすいからだ。犯人は近くに潜んでいるというデカの感覚と同じである。
疲れてくると、 途中で吐き出されるログはともかく、最終結果さえ合えばいいという安直デバグに走ってしまう。歳だ……。というわけで、最終結果の帳尻を合わせてそれらしい結果に修正するfinal-checkという尻拭い関数を作る。お、最終結果はそれらしくなる! もう眠いときはしょうがない。
ところが、次の日、朝起きると、尻拭いをして帳尻を合わせるなどもってのほかという「良心」ががっちり復活する。そうして、途中のログも正しくなるように執念深いデバグをすることになる。まさに求道精神だ。ある程度それらしくなってきたので、帳尻合わせのために付け加えたfinal-checkを外してみる。なんと、ちゃんと動くではないか。「3人の賢者の問題」は、その手の実に厄介というか、デバグしがいのある問題であった。「それはどんな問題かね?」と興味を持たれた読者のために問題を付録に紹介しておく。ロートルでない人だともっと短い時間でプログラムが書けると思う。
プログラマ35歳定年説について遺言状第1回と遺言状第23回でもちょっと言及したが、私の生涯最大のデバグ体験は私が45〜50歳のころである。十分に常規を逸している。仲間たちと一緒に開発したSILENTというLispマシンの上にOSも言語もゼロから構築するという野望に満ちたというか、あたかもガレージで行うようなシステム開発で、私がOSの地底から全部マイクロプログラムで書いた。このとき私が書いていたプログラムの一部が遺言状第13回の写真2のダシに使われている。同じ部分をテキストで紹介しておく(リスト1)。目の毒になるので数奇者以外の読者は目をつぶって先に進んでほしい。
機械語を知っている方なら、カッコだらけなのに、これは機械語だということがすぐ分かるだろう。つまり、LispのS式で書かれた80ビットの水平マイクロプログラムなのである。この調子でOSの基底にあるスケジューラやメモリ管理を全部書いた。でもまぁ、どれも2、3カ月もあれば大体完了である。こういうプログラムは「3人の賢者の問題」のように、どうもよく分からんということはあまりなく、バグがあると大体その近辺で変なことを起こす。
最も大変だったのは、1997年暮から1998年2月ごろにかけて暮も正月もなくデバグを続けた実時間ゴミ集めのプログラムである。これは1994年ごろに一旦サクッと書いて放置していたもので、3年以上前のドキュメントとプログラムを思い出しながらのデバグであった。スケジューラなどはすでにバッチリ動いていたので、数万個のプロセスに滅多やたらにメモリを消費させてはゴミを出させ、それをそれらと並行に動く7個のゴミ集めプロセスにゴミ回収、つまりメモリ資源のリサイクルをさせるというものである。
並列であれ、並行であれ、同時に複数のプロセスがお互いに絡み合いながら動くというプログラムのデバグは本当に難しい。特に実時間ゴミ集めの場合は何か変なことが起こったとしても犯人はその近くに潜んでいない。練馬区の突然の豪雨が東太平洋の遠くのエルニーニョに起因するといった類のことが起こる。
実際はそう単純ではないが、実時間ゴミ集めで起こる変なことの2本柱は、生きている、つまり使用中のメモリをゴミと思って回収してしまう殺人事件と、もうゴミになっているメモリを回収し損ねてしまう街中ゴミだらけ事件(メモリリークとも言う)である。ゴミだらけ事件はどんどん街が汚くなってきて暮らしにくくなっていくだけだが、殺人事件は回収された、まだ生きている死体を再利用してしまうため、プログラムにバグはないのに、計算が狂ってしまう。要するにわけの分からないことが起こる。遺言状第21回のセル供養はそうした気の毒なセル(Lispのメモリの単位)の成仏を祈るためのものであった。
こういうややこしい事件をどうやって解決したか。灰色の脳細胞の活動だけで推測できた場合はラッキー。最初のうちはそれでも結構バグが取れる。だんだん長時間安定して動き始めると、これが大変。延々と計算を回し続けて丸1時間とか丸1日動いてから変なことが起こるようになる。DRAM(ダイナミックRAM)のリフレッシュなどというCPUのクロックと無縁の周期で起こる事象がプロセスのスイッチタイミングに干渉するので、同じことを再現しようという通常の試みは不可能となる。
こういうときは、いわゆるポストモーテムダンプ(※3)しかない。つまり検死学である。変なことが起こったときのメモリ、レジスタの内容を眺め回して異状を探し回る。あや、この番地のこの値は変だぞと嗅ぎ回るいうわけだ。場合によっては1万〜2万行の16進数のダンプをじーっと眺めて、きな臭いところを見つける。まさにバグ嗅覚だ。
実時間ゴミ集めのデバグは、途中、進捗が非常に遅くなってしまったので、心機一転、プログラムの仕様を全部論理式で書き直してみることにした。違う視点での記述になるので、いくつかの発見はあったが、それでも2日連続で走らせていると出るバグがある。最後の手段として、プログラムのすべての実行パスにカウンタを入れてみた。さらに複数台のマシンを同時に走らせて、どのパスも十分に走っておかしなことが起こらなければOKというわけだ。どうもこの最後の手段ですべてのバグが取れた。その後はマシンの電源を落とさず、CPU稼動率100%の苛酷な連続走行試験を行っても止まらなくなった。止まったのは100日後である。原因は定期点検のための研究所停電だった。さすがにこれには勝てない。
こう書くと、デバグは超ネクラな作業のように思われるかもしれない。実際、そういうふうにデバグをしている人は少なくないような気がする。しかし、デバグは楽しいゲームとも言える。
面倒なマイクロプログラムは文字どおり書くのが面倒だ。よい子のみなさんは真似してはいけないが、私は夜アルコールが入ってから、一気呵成に100〜200ステップのプログラムを書いた。大胆不敵な行為だが、コードも大胆になる。それ自体は悪くない。
そして、(そのときだけ、私は朝型になったので)朝起きたら、そのプログラムに詳細なコメントをつけていく。酔った勢いのバグは大体そのときに取れてしまう。各ステップにコメントをつけていくということは、私の感覚では自然言語で、同じことをするもう1つ別のプログラムを書いているのと同じことなのである(※4)。
ある程度コードがまとまったところで走行試験をする。そこからのデバグは私にはドラクエと同じ種類のゲームであった。だから楽しい。つまり、自分でエンバグ(デバグと逆でバグをプログラムに入れること)して、「新しいゲーム」を作り、それを解決するゲームを遊ぶのである。エンバグのパタンは予測もつかず、実に多様なので、このマッチポンプゲームに飽きることはない。
なお、実際はこんな行き当たりばったりにシステム開発しているわけではなく、まずきちんと「時間軸を射影した空間軸記述ドキュメント」をしっかり用意してから始めるということは強調しておきたい。自作ドラクエのマイクロプログラミングは、空間軸記述ドキュメントから時間軸への展開作業。そしてバグが悪さをしたあとのポストモーテムダンプはそれをもう一度空間軸で解析するプレイなのである。ここ、何を言っているのか分からなくてもいいのだが、デバグがかなり高級な遊びであることを感じていただければありがたい。
こういうデバグも大変だが、もっと大変なデバグもある。それは自分の作ったプログラムが原因でないバグだ。コンパイラにバグがあったとか、マシンそのものに何らかのバグがあった場合だ。自作のマシンの場合は、こういうことがたまにある。ここまで来ると世界をすべて疑う懐疑主義になってしまいそうだが、もともと自分たちは(お客様の要求仕様がいつもそうなのだが)コンニャクの上で踊っていると思えばいい。コンニャク問答ならぬ、コンニャクデバグだ。自分の書いたプログラムのバグじゃなくて、ほかの部分のバグと証明できたときの喜びは格別だ。
ところで、バグや間違いは創造の素になることがある。かな漢字変換ミスは常に創造のタネだ。試験管を洗い忘れて大発見につながることもある。大発見ではないが、使い慣れないスマホのカメラボタンにうっかり触れてしまったら、写真1のようなものが撮れた。レンズにかかった私の指が写ったようだ。このバグが気に入ってしまい、私はこれをスマホの背景画面にしている。
要するに、トートロジーなのだが(当り前なのだが)、何があってもそれを楽しみや喜びに転化すれば、世の中楽しいのである。
付録:3人の賢者の問題
3人の賢者A、B、Cがカード遊びをしている。1からn(nは3以上の適当な整数)までの数が書かれたn枚のカードが円卓上に裏向きに置いてあり、賢者たちは1枚ずつ手元に取る(図1)。大小比較で中間の数を取った賢者が勝利する。しかし、いっせいにカードをオープンするだけでは脳がへたれるというので、賢者たちは次のようにゲームを変形した。
取ったカードを自分の右隣り(BはA、CはB、AはC)にだけ見せる。そして、Aから順にA、B、Cと右回りに勝敗について確定したことを発言させる。ただし、勝敗に関する新しい確定情報がないときはニッコリ会釈するだけにする。
ニッコリ以外の発言は「誰かの負けじゃ」と「誰かが勝者じゃ」の2種類だが、「誰かが勝者じゃ」という発言をしたときにゲームは終わる。ゲームが終わったとき、各賢者が自分の右隣りの賢者のカード候補をどこまで絞りこめているかを計算せよ。
どの賢者も推論能力は完璧であり、発言は常に情報量を最大にするものとする。なお、ある賢者がニッコリしただけでも、両隣りの賢者にはそれなりの情報が伝わるというのがこの問題の肝である。n=3, 4, 5あたりで試してみると問題の面白みが分かると思う。(つづく)
※1:これについては、遺言状第3回でも言及した。
※2:コンパイラがそんなのチェックしてくれると言うなかれ。コンパイラはx+1とすべきところx-1と書いてもチェックはしてくれない。その昔ピリオド1つ打ち間違えたのをFortranコンパイラが通してしまい、そのおかげでロケットが墜ちたという話は有名である(※5)。
※3:エラーで止まったときのメモリやレジスタの情報をファイルに保存すること。必要に応じて1万〜2万行にもなる。
※4:それは逆順だろうとおっしゃるかもしれない。そうだとも思うが、私にはこの順序のほうが楽だった。自然言語で考えるより、プログラミング言語で考えるほうが私には直接的でよかったのである。
※5:読者の岸本誠さまから以下の指摘を受けました。記憶だけで書いてしまった私の不明でした。お詫びして訂正します。「その昔ピリオド1つ打ち間違えたのをFortranコンパイラが通してしまい、そのおかげでロケットが墜ちたという話」というのは有名な誤伝(?)で、前半のカンマをピリオドに間違えたバグはマーキュリー計画で起きたバグですが実際に打ち上げられたロケットには関与していません。後半のロケットが落ちたバグはマリナー1号で、世に恐ろしき仕様バグ(プログラムは正しかったが、プログラムすべきとして示された数式にバグがあったという……)により起きたものです。
竹内先生への質問や相談を広く受け付けますので、編集部、または担当編集の風穴まで、お気軽にお寄せください。(編集部)
変更履歴:
2015年12月28日:「プログラムにバグはないのに。計算が狂ってしまう。」を「プログラムにバグはないのに、計算が狂ってしまう。」に修正しました。
2015年12月28日:脚注2に、脚注5を追記しました。
2015年12月28日:インデントがずれていたため、リスト1を画像に変更しました。
2015年12月25日:「7個のゴミ集めプロセスがゴミ回収、」を「7個のゴミ集めプロセスにゴミ回収、」に修正しました。編集部の修正漏れです。お詫びして訂正いたします。
2015年12月25日:脚注3の「すべてファイルに保存すること。ときには1万〜2万行になる。」を「ファイルに保存すること。必要に応じて1万〜2万行にもなる。」に修正しました。こちらも編集部の修正漏れです。お詫びして訂正いたします。
SNSシェア