マイコン開発では、スタックオーバーフローによって突然Hard Faultが発生することがあります。しかし、スタック領域の伸びる方向やメモリ配置を考えると「本当に戻り先アドレスが上書きされるのか」と疑問に感じる方も少なくありません。この記事では、C言語で動作する組み込みマイコンを例に、スタックオーバーフローからHard Faultが発生するまでの流れを、スタックの仕組みやメモリ配置を含めて分かりやすく解説します。
マイコンのスタック領域とは何か
スタックとは、関数呼び出し時に一時的なデータを保存するためのメモリ領域です。C言語のプログラムでは、ローカル変数、関数の引数、戻り先アドレス、レジスタ退避情報などがスタックに保存されます。
例えば関数Aから関数Bを呼び出した場合、CPUは「関数Bが終了した後に戻る場所」を覚えておく必要があります。そのため、関数呼び出し時には戻り先アドレスがスタックへ保存されます。
ARM系マイコンなど多くの組み込みCPUでは、スタックはRAMの高アドレス側から低アドレス側へ伸びる方式が一般的です。ただし、重要なのはスタックがどちら向きに伸びるかではなく、スタック領域の範囲を超えて書き込みが発生すると、隣接する別領域を破壊する可能性があるという点です。
スタックオーバーフローで戻り先アドレスが壊れる理由
スタックオーバーフローが発生すると、スタックポインタが指している正常な領域を超えてデータを書き込むことになります。
例えば、ある関数のスタック上に以下のような配置があったとします。
| アドレス | 保存内容 |
|---|---|
| 高アドレス側 | 呼び出し元へ戻るアドレス |
| 中間 | 保存レジスタ |
| 低アドレス側 | ローカル配列や変数 |
ここで大きすぎる配列への書き込みや、配列範囲外アクセスが発生すると、本来書き込むべきでないスタック領域までデータが侵入します。
例えば、char型配列を10バイトしか確保していないのに100バイト書き込むような処理を行うと、配列の後ろに配置されている保存データが破壊されます。その結果、戻り先アドレスが不正な値になる場合があります。
スタックは一方向なのになぜ上書きが起きるのか
スタックが一方向に伸びるという認識は正しいですが、それは「新しいデータを配置する方向」を示しているだけです。スタック以外のコードが、その範囲を守って書き込みを行う保証はありません。
つまり問題になるのは、スタック領域を新しく確保する動作ではなく、既に確保されたスタック上のデータに対して、プログラムが誤った書き込みを行うことです。
例えば、スタック領域が下方向へ伸びるCPUで、低アドレス側にある配列へ大量のデータを書き込むと、上位アドレス側にある保存情報まで到達することがあります。その結果、戻り先アドレスや保存レジスタが破壊されます。
Hard Faultが発生するまでの具体的な流れ
スタックオーバーフローからHard Faultが発生する流れは、一般的には以下のようになります。
- 関数呼び出しによってスタック領域が使用される
- 大きすぎるローカル配列や再帰処理などでスタック使用量が増える
- スタック領域の限界を超えてメモリ破壊が発生する
- 戻り先アドレスや保存されたCPU情報が壊れる
- 関数終了時にCPUが壊れたアドレスへジャンプしようとする
- 存在しないアドレスや実行禁止領域へアクセスする
- CPUの例外処理によってHard Faultが発生する
例えば、関数終了時に本来なら0x08001234番地へ戻るはずだったものが、スタック破壊によって0xFFFFFFFFのような不正値になった場合、CPUはそのアドレスを実行しようとして異常を検出します。
スタックオーバーフローの主な原因
組み込み開発でスタックオーバーフローを起こしやすい原因には、大きなローカル変数の確保があります。
例えば、以下のようなコードは注意が必要です。
char buffer[10000];
このような大きな配列を関数内で宣言すると、RAM容量が限られるマイコンでは簡単にスタックを圧迫します。
また、終了条件のない再帰関数や、多重割り込みによるスタック使用量の増加も原因になります。
スタックオーバーフローを防ぐ方法
スタックオーバーフローを防ぐには、まずスタック使用量を把握することが重要です。多くの開発環境では、スタック領域のサイズ設定や使用量確認機能があります。
大きな配列はスタックではなく、グローバル領域や静的領域へ配置することでスタック消費を減らせます。
また、配列アクセスでは境界チェックを行い、バッファサイズを超える書き込みを防ぐことも重要です。組み込みシステムでは小さなメモリ破壊が、後からHard Faultとして現れることがあります。
まとめ
スタックオーバーフローによってHard Faultが発生する原因は、スタックが一方向に伸びることではなく、プログラムの誤ったメモリアクセスによってスタック領域外へデータを書き込んでしまうことです。
スタック上にはローカル変数だけでなく、戻り先アドレスやCPUの保存情報も存在するため、範囲外アクセスによってそれらが破壊される可能性があります。
マイコン開発では、スタックサイズの管理、大きな配列の扱い、メモリ破壊の防止を意識することで、原因不明のHard Faultを大きく減らすことができます。


コメント