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)

不正な操作を試しました。存在していないメモリを読み込んだ結果、プロセッサは例外、つまりハードウェア例外を発生させました。

多くの場合、例外はプロセッサが不正な操作を実行しようとした時に発生します。 例外はプログラムの通常実行フローを停止し、プロセッサに例外ハンドラを実行させます。 例外ハンドラは、1つの関数/サブルーチンです。

異なる種類の例外が存在します。各種の例外は、異なる条件で発生し、各々が異なる例外ハンドラで処理されます。

aux7クレートは、cortex-m-rtクレートに依存しています。cortex-m-rtクレートは、 UserHardFaultと言うデフォルトのハードフォルトハンドラを定義しており、「不正なメモリアドレス」例外を処理します。 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             // add some side effect to prevent this from turning into a UDF instruction
562             // see rust-lang/rust#28728 for details
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です。 このレジスタのアドレスは、例外を発生させた命令を指しています。 不正な命令の周辺プログラムを逆アセンブルしてみましょう。

(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            // Not panicking to keep codegen impact smaller.
1049            abort();
1050        }
1051        // SAFETY: the caller must uphold the safety contract for `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レジスタの値が確認できると、良いと思いませんか? 既に確認できています! ここまでに表示したefr0フィールドの値が、例外発生時の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関数を呼ぶ時に指定した不正なアドレスです。