システムテストの必須項目(3)・タイマやカウンタのロールオーバーテスト

2021年9月10日テスト品質

ソフトの安定動作を脅かすバグにはタイマやカウンタのロールオーバーバグもあります

タイマやカウンタのロールオーババグも、メモリリークのバグと同じように市場に漏れだすと大きな問題を引き起こす時限爆弾バグです。ロールオーバ処理とは、タイマやカウンタの値が最大値に達してさらに増えた時に初期値に戻る事を指します、時計の針が23時59分の次ぎに0時0分になるのと同じですね。このロールオーバが起きた時の処理が適切に実装されていないと、タイマやカウンタを利用している機能が動作不良を起こしてしまいます。

ロールオーバのバグで一番多いパターンは、タイマやカウンタの値を引き算して経過した時間を求める処理です。タイマやカウンタが時間と共に増加する事を前提に引き算で経過時間を求めるのですが、そのタイマやカウンタの値が急に初期値のゼロになると、引き算の結果がおかしな事になります。

タイマやカウンタ値はソフトの起動時には初期値のゼロから始まる事が多いので、最大値に達するまでには長い時間が必要なために、ロールオーバーのバグはなかなか見つかりません。ロールオーバーのバグを効果的に見つけるには、専用のロールオーバーテストが必要です。この記事では、ロールオーバーバグの紹介とそれを事前に見つけ出すためのロールオーバテストについて紹介します。

タイマやカウンタのロールオーバーて何でしょう

タイマやカウンターのロールオーバーバグを理解するに、まずはロールオーバーについて簡単に説明しましょう。ソフトの内部処理で使うタイマやカウンタは通常、符号無し整数の型で宣言された変数に現在の値を保持する事で実現されています。処理系が32bitの場合だと符号無し整数の型で宣言された変数にはは32bit長の符号無しの2進数が割り当てられるので、表現できる値は2進数で表すと 0000 0000 0000 0000 0000 0000 0000 0000から 1111 1111 1111 1111 1111 1111 1111 1111です。ちょっと見難いので16進数表現に書き換えると 00000000 から FFFFFFFF です。まだ見難いので10進数に書き換えると 0 から 42949672960 です。

この 0 から 42949672960 の値を表現できる符号無整数で宣言された変数をインクリメントカウンタとして使う場合を想定してください。インクメントカウンタなので初期が0ならばそこから 1, 2, 3, 4 とカウンタ値が増加していきます。カウンタ値がどんどん増加していって、表現できる最大値の42949672960 になって、さらに 1増加すると値はどうなるでしょうか? 32bit 長の符号無し整数の型で宣言された変数の場合には、 42949672959 –> 42949672960  –> 0 –> 1 というように、42949672960 の次ぎは 0 になります。十進数で考えていると不思議ですが元の 32bit の2進数で考えると 1111 1111 1111 1111 1111 1111 1111 1111 に 1 を足すと 1 0000 0000 0000 0000 0000 0000 0000 0000 になるのは容易に判ります。桁上がりが起きているのですが、32bit 分しか値を維持するbit が無いので桁上がりの 1 は消え去って残り 32 bit が全て 0 になった状態がおこります。これが32bit長カウンタのロールオーバーです。十進数でいうと 42949672960 + 1 =  0 になるという状態ですね。

インクリメントカウンタの場合には42949672960 の次ぎは 0 がロールオーバーですが、デクリメントカウンタや減算タイマーの場合には、2 –> 1 –> 0 –> 42949672960  –> 42949672959 と 0 からさらに 1減ると -1 になるのではなく 42949672960  になるのがロールオーバーです。

ロールオーバー処理にどんなバグが潜むのか

タイマやカウンタのロールオーバー処理は判りましたが、これのどこにバグが潜むのでしょうか? 実はロールオーバー処理そのものにはバグは潜みません。タイマやカウンタを使っている処理に、ロールオーバー対策が正しく実装されていいないと、ロールオーバーが発生した後におかしな事が起きるのです。

例えば、インクリメントカウンタとして count-1 というカウンタを使っていると想定しましょう。処理-A の中では count-1 の値が 5以上増加したら処理-B を実行する事にしましょう。count-1 の値が 5 以上増加した事の判定方法として、(現在の count-1 の値) – (以前の count-1  の値)を計算してそれが 5 以上になっていたら処理B を実行する、というソフトの実装をしていたとします。例えば count-1 の現在の値が 1005 で以前の値が 1003 なら、まだ処理Bは実行しなくてよいですし、現在の値が 1009 で以前の値が 1003 なら処理Bが正しく実行されます。

