WindowsXP SP2でサポートされるようになったDEP(Data Execute Prevention, データ実行防止)機能。この機能を使うにはCPU側にNX bitが必要ですが、このNX bitについて考えてみます。
(本記事の初稿は2004年です。64ビットCPUが普及した現在でも有用な内容と思われるので、そのままの内容で公開します。)
■データ領域からの実行
NX bitのあるCPUでは、「実行禁止」とマーキングしたメモリ領域からの実行を禁止できます。正確には、実行しようとすると#PF(Page Fault)が発生し、OS側でそれを察知して当該プロセスを停止させることができます。
NX bitはページング機能の一部として実装されています。正確には、NX bitはリニアアドレス・変換テーブル(paging tables)のエントリーの中にあり、ページ単位でNX bitを設定することができます。さらにこの場合のページングはPAE(Physical Address Extension)モードである必要があります。(Long mode(IA32e mode)では、PAEモードは必ずEnableになっています。)
■セグメント・ディスクリプタによる保護
286で導入されたセグメンテーション機能(セグメント・ディスクリプタ)による保護機能を見てみます。NX bit以前にどんな保護機能が考えられていたのかが見えてきます。なお以下の記述は(x86系で最初の32ビットCPUである)386以降を想定しています。
セグメンテーション機能では、プログラムはセグメントレジスタ(CS, DS, SS, ES, FS, GS)に入っているセレクタ値とオフセットアドレス(IP(インストラクションポインタ) SP(スタックポインタ)など)でメモリにアクセスします。セレクタ値の属性(そのセレクタでアクセスできるメモリのベースアドレス、アドレス範囲など)はセグメント・ディスクリプタに設定します。セグメント・ディスクリプタには「当該メモリはコード領域かデータ領域か」を設定する箇所があり、この箇所を「コード領域」と設定すると、その領域は書き換えができなくなります。(コードセグメントは書き換え禁止。)「データ領域」についてはRead-Only, Read/Writeなどいくつかのパターンがあるものの、実行を禁止する設定はありません。
【セグメント・ディスクリプタに設定できる項目】
bit 機能
0-15 セグメント・リミット 15:00
16-39 ベース・アドレス 23:00
40-43 Type(セグメントのタイプ)
44 S(ディスクリプタのタイプ)
45-46 DPL(ディスクリプタの特権レベル)
47 P(セグメント存在フラグ)
48-51 セグメント・リミット 19:16
52 AVL(OSが自由に使えるフラグ)
53 0
54 D/B(デフォルトのオペレーションサイズ)
55 G(グラニュラリティ)
57-63 ベースアドレス 31:24
【Type(セグメントのタイプ)】 値(10進数) ディスクリプター・タイプ 説明 0 Data Read-Only 1 Data Read-Only, accessed 2 Data Read/Write 3 Data Read/Write, accessed 4 Data Read-Only, expand-down 5 Data Read-Only, expand-down, accessed 6 Data Read/Write, expand-down 7 Data Read/Write, expand-down, accessed 8 Code Execute-Only 9 Code Execute-Only, accessed 10 Code Execute/Read 11 Code Execute/Read, accessed 12 Code Execute-Only, conforming 13 Code Execute-Only, conforming, accessed 14 Code Execute/Read-Only, conforming 15 Code Execute/Read-Only, conforming, accessed
OSがプロセス起動時にコード領域にはCodeタイプのセグメント・ディスクリプタ、データ領域にはDataタイプのセグメント・ディスクリプタ、スタック領域にはDataタイプでかつexpand-down型のセグメント・ディスクリプタを割り当てるような使い方が想定されています。
■ページングによる保護
でもWindows等のOSはセグメンテーション機能(セグメント・ディスクリプタ)による保護機能を使っていません。
0~4GB(64bitでは0~16EB(16 ExaBytes≒172億GB))のアドレス空間をセグメント機能で分割して、各々の領域へのアクセス用にセレクタを割り当てるのではなく、ひとつのセレクタで0~4GBのアドレス空間にアクセスできるようにします。(フラット・メモリ・モデル)これによって各プロセスには0~4GBのフラットな仮想メモリ空間を割り当てられ、さらにプロセス毎にページング・テーブルを切り替えることでプロセス間のアドレス空間分離が行われているわけです。
■32ビットCPUでのページング
ページングの話に入る前に、アドレス空間の呼称をまとめておきます。アドレス空間には基本的に2種類あります。論理アドレス空間と物理アドレス空間です。論理アドレスとは、プロセスから見えるアドレスで、物理アドレスとはCPUのアドレスバスから実際に出力されるアドレスのことです。似たものとして仮想アドレス空間、リニア・アドレス空間があります。これらはいずれも論理アドレス空間のことですが、ニュアンスが微妙に異なります。仮想アドレス空間はページングと仮想メモリ(Virtual Memory)がEnableなときの論理アドレス空間、リニア・アドレス空間はページングがEnableなときの論理アドレス空間のことです。ページングがDisableなときは論理アドレス空間=物理アドレス空間ですが、ページングがEnableなときは論理アドレス空間≠物理アドレス空間になります。
ページングとはリニア・アドレスを物理アドレスに変換する仕組みのことです。あらかじめ(メモリ上に)作成しておいたページング・テーブル(paging tables)をもとに変換します。ページング・テーブルには論理アドレスと物理アドレスの対照表が書かれており、この情報を元にCPUが自動的にリニア・アドレスを物理アドレスに変換してくれます。ページング機能のEnable/DisableはCR0レジスタのPGビット(Paging)で制御します。アドレス変換の仕組みの詳細は以下のようになります。
【IA32でのリニア・アドレス変換(4KByte page, PAEなし)】 (1)リニア・アドレスを3分割 リニア・アドレスを下記のように3分割して、Directory Offset値、 Table Offset値、Page Offset値とする。 31 22 21 12 11 0 ┌───────────┬───────────┬──────────┐ │Directory │Table │Page │ │Offset │Offset │Offset │ └───────────┴───────────┴──────────┘ (2)Page Directoryを検索 Page Directory(というテーブル)内のDirectory Offset値に対応する Page-Directory Entry(Directory Offset値番目の項目)を見る。 Page Directoryそのものの先頭の物理アドレス(ベースアドレス)は CR3レジスタに入っている。 Page-Directory Entry 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌────────────────────────────────┬──────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ P │ │ │ P │ P │ U │ R │ │ │Page-Table Base Address │Avail │ G │ S │ 0 │ A │ C │ W │ / │ / │ P │ │ │ │ │ │ │ │ D │ T │ S │ W │ │ └────────────────────────────────┴──────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Directory EntryからPage-Table Base Address(20bit)を取り出し、 下部にゼロを12bit(2^12=4096=4K-byte=page-size)付け加えたアドレス(物理アドレス)を求める。 このアドレス(32ビットの物理アドレス)から始まるPage Tableを検索する。 (3)Page Tableを検索 Page Table内のTable Offset値に対応するPage-Table Entry(Table Offset値番目の項目)を見る。 Page-Table Entry 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌────────────────────────────────┬──────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ P │ │ │ P │ P │ U │ R │ │ │Page Base Address │Avail │ G │ A │ D │ A │ C │ W │ / │ / │ P │ │ │ │ │ T │ │ │ D │ T │ S │ W │ │ └────────────────────────────────┴──────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Table EntryからPage Base Address(20bit)を取り出し、 下部にPage Offset値(12bit, 2^12=4096=4K-byte=page-size)を付け加えたアドレスが、(1)のリニア・アドレス(32ビット)に対応する32ビットの物理アドレスである。
次にNX bit双方がEnableなときのリニア・アドレス変換を見てみます。(NX bitはPAEモードがEnableでないと使えませんので、必然的にPAEモードもEnableになります。)PAEモードのEnable/DisableはCR4レジスタのPAEビット(Physical Address Extension)で制御し、NX bitのEnable/DisableはExtended Feature Enable MSR(AMD64ではExtended Feature Enable Register)のNXEビット(No-Execute Enable)で制御します。
なおPAEモードとは、32ビットCPUで32ビット(4GB)を超える物理アドレスを扱うために導入された仕組みです。32ビットCPUのPAEモードでは36ビット(64GB)の物理アドレスを扱うことが出来ます。PAEモードはPentium PRO以降のCPUに実装されています。(ただしPAEモードが実装されていても、NX bitが実装されているとは限りません。)AMD64/EM64TのLong mode(IA32e mode)ではPAEモードは必ずEnableになります。(というかPAEモードがEnableでないと、Long mode(IA32e mode)に切り替えられない。)
【IA32でのリニア・アドレス変換(4KByte page, PAEあり, NX bitあり)】 (1)リニア・アドレスを4分割 リニア・アドレスを下記のように4分割して、 Directory-Pointer Offset値、Directory Offset値、 Table Offset値、Page Offset値とする。 31 30 29 21 20 12 11 0 ┌───────────────────┬───────────┬───────────┬───────────┐ │Directory-Pointer │ Directory │ Table │ Page │ │Offset │ Offset │ Offset │ Offset │ └───────────────────┴───────────┴───────────┴───────────┘ (2)Page-Directory-Pointer Tableを検索 Page-Directory-Pointer Table内のDirectory-Pointer Offset値に対応する Page-Directory-Pointer-Table Entry(Directory-Pointer Offset値番目の項目)を見る。 Page-Directory-Pointer Tableそのものの先頭の物理アドレス(ベースアドレス)は CR3レジスタに入っている。 Page-Directory-Pointer-Table Entry 63 36 35 32 ┌───────────────────────────────┬───────────────────────────────────────────┐ │ │ │ │Reserved │ Page-Directory Base Address │ │ │ │ └───────────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 5 4 3 2 1 0 ┌───────────────────────────────┬───────┬───────────────┬───┬───┬───────┬───┐ │ │ │ │ P │ P │ │ │ │Page-Directory Base Address │ Avail │ Reserved │ C │ W │ Res. │ P │ │ │ │ │ D │ T │ │ │ └───────────────────────────────┴───────┴───────────────┴───┴───┴───────┴───┘ Page-Directory-Pointer-Table EntryからPage-Directory Base Address(24bit)を取り出し、 下部にゼロを12bit(2^12=4096=4K-byte=page-size)付け加えたアドレス(物理アドレス)を求める。 このアドレス(36ビットの物理アドレス)から始まるPage Directoryを検索する。 (3)Page Directoryを検索 Page Directory(というテーブル)内のDirectory Offset値に対応する Page-Directory Entry(Directory Offset値番目の項目)を見る。 Page-Directory Entry 63 62 36 35 32 ┌─────┬─────────────────────────┬───────────────────────────────────────────┐ │ │ │ │ │ NX │ Reserved │ Page-Table Base Address │ │ │ │ │ └─────┴─────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌───────────────────────────────┬───────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ │ │ │ P │ P │ U │ R │ │ │Page-Table Base Address │ Avail │ 0 │ 0 │ 0 │ A │ C │ W │ / │ / │ P │ │ │ │ │ │ │ │ D │ T │ S │ W │ │ └───────────────────────────────┴───────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Directory EntryからPage-Table Base Address(24bit)を取り出し、 下部にゼロを12bit(2^12=4096=4K-byte=page-size)付け加えたアドレス(物理アドレス)を求める。 このアドレス(36ビットの物理アドレス)から始まるPage Tableを検索する。 (4)Page Tableを検索 Page Table内のTable Offset値に対応するPage Table Entry(Table Offset値番目の項目)を見る。 Page-Table Entry 63 62 36 35 32 ┌─────┬──────────────────────────┬───────────────────────────────────────────┐ │ │ │ │ │ NX │ Reserved │ Page Base Address │ │ │ │ │ └─────┴──────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌───────────────────────────────┬───────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ P │ │ │ P │ P │ U │ R │ │ │Page Base Address │ Avail │ G │ A │ D │ A │ C │ W │ / │ / │ P │ │ │ │ │ T │ │ │ D │ T │ S │ W │ │ └───────────────────────────────┴───────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Table EntryからPage Base Address(24bit)を取り出し、 下部にPage Offset値(12bit, 2^12=4096=4K-byte=page-size)を付け加えたアドレスが、 (1)のリニア・アドレス(32ビット)に対応する36ビットの物理アドレスである。
NX bitが出てきました。Entry内のNX bitが1になっていると、そのEntry内のBase Addressがポイントするページング・テーブルに記載されているページを実行することができなくなります。(実行しようとすると、#PF(page fault)が発生します。)
ここまで述べてきたリニア・アドレス変換はすべてCPUが自動的に行います。アドレス変換の際には、各種ページング・テーブル(Page-Directory-Pointer Table, Page Directory, Page Tableなど)内のEntryがCPU内のアドレス変換専用のキャッシュ領域に読み込まれます。このキャッシュ領域をTLB(Translation-Lookaside Buffers)と呼びます。NX bitがチェックされるのは、命令を実行しようとした時ではなく、Entryが(命令用の)TLBに読み込まれたときです。すなわち命令実行よりもずっと先立ってチェックされるわけです。
■64ビットCPUでのページング
最後にAMD64/EM64TのLong mode(IA32e mode)でのページングを見てみます。考え方はIA32のときと同じです。
なおAMD64/EM64Tでは物理アドレスは52bit(4PB(PetaBytes)≒420万GB(GigaBytes))に
制限されています。(現行のAMD64/EM64Tではこれよりも少ない40bit(1TB(TeraBytes)=1024GB(GigaBytes))に制限されています。)
なおLong mode(IA32e mode)でもセグメンテーション機能(セグメント・ディスクリプタ)は有効ですが、IA32の場合と比べると一部の設定が無視されるなど簡略化されています。これはページング機能を用いてフラット・メモリ・モデルを使うことを前提としていることによるものと思われます。
【AMD64/EM64Tでのリニア・アドレス変換(Long mode(IA32e mode), 4KBbyte page)】 (1)リニア・アドレスを6分割 リニア・アドレスを下記のように6分割して、 Page-Map Level-4 Offset値、 Directory-Pointer Offset値、Directory Offset値、 Table Offset値、Page Offset値とする。 (最上位のフィールド(48ビット目から63ビット目まで)は使わない。すなわちリニアアドレス(仮想アドレス)は48ビット(=256TB)に制限される。) 63 48 47 39 38 30 29 21 20 12 11 0 ┌─────────┬───────────────┬───────────────────┬───────────┬───────────┬───────────┐ │Sign │ Page-Map │ Directory-Pointer │ Directory │ Table │ Page │ │Extend │ Level-4 Offset│ Offset │ Offset │ Offset │ Offset │ └─────────┴───────────────┴───────────────────┴───────────┴───────────┴───────────┘ (2)Page-Map Level-4 Tableを検索 Page-Map Level-4 Table内のPage-Map Level-4 Offset値に対応する Page-Map Level-4 Entry(Page-Map Level-4 Offset値番目の項目)を見る。 Page-Map Level-4 Tableそのものの先頭の物理アドレス(ベースアドレス)は CR3レジスタに入っている。 Page-Map Level-4 Entry 63 62 52 51 32 ┌─────┬─────────────────────────┬───────────────────────────────────────────┐ │ │ │ │ │ NX │ Available │ Page-Directory-Pointer Base Address │ │ │ │ │ └─────┴─────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌────────────────────────────────┬───────┬──┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ │ 無│ │ P │ P │ U │ R │ │ │ Page-Directory-Pointer │ Avail │0 │ 0 │ 視│ A │ C │ W │ / │ / │ P │ │ Base Address │ │ │ │ │ │ D │ T │ S │ W │ │ └────────────────────────────────┴───────┴──┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Map Level-4 EntryからPage-Directory-Pointer Base Address(40bit)を取り出し、 下部にゼロを12bit(2^12=4096=4K-byte=page-size)付け加えたアドレス(物理アドレス)を求める。 このアドレス(52ビットの物理アドレス)から始まるPage-Directory-Pointer Tableを検索する。 (3)Page-Directory-Pointer Tableを検索 Page-Directory-Pointer Table内のDirectory-Pointer Offset値に対応する Page-Directory-Pointer-Table Entry(Directory-Pointer Offset値番目の項目)を見る。 Page-Directory-Pointer-Table Entry 63 62 52 51 32 ┌─────┬─────────────────────────┬───────────────────────────────────────────┐ │ │ │ │ │ NX │ Available │ Page-Directory Base Address │ │ │ │ │ └─────┴─────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌───────────────────────────────┬───────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ │ 無│ │ P │ P │ U │ R │ │ │ Page-Directory Base Address │ Avail │ 0 │ 0 │ 視│ A │ C │ W │ / │ / │ P │ │ │ │ │ │ │ │ D │ T │ S │ W │ │ └───────────────────────────────┴───────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Directory-Pointer-Table EntryからPage-Directory Base Address(40bit)を取り出し、 下部にゼロを12bit(2^12=4096=4K-byte=page-size)付け加えたアドレス(物理アドレス)を求める。 このアドレス(52ビットの物理アドレス)から始まるPage Directoryを検索する。 (4)Page Directoryを検索 Page Directory(というテーブル)内のDirectory Offset値に対応する Page-Directory Entry(Directory Offset値番目の項目)を見る。 Page-Directory Entry 63 62 52 51 32 ┌─────┬─────────────────────────┬───────────────────────────────────────────┐ │ │ │ │ │ NX │ Available │ Page-Table Base Address │ │ │ │ │ └─────┴─────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌───────────────────────────────┬───────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ 無│ │ 無│ │ P │ P │ U │ R │ │ │ Page-Table Base Address │ Avail │ 視│ 0 │ 視│ A │ C │ W │ / │ / │ P │ │ │ │ │ │ │ │ D │ T │ S │ W │ │ └───────────────────────────────┴───────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Directory EntryからPage-Table Base Address(40bit)を取り出し、 下部にゼロを12bit(2^12=4096=4K-byte=page-size)付け加えたアドレス(物理アドレス)を求める。 このアドレス(52ビットの物理アドレス)から始まるPage Tableを検索する。 (5)Page Tableを検索 Page Table内のTable Offset値に対応するPage-Table Entry(Table Offset値番目の項目)を見る。 Page-Table Entry 63 62 52 51 32 ┌─────┬──────────────────────────┬───────────────────────────────────────────┐ │ │ │ │ │ NX │ Available │ Page Base Address │ │ │ │ │ └─────┴──────────────────────────┴───────────────────────────────────────────┘ 31 12 11 9 8 7 6 5 4 3 2 1 0 ┌───────────────────────────────┬───────┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ │ │ │ P │ │ │ P │ P │ U │ R │ │ │ Page Base Address │ Avail │ G │ A │ D │ A │ C │ W │ / │ / │ P │ │ │ │ │ T │ │ │ D │ T │ S │ W │ │ └───────────────────────────────┴───────┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ Page-Table EntryからPage Base Address(40bit)を取り出し、 下部にPage Offset値(12bit, 2^12=4096=4K-byte=page-size)を付け加えたアドレスが、 (1)のリニア・アドレス(64ビット)に対応する52ビットの物理アドレスである。
■ページのサイズ
ここまで述べてきたページングはすべてページサイズが4KByteの場合のものです。これ以外にもIA32では2MByteと4MByteのページサイズ、Long mode(IA32e mode)では2MByteのページサイズがあります。いずれも考え方はIA32のページングの場合と同じです。
執筆日:2004年10月3日(日)(www.marbacka.net内の別のサイトで公開)
最終更新日:2017年2月19日(日)