はじめて読む Pentium マシン語

入社 2 年目のとき、まだセキュリティをやっていた頃にお世話になった本。本当に基本的な内容*1ではあるが、手を動かす演習が多かった*2こともあり、すごくわかりやすかった記憶がある。これがあったから、Java仮想マシンの仕組みもよくわかった。
この週末、久方ぶりに読んだので、骨格だけメモしておく。

はじめて読むPentium マシン語入門編

はじめて読むPentium マシン語入門編

アセンブリ言語全般

  • アドレッシングモード:データ転送ができる、レジスタとメモリの組み合わせ。例えば、以下
    • 基本的には、メモリからメモリへのデータ転送はできない
    • セグメントレジスタにデータを直接入れることができない
  • セグメントアドレスとオフセットアドレス
    • セグメントアドレス:アドレスを指すための基準点。セグメントベース
    • オフセットアドレス:セグメントベースから、相対的にアドレスを指定

レジスタの種類

  • 一般レジスタ
    • 汎用レジスタ
      • EAX, EBX, ECX, EDX(32 ビット)。16 ビットや 8 ビットでも使える。例えば EAX なら、下位 16 ビットを AX と呼び、AX 内の 8 ビットずつを AH, AL と呼ぶ
      • ESI(ソースインデックス), EDI(ディスティネーションインデックス), EBP(ベースポインタ), ESP(スタックポインタ)。16 ビットでも使える。例えば、EBP なら、下位 16 ビットを BP と呼ぶ
    • フラグレジスタ:EFLAGS(32 ビット)。下位 16 ビットを FLAGS と呼ぶ。具体的には、以下などが含まれる
      • OF:オーバーフローフラグ:符号つき演算で桁あふれのときセット
      • DF:ディレクションフラグ:ストリング操作命令でポインタの増減方向
      • IF:インタラプト・イネーブルフラグ:クリアすると割り込みを受け付けない
      • TF:トラップフラグ:ステップ実行で使用
      • SF:サインフラグ:演算結果が負のときにセット
      • ZF:ゼロフラグ:演算結果がゼロのときにセット
      • AF:補助キャリーフラグ:BCD 演算で使用
      • PF:パリティフラグ:演算の結果 1 であるビット数が偶数のときにセット、奇数のときにリセット
      • CF:キャリーフラグ:演算の結果、桁上がりが生じたときにセット
    • インストラクションポインタ:EIP(32 ビット)。下位 16 ビットを IP と呼ぶ
    • セグメントレジスタ:CS, DS, ES, SS, FS, GS(16 ビット)
      • セグメント(メモリのかたまり)を指定する
  • その他のレジスタ

EIP と ESP

  • EIP(インストラクションポインタ)
    • プログラムの実行の流れを制御する
    • 次に実行しようとする命令のオフセットアドレスを指す。具体的には、EIP レジスタで指定したメモリのアドレスからマシン語命令を読み込み、解析して実行する。EIP の値は、自動的に次の命令のアドレスを指す。また、ジャンプ命令を実行すると、EIP レジスタにジャンプ先のオフセットアドレスの値が強制的にロードされ、そのアドレスからプログラムが続行する
    • EIP を操作してプログラムの流れを変える命令は、次の 4 種類
      • ジャンプ命令
      • コール命令
      • リターン命令
      • 割り込み命令
  • ESP(スタックポインタ)
    • スタックの一番上に積まれているアドレスを指す(アドレスの高い方から低い方へ進む)
    • データの出し入れは、1 ワード(2 バイト)or ダブルワード(4 バイト)ずつ。オペランドのサイズに依存する

インストラクションセット(命令群)

(1) データ転送命令
  • MOV(ムーブ*3):データ転送(データがコピーされるので、実質「複写」)
; 1 バイトのデータ 5F を、AL レジスタにロードする
    MOV AL, 5FH
; EBX レジスタでオフセットアドレスを指定したメモリに、EAX レジスタの内容をストアする
    MOV [EBX], EAX
; EBX レジスタの内容 + 5 をオフセットアドレスとするメモリの内容を、EAX レジスタにロードする
    MOV EAX, [EBX+5]
(2) 算術演算命令

四則演算、論理演算(CPU 内部の演算ユニットに転送される)を実行する。演算が実行された後の状態が、フラグレジスタにセットされる。

  • 加算・減算命令
    • ADD, SUB
    • INC, DEC(インクリメント, デクリメント)
; EAX レジスタに、00000005(4 バイト)を加える。演算結果は、EAX レジスタ転送される
    ADD EAX, 00000005H
  • 比較命令
    • CMP(コンペア):演算は引き算と同じ(ただし、演算結果は捨てられる)。演算結果の状態が、「フラグ」レジスタにセットされる
  • 乗除算命令
    • MUL, DIV:基本的には、EAX(AH, AL)レジスタを使用する。演算の相手は、レジスタかメモリ(データそのものの指定は不可)
    • 例えば、32 ビットのデータの乗除算では
