自作エミュレータで学ぶx86アーキテクチャ 3.7 leave
https://github.com/mas454/x86emu
(define (leave emu) (let ([ebp (get-register32 emu EBP)]) (set-register32 emu ESP ebp) (set-register32 emu EBP (pop32 emu)) (eip-add emu 1)))
leaveはleave命令に対応している関数です。
leaveはスタックフレームを破棄するのに使う命令なのですが、
やっていることは単純で
mov esp,ebp pop ebp
のようなプログラムを実行しています。
スタックフレームに関しては、「コンピュータシステムの理論と実装」を
読むのをおすすめします。
スタックフレームはコンパイラの処理も重要だと思うので、
cpuだけじゃなくコンパイラの実装もするこっちの本も読むと、より詳しく理解できると思います。
自作エミュレータで学ぶx86アーキテクチャ 3.7 call_rel32,ret
call-rel32とretはcall命令とret命令に対応した関数です。
(define (call-rel32 emu) (let1 diff (get-sign-code32 emu 1) (push32 emu (+ (ref emu 'eip) 5)) (eip-add emu (+ diff 5)))) (define (ret emu) (set! (ref emu 'eip) (pop32 emu)))
call-rel32はcallの次の命令の番地を基準にして前後32ビットの範囲でジャンプできる
命令です。
例えば、以下のようなプログラムだと
call hoge mov eax,0x0000
「mov eax,0x0000」がある番地を基準にして前後32ビットの範囲でジャンプできます。
call-rel32の以下の部分で
(eip-add emu (+ diff 5))))
5を足しているのは、call命令の全体が5バイトだからです。
例えば上の「call hoge」のある番地に5を足すと、基準になる「mov eax,0x0000」の
番地になります。
(push32 emu (+ (ref emu 'eip) 5))
この部分はcall命令を実行した後に戻ってくる番地をプッシュしています。
ここで5を足しているのも上の理由とだいたい同じです。
retでは、ここでプッシュした値をポップしてeipに設定しています。
(set! (ref emu 'eip) (pop32 emu)))
プログラムの全体はgithubに置いています。
https://github.com/mas454/x86emu
自作エミュレータで学ぶx86アーキテクチャ 3.7 push_r32、pop_r32
push-r32とpop-r32は、push命令とpop命令に対応した関数です。
(define (push-r32 emu) (let1 reg (- (get-code8 emu 0) #x50) (push32 emu (get-register32 emu reg)) (eip-add emu 1))) (define (pop-r32 emu) (let1 reg (- (get-code8 emu 0) #x58) (set-register32 emu reg (pop32 emu)) (eip-add emu 1)))
(let1 reg (- (get-code8 emu 0) #x50)
この部分はメモリから1バイト読んで、対応するレジスタの番号を
得ています。
例えば0ならeax、1ならecxという感じです。
(let1 reg (- (get-code8 emu 0) #x58)
この部分も同じようにレジスタの番号を得ています。
全体のコードはGitHubにあります。
https://github.com/mas454/x86emu
自作エミュレータで学ぶx86アーキテクチャ 3.7 push32,pop32
(define (push32 emu value) (let* ([esp (get-register-number 'esp)] [address (- (get-register32 emu esp) 4)]) (set-register32 emu esp address) (set-memory32 emu address value))) (define (pop32 emu) (let* ([esp (get-register-number 'esp)] [address (get-register32 emu esp)] [ret (get-memory32 emu address)]) (set-register32 emu esp (+ address 4)) ret))
自作エミュレータで学ぶx86アーキテクチャ 3.5
メモ:3.5 無条件分岐命令
jmpはeipに値を入れるだけでなく、高速化のために先読みした命令を
捨てるという役割がある。
cpuは高速化のために「パイプライン」という仕組みを持っている。
「パイプライン」は、ある命令を実行するときにはすでに何命令も
先の命令をフェッチしたりデコードしたりしている。
jmpでeipの値が変わるので先読みしていたものを捨てなければならない。
これを「パイプラインのフラッシュ」などと呼ぶ。