PE32からx86_64の命令を使う
x8664は、レジスタ幅2倍、レジスタ数2倍のあわせて4倍ほどの強さを持っており、強さって何の強さだろうか?
気が付いたらCeleronさんもNehalemになっている昨今、64bit CPUは十分普及したと言ってよいだろう。(というのは気のせいで、実家のマシンはCeleron Dが頑張っていた)
と言っても、まだWin64が十分普及したと言えるレベルではないのだが、まあ、そういうのは気にしないとして、Win64環境下の32bitアプリから無理矢理x86-64命令を使おうというネタである。
Win64は、システムコールが64bit用しか用意されておらず、32bitアプリケーションはそのままでは実行できない。これをなんとかするのが、例のWOW64というやつで、Win64環境下の32bitアプリはシステムコールを呼ぶとき大体以下のような動きをしている。
- 32bitのcalling convensionに従ってレジスタとかスタックが設定されてntdll.dllの関数が呼ばれる
- ntdll.dllの中で64bitモードになる
- 32bitのレジスタとかスタックにあるパラメータを64bit用に変換する
- システムコールを呼ぶ
- 32bitに戻る
このとき、2番目の処理はユーザー空間で行われているので、Windowsでは、ユーザ空間で、32bit<->64bitの切り替えが可能である。
Windows7では、セレクタ値0x33が指すlocal descriptorに64bit用のlocal descriptorが設定されてあって、これを使うことで切り替えを実現してるようだ。戻るときは0x23。
実際にやってみたコードは以下。
http://morihyphen.hp.infoseek.co.jp/files/wow64.zip
run64 にプログラムのアドレスを引数として渡すと、そのプログラムが64bitで実行される。実際のコードはこんな感じ。
section rodata fword_call32: ;; 32bit 戻るようのセレクタ値 + アドレス dd call32 dw 0x23 section text bits 32 call64: ;; 64bit モードで実行 call eax ;; 実際はcall rax ;; mov rax, fword_call32 db 0x48, 0xb8 dd fword_call32 dd 0x0 ;; jmp dword far [rax] db 0xff, 0x28 call32: ret global _run64 _run64: mov eax, [esp+4] mov ecx, [esp+8] jmp 0x33:call64 ;; セレクタ値0x33へジャンプ
Core2だと200cycle ぐらいで行って帰ってこれるっぽい。このぐらいなら気軽に使えそう。
気軽に使えたからといってどうしたという感じだが…
64bitの乗除算はかなり速くできるので…と、思ったが、そうでもなかった。あれ?なんで?64bit div遅いのか…