; AX レジスタの内容を、BL レジスタの内容で割り、商を AL レジスタに、余りを AH レジスタにセットする
    DIV BL
  • PTR 演算子
    • 加減算では、使用するレジスタによって、対象のメモリが、バイト、ワード、ダブルワードかわかった。しかし、インクリメント、デクリメント、乗除算の場合などに、どのメモリを対象とするかがわからない。そこで、バイト、ワード、ダブルワードを指定する
      • BYTE PTR <アドレス指定>
      • WORD PTR <アドレス指定>
      • DWORD PTR <アドレス指定>
; レジスタ EBX の値が示すメモリを、ダブルワードのデータとしてインクリメント
    INC DWORD PTR [EBX]
(3) ジャンプ命令
  • 無条件ジャンプ
    • JMP <アドレス指定>
      • アドレス指定:ラベル(名前:)or 変数名 or レジスタ
      • 指定されたオフセットアドレスが EIP レジスタにロードされ、ジャンプする
  • 条件ジャンプ(比較した値が符号なしの 4 バイトデータ)
    • JE(Jump if Equal)
    • JNE(Jump if Not Equal)
    • JA(Jump if Above)
    • JAE(Jump if Above or Equal)
    • JB(Jump if Below)
    • JBE(Jump if Below or Equal)
  • 条件ジャンプ(比較した値が符号ありの 4 バイトデータ)
    • JG(Jump if Greater)
    • JGE(Jump if Greater or Equal)
    • JL(Jump if Less)
    • JLE(Jump if Less or Equal)
(4) スタックのプッシュ・ポップ
; ESP を 4 減らし、その値をオフセットアドレスとするスタック領域に、EAX レジスタの内容を退避する
    PUSH EAX
; ESP の値をオフセットアドレスとするスタック領域から、EAX レジスタにダブルワードデータを復帰し、ESP に 4 を加える
    POP EAX
; ESP を 4 減らし、その値をオフセットアドレスとするスタック領域に、EFLAGS レジスタの内容を退避する
    PUSHFD
; ESP の値をオフセットアドレスとするスタック領域から、EFLAGS レジスタにダブルワードデータを復帰し、ESP に 4 を加える
    POPFD
(5) サブルーチンのコール/リターン

スタックにより実現する。ネスト(入れ子構造)ができる。

  • コール
    • CAL <アドレス指定>
      • アドレス指定の方法は、ラベル(名前:)or 変数名 or レジスタ
  • リターン
    • RET
  1. コール命令で、メインルーチンからサブルーチンへ
    • EIP レジスタの内容(コール命令の次の命令を指している)が、スタックにプッシュ
    • EIP レジスタに、ジャンプ先のオフセットアドレスが、強制的にロード
  2. EAX レジスタと EDX レジスタの内容を、スタックエリアに退避
  3. サブルーチンの中で EAX と EDX レジスタを使う
  4. 退避していた EAX や EDX レジスタの内容をもとに復帰
  5. リターン命令でサブルーチンからメインルーチンに戻る
    • スタックにプッシュしていた内容を EIP にロード
    CALL SomeSubroutine
    ...
SomeSubroutine:
    ...
    RET
(6) 論理演算命令

OR, AND, XOR など。

; AL レジスタと、1 バイトのデータ 5F の AND をとり、結果を AL レジスタに残す
    AND AL 5FH
(7) ローテート/シフト命令

右または左に 1 ビットずらす命令。

  • ローテート:押し出されたビットを、反対側の空いたビットに返す(円環のよう)。スルーキャリーは、キャリーフラグの内容が、空いたビットに入る。押し出されたビットがキャリーフラグに入る
    • 左ローテート:RCL(Rotate Left through Carry), ROL(ROtate Left)
    • 右ローテート:RCR(Rotate Right through Carry), ROR(ROtate Right)
  • シフト:押し出されたままずらす。算術シフトは、ビットパターンを符号つきの数値ととらえる(シフトしても符号が変わらないようにする)
    • 左シフト:SHL(SHift logical Left), SAL(Shift Arithmetic Left)
    • 右シフト:SHR(SHift logical Right), SAR(Shift Arithmetic Right)
; BL レジスタの内容を、CL レジスタで指定する回数だけ、右にローテートする
    ROR BL, CL
(8) 本書に出てこない命令
  • LEA:MOV が値を取得するのに対し、LEA は、アドレスを取得する(ソースオペランドは、[ ] で囲まれている)
  • INT:割り込みを発生させる。オペランドの数字に応じて、異なる処理を実行する
  • NOP:何もしない


正直、ほぼほぼ忘れていた。コンピュータがどう動くかのイメージしか残っていなかった。きっとまた、何年か経つと、忘れ去ってしまうんだろうな。。

*1:私のような、Javaプログラマがおさえておくのには、ちょうどよい内容だったと思う。

*2:図も豊富。だが「そこまで図が必要なのか?」と、今でも思う。

*3:とある後輩に、突然「モブって知ってます?」と聞かれたことが忘れられない。なぜだろう。