学校では教えてくれないこと

ビリーへの質問:スタックオーバーフローについて

2013.11

やぁみんな、ビリーだよ。

今日は、ビリー宛てにもらった質問を紹介しよう。

読者からの質問

スタックってなあに?(2)のスタックオーバーフローの説明で、「この関数を「スタックメモリを1KBしか持っていない」タスクAがコールしたらどうなるだろう」と書いてありますね。どうして、1KBしかないか? どうして2KBあるいは512Bではないか? 1KBになる理由はなんでしょうか?
また、実は何Byteなら大丈夫かを調べる方法はありますか?


質問ありがとう。
「この関数を「スタックメモリを1KBしか持っていない」タスクAがコールしたら どうなるだろう」
と書いたけど、少しわかりにくかったかもしれないね。

ビリーからの回答

この例では、タスクが持つスタックサイズよりも大きな配列(char buf[4096])を取るとオーバーフローしてしまう、という例を挙げただけで、この場合のタスクAのスタックサイズは 2KB でも 4KB でも構わないよ(オーバーフローするからね)。

最後に書いてある、『では何バイトであれば大丈夫か?』という質問は非常に重要なんだ。これを論理的に計算するのはなかなか難しいんだよ。

例えば open()や read()関数自体も内部的にスタックを使用するし、ファイルの大きさなどにも影響されるかもしれないからね。

実際にはある程度大きなスタック領域を割り当てておき、実際にプログラムを何度も動作させて消費されているスタックサイズを実測し、問題のない範囲で減らしていく、というのが現実的なチューニング方法として広く採用されているみたいだ。

「実際に消費されているスタックサイズ」を測定する方法は、これまたいろいろとあるのだけどICE とデバッガがそのような機能を持っていることがあり、これを使うのが一例だよ。スタックポインタ(SP レジスタ)の内容をタスクごとに記憶しておき、その最大値が最大消費量ということになるね。

ソフトウェアで簡易的に調べる方法もあるんだ。タスク起動時にスタックを割り当てるけど、これをあるデータパターン(例えば 0xef とか)で埋めておく。

スタックの最大消費量は、0xef のデータがどこまで書き換わっているかを見ればわかるよ。例えばスタックの 100 バイト分が 0xef 以外の値、100 バイト以降が 0xef 以外であれば最大消費量は 100 バイトだったとわかるんだ。

ただしこちらの方法は、たまたま 0xef というデータをスタックに書き込んだような場合は起動時に設定されたものか、途中で書き込まれたものかが区別できないため、必ずしも正確ではない、という問題もあるけどね (でも、おおよその目安をつけるという意味では有効だから、市販 OS などでも採用されている方法だよ)。

例えば4KB(4096バイト)のスタックを持つタスクのスタック消費量を確認する方法を考えてみよう。スタックはCPUによって、初期値からアドレス減少方向に伸びていくものとアドレス増加方法に伸びていくものがあるけど、下に書くのは、アドレス減少方向に伸びていく場合の例だよ。

(1)タスク起動時

スタックを0xefというデータで埋めておく。このとき、スタックの初期値(ベースアドレス)を覚えておく。
例:ベースアドレス(0x6000)

タスクのスタック

(2)スタック最大消費時

ある時点で、スタックの最大消費サイズが2800(=0xAF0)バイトまで進んだとする。
スタックポインタは0x5510まで進んだ。スタック消費量は0x6000-0x5510=0xaf0(=2800)。
図中の黄色部分がスタックとして使用されたことになるね。

タスクのスタック

たまたまefというデータが入っている場合があるので、例えば0xefが連続で3回出てきたら未使用データと判定するといった工夫をしておくよ。

(3)現在の状態

スタックの現在消費量および過去の最大消費量は、以下のように計算できるよ。
スタックポインタは0x5780。
スタックの現在消費量は、0x6000-0x5780=0x880(=2176)バイト。
スタックの過去最大消費量は、SP現在値からアドレス減少方向にデータをたどり、0xefが3回連続で出てくる箇所(0x5510)を探し、これが0x5510番地なので(2)の通り2800バイトとなるね。

タスクのスタック

さて、ビリーはいつでもみんなからの質問を待っているよ。また、解説してほしいテーマがあれば、リクエストも受付中!!

ページのトップへ