i386のアドレス変換の話

この記事はNetBSD Advent Calendar 2015 112日目の記事です(嘘)。


で外部からの観察ができたので、まとめておきます。

まずmi_switch(9)に以下のような細工をすることで、
CR3レジスタの内容を出力させました。

mi_switch(9) - NetBSD Manual Pages

#include <machine/pcb.h>
        if(strcmp(curproc->p_comm,"hoge") == 0) {
                struct pcb *pcb = lwp_getpcb(l);
                printf("cr3 = %x\n", pcb->pcb_cr3);
        }


http://nxr.netbsd.org/xref/src/sys/kern/kern_synch.c#780
あたりに追記します。

カーネルコンパイルし、mi_switchに細工をしたカーネルで再起動します。

とりあえず何かの仮想アドレスを取得したいので、

#include <stdio.h>
 
char foo [] = "WELCOME TO NETBSD!";
 
int main(void){
 
    printf("address foo = %d\n", foo);
    for(;;) {}
 
}

というプログラムを書き、
gcc -o hoge xxxx.c
コンパイルします。

また、物理アドレスのダンプを取りたいので、
デバイスドライバに頼らないハードウェア操作
を参考に、

int main(int argc,char **argv)
{
  char buff[1024];
  int fd;
  unsigned int st,len;
  int r;
 
  st=strtol(argv[1],NULL,16);
  len=strtol(argv[2],NULL,16);
 
  fd=open("/dev/mem",O_RDONLY);
  if(fd<0)
    {
      fprintf(stderr,"cannot open\n");
      return 1;
    }
  lseek(fd,st,SEEK_SET);
  while(len)
    {
      r=read(fd,buff,len>1024?1024:len);
      if(r<1) break;
      write(1,buff,r);
      len-=r;
    }
  close(fd);
  return 0;
}

のようなコードを書き(コピペ)、
gcc -o physdump xxxx.c
コンパイルしました。

僕はあまりプログラムを書いた事が無いので、

とつぶやいたところ、

とoshimayaさんに教えてもらいました!

1.mi_switchに細工をしてhogeというプログラムを実行すると、
 hogeプロセスのCR3レジスタの内容を出力するカーネル
2.仮想アドレスを取得するプログラム(プログラム名はhoge)
3./dev/mem経由で物理アドレスをダンプするプログラム(プログラム名はphysdump)
の3点セットが揃ったので、動かしてみます。

はじめに、仮想アドレスとCR3レジスタの内容を取得します。
hogeを実行すると

$ ./hoge
address foo = 134519060

とfooの仮想アドレスを表示し、ループに入ります。

すると、コンソールには

cr3 = 5f215000
cr3 = 5f215000
cr3 = 5f215000
cr3 = 5f215000
cr3 = 5f215000
cr3 = 5f215000

という表示が延々出力されるはずです。

これで、
fooの仮想アドレス(134519060)

CR3レジスタに格納されている物理アドレス(0x5f215000)が取得出来ました。

では仮想アドレス->物理アドレスの変換をやってみます!

まず、取得できた仮想アドレス134519060を2進数に変換します。
134519060 -> 0000.1000.0000.0100.1001.1001.0001.0100

CR3レジスタに格納されている物理アドレスはPD(ページディレクトリ)の開始物理アドレスです。
仮想アドレスの上位10ビットはPDのインデックスです。
PDのインデックスの値を2ビット左にシフトしたものと
CR3レジスタに格納されている物理アドレス(PDの開始物理アドレス=0x5f215000)を加算すると、
PDEが格納されている物理アドレスが得られます。

まず、仮想アドレス134519060のPDのインデックスは
0000.1000.00 -> 0000.1000.0000 -> 80
になります。

次に、取得したCR3に格納されている物理アドレス(PDの開始物理アドレス)を指定してダンプしてみます。

# ./physdump 5f215000 100 | hexdump -C
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
00000080  67 20 75 5e 00 00 00 00  00 00 00 00 00 00 00 00  |g u^............|
00000090  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
00000100

80の所には、67 20 75 5eが格納されていました。
i386はリトルエンディアンなので、逆転させて、
5e 75 20 67がPDEに格納されている内容になります。
5e 75 20 67のうち、下位12ビットはアクセス権やら、キャッシュやらの設定なので、オフします。

したがって、
0x5e752000がPT(ページテーブル)の開始物理アドレスになります。

さらに、PTEに格納されている内容を調べます。

仮想アドレスの中10ビットはPTのインデックスです。
PTのインデックスの値を2ビット左にシフトしたものと
PDEに格納されている物理アドレス(PTの開始物理アドレス=0x5e752000)を加算すると、
PTEが格納されている物理アドレスが得られます。

まず、仮想アドレス134519060のPTのインデックスは
00.0100.1001 -> 01.0010.0100 -> 124
になります。

次に、取得したPDEに格納されている物理アドレス(PTの開始物理アドレス)を指定してダンプしてみます。

# ./physdump 5e752000 200 | hexdump -C
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
00000120  25 0c 2e 5e 67 94 68 5e  00 00 00 00 00 00 00 00  |%..^g.h^........|
00000130  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
...
00000200

124の所には、67 94 68 5eが格納されていました。
i386はリトルエンディアンなので、逆転させて、
5e 68 94 67がPTEに格納されている内容になります。
5e 68 94 67のうち、下位12ビットはアクセス権やら、キャッシュやらの設定なので、オフします。

したがって、
0x5e689000がページフレームの開始物理アドレスになります。

最後に、仮想アドレスの下位12ビットはページ内のオフセットアドレスになります。
1001.0001.0100 -> 914

ページフレームの開始物理アドレス(0x5e689000)にオフセットアドレス(0x914)を加算して
ダンプを取ってみると...

# ./physdump 5e689914 30 | hexdump -C
00000000  57 45 4c 43 4f 4d 45 20  54 4f 20 4e 45 54 42 53  |WELCOME TO NETBS|
00000010  44 21 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |D!..............|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 f0 ff bf bf  |................|

となっています。
これはfooの中身ですね。

これで仮想アドレス->物理アドレスマッピングが勉強出来ました!

参考サイトに以下をあげておきます。softwaretechnique.jp