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


以前、[Golangから物理メモリを読み書きする](https://qiita.com/tetsu_koba/items/dba170bf220c45781428)という記事を書きました。似たようなことをRustでやってみました。ただし今回は `/dev/mem ` をmmapするのでroot権限が必要です。

ソースコードはgistに貼りました。[ここ](https://gist.github.com/tetsu-koba/13206f80b46db7a8a4545029423142ab#file-main-rs)

# 使用例

アクセスサイズ、物理アドレス、長さを引数で指定します。
アクセスサイズは 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 0x8
0000000012030078: 02780278
000000001203007c: 02780279
0000000012030080: 02780278
0000000012030084: 02790279
0000000012030088: 00000000
000000001203008c: 00000000
0000000012030090: 00000010
0000000012030094: 00000000
# ./dump_pmem 2 0x12030078 4
0000000012030078: 0278
000000001203007a: 0278
000000001203007c: 0278
000000001203007e: 0278
# 
```

(2018.9.28: アドレスの表示が間違っていたので修正しました。)

# mmapの方法

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

1. [libcクレートでmmapを呼び出す](https://docs.rs/libc/0.2.43/libc/fn.mmap.html)
2. [mmapクレート](https://crates.io/crates/mmap)を使用する
3. [memmapクレート](https://crates.io/crates/memmap)を使用する

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


# memmap クレートを使用する

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

```rust
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]`の部分がバイト単位のアクセスです。
簡単ですね！ 

```rust
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()`を使用します。
`m` を`memmap::Mmap`とすると以下のような感じになります。

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

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

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

```rust
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 + sz * x, *p.offset(xi)),
            2 => println!("{:016x}: {:04x}", addr + sz * x, *p.offset(xi)),
            4 => println!("{:016x}: {:08x}", addr + sz * x, *p.offset(xi)),
            8 => println!("{:016x}: {:016x}", addr + sz * x, *p.offset(xi)),
            _ => println!("{:016x}: {:x}", addr + x, *p.offset(xi)),
        }
    });
}
```

## 余談

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

## 2018.9.28 更新
`dump_pmem`コマンドでアドレスの表示が間違っていたので修正しました。
fmtで表示幅をパラメータで渡す方法があることを教えていただきました。
https://doc.rust-lang.org/std/fmt/#width　
物理メモリから読むときには`std::ptr::read_volatile` を使うようにしました。
ということで、更新した関数はこちら。

```rust
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 {
        println!(
            "{:016x}: {:02$x}",
            addr + sz * x,
            std::ptr::read_volatile(p.offset(x as isize)),
            sz * 2
        );
    });
```

`write_pmem`コマンドも作ってgistに貼りました。[ここ](https://gist.github.com/tetsu-koba/13206f80b46db7a8a4545029423142ab#file-main-rs_write)