Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

以前、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 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を呼び出す
  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 + 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 を使うようにしました。
ということで、更新した関数はこちら。

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に貼りました。ここ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away