0xBAAAAAAD アドレス
すべてのペリフェラルメモリにアクセスできるわけではありません。次のプログラムを見てください。
#![no_main]
#![no_std]
use core::ptr;
#[allow(unused_imports)]
use aux7::{entry, iprint, iprintln};
#[entry]
fn main() -> ! {
aux7::init();
unsafe {
ptr::read_volatile(0x4800_1800 as *const u32);
}
loop {}
}
このアドレスは、以前に使った GPIOE_BSRR のアドレスに近いですが、このアドレスは無効です。
無効というのは、このアドレスにはレジスタが存在しないという意味です。
では、試してみましょう。
$ cargo run
(..)
Breakpoint 1, registers::__cortex_m_rt_main_trampoline () at src/07-registers/src/main.rs:9
9 #[entry]
(gdb) continue
Continuing.
Breakpoint 3, cortex_m_rt::HardFault_ (ef=0x20009fb0)
at ~/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:560
560 loop {
(gdb)
存在しないメモリを読み取るという無効な操作を実行しようとしたため、プロセッサは 例外、つまりハードウェア例外を発生させました。
ほとんどの場合、例外はプロセッサが無効な操作を実行しようとしたときに発生します。 例外はプログラムの通常の流れを中断し、プロセッサに例外ハンドラを実行させます。 これは単なる関数 / サブルーチンです。
例外にはさまざまな種類があります。各種類の例外は異なる条件で発生し、 それぞれ異なる例外ハンドラによって処理されます。
aux7 クレートは cortex-m-rt クレートに依存しており、後者は
「無効なメモリアドレス」例外を処理する、HardFault という名前のデフォルトの
ハードフォールトハンドラを定義しています。openocd.gdb は HardFault に
ブレークポイントを置いていました。そのため、デバッガは例外ハンドラを実行している
最中にプログラムを停止させたのです。
デバッガから、この例外についてさらに詳しい情報を取得できます。見てみましょう。
(gdb) list
555 #[allow(unused_variables)]
556 #[doc(hidden)]
557 #[link_section = ".HardFault.default"]
558 #[no_mangle]
559 pub unsafe extern "C" fn HardFault_(ef: &ExceptionFrame) -> ! {
560 loop {
561 // UDF 命令に変換されるのを防ぐために何らかの副作用を追加する
562 // 詳細は rust-lang/rust#28728 を参照
563 atomic::compiler_fence(Ordering::SeqCst);
564 }
ef は、例外が発生する直前のプログラム状態のスナップショットです。調べてみましょう。
(gdb) print/x *ef
$1 = cortex_m_rt::ExceptionFrame {
r0: 0x48001800,
r1: 0x80036b0,
r2: 0x1,
r3: 0x80000000,
r12: 0xb,
lr: 0x800020d,
pc: 0x8001750,
xpsr: 0xa1000200
}
ここにはいくつかのフィールドがありますが、最も重要なのは pc、つまり Program Counter レジスタです。
このレジスタ内のアドレスは、例外を引き起こした命令を指しています。
問題のある命令の周辺を逆アセンブルしてみましょう。
(gdb) disassemble /m ef.pc
Dump of assembler code for function core::ptr::read_volatile<u32>:
1046 pub unsafe fn read_volatile<T>(src: *const T) -> T {
0x0800174c <+0>: sub sp, #12
0x0800174e <+2>: str r0, [sp, #4]
1047 if cfg!(debug_assertions) && !is_aligned_and_not_null(src) {
1048 // コード生成への影響を小さく保つため、panic は行わない。
1049 abort();
1050 }
1051 // SAFETY: 呼び出し元は `volatile_load` の安全性契約を守らなければならない。
1052 unsafe { intrinsics::volatile_load(src) }
0x08001750 <+4>: ldr r0, [r0, #0]
0x08001752 <+6>: str r0, [sp, #8]
0x08001754 <+8>: ldr r0, [sp, #8]
0x08001756 <+10>: str r0, [sp, #0]
0x08001758 <+12>: b.n 0x800175a <core::ptr::read_volatile<u32>+14>
1053 }
0x0800175a <+14>: ldr r0, [sp, #0]
0x0800175c <+16>: add sp, #12
0x0800175e <+18>: bx lr
End of assembler dump.
例外は ldr r0, [r0, #0] 命令、つまり読み取り命令によって発生しました。この命令は
r0 レジスタが示すアドレスのメモリを読み取ろうとしました。ちなみに、r0 は CPU
(プロセッサ) レジスタであり、メモリマップトレジスタではありません。たとえば
GPIO_BSRR のように、対応するアドレスを持っているわけではありません。
例外が発生したまさにその瞬間に、r0 レジスタの値が何だったのかを確認できたら便利だと
思いませんか? 実は、もうすでに確認しています! 先ほど出力した ef の値にある r0
フィールドが、例外発生時の r0 レジスタの値です。もう一度示します。
(gdb) print/x *ef
$1 = cortex_m_rt::ExceptionFrame {
r0: 0x48001800,
r1: 0x80036b0,
r2: 0x1,
r3: 0x80000000,
r12: 0xb,
lr: 0x800020d,
pc: 0x8001750,
xpsr: 0xa1000200
}
r0 には 0x4800_1800 という値が入っており、これは read_volatile
関数に渡した無効なアドレスです。