バグ検出ドリル(13)のたうち回る地獄、煮詰まったバグを解決せよ:山浦恒央の“くみこみ”な話(113)(3/3 ページ)
バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第13回の問題は、筆者の経験を基にして作った「煮詰まったバグ」です。プログラムが思い通りに動かず「のたうち回る地獄」のような状況に対処してください!
4.今回の解答
バグの原因は、「バッファオーバーランによるメモリ破壊」です※2)。以下、詳細を解説します。
※2)バッファオーバーランは、確保しているデータ領域を超えて、隣接するデータ領域を書き換えてしまうバグです。バッファオーバーフローとか、「爆撃」とも呼びます。
本プログラムは、「examdata.txt」をカンマ区切りで読み出し、コンソールに表示するプログラムです。「YAMADA YUYA」と「YAMAMOTO AOI」の理科(science)のデータが正しく表示できていません。正しく表示できない原因は、ソースコードの以下の部分です。
//生徒の名前を取得する strcpy(name[no],token);
strcpy関数は、nameにtokenの文字列をコピーする関数です。名前の変数は、name[N][15]」と宣言してあります。つまり、名前は14文字までしか入力できません。3人目のデータ「WATANABE SHUICHIRO」は、18文字あり、strcpy関数を使用してしまうと、領域外の場所にデータを上書きする可能性があります。今回の場合は、メモリ上にnameの次のアドレスに理科のデータ(science)が入っており、その部分まで上書きしています。つまり、メモリ内は以下の表1のようになっており、理科のデータが壊れています※3)。
※3)今回の問題は、お手持ちの環境によっては動作が一致しない可能性があります。今回は、CygwinのGCC 7.3.0を使用しています。
1人目の「YAMADA YUYA」の理科の点数が70ではなく、79となっているのは、3人目のデータのアルファベットの「O」をコピーしているためです。コンソールへの表示は、10進数表記ですから、アルファベットの「O」は、ASCIIコードで「4F」で、「4F」を10進に変換すると「79」になります。
2人目の「YAMAMOTO AOI」の理科の点数が0となっているのは、3人目のデータの末尾「'\0'」が入っているためです。「\0」は、ASCIIコードで0を表すので、コンソールには数字の「0」が出ます。
このバグのトリッキーなところは、生徒の名前が「渡辺一郎」なら正常に表示し、「渡辺秀一郎」なら表示不正になる点です。最初の動作確認テストで、「渡辺一郎」をテストデータにしていれば「たまたま」正常動作し、プログラム作成者のAさんは「このプログラムでOK」と思ったはずです。で、実際のデータに「渡辺秀一郎」があると、メモリ破壊が発生し、渡辺秀一郎と関係がない生徒のデータが壊れます。この現象から「渡辺秀一郎のアルファベットの文字数が多いことが原因となり、他者のメモリを破壊した」とは思い付かないでしょう。OSのバグや、ハードウェアの不良まで疑い、Aさんは煮詰まってしまいます。
さらに、「渡辺太一郎(ワタナベタイチロウ)」と「渡辺秀一郎」の場合では、データの壊れ方が異なりますし、テストで「松本明子(マツモトアキコ)」を使い、実際のデータに「松本吉彦(マツモトヨシヒコ)」が入っている場合、「2人の名前の漢字の文字数は同じで、性別が違う」とミスリードの要素が増え、「名前のアルファベットの文字数が多く、バッファを破壊した」という原因特定に至りません。バグと無関係の場所に異常現象が現れるバグは、タチが悪く、原因究明に時間がかかります※4)。
上記のバグを引き起こさないためには、「配列nameの領域を増やす」「領域内までしかコピーしない」などの対策をせねばなりません。strcpy関数の使用方法を誤るとバグにつながるので、注意が必要です。
※4)連載「山浦恒央の”くみこみ”な話」の第64回、「クイズ!! バグはどこだ? 〜ちょっと休憩して、バグ探しに挑戦〜」では、ミスリードが非常に多く、バグの解決に時間がかかったケースとして、筆者の体験談を取り上げました。
5.自己採点シート
今回の自己採点シートを以下に示します。
問題 | 内容 | 配点(点) |
---|---|---|
テストデータ表示プログラム | 問題文を一通り読んだ | 50 |
バッファオーバーランのバグであると推察できた | 50 | |
リスト6 自己採点シート |
6.終わりに
今回は、バッファオーバーランのバグを仕込んだ、テストデータ表示プログラムを出題しました。皆さんは、バグを見つけることができたでしょうか。バッファオーバーランは、関係のない場所に異常が現れる「タチの悪いバグ」だと思います。筆者も、プログラムが想定通りにいかないことがありました。「おかしいなぁ」と数時間悩み、あらゆる場所にprintf関数を仕掛け、バッファオーバーランを見つけた経験があります。
組み込みソフトの開発では、メモリ領域を参照したり、書き換えたりすることが多くあります。メモリの領域を誤って書き換えると、プログラムは通常では考えられない不可解な動作をします。筆者のように、煮詰まって苦労する前にバッファオーバーランを見つける嗅覚を備えていただければと思います。
東海大学 大学院 組込み技術研究科 非常勤講師(工学博士)
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- ≫連載「山浦恒央の“くみこみ”な話」バックナンバー
- クイズ!! バグはどこだ? 〜ちょっと休憩して、バグ探しに挑戦〜
あれは米国ボストンに駐在していたときのこと――。ある日、私が管理していたオフィスの入退室セキュリティシステムが動作しなくなった。その当時、大流行がウワサされていたあるウイルスの存在のせいなのか、あるいは……。 - バグ検出ドリル(12)コンパイルエラーにならないコンパイルエラー
バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第12回は、前回に引き続き「コンパイルエラー」がテーマです。コンパイルエラーにはならないのに、うまく動かないプログラムのバグを見つけ出してください! - バグ検出ドリル(11)コンパイルエラーは意外に難しい
バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第11回では、初心者が苦労する「コンパイルエラー」がテーマです。コンパイラが指し示すエラーの箇所から、バグを見つけ出してください! - バグ検出ドリル(10)“昔のバグ”は生命力が最強
バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第10回では、生命力最強という“昔のバグ”がテーマです。前回の第9回で扱った迷路探索プログラムの改造版からバグを見つけ出してください! - バグ検出ドリル(9)デバッグの基本的な心構えは「人を見たら泥棒と思え」
バグは至るところに、しかも堂々と潜んでおり、自信満々なプログラマーほど、目の前のバグに気付かないものです。「バグ検出ドリル」の第9回では、比較的大きなソースコードである迷路探索プログラムに潜むバグを見つけ出してください!