Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

0xBAAAAAAD アドレス

すべてのペリフェラルメモリにアクセスできるわけではありません。このプログラム(examples/bad.rs)を見てください。

#![no_main]
#![no_std]

use core::ptr;

#[allow(unused_imports)]
use registers::entry;

#[entry]
fn main() -> ! {
    registers::init();

    unsafe {
        ptr::read_volatile(0x5000_A784 as *const u32);
    }

    loop {
        core::hint::spin_loop();
    }
}

このアドレスは前に使った OUT アドレスに近いですが、このアドレスは 無効 です。つまり、 このアドレスにはレジスタが存在しません。

では、試してみましょう。

$ cargo run
(..)
Resetting and halting target
Target halted
(gdb) continue
Continuing.

Breakpoint 1, registers::__cortex_m_rt_main_trampoline () at src/07-registers/src/main.rs:9
9	#[entry]
(gdb) continue
Continuing.

Program received signal SIGINT, Interrupt.
registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:10
10	fn main() -> ! {
(gdb) continue
Continuing.

Breakpoint 3, cortex_m_rt::HardFault_ (ef=0x2001ffb8) at src/lib.rs:1046
1046	    loop {}
(gdb) 

存在しないメモリを読むという無効な操作を行おうとしたため、プロセッサは 例外、 つまり ハードウェア 例外を発生させました。

多くの場合、例外はプロセッサが無効な操作を実行しようとしたときに発生します。 例外はプログラムの通常の流れを中断し、プロセッサに 例外ハンドラ を実行させます。 これは単なる関数/サブルーチンです。

例外にはさまざまな種類があります。各種類の例外は異なる条件で発生し、 それぞれ別の例外ハンドラによって処理されます。

registers クレートは cortex-m-rt クレートに依存しており、このクレートは HardFault_ という名前のデフォルトの ハードフォールト ハンドラを定義しています。 これは「無効なメモリアドレス」例外を処理します。embed.gdbHardFault に ブレークポイントを設定していたため、デバッガは例外ハンドラの実行中にプログラムを 停止しました。デバッガからこの例外についてさらに詳しい情報を得ることができます。 見てみましょう:

(gdb) list
1040  #[allow(unused_variables)]
1041	#[doc(hidden)]
1042	#[cfg_attr(cortex_m, link_section = ".HardFault.default")]
1043	#[no_mangle]
1044	pub unsafe extern "C" fn HardFault_(ef: &ExceptionFrame) -> ! {
1045	    #[allow(clippy::empty_loop)]
1046	    loop {}
1047	}
1048	
1049	#[doc(hidden)]
1050	#[no_mangle]

ef は、例外が発生する直前のプログラム状態のスナップショットです。これを調べてみましょう:

(gdb) print/x *ef
$1 = cortex_m_rt::ExceptionFrame {
  r0: 0x5000a784,
  r1: 0x3,
  r2: 0x2001ff24,
  r3: 0x0,
  r12: 0x1,
  lr: 0x4403,
  pc: 0x43ea,
  xpsr: 0x1000000
}

ここにはいくつかのフィールドがありますが、最も重要なのはプログラムカウンタレジスタである pc です。この レジスタ内のアドレスは、例外を発生させた命令を指しています。問題のある命令の周辺を逆アセンブルしてみましょう。

(gdb) disassemble /m ef.pc
Dump of assembler code for function core::ptr::read_volatile<u32>:
1654	pub unsafe fn read_volatile<T>(src: *const T) -> T {
   0x000043d2 <+0>:	push	{r7, lr}
   0x000043d4 <+2>:	mov	r7, sp
   0x000043d6 <+4>:	sub	sp, #16
   0x000043d8 <+6>:	str	r0, [sp, #4]
   0x000043da <+8>:	str	r0, [sp, #8]

1655	    // SAFETY: 呼び出し元は `volatile_load` の安全性契約を守らなければならない。
1656	    unsafe {
1657	        assert_unsafe_precondition!(
   0x000043dc <+10>:	b.n	0x43de <core::ptr::read_volatile<u32>+12>
   0x000043de <+12>:	ldr	r0, [sp, #4]
   0x000043e0 <+14>:	movs	r1, #4
   0x000043e2 <+16>:	bl	0x43f4 <core::ptr::read_volatile::precondition_check>
   0x000043e6 <+20>:	b.n	0x43e8 <core::ptr::read_volatile<u32>+22>

1658	            check_language_ub,
1659	            "ptr::read_volatile requires that the pointer argument is aligned and non-null",
1660	            (
1661	                addr: *const () = src as *const (),
1662	                align: usize = align_of::<T>(),
1663	            ) => is_aligned_and_not_null(addr, align)
1664	        );
1665	        intrinsics::volatile_load(src)
   0x000043e8 <+22>:	ldr	r0, [sp, #4]
   0x000043ea <+24>:	ldr	r0, [r0, #0]          ; <-- これです!
   0x000043ec <+26>:	str	r0, [sp, #12]
   0x000043ee <+28>:	ldr	r0, [sp, #12]

1666	    }
1667	}
   0x000043f0 <+30>:	add	sp, #16
   0x000043f2 <+32>:	pop	{r7, pc}

End of assembler dump.

この例外は、読み取り命令である ldr r0, [r0, #0] 命令によって引き起こされました。この命令 は、r0 CPU レジスタ が示すアドレスのメモリを読み取ろうとしました。ちなみに、CPU (プロセッサ)レジスタはメモリマップドレジスタではありません。したがって、たとえば OUT のような対応するアドレスはありません。

例外が発生したまさにその瞬間に、r0 レジスタの値が何だったのか確認できたら便利だと 思いませんか。実は、もう確認しています!先ほど表示した ef の値にある r0 フィールド が、例外発生時の r0 レジスタの値です。もう一度示します:

(gdb) print/x *ef
$1 = cortex_m_rt::ExceptionFrame {
  r0: 0x5000a784,
  r1: 0x3,
  r2: 0x2001ff24,
  r3: 0x0,
  r12: 0x1,
  lr: 0x4403,
  pc: 0x43ea,
  xpsr: 0x1000000
}

r0 には 0x5000_A784 という値が入っており、これは read_volatile 関数を呼び出すときに渡した無効なアドレスです。