それでは count-1 の値がどんどん増加していって、ロールオーバーをしたら何が起きるでしょうか? オールオーバして少し経った時点を想定すると現在の coutn-1 の値が 6 で以前の count-1 の値が 42949672959  だったとします。(現在の count-1 の値) – (以前の count-1  の値)を計算すると 6 – 42949672959  = – 42949672953 です。引き算や比較演算が符号付整数(通常はデフォルトの型ですね)で実行される場合には、この値は 5 以下なので処理Bは実行されません。42949672959  から 6 になるまでにはカウンタの値は 8 インクリメントされているので本来は処理Bが実行されないといけないのですが、処理Bが実行されないとう問題が起きます。これがロールオーバー処理に潜むバグです。

コーディング的な対策の一つの方法としては、32bit長符号無し整数のタイマやカウンタの演算(引き算)を行う時には、値を64bit 長の符号付き整数(C言語系なら long の integer ですね)に代入してから演算するという方法があります。他にもいろいろな対策方法がありますが、このようなロールオーバー対策が実装されていれば問題は防げます。

ロールオーバーバグはなぜ時限爆弾になるのか

タイマやカウンタを使ってなんらかの判断を行う処理に、タイマやカウンターがロールオーバーした時の対策が実施されていないと不具合が起きまるのですが、なぜ時限爆弾バグになるのでしょうか? これはタイマやカウンタの初期値に関係してきます。インクリメントカウンタを使う場合、大抵は初期値として最小値つまりは0を設定します。するとロールオーバーが起きるまでにはカウンタのインクリメントが 42949672960 回は必要です。これは普通に考えると、社内のテスト期間中にはなかなか発生しませんね。そのためにリリース前の社内テストではロールアップ処理に潜在するバグを見付ける事が難しいのです。

しかし一旦市場でソフトが稼働し始めると、組み込み系ソフトの場合には 24時間 365 日稼働し続ける事が多いので、電源オンのあと相当の時間が経つと初期値が0のインクリメントカウンタの値がロールオーバーする瞬間を迎えてしまいます。その結果市場で長時間の稼働の後にソフトの機能に何等かの不具合が起きてしまいます。最も困るのは、ロールオーバーバグがあると全ての装置でそのバグによる不具合が起きてしまう、という部分です。これが、時限爆弾バグの怖いところですね。

ロールオーババグのテストはどうやるの、どこが難しいの?

ロールオーバーバグのテストは実は簡単にできます。タイマやカウンタの値をロールオーバー直前の値に設定してソフトを実行すれば良いのです。デバッガでタイマやカウンタの値を操作してロールオーバー直前の値に変更しても良いですし、ソフトを少し修正して初期値をロールオーバの直前の値にしておくのでも構いません。要するに、タイマーやカウンタの値がロールオーバーする状態を作り出して、その状態でも正常にソフトが稼働している事を確認するのが、ロールオーバーテストです。

テスト自体は難しい事ではないのですが、タイマやカウンタ―はソフトの中であちこちに多数使われています。それらのタイマやカウンタを洗い出して、全てのタイマやカウンタについてのロールオーバーテストを実施するという、テストの網羅性のをどうやって維持するのかがロールオーバーテストの難しさです。

なお、最近のソフトではロールオーバーバグの潜在を防ぐために、タイマやカウンタの初期値をロールオーバーの直前の値に設定するという実装上の工夫がされている事も多くなってきました。

497日問題とか49日問題が有名なロールオーバーバグ

497日問題とか49日問題という言葉を聞いた事がある人も多いと思います。この 497日問題とか49日問題とかは OS の基本部分(カーネルと呼びます)で使っているタイマに潜むロールオーバーバグの事です。OS の基本部分(カーネル)には起動されてからの経過時間を記録する機能があってそのために 32bit長の符号無の型宣言されたカウンタ変数を使っています。このカウンタ変数は、装置のハードウエアからのタイマ割り込みのたびにカウントアップされます。多くの装置ではハードウエアからのタイマ割り込みは 10m秒か 1m秒で設計されています。

タイマ割り込みが 10m秒だった場合には、カウンタ変数がロールアップするまでの時間は 10m秒 X 42949672960 回 = 429496729600 m秒 = 715827分 = 11930時間 = 497日 なのです。つまり、起動してから 497日後に OS の基本部分で使っているカウンタ変数がロールオーバーするので、ロールオーバーバグが潜在しているとその時点でソフトに不具合が起きる、というのが 497日問題です。タイマ割り込みが 1m秒だった場合には 10倍速くロールオーバーが起きるので、起動後49日目に不具合が起きるので、これが 49日問題と呼ばれるやつですね。

時限爆弾バグの次ぎは複数機能の同時実行テストです

組み込み系ソフトの安定稼働を妨げる時限爆弾バグとなる動的資源のリークとタイマ/カウンターのロールオーバを社内テストで洗い出すためのテストにつて紹介してきました。安定稼働を妨げるバグには他にもいろいろとあって、これを洗い出すためのテストもまた色々とあります、次は複合機能の同時動作テストを紹介しましょう。