PE32からx86_64の命令を使う

x8664は、レジスタ幅2倍、レジスタ数2倍のあわせて4倍ほどの強さを持っており、強さって何の強さだろうか?

気が付いたらCeleronさんもNehalemになっている昨今、64bit CPUは十分普及したと言ってよいだろう。(というのは気のせいで、実家のマシンはCeleron Dが頑張っていた)


と言っても、まだWin64が十分普及したと言えるレベルではないのだが、まあ、そういうのは気にしないとして、Win64環境下の32bitアプリから無理矢理x86-64命令を使おうというネタである。


Win64は、システムコールが64bit用しか用意されておらず、32bitアプリケーションはそのままでは実行できない。これをなんとかするのが、例のWOW64というやつで、Win64環境下の32bitアプリはシステムコールを呼ぶとき大体以下のような動きをしている。

  1. 32bitのcalling convensionに従ってレジスタとかスタックが設定されてntdll.dllの関数が呼ばれる
  2. ntdll.dllの中で64bitモードになる
  3. 32bitのレジスタとかスタックにあるパラメータを64bit用に変換する
  4. システムコールを呼ぶ
  5. 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遅いのか…