Linux
rust
embedded
0

Rustで組み込みLinuxで物理メモリ空間のレジスタをダンプする

以前、Golangから物理メモリを読み書きするという記事を書きました。似たようなことをRustでやってみました。ただし今回は /dev/mem をmmapするのでroot権限が必要です。

ソースコードはgistに貼りました。ここ

使用例

アクセスサイズ、物理アドレス、長さを引数で指定します。
アクセスサイズは 1,2,4,8 のいずれか。物理アドレスと長さは16進数です。 先頭に0xをつけてもつけなくてもOK。

# ./dump_pmem
Dump physical memory by specified size.
Usage: ./dump_pmem size address [len]
  where size={1,2,4,8}, address and len in hexadecimal.

表示は一行に一個づつです。

# ./dump_pmem 4 0x12030078 0x10
0000000012030078: 02580257
0000000012030079: 02570258
000000001203007a: 02570257
000000001203007b: 02570257
000000001203007c: 00000000
000000001203007d: 00000000
000000001203007e: 00000010
000000001203007f: 00000000
0000000012030080: 00000000
0000000012030081: 18000000
0000000012030082: 00000000
0000000012030083: 00000555
0000000012030084: 00000000
0000000012030085: 66666662
0000000012030086: 00066666
0000000012030087: 00000033
# ./dump_pmem 1 0x12030078 0x4
0000000012030078: 5b
0000000012030079: 02
000000001203007a: 5a
000000001203007b: 02
# 

アドレスの表示が間違ってました。。明日以降に修正します。

mmapの方法

Rustでmmapシステムコールを使うには以下の3つの方法が考えられます。

  1. libcクレートでmmapを呼び出す
  2. mmapクレートを使用する
  3. memmapクレートを使用する

1.の方法が一番原始的で後始末のmunmapも自分で明示的に行う必要があります。
2. と 3. を試したのですが3.が一番使い勝手がよかったのでこれを採用しました。

memmap クレートを使用する

以下の関数で、引数で指定したaddr, len で物理メモリをマッピングできます。

extern crate memmap;
use memmap::Mmap;
use std::fs::File;
use std::error::Error;

fn map_physical_mem(addr: usize, len: usize) -> Result<Mmap, Box<Error>> {
    let m = unsafe {
        memmap::MmapOptions::new()
            .offset(addr as u64)
            .len(len)
            .map(&File::open("/dev/mem")?)?
    };
    Ok(m)
}

取得できたm[u8] (u8のスライス)として扱うことができます。アクセスするときに unsafe で囲む必要もありません。
これのスコープが外れたときに自動的にmunmapが行われます。
実際のmmapはページサイズ単位で行われますが、ここで指定するアドレスはそれを意識する必要はありません。

バイトアクセスでダンプするコードはこちら。m[x]の部分がバイト単位のアクセスです。
簡単ですね!

fn dump_mem_u8(addr: usize, len: usize) {
    let m = match map_physical_mem(addr, len) {
        Ok(m) => m,
        Err(err) => {
            panic!("Failed to mmap: Err={:?}", err);
        }
    };
    (0..len).for_each(|x| println!("{:016x}: {:02x}", addr + x, m[x]));
}

物理メモリ空間上のレジスタを読む場合は、アクセスサイズは重要です。必ずスペックシートで指定されたサイズでアクセスしなければなりません。
バイト以外でアクセスしたい場合は、ポインタを取得してキャストします。ポインタの演算に + は使えないのでoffset()を使用します。
mmemmap::Mmapとすると以下のような感じになります。

    let p = m.as_ptr() as *const u32;
    let value = unsafe {*p.offset(x as isize)};

ここでのpmが生きている間だけ有効です。pだけ取っておいて後で使おうとすると、mのスコープが外れたときにmunmapされているのでSEGVを引き起こしますので注意が必要です。(実際にそれをやってしまって少し悩みました。:)
それを考えると、バイトアクセスのときにmがそのままu8のスライスとして使えるようになっているのはよくできています。このようなミスの発生を未然に防いでいます。

実際に作った関数はこちら。ジェネリクスを使ってみました。

fn dump_mem<T>(addr: usize, len: usize)
where
    T: std::fmt::LowerHex,
{
    let sz = std::mem::size_of::<T>();
    let m = match map_physical_mem(addr, len * sz) {
        Ok(m) => m,
        Err(err) => {
            panic!("Failed to mmap: Err={:?}", err);
        }
    };
    let p = m.as_ptr() as *const T;
    (0..len).for_each(|x| unsafe {
        let xi = x as isize;
        match sz {
            1 => println!("{:016x}: {:02x}", addr + x, *p.offset(xi)),
            2 => println!("{:016x}: {:04x}", addr + x, *p.offset(xi)),
            4 => println!("{:016x}: {:08x}", addr + x, *p.offset(xi)),
            8 => println!("{:016x}: {:016x}", addr + x, *p.offset(xi)),
            _ => println!("{:016x}: {:x}", addr + x, *p.offset(xi)),
        }
    });
}

余談

println!などのformat!マクロでは最初の引数は必ずstringのリテラルでなくてはいけません。ここに変数を置くとコンパイルエラーになります。そのために、このように matchでずらずらと並べざるを得ませんでした。
しかしよく考えてみると、このszはコンパイル時には定数の扱いですから、matchの選択肢は一つだけ除いて残りは最適化で消えます。そうするとすっきりしたコードが残ることがわかります。
Rustのコンパイルを通すのはなかなかつらいけど、苦労した後でできたコードがすっきりするのが気持ちいいですね。