Today アクセスカウンター Yesterday アクセスカウンター

伊東家の食卓 : 80386 以降のリアルモードでメモリ空間 4 G をフルに使う裏技

mixi で syd さんに教えてもらいました.こんな裏技があったとは…

ja.wikipedia リアルモード

Intel 80386では、プロテクトモードに移行した後、セグメントリミットを設定した後にリアルモードに復帰すると、命令にプレフィックスをつけることでそのセグメントリミットまでの実メモリ空間にアクセスすることが可能になるバグと思われるものもあり、この状態を Unreal mode と呼ぶことがある。この仕様は以降のすべてのCPUで有効となっている。

日本語ではこれ以外にほとんど情報が無いなぁ… ちなみに syd さんは, 高校のころ,東大の TSG とかいうコンピュータサークルがこの裏技に関する記事を書いていたのを見て知ったそうです.ほえー東大すごす.

具体的には,こういうコードで unreal mode に以降できるのだそう.初めて読む 486 と OS 自作入門を参考にしつつ,テキトーにコメントを書いておきました.

unreal.asm


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Enable "unreal" mode
; This code is public domain (no copyright).
; You can do whatever you want with it.
;
; Unreal mode is identical with real mode with one exception: 32-bit
; addresses are allowed (they do not cause INT 0Dh, as they do in real mode)
;
; This code will fail if run in virtual 8086 mode (Windows DOS box
; or EMM386 loaded). Oh yeah, a 32-bit CPU is required (386+)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; 現在はリアルモード
BITS 16

push ds ; データセグメントレジスタとエクストラセグメントレジスタ
push es ; を退避
xor eax,eax ; eax を 0 に
mov ax,ds ; ax にデータセグメント基底を代入
shl eax,4 ; 基底 (eax) を 16 倍する (セグメント先頭のリニアアドレス算出)
add eax,gdt ; GDT のオフセットアドレスを eax に加算
mov [gdt_ptr + 2],eax ; GDT のリミット値を計算
cli ; 割り込み停止
lgdt [gdt_ptr] ; GDT レジスタをセット
; プロテクトモードへ以降 (GR0 の PE ビットを 1 にするだけ)
mov eax,cr0 ; Control Register 0 の値を eax に代入
or al,1 ; PE ビットを立てる
mov cr0,eax ; CR0 の PE ビットを立てる
mov bx,DATA_SEL ; 4 G リミットのセグメントセレクタ (GDT[1]) を
mov ds,bx ; DS と ES に代入
mov es,bx ; ES はセグメントキャッシュ (らしい)
; リアルモードへ以降
dec al ; PE ビットを落とす
mov cr0,eax ; (un)real モードへ以降
pop es ; セグメントレジスタを復旧
pop ds
; unreal モードに突入
; ここからは 32 bit (4 G) のアドレス空間をフルに使用できる

; Global segment Descriptor Table (8 バイトの構造体の配列)
gdt:
; GDT[0] は使われない (NULL Descriptor)
dw 0
dw 0
db 0
db 0
db 0
db 0
; GDT[1]
; リアルモードのディスクリプタテーブルとの互換性を保つため
; 複雑な構造になっている
DATA_SEL equ $-gdt
dw 0FFFFh ; リミット値の下位 16 bit
dw 0 ; セグメントベースの下位 3 バイト
db 0 ; 0 〜 4 G までアクセスできる巨大セグメントとなる
db 92h ; セグメント属性の下位 1 バイト
; ちなみに…
; 0x00 : 未使用
; 0x92 : システム専用の読み書き可能なセグメント (実行は不可)
; 0x9a : システム専用の実行・読み込み可能セグメント (書き込み不可)
; 0xf2 : アプリケーション用,読み書き可能 (実行不可)
; 0xfa : アプリケーション用,読み込み・実行可能 (書き込み不可)
db 0CFh ; 上位 4 bit (= 0xC) が属性の上位 4 bit (計 12 bit)
; G ビット (粒度が 4096 倍) と D ビット (32 ビットセグメント)
; つまり第 4 と第 3 ビットが立つので,0b1100 = 0xC となる
; 下位 4 bit (= 0xF) がリミット値の上位 4bit (計 20 bit)
; つまり 0xFFFFF (1 M) がリミット値になる
; そして,G ビットを立てることにより,1 M * 4096 = 4 G がリミットになる
db 0 ; セグメントベースの上位 1 バイト (計 4 バイト)
gdt_end:

; GDT レジスタに代入する値
; GDT のリミット値 (16 bit) + GDT の先頭アドレス (32 bit)
gdt_ptr:
dw gdt_end - gdt - 1 ; ディスクリプタテーブルのバイト数 - 1
dd 0 ; GDT の先頭アドレス (上のコードでセットされる)


バッドノウハウというか,これは CPU の仕様書に載ってない (ほとんどバグみたいな副作用的仕様) らしいから,もはやクラックレベルに近いよなぁ.

何にせよ,複雑さの元凶と悪名高い x86 のセグメントも,これでもう怖くありませんね !
明日から使える生活の裏技でした.

(今時リアルモードでプログラムを書くやつなんていない罠)




追記

セグメントレジスタは 16 bit しか無いので,セグメントの先頭 (リニア) アドレスではなく,セグメント番号がセグメントベース (基底)として入ってます.そして,リアルモードでは,32 bit のアドレス空間に対して,セグメントレジスタの値を 4 bit 左シフトして残りのビットを全て 0 にしたリニアアドレスに,各セグメントは固定的にマッピングされます.

# リアルモードにおけるリニアアドレスってのは,そのまま実メモリの先頭からのアドレスのことです.要するに,リアルモードでは,セグメントレジスタの値を 16 倍すると,セグメント先頭の実メモリアドレスが出るということ.

