嵌め込み開発における問題解決において、プログラムが予期せず停止する場合、効率的なデバッグ方法としてARMレジスタの状態を確認することが非常に有効です。この記事では、Keil MDKを使用してARMコアのレジスタ値を確認し、プログラムの異常動作の原因を特定する方法について解説します。
レジスタがなぜ重要か?
Cortex-MシリーズのARMアーキテクチャでは、16個の32ビット汎用レジスタ(R0〜R15)といくつかの特殊機能レジスタが定義されています。その中でも特に重要なのは以下の4つです:
| レジスタ | 別名 | 主要な役割 |
|---|---|---|
| R13 | SP | スタックポインタ — 現在のスタックトップ位置 |
| R14 | LR | リンクレジスタ — 関数の戻りアドレス |
| R15 | PC | プログラムカウンタ — 次に実行される命令のアドレス |
| xPSR | ステータスレジスタ | 割り込み番号や条件フラグなどを含む |
これらのレジスタは、プログラムの実行コンテキストを示す核心的なスナップショットとなります。
例えば、以下のようなシナリオが考えられます:
- SPが不正なメモリ領域を指している場合、スタックオーバーフローが発生している可能性が高い。
- LRが
0xFFFFFFFFや0xFFFFFFFEの場合、関数呼び出しチェーンが破損している。 - PCが異常なアドレス(例: 0)に停止している場合、空ポインタ参照や割り込みベクタテーブルのエラーが原因である可能性がある。
Keil MDKでレジスタを確認する手順
ステップ1: デバッグモードへの移行
プロジェクトが正常に構築されていることを確認してください。
- ツールバー上の "Debug"ボタン(虫アイコン)をクリックします。
- Keilが自動的にプログラムをMCUに書き込み、
main()関数の最初の行で一時停止します。 - この時点でターゲットチップはhalted状態となり、すべてのレジスタを安全に読み取ることができます。
ステップ2: レジスタビューの表示
メニューから次のように選択します:
View → Registers Window
Core Registersタブには、次の内容が表示されます:
R0 = 0x00000000
R1 = 0x20000200
R2 = 0x08001234
...
SP = 0x20001000 ← 現在のスタックポインタ
LR = 0xFFFFFFFD ← 特殊な戻りマーク
PC = 0x08000123 ← 次に実行される命令のアドレス
xPSR= 0x01000000 ← 現在Reset Handlerにある
MSP = 0x20001000
PSP = 0x00000000
CONTROL = 0x00000000
ステップ3: 動的観察
ブレークポイントを設定することで、プログラムの実行中にレジスタの変化を追跡できます。
例えば、タイマ割り込みハンドラ内でシステムが再起動する場合、次の手順で調査可能です:
- 割り込みハンドラの入口にブレークポイントを設定します。
- プログラムを実行し、ブレークポイントで一時停止します。
- Registersウィンドウを開き、次の点を確認します:
- PCが正しい関数に遷移しているか。
- SPが適切に減少しているか。
- LRが有効な戻りアドレスを持っているか。
ステップ4: コールスタックの利用
Call Stack + Localsウィンドウを使用すると、現在の関数呼び出し階層や各スタックフレーム内のローカル変数を確認できます。
実践例:ハードフォールトの解析
プログラムが数秒後に死に、出力がない場合、次の手順で調査します:
- デバッグモードでプログラムを実行し、一時停止します。
- xPSRを確認し、ハードフォールトであることを確認します。
- PCが不正なアドレスを指している場合、空ポインタ参照または未マッピングメモリへのアクセスが原因と考えられます。
- SPがSRAM上限に近い場合、スタックオーバーフローが発生している可能性があります。
解決策としては、スタックサイズを増やすことで問題が解決する場合があります。
extern uint32_t _estack; // スタックトップアドレス(リンカースクリプトで定義)
extern uint32_t __initial_sp; // 初期スタックトップ
uint32_t get_stack_usage(void) {
uint32_t *p = (uint32_t*)&_estack;
uint32_t cnt = 0;
while (p > (uint32_t*)&__initial_sp && *p == 0xCC) {
p--;
cnt++;
}
return cnt * 4; // 各ワードは4バイト
}
高度な技術:ハードフォールト中のコンテキスト保存
以下のコードは、ハードフォールト時にレジスタのスナップショットを取得します。
__attribute__((naked)) void HardFault_Handler(void) {
__asm volatile (
"TST LR, #4 \n" // EXC_RETURNがPSPを使用しているか判定
"ITE EQ \n"
"MRSEQ R0, MSP \n" // MSPを使用
"MRSNE R0, PSP \n" // PSPを使用
"B hard_fault_c \n" // C関数へジャンプ
);
}
void hard_fault_c(uint32_t *sp) {
uint32_t r0 = sp[0];
uint32_t r1 = sp[1];
uint32_t r2 = sp[2];
uint32_t r3 = sp[3];
uint32_t r12 = sp[4];
uint32_t lr = sp[5]; // 戻りアドレス
uint32_t pc = sp[6]; // エラー発生時の命令アドレス
uint32_t psr = sp[7];
__disable_irq();
while (1);
}
これにより、ハードフォールト発生時の詳細情報を取得できます。