ベースが 16 bit で,4 bit ずらすので,結局 20 bit しかアドレス空間がありません (しかも強制 16 bit アライン.16 bit 単位でしかアクセスできない).これが有名な,リアルモードにおけるメモリ空間 の 「 1M バイトの壁」です.どれだけマシンがメモリを積んでいても,リアルモードでは 1 M (20 bit) までのメモリ空間しかアクセスできないのです.

さらに最悪なことに,各セグメントのオフセットが 16 bit までしか指定できないので,全てのセグメントが 65536 バイトまでしか使えない (しかも,各セグメント間には一切保護が効かない) という融通の利かなさ.これが悪名高い,リアルモードにおけるセグメントの 「64 Kの壁」 です.

今回のハックは,このハードウェア的な制限二つを,CPU のバグを突いて取り払うというものだったのですが.

プロテクトモードで,適切な OS の管理が行き届いていれば,ページング機能 (+ 仮想記憶) によって,任意の 32 bit アドレス空間を任意の実メモリ空間 (ハードディスクのスワップ領域含む) にマッピングできるのですが…

リアルモードではページング機能が働かない (当然仮想記憶も無い) ので,いくら 32 bit のアドレス空間にアクセスできると言っても,実際は,リニアアドレスの範囲そのまま (= マシンに搭載された実メモリの容量内のアドレス範囲) にしかアクセスできないので,あまり意味は無いかもしれません (一切保護が効かないモードなので,範囲外へのアクセスは何が起こるかわかりません).

あと,486 以降ではパイプライン処理が行われるため,リアルモード ⇔ プロテクトモードを以降する際には,わざと無意味な jmp 命令を入れてパイプラインをフラッシュする必要があると思います (リアルモードとプロテクトモードでは,同じ命令のビット列でも意味が変わるから).



追記

A20 ビットのマスク (メモリ 1 M のリミッタ) 解除とか,いろいろ大事なことが抜けてました… コメント欄でいろいろ有益な情報を教えていただきましたので,参考にしてみてください m(_ _)m

コメント

Secret

みつけました

みつけました。これ↓ですね。
http://www.tsg.ne.jp/oldbuho/tsg189.html#8086

なつかしいというか

EMSとかバンク切り替えとか言ってた時代があったなあ(遠い目)

A20

ポートA20の処理が抜けてますよ。

> syd_syd さん

おお,これは素晴らしい資料ですね ! ありがとうございます.

> おやじです さん

EMS は 「はじめて読む〜」 にも載っていたので知ってましたが,バンク切替えってのは知りませんでした…

http://ja.wikipedia.org/wiki/%E3%83%90%E3%83%B3%E3%82%AF%E5%88%87%E3%82%8A%E6%8F%9B%E3%81%88

> Sato さん

ご指摘ありがとうございます 「はじめて読む〜」 の p.172 〜 4 の 「アドレスバス A 20 の制御」 のところですね.

A20マスク回路によるガードを外しておかないと 1 M 以上のメモリにアクセスできないと… 肝心なところを忘れていました (^-^;

http://caspar.hazymoon.jp/OpenBSD/annex/gate_a20.html
http://www.ctrlz.jp/lb/setupAT.html

リアル/プロテクト

Prologプログラムはありがたく勉強のために使わせていただきます :)
で、エントリ最後のまとめで、リアルモードとプロテクトモードが混乱してない?

> 要するに,プロテクトモードでは,セグメントレジスタの値を 16 倍すると,セグメント先頭の実メモリアドレスが出るということ.
こことか

> これが悪名高い,プロテクトモードにおけるセグメントの 「64 Kの壁」 です.
ここはリアルモードでしょう?

ところで会社で資料の整理してたらDPMIとかVCPIでのメモリ管理の資料が出てきた。
いまさらなんのやくにもたたねーw
今回のセグメント管理(LDTとかGDTが登場する)と関連があったりなかったり。

> きむらさん

ややや,これはありえないタイポですな.全てご指摘のとおりだと思います.修正しておきました m(_ _)m

# まぁ,(きむらさんは知っていると思いますが) 半分熱にうなされながら書いた走り書きなので,ご勘弁 (千の言い訳を持つ管理人)

http://ja.wikipedia.org/wiki/DPMI
http://ja.wikipedia.org/wiki/VCPI

ふ〜む,やはり偉大な先人たちは,プロテクトモードでも BIOS とか 16 bit の DOS を使えるように,いろいろがんばっていたんですねぇ…

こういう懐かしいネタを書くと,親切な大人の方々にいろいろ面白い情報をいただけてうれしいです :-)

# Windows 95 の時でさえ,僕小学生なので… CP/M の時代だと,まだ生まれてません… (1983 年生まれ管理人)

http://ja.wikipedia.org/wiki/MS-DOS
プロフィール
  • Author:あろは (alohakun)
  • 京都のデバッガベンダーに勤めるアラサー会社員。

    本ブログの内容は,あくまでも個人的な感想や意見であり,会社の意見を代表するものでは一切ありません.

    連絡先 : alohakun ___at___ gmail.com
    mixi : http://mixi.jp/show_friend.pl?id=182927
    twitter : http://twitter.com/alohakun













    あわせて読みたい


    この日記のはてなブックマーク数


    スカウター : ホワット・ア・ワンダフル・ワールド


    Map
FC2カウンター
ブロとも申請フォーム

この人とブロともになる

最近のコメント
リンク
最近のトラックバック
人生の残り日数
日本人男性の平均寿命は 28700日.
RSSフィード
カテゴリー
  1. RSSリーダー