リバースエンジニアリングへの道
出田 守です。
最近、情報セキュリティに興味を持ち、『リバースエンジニアリング-Pythonによるバイナリ解析技法』という本(以降、「教科書」と呼びます)を読みました。
「こんな世界があるのか!かっこいい!」と感動し、私も触れてみたいということでド素人からリバースエンジニアリングができるまでを書いていきたいと思います。
ちなみに、教科書ではPython言語が使用されているので私もPython言語を使用しています。
ここを見ていただいた諸先輩方からの意見をお待ちしております。
軌跡
環境
OS: Windows10 64bit Home (日本語)
CPU: Intel® Core™ i3-6006U CPU @ 2.00GHz × 1
メモリ: 2048MB
Python: 3.6.5
私の環境は、普段Ubuntu16.04を使っていますが、ここではWindows10 64bitを仮想マシン上で立ち上げております。
ちなみに教科書では、Windowsの32bitで紹介されています。
Intel Deveroper's Manual [1] Chapter3 - Chapter4
前回はIDMのChapter2までをまとめてみました。結構大変です!でも無駄にはならないので続けていきます。
今回はChapter3 - Chapter4までをまとめます。
Chapter3 BASIC EXECUTION ENVIRONMENT
この章ではアセンブリ言語プログラマにみられるIntel64またはIA-32プロセッサの実行環境について説明します。どのように命令を実行しているのかと、どのようにデータを格納し、操作するのかを説明します。
この実行環境の説明にはメモリ(アドレススペース)や、GPR、セグメントレジスタ、フラグレジスタ、命令ポインタレジスタも含まれます。
3.1 MODES OF OPERATION
IA-32には3つの基本操作モードがあります。
- Protected Mode(保護モード)
このモードはプロセッサのネイティブ状態のモードです。 - Real-address mode
このモードは8086のプログラミング環境の拡張機能(保護モードまたはシステム管理モードを切り替える機能など)として実装されました。プロセッサは起動または再起動後にReal-addressモードになります。 - System management mode(SMM:システム管理モード)
このモードは、オペレーティングシステムまたは電源管理やシステムセキュリティなどの管理プラットフォーム固有の機能を実装するためのメカニズムを提供します。プロセッサはSMM割り込みピン(SMI#)が有効またはSMIがAPICから受信した時点でSMMに入ります。
3.1.1 Intel ® 64 Architecture
Intel64にはIA-32eモードが追加されました。
- Compatibility mode(互換モード)
このモードは16bitまたは32bitアプリケーションを再コンパイルなしで64bitOS上で実行できます。互換モードは、オペレーティングシステム(OS)によってコードセグメントベースで有効になっています。これは、単一の64bitOSが、64bitモードで動作する64bitアプリケーションをサポートし、互換モードで動作する従来の32bitアプリケーションをサポートできることを意味します。互換モードは32bit保護モードに似ています。 アプリケーションは最初の4GBのリニアアドレススペースにのみアクセスします。 互換性モードでは、16bitおよび32bitのアドレスとオペランドサイズが使用されます。 保護モードと同様に、このモードでは、アプリケーションはPAE(物理アドレス拡張)を使用して4GBを超える物理メモリにアクセスできます。 - 64bit mode
このモードは64bitリニアアドレススペースで書かれたアプリケーションを64bitOS上で実行できます。このモードでは、レジスタ拡張にアクセスするための新しいオペコードプレフィックス(REX)も導入されています。64bitモードはオペレーティングシステム(OS)によってコードセグメントベースで有効になっています。デフォルトのアドレスサイズは64bitで、デフォルトのオペランドサイズは32bitです。デフォルトのオペランドサイズは、オペランドサイズオーバーライドと組み合わせてREXオペコードプレフィックスを使用して、命令ごとにオーバーライドできます。
3.2 OVERVIEW OF THE BASIC EXECUTION ENVIRONMENT
IA-32プロセッサ上で動作するプログラムまたはタスクは命令実行のためのリソースや格納されているコードやデータ、状態などのリソースを与えられます。これらのリソースは、IA-32プロセッサの基本的な実行環境を構成します。
Intel64はIA-32の基本実行環境や、64bitプログラムと32bitプログラムを実行できるIA-32eに似た環境をサポートしています。
- Address space
基本実行環境はアプリケーションプログラムとOSまたは実行プロセッサと共同で使用されます。
IA-32で動作するタスクやプログラムは最大4GBのリニアアドレススペースや、最大64GBの物理アドレススペースを処理できます。 - Basic program execution registers
8つの汎用レジスタ、6つのセグメントレジスタ、EFLAGSレジスタ、およびEIP(命令ポインタ)レジスタは、汎用命令のセットを実行するための基本実行環境を含みます。それらの命令はByteやWord、Dword整数の基本整数演算やプログラムのフロー制御の処理、bitやByte文字列の操作、アドレスメモリを実行します。 - x87 FPU registers
8つのx87 FPU data registers, x87 FPU control register, the status register, x87 FPU instaruction pointer register, x87 FPU operand(data) pointer register, the x87 FPU tag register, the x87 FPU opcode registerは単精度、倍精度、拡張倍精度浮動小数点やWord整数、Dword整数、Qword整数、binary coded decimal(BCD, 二進化十進数)を操作するために基本実行環境を提供します。 - MMX registers
8つのMMXレジスタは64bitにパックされたByte, Word, DWord整数の単一命令、マルチデータ(SIMD)演算をサポートします。 - XMM registers
8つのXMMデータレジスタとMXCSRレジスタは128bitにパックされた単精度と倍精度浮動小数点と128bitにパックされたByte, Word, Dword, Qword整数のSIMD演算の実行をサポートします。 - YMM registers
YMMレジスタは256bitにパックされた単精度と倍精度浮動小数点と256bitにパックされたByte, Word, Dword, Qword整数の256bitSIMD演算の実行をサポートします。 - Bounds registers
BND0-BND3レジスタはそれぞれ、メモリバッファへのポインタに関連する下限および上限(それぞれ64bit)を格納します。インテルMPX命令の実行をサポートします。 - BNDCFGU and BNDSTATUS
BNDCFGUは、境界チェックでユーザーモードMPX操作を構成します。
BNDSTATUSは、MPX操作によって発生した#BRに関する追加情報を提供します。 - Stack
プロシージャまたはサブルーチンコール、およびプロシージャまたはサブルーチン間でのパラメータの受け渡しをサポートするために、スタックおよびスタック管理リソースが実行環境に含まれています。 スタックはメモリ内にあります。
加えてIA-32ではシステムレベルのアーキテクチャの一部として以下を提供します。
- I/O ports
- Control registers
5つの制御レジスタ(CR0〜CR4)は、現在実行中のタスクの特性に基づいて決定されます。 - Memory management registers
GDTR、IDTR、タスクレジスタ、およびLDTRは、保護モードメモリ管理で使用されるデータ構造の場所を指定します。 - Debug registers
デバッグレジスタ(DR0〜DR7)は、プロセッサのデバッグ動作を制御し、監視することができます。 - Memory type range registers
MTRRはメモリ領域へメモリタイプを割り当てるために使用されます。 - Machine specific registers (MSRs)
プロセッサは、プロセッサの性能を制御および報告するために使用される様々な機械固有のレジスタを提供します。事実上、すべてのMSRはシステムに関連する機能を処理し、アプリケーションプログラムからアクセスできません。 このルールの例外の1つは、タイムスタンプカウンタです。 - Machine check registers
マシンチェックレジスタはハードウェア(マシン)エラーを検出、報告するために使われるMSRの制御、ステータス、エラー報告を構成します。 - Performance monitoring counters
パフォーマンス監視カウンタはプロセッサパフォーマンスイベントの監視を許可します。
3.2.1 64-Bit Mode Execution Environment
64bitモードでのIA-32との違いを以下に説明します。
- Address space
IA-32における64bitモードでのタスクやプログラムは最大$2^{64}$Byteのリニアアドレススペースと最大$2^{46}Byteの物理アドレススペースで動作します。ソフトウェアは、プロセッサによってサポートされる物理アドレスサイズについてCPUIDを照会することができます。 - Basic program execution registers
汎用レジスタが16に増加しました。汎用レジスタは64bit幅でWord, Dword, Qword整数の演算をサポートします。バイトレジスタへのアクセスは、一番下の8ビットに一律に行われます。 命令ポインタレジスタは64ビットになります。EFLAGSレジスタは64ビット幅に拡張され、RFLAGSレジスタと呼ばれます。 RFLAGSの上位32ビットは予約されています。 RFLAGSの下位32ビットはEFLAGSと同じです。 - XMM registers
SIMD演算のために16XMMデータレジスタがあります。 - YMM registers
SIMD演算のために16YMMデータレジスタがあります。 - Stack
スタックポインタサイズが64bitに拡張されます。スタックサイズはSSディスクリプタのビットによって制御されません(64bit以外のモードと同様)。また、ポインタサイズは命令プレフィックスによって上書きされません。 - Control registers
コントロールレジスタも64bitに拡張されました。新しいコントロールレジスタ(タスクプライオリティレジスタ:CR8またはTPR)が追加されます。 - Debug registers
デバッグレジスタも64bitに拡張されます。 - Descriptor table registers
グローバルデスクリプタテーブルレジスタ(GDTR)と割り込みデスクリプタテーブルレジスタ(IDTR)は完全な64bitアドレスを保持できるように10Byteに拡張されます。ローカルディスクリプタテーブルレジスタ(LDTR)とタスクレジスタ(TR)も完全な64ビットのベースアドレスを保持するために拡張されます。
3.3 MEMORY ORGANIZATION
バス上のプロセッサアドレスにおけるメモリは物理メモリと呼ばれます。物理メモリは、8bitのバイト列として編成されています。それぞれのByteはユニークなアドレスが割り当てられます。これを物理アドレスとよびます。もしプロセッサがIntel64をサポートしていなければ、物理アドレススペースは0から最大$2^{36}-1$(64GB)の範囲を持ちます。Intel64は物理とリニアアドレススペースの変更を導入しました。IA-32またはIntel 64プロセッサで動作するように設計されたオペレーティングシステムまたは実行環境は、プロセッサのメモリ管理機能を使用してメモリにアクセスします。これらの機能は、セグメンテーションやページングなどの機能を提供し、メモリを効率的かつ確実に管理することができます。
3.3.1 IA-32 Memory Models
プロセッサのメモリ管理機能を使用する場合、プログラムは物理メモリに直接アクセスしません。代わりに、プログラムは3つのメモリモデル、Flat memory model, Segmented memory model, Real-address mode memory modelを使ってメモリにアクセスします。
- Flat memory model
メモリは、単一の連続したアドレス空間としてプログラムに現れます。この空間は、リニアアドレススペースとよばれます。コード、データ、スタックは全てこのアドレススペースに含まれます。リニアアドレススペースはByteアドレス可能であり、アドレスは0から$2^{32}-1$(64bitモードでない場合)から連続して実行されます。リニアアドレススペースでのByteアドレスはリニアアドレスとよばれます。 - Segmented memory model
メモリは独立したアドレススペースとしてプログラムに現れます。これをセグメントとよびます。コード、データ、スタックは一般的に分離されたセグメントに含まれます。セグメント内のバイトをアドレス指定するには、プログラムは論理アドレスを発行します。これはセグメントセレクタとオフセットから構成されます(論理アドレスはfarポインタと呼ばれることが多いです)。セグメントセレクタはアクセスされるセグメントを認識し、オフセットはセグメントのアドレス空間内のバイトを認識します。IA-32で動作するプログラムは異なるサイズ、種類のセグメントを最大16,383アドレス指定可能で、各セグメントは$2^{32}$の大きさにすることができます。内部的には、システム用に定義されたすべてのセグメントがプロセッサのリニアアドレススペースにマップされます。メモリ位置にアクセスするために、プロセッサは、各論理アドレスをリニアアドレスに変換します。セグメント化されたメモリを使用する主な理由は、プログラムとシステムの信頼性を高めることです。例えばスタックを別のセグメントに配置することで、コードやデータを上書きすることを防止します。 - Real-address mode memory model
これはIntel8086のためのメモリモデルです。このメモリモデルはすでにIntel8086用に書かれたプログラムの互換性を提供するためにサポートされます。実アドレスモードでは、プログラムおよびオペレーティングシステム/実行環境のリニアアドレススペースが、それぞれ最大64KBのセグメントの配列からなるセグメント化メモリの特定の実装を使用します。実アドレスモードでのリニアアドレススペースの最大サイズは$2^{20}$Byteです。
3.3.2 Paging and Virtual Memory
Flat memory modelまたはSegment memory modelでのリニアアドレススペースはプロセッサの物理メモリへ直接またはページングを通してマップされます。直接マップされる場合は、それぞれのリニアアドレスは物理アドレスと1対1に対応します。リニアアドレスは、変換せずにプロセッサのアドレス行に送信されます。
IA-32のページングを使用する場合は、それぞれのリニアアドレスは仮想メモリへマップされたページとして分割されます。仮想メモリのページは必要に応じて物理メモリにマップされます。OSまたは実行環境がページングを使用する場合、ページングはアプリケーションにとって透過的です。全てのアプリケーションからはリニアアドレススペースとして見えます。
加えて、IA-32のページングには以下の拡張機能が含まれます。
- Physical Address Extensions(PAE)により4GB以上の物理アドレススペースにアドレス指定できます。
- Page Size Extensions(PSE)によりリニアアドレスを4MBのページアドレスへマップ出来ます。
3.3.3 Memory Organization in 64-Bit Mode
Intel64では64GB以上の物理アドレススペースがサポートされます。64bitモードでは、64bitのリニアアドレススペースに対するアーキテクチャ上のサポートがあります。しかしながらIntel64をサポートするプロセッサは64bit未満の実装かもしれません。リニアアドレススペースはPAEページングを通してプロセッサの物理アドレススペースへマップされます。
3.3.4 Modes of Operation vs. Memory Model
IA-32またはIntel 64プロセッサ用のコードを書くとき、プログラマは、コードを実行するときにプロセッサが使用される動作モードと、使用されているメモリモデルを知る必要があります。動作モードとメモリモデルの関係を以下に示します。
- Protected mode
保護モードの場合、プロセッサはいずれのメモリモデルも使用できます。(実アドレス指定モードのメモリモデルは、通常、プロセッサが仮想8086モードにある場合にのみ使用されます。)メモリモデルはOSまたは実行環境に依存します。マルチタスクが実装されている場合、個々のタスクは異なるメモリモデルを使用することが出来ます。 - Real-address mode
実アドレスモードの場合、プロセッサは実アドレスモードメモリモデルのみサポートします。 - System management mode
SMMの場合、プロセッサはsystem management RAM(SMRAM)と呼ばれる分離アドレススペースへ切り替えます。メモリモデルは実アドレスモードメモリモデルに似たアドレススペースでバイトをアドレス指定します。 - Compatibility mode
互換モードで実行する必要があるソフトウェアは、32bitプロテクションモードで動作するように設計されたものと同じメモリを観察する必要があります。 - 64bit mdoe
セグメンテーションは一般的に(絶対ではない)無効にされ、フラットな64bitリニアアドレススペースが作成されます。具体的には、プロセッサは、64bitモードでCS、DS、ES、およびSSのセグメントベースをゼロとして扱います(これにより、リニアアドレスは実効アドレスと等しくなります)。64bitモードではセグメント化されたアドレスモードと実アドレスモードは使用できません。
3.3.5 32-Bit and 16-Bit Address and Operand Sizes
保護モードにおけるIA-32は32bitまたは16bitのアドレスとオペランドサイズを設定できます。
(以下省略)
3.3.6 Extended Physical Addressing in Protected Mode
(省略)
3.3.7 Address Calculations in 64-Bit Mode
ほとんどの場合、64bitモードはコード、データ、スタックのフラットアドレススペースを使用します。64bitモード(アドレスサイズのオーバーライドがない場合)では、実効アドレス計算のサイズは64bitです。実効アドレス計算では、64bitのベースレジスタとインデックスレジスタが使用され、64bitに符号拡張された変位が使用されます。
64bitのフラットリニアアドレススペースの場合、リニアアドレスはベースアドレスが0なので実行アドレスと同じになります。FSセグメントまたはGSセグメントが非ゼロベースで使用される場合、このルールは成立しません。 64bitモードでは、完全な64bitセグメントベースを追加する前に、実効アドレスコンポーネントが追加され、実効アドレスが切り捨てられます(たとえば、命令LEAを参照)。64bitモードのアドレッシングモードに関係なく、ベースは切り捨てられません。(難しいですね・・・)
命令ポインタは、64bitコードオフセットをサポートするために64bitに拡張されます。 64bit命令ポインタはRIPと呼ばれます。
[IDMより掲載]
(以下省略)
3.3.7.1 Canonical Addressing
(省略)
3.4 BASIC PROGRAM EXECUTION REGISTERS
IA-32は、一般的なシステムおよびアプリケーションのプログラミングで使用するための16の基本プログラム実行レジスタを提供します。
3.4.1 General-Purpose Registers
汎用レジスタ(EAX, EBX, ECX, EDX, ESI, EDI, EBS, ESP)は以下の要素を提供します。
- 論理演算、数値演算のためのオペランド
- アドレス計算のためのオペランド
- メモリポインタ
これらのレジスタはすべてオペランド、結果、およびポインタの一般的な格納に使用できますが、ESPレジスタを参照するときは注意が必要です。ESPレジスタはスタックポインタを保持しており、一般的な規則として別の目的に使用すべきではありません。
多くの命令はオペランドを保持する特定のレジスタを割り当てます。例えば文字列命令はオペランドとしてECX, ESI, EDIの内容を使用します。セグメントメモリモデルを使用している場合、いくつかの命令は、特定のレジスタ内のポインタが特定のセグメントに相対的であると仮定します。例えば、いくつかの命令は、EBXレジスタ内のポインタがDSセグメント内のメモリ位置を指し示すと仮定します。
汎用レジスタの特殊用途について以下に示します。
- EAX
オペランドと結果を格納 - EBX
DSセグメントのデータポインタ - ECX
文字列とループのカウンタ - EDX
I/Oポインタ - ESI
DSレジスタが指すセグメント内のデータへのポインタ
文字列操作のソースポインタ - EDI
ESレジスタが指すセグメント内のデータ(または宛先)へのポインタ
文字列操作の宛先ポインタ - ESP
(SSセグメント内の)スタックポインタ - EBP
(SSセグメント内の)スタック上のデータポインタ
汎用レジスタの下位16bitは、8086およびIntel 286にあるレジスタセットに直接マッピングされ、AX、BX、CX、DX、BP、SI、DI、SPの名前で参照できます。 EAX、EBX、ECX、EDXの各レジスタの下位2バイトは、AH、BH、CH、DH(上位バイト)、AL、BL、CL、DL(下位バイト)の名前で参照できます。
3.4.1.1 General-Purpose Registers in 64-Bit Mode
64bitモードでは16の汎用レジスタとオペランドサイズは32bitです。しかしながら、汎用レジスタは32bitまたは64bitオペランドで動作できます。もし32bitオペランドならEAX, EBX, ECX, EDX, ESI, EDI, EBS, ESP, R8D-R15Dが使えます。もし64bitオペランドならRAX, RBX, RCX, RDX, RSI, RDI, RBS, RSP, R8-R15が使えます。
全てのレジスタはByte, Word, Dword, Qwordで利用可能です。
REX prefixは、64ビットオペランドサイズを生成するために、またはレジスタR8-R15を参照するために使用されます。
3.4.2 Segment Registers
セグメントレジスタ(CS, DS, SS, ES, FS, GS)は16bitセグメントセレクタで保持されます。セグメントセレクタはメモリの中のセグメントを認識するための特別なポインタです。メモリ内の特定のセグメントにアクセスするには、そのセグメントのセグメントセレクタが適切なセグメントレジスタに存在していなければなりません。アプリケーションコードを書くとき、プログラマは一般的にアセンブラディレクティブとシンボルでセグメントセレクタを作成します。
セグメントレジスタの使用方法は、オペレーティングシステムまたは実行環境が使用しているメモリ管理モデルのタイプによって異なります
各セグメントレジスタは、コード、データ、またはスタックの3種類のうちの1つに関連します。たとえば、CSレジスタには、実行中の命令が格納されているコードセグメント用のセグメントセレクタが含まれています。プロセッサは、CSレジスタ内のセグメントセレクタとEIPレジスタの内容からなる論理アドレスを使用して、コードセグメントから命令をフェッチします。EIPレジスタには、実行される次の命令のコードセグメント内のオフセットが含まれます。CSレジスタは、アプリケーションプログラムによって明示的にロードすることはできません。代わりに、プログラム制御(プロシージャ呼び出し、割り込み処理、またはタスク切り替えなど)を変更する命令または内部プロセッサ操作によって暗黙的にロードされます。
[IDMより掲載]
3.4.2.1 Segment Registers in 64-Bit Mode
64ビットモードでは、CS、DS、ES、SSは、セグメント記述子ベースに関連する値にかかわらず、各セグメントベースが0であるかのように扱われます。これにより、コード、データ、およびスタックのためのフラットなアドレス空間が作成されます。FSとGSは例外です。
両方のセグメントレジスタは、(ローカルデータおよび特定のオペレーティングシステムデータ構造のアドレス指定における)リニアアドレス計算における追加のベースレジスタとして使用することができます。
3.4.3 EFLAGS Register
32bit EFLAGSレジスタにはステータスフラグ、コントロールフラグ、システムフラグが含ます。(RESETピンまたはINITピンのアサートによる)プロセッサの初期化後、EFLAGSレジスタの状態は00000002Hになります。bit1, 3, 5, 15, 22-31は予約ビットです。EFLAGSレジスタのいくつかのフラグは特別な命令を使うことで直接変更することが出来ます。レジスタ全体を直接調べたり変更したりすることはできません。
LAHF、SAHF、PUSHF、PUSHFD、POPF、およびPOPFDの命令を使用して、プロシージャースタックまたはEAXレジスタとの間でフラグのグループを移動することができます。
EFLAGSレジスタの内容がプロシージャスタックまたはEAXレジスタに転送された後、フラグはプロセッサのビット操作命令(BT、BTS、BTR、およびBTC)を使用して検査および変更できます。
(プロセッサのマルチタスキング機能を使用して)タスクをサスペンドすると、EFLAGSレジスタの状態は、サスペンドされているタスクのtask state segment(TSS)に自動的に保存されます。自身を新しいタスクにバインドするとき、プロセッサは新しいタスクのTSSからのデータをEFLAGSレジスタにロードします。
割り込みまたは例外ハンドラプロシージャが呼び出されると、プロセッサは自動的にEFLAGSレジスタの状態をプロシージャスタックに保存します。 割り込みまたは例外がタスクスイッチで処理されると、EFLAGSレジスタの状態は、中断されているタスクのためにTSSに保存されます。
[IDMより掲載]
3.4.3.1 Status Flags
ステータスフラグはADDやSUB、MUL、DIVのような算術命令の結果を示します。
- CF(Carry flag: bit 0)
算術演算が結果の最上位ビットからcarryまたはborrow outをする場合にセットされます。それ以外はクリアされます。このフラグは、符号なし整数演算のオーバーフロー条件を示します。多精度演算でも使用されます。 - PF(Parity flag: bit 2)
結果の最下位バイトが1bitが偶数個を含む場合にセットされ、それ以外の場合はクリアされます。 - AF(Auxillary flag: bit 4)
演算の結果のbit3からcarryまたはborrow outする場合にセットされます。それ以外はクリアされます。このフラグは、BCD算術で使用されます。 - ZF(Zero flag: bit 6)
結果がゼロの時にセットされます。それ以外はクリアされます。 - SF(Sign flag: bit 7)
結果の最上位ビット(符号付き整数の符号ビット)と同じ値を設定します。(0は正の値を示し、1は負の値を示します)。 - OF(Overflow flag: bit 11)
整数結果の正の数が大きすぎて、または負の数(符号bitを除く)が小さすぎて宛先のオペランドに収まらない場合にフラグがセットされます。それ以外はクリアされます。このフラグは、符号付き整数(2の補数)演算のオーバーフロー条件を示します。
これらのステータスフラグのうち、STC、CLC、およびCMC命令を使用して、CFフラグのみを直接変更できます。また、bit命令(BT、BTS、BTR、およびBTC)は、指定されたbitをCFフラグにコピーします。ステータスフラグにより、1つの算術演算で、符号なし整数、符号付き整数、およびBCD整数の3つの異なるデータ型の結果が生成されます。算術演算の結果が符号なし整数として扱われる場合、CFフラグは範囲外の状態(carryまたはborrow)を示します。符号付き整数(2の補数)として扱われる場合、OFフラグはcarryまたはborrowを示します。BCDとして扱われた場合、AFフラグはcarryまたはborrowを示します。SFフラグは、符号付き整数の符号を示します。ZFフラグは、符号付き整数または符号なし整数のいずれかを示します。
3.4.3.2 DF Flag
Direction flagは、文字列命令(MOVS、CMPS、SCAS、LODS、およびSTOS)を制御します。 DFフラグを設定すると、文字列命令が自動でデクリメントされます(文字列を高アドレスから低アドレスに処理する)。DFフラグをクリアすると、文字列命令が自動でインクリメント(低アドレスから高アドレスへの文字列の処理)になります。
STD命令とCLD命令はそれぞれDFフラグをセット、クリアします。
3.4.3.3 System Flags and IOPL Field
EFLAGSレジスタのsystem flagとIOPL fieldはOSまたは実行環境を制御します。これらはアプリケーションプログラムから変更すべきではありません。
- IOPL(I/O privilege level field: bit 12-13)
現在実行中のプログラムまたはタスクのI/O権限を示します。現在実行中のプログラムまたはタスクのcurrent privilege level(CPL)はI/OアドレススペースにアクセスするためのI/O権限レベル以下でなければなりません。$CPL<=アクセスに必要なI/O権限レベル$
POPF命令とIRET命令は、CPLが0のときにのみこのフィールドを変更できます。 - NT(Nested task flag: bit 14)
割り込みチェーンと呼びだされたタスクを制御します。現在のタスクが前に実行されたタスクへリンクされればセットされます。現在のタスクが他のタスクにリンクされなければクリアされます。 - RF(Resume flag: bit 16)
デバッグ例外への返答を制御します。 - VM(Virtual-8086 mode flag: bit 17)
virtual-8086 modeが有効の場合にセット。 - AC(Alignment check(or access control) flag: bit 18)
AM bitがCR0レジスタに設定されている場合、このフラグが1の場合に限り、ユーザーモードデータアクセスのアライメントチェックが有効になります。
SMAP bitがCR4レジスタに設定されている場合、このbitが1の場合に限り、ユーザーモードページへの明示的なスーパバイザモードデータアクセスが許可されます。 - VIF(Virtual Interrupt flag: bit 19)
仮想イメージのためのIFフラグです。VIPフラグと組み合わせて使用されます。 - VIP(Virtual interrupt pending flag: bit 20)
割り込みが保留中の場合にセットされます。 - ID(Identification flag: bit 21)
このフラグをセットまたはクリアするプログラムの能力は、CPUID命令のサポートを示します。
3.4.3.4 RFLAGS Register in 64-Bit Mode
64bitモードではEFLAGSは64bitに拡張されRFLAGSと呼ばれます。上位32bitは予約ビットです。下位32bitはEFLAGSと同じです。
3.5 INSTRUCTION POINTER
命令ポインタ(EIP)レジスタは、現在のコードセグメントにおける次に実行される命令のオフセットを含みます。EIPレジスタにソフトウェアで直接アクセスすることはできません。制御転送命令(JMP、Jcc、CALL、RETなど)、割り込み、および例外によって暗黙的に制御されます。 EIPレジスタを読み取る唯一の方法は、CALL命令を実行してから、プロシージャスタックからの戻り命令ポインタの値を読み取ることです。 EIPレジスタは、プロシージャスタック上のリターン命令ポインタの値を変更し、リターン命令(RETまたはIRET)を実行することによって、間接的にロードすることができます。
(以下省略)
3.5.1 Instruction Pointer in 64-Bit Mode
64bitモードでは命令ポインタはRIPと呼ばれ、64bitに拡張されています。
3.6 OPERAND-SIZE AND ADDRESS-SIZE ATTRIBUTES
(省略)
3.6.1 Operand Size and Address Size in 64-Bit Mode
(省略)
3.7 OPERAND ADDRESSING
IA-32マシン命令は、0個以上のオペランドに対して作用します。いくつかのオペランドは明示的に指定され、他は暗黙的に指定されます。ソースオペランドのデータは、次の場所にあります。
- 命令自身
- レジスタ
- メモリ位置
- I/Oポート
命令の戻り値を返す先は次の場所にあります。
- レジスタ
- メモリ位置
- I/Oポート
3.7.1 Immediate Operands
いくつかの命令はソースオペランドとして命令自身にエンコードされたデータを使用します。これらのオペランドは即値オペランド(単に即値)と呼ばれます。
例:
ADD EAX, 14
3.7.2 Register Operands
(省略)
3.7.2.1 Register Operands in 64-Bit Mode
(省略)
3.7.3 Memory Operands
メモリ内のソース・デスティネーションオペランドはセグメントセレクタとオフセットを参照します。セグメントセレクタは、オペランドを含むセグメントを指定します。オフセットは、オペランドのリニアアドレスまたは実効アドレスを指定します。オフセットは、32ビット(表記m16:32で表される)または16ビット(表記m16:16で表される)にすることができます。
3.7.3.1 Memory Operands in 64-Bit Mode
(省略)
3.7.4 Specifying a Segment Selector
セグメントセレクタは、暗黙的にまたは明示的に指定できます。
メモリにデータを格納するとき、またはメモリからデータをロードするときに、DSセグメントのデフォルトを上書きして、他のセグメントにアクセスできるようにすることができます。 アセンブラ内では、セグメントのオーバーライドは一般的にコロン ":"演算子で処理されます。 たとえば、次のMOV命令は、EAXレジスタからESレジスタが指すセグメントに値を移動します。 セグメントへのオフセットは、EBXレジスタに格納されます。
MOV ES:[EBX], EAX
3.7.4.1 Segmentation in 64-Bit Mode
(省略)
3.7.5 Specifying an Offset
メモリアドレスのオフセット部分は、静的(ディスプレースメントと呼ばれます)に直接指定することも、次のコンポーネントの1つ以上で構成されるアドレス計算を介して指定することもできます。
- Displacement
8, 16, 32bit値 - Base
汎用レジスタの値 - Index
汎用レジスタの値 - Scale factor
インデックスを掛けた2,4または8の値
これらの成分を加算した結果のオフセットを実効アドレスといいます。
3.7.5.1 Specifying an Offset in 64-Bit Mode
(省略)
3.7.6 Assembler and Compiler Addressing Modes
機械コードレベルでは、ディスプレースメント、ベースレジスタ、インデックスレジスタ、スケールファクタの選択された組み合わせが命令でエンコードされます。すべてのアセンブラでは、プログラマは、これらのアドレッシングコンポーネントの許容可能な組み合わせのいずれかを使用して、オペランドをアドレス指定することができます。高水準言語コンパイラは、プログラマが定義する言語構成に基づいて、これらのコンポーネントの適切な組み合わせを選択します。
3.7.7 I/O Port Addressing
プロセッサは最大65,536の8bit I/Oポートを含むI/Oアドレススペースをサポートしています。I/Oアドレススペースには、16bitおよび32bitのポートを定義することもできます。 I/Oポートは、即値オペランドまたはDXレジスタの値でアドレス指定できます。
Chapter3まとめ
- Chapter3では実行環境、動作モード、メモリモデル、各種フラグを説明
- 64bitモードではアドレスは64bit、オペランドは32bit
- 64bitモードでは基本的にセグメンテーションが無効
Chapter4 DATA TYPES
この章ではIntel64とIA-32におけるデータタイプの定義を紹介します。
4.1 FUNDAMENTAL DATA TYPES
基本データ型はByte, Word, Dword, Qword, Dqwordです。
Byteは8bit, Wordは2Byte, Dwordは4Byte, Qwordは8Byte, Dqwordは16Byteです。
4.1.1 Alignment of Words, Doublewords, Quadwords, and Double Quadwords
それぞれの基本データ型をメモリ内で整列させるとプロセッサのアクセス効率が上がります。
4.2 NUMERIC DATA TYPES
バイト、ワード、ダブルワードは基本的なデータ型ですが、いくつかの命令では、これらのデータ型に加えて数値データ型(符号付きおよび符号なし整数、および浮動小数点数)に対して演算を実行できます。単精度(32bit)浮動小数点および倍精度(64bit)浮動小数点データ型は、すべての世代のSSE拡張およびIntel AVX拡張でサポートされています。 半精度(16bit)浮動小数点データ型は、F16C拡張(VCVTPH2PS、VCVTPS2PH)でのみサポートされています。
4.2.1 Integers
Intel64とIA-32では2つのタイプ(符号付き、符号なし)が定義されています。
4.2.1.1 Unsigned Integers
符号なし整数はByte, Word, Dword, Qwordを含む符号なしバイナリです。値の範囲は符号なしByte整数は0-255、符号なしWord整数は0-65,535、符号なしDword整数は0-$2^{32}-1$、符号なしQword整数は0-$2^{64}-1$です。符号なし整数は時々ordinals(序数)とよばれます。
4.2.1.2 Signed Integers
符号付き整数はByte, Word, Dword, Qwordの符号付きバイナリです。
符号付き整数の全ての操作は2の補数を想定しています。符号ビットはByteでbit 7, Wordでbit 15, Dwordでbit 31, Qwordでbit 63です。
符号ビットがセットされている場合は負の数で、0の場合は正の数です。
符号付き整数の範囲でByteは-128から127, Wordは-32,768から32,767, Dwordは$-2^{31}$から$2^{31}-1$, Qwordは$-2^{63}$から$2^{63}-1$です。
メモリに整数値を格納する場合、Wordの整数は2Byteが、Dwordの整数は4Byteが、Qword整数は8Byteが連続して格納されます。
4.2.2 Floating-Point Data Types
IA-32では単精度浮動小数点、倍精度浮動小数点、拡張倍精度浮動小数点の3種類が定義・操作されます。
(以下省略)
4.3 POINTER DATA TYPES
ポインタはメモリ内のアドレス位置です。
64bitモードでない場合は2種類(near pointerとfar pointer)が定義されています。near pointerはセグメント内の32bitまたは16bitのオフセットです。near pointerはフラットメモリモデルの全てのメモリ参照または、セグメントメモリモデルセグメントを暗示されたアクセスを認識するために使われます。
far pointerは16bitのセグメントセレクタと32bit(または16bit)のオフセットで構成された論理アドレスです。far pointerはセグメントメモリモデルで明示的に指定された識別のために使われます。
4.3.1 Pointer Data Types in 64-Bit Mode
64bitモード(サブモードIA-32e)ではnear pointerは64bitです。これは実行アドレスと同値です。
64bitモードのfar pointerには3つの形式があります。
- もしオペランドサイズが32bitなら16bitのセグメントセレクタと16bitのオフセット
- もしオペランドサイズが32bitなら16bitのセグメントセレクタと32bitのオフセット
- もしオペランドサイズが64bitなら16bitのセグメントセレクタと64bitのオフセット
4.4 BIT FIELD DATA TYPE
bit領域は連続したbitシーケンスです。メモリ内の任意のByteの任意のbit位置から開始することができ、最大32bitを含むことができます。
4.5 STRING DATA TYPES
文字列は連続したbit, Word, Dwordのシーケンスです。bit文字列はメモリ内の任意のByteの任意のbit位置から開始することができ、最大$2^{32}-1$bitを含むことができます。Byte文字列はByte, Word, Dwordを含むことができ、範囲は0-$2^{32}-1$Byte(4GB)です。
4.6 PACKED SIMD DATA TYPES
Intel64とIA-32はSIMD演算を行うために64bitと128bitのパックされたデータ型を定義・操作されます。
4.6.1 64-Bit SIMD Packed Data Types
64bitのパックされたSIMDデータ型はMMXのIA-32の中に導入されました。これらはMMXレジスタ上で操作されます。基本的なSIMDデータ型はパックされたByte, Word, Dwordです。
4.6.2 128-Bit Packed SIMD Data Types
128ビットのパックドSIMDデータ型は、SSE拡張でIA-32アーキテクチャに導入され、SSE2、SSE3、およびSSSE3拡張で使用されました。
これらは主に128bitXMMレジスタとメモリで操作されます。基本的な128bitSIMDデータ型は、パックされたByte, Word, Dword, Qwordです。
4.7 BCD AND PACKED BCD INTEGERS
BCDは範囲が0-9の符号なし4bit整数です。
(以下省略)
4.8 REAL NUMBERS AND FLOATING-POINT FORMATS
この節では、x87 FPUおよびSSE/SSE2/SSE3/SSE4.1およびインテル®AVX浮動小数点命令で、実数が浮動小数点形式でどのように表されるかについて説明します。
4.8.1 Real Number System
実数系は、マイナス無限大(-∞)からプラス無限大(+∞)までの実数の連続値で構成されています。
任意のコンピュータが持つことができるレジスタのサイズと数が限られているため、実数連続値のサブセットのみが実数(浮動小数点)計算で使用できます。
4.8.2 Floating-Point Format
コンピュータやマイクロプロセッサは一般的に計算速度や精度を上げるために、実数にはバイナリ浮動小数点を用います。この形式は実数の表現を符号、仮数部、指数部で表されます。
符号は0か1で示されます。仮数部は、1bitの2進整数(Jbitとも呼ばれる)と2進数の2つの部分で構成されます。指数部は、基数2の累乗を表す2進数の整数です。
[IDMより掲載]
4.8.2.1 Normalized Numbers
(省略)
4.8.2.2 Biased Exponent
(省略)
4.8.3 Real Number and Non-number Encodings
IEEE標準754浮動小数点形式では、さまざまな実数と特殊値を符号化できます。
- Signed zeros
- Denormalized finite numbers
- Normalized finite numbers
- Signed infinities
- NaNs
- Indefinite numbers
4.8.3.1 Signed Zeros
ゼロは、符号ビットに応じて+0または-0として表すことができます。
(以下省略)
4.8.3.2 Normalized and Denormalized Finite Numbers
非ゼロの有限数は、正規化と非正規化の2つのクラスに分類されます。正規化された有限数は、0と∞との間の正規化された実数形式で符号化され得るすべての非ゼロ有限値を含む。
(以下省略)
4.8.3.3 Signed Infinities
2つの無限大+∞と - ∞は、それぞれ浮動小数点形式で表現できる最大の正の実数と負の実数を表します。無限大は、常に1.00 ... 00(整数ビットが暗示される可能性があります)と指定された形式で許容される最大偏り指数で表されます。
(以下省略)
4.8.3.4 NaNs
NaNは非数であるため、実数行の一部ではありません。このスペースには、最大許容偏り指数とゼロ以外の任意の値が含まれます(符号ビットはNaNでは無視されます)。
IA-32アーキテクチャーは、quiet NaN(QNaN)とsignaling NaN(SNaN)の2つのクラスのNaNを定義します。QNaNは、最も重要な部分ビットが設定されたNaNです。SNaNは、最も重要な部分ビットをクリアしたNaNです。QNaNは、例外を通知することなくほとんどの算術演算を伝播することができます。SNaNは一般に、浮動小数点無効演算例外が算術演算のオペランドとして現れると、その例外を通知します。
SNaNは通常、例外ハンドラをトラップまたは呼び出すために使用されます。それらはソフトウェアで挿入する必要があります。つまり、プロセッサは浮動小数点演算の結果としてSNaNを生成しません。
4.8.3.5 Operating on SNaNs and QNaNs
(省略)
4.8.3.6 Using SNaNs and QNaNs in Applications
(省略)
4.8.3.7 QNaN Floating-Point Indefinite
(省略)
4.8.3.8 Half-Precision Floating-Point Operation
半精度浮動小数点は直接数値演算で使用されません。
(以下省略)
4.8.4 Rounding
浮動小数点演算を実行する際、可能であれば、プロセッサは結果として(単精度、倍精度、または倍精度の倍精度浮動小数点)で無限に正確な浮動小数点結果を生成します。しかし、IEEE標準754浮動小数点形式では実数連続体の数値のサブセットしか表現できないため、無限に正確な結果を宛先オペランドの形式で正確に符号化できないことがよくあります。
丸めでは、結果が丸められる最後の場所(浮動小数点値の最下位bit位置)の1単位未満の結果にエラーが発生します。
IEEE標準754浮動小数点形式では4つのモードがあります。
[IDMより掲載]
(以下省略)
4.8.4.1 Rounding Control (RC) Fields
Intel64とIA-32では、丸めはRCフィールドで制御します。RCフィールドは2つの異なる場所に実装されています。
- x87 FPU control register(bit 10-11)
- The MXCSR register(bit 13-14)
2つは同じ機能を提供しますが、プロセッサ内の異なる実行環境のために用意されています。x87 FPU control registerはx87 FPU命令で使用されます。MXCSR registerはSSE/SSE2命令のSIMD浮動小数点演算を実行した時に使用されます。
4.8.4.2 Truncation with SSE and SSE2 Conversion Instructions
(省略)
4.9 OVERVIEW OF FLOATING-POINT EXCEPTIONS
この節ではIA-32における浮動小数点例外と例外処理について説明します。
浮動小数点オペランドを実行した時、IA-32では6つの例外クラスを認識します。
- Invalid operation (#I)
- Divide-by-zero (#Z)
- Denormalized operand (#D)
- Numeric overflow (#O)
- Numeric underflow (#U)
- Inexact result (precision) (#P)
Invalid operation、Divide-by-zero、Denormalized operandは、事前計算の例外です。The numeric-underflow、numeric-overflow、precisionは事後計算の例外です。
それぞれの例外はflag bit(IE, ZE, OE, UE, DE, PE)とmask bit(IM, ZM, OM, UM, DM, PM)に対応しています。1つ以上の例外が検出された場合、プロセッサは、適切なflag bitをセットし、対応するmask bitの設定に応じて、2つの可能なアクションのうちの1つを取ります。
- mask bit set
プログラムの実行が妨げられないようにしながら、例外を自動的に処理して、あらかじめ定義された(使用可能な)結果を生成します。 - mask bit clear
例外を処理するソフトウェア例外ハンドラを呼び出します。
例外フラグは「sticky」なので、最後にクリアされてから発生した例外の累積レコードを提供します。したがって、プログラマはすべての例外をマスクし、計算を実行し、例外フラグを検査して、計算中に例外が検出されたかどうかを調べることができます。
exception flagとmask flagは2つの異なる場所に実装されています。
- x87 FPU status word and control word
flag bitはstatus wordのbit 0-5、mask bitはcontrol wordのbit 0-5。 - MXCSR register
flag bitはbit 0-5、mask bitはbit 7-12。
4.9.1 Floating-Point Exception Conditions
この節では各例外や各例外が検出された時のアクションを説明します。
4.9.1.1 Invalid Operation Exception (#I)
プロセッサは一つ以上の無効な数値オペランドを指定された場合に例外を発行します。
もし例外がマスクされている場合、IEフラグをセットし未定義値かQNanを返します。この値は命令による宛先レジスタへ上書きします。もし例外がマスクされていない場合、IEフラグをセットしソフトウェア例外ハンドラを呼び出し、オペランドは変更されません。
(以下省略)
4.9.1.2 Denormal Operand Exception (#D)
算術命令が非正規オペランドを操作しようとすると、プロセッサは非正規オペランド例外を発行します。
例外がマスクされている場合、DEフラグをセットし、命令を続行します。
もし例外がマスクされていない場合、DEフラグをセットしソフトウェア例外ハンドラを呼び出し、オペランドは変更されません。
4.9.1.3 Divide-By-Zero Exception (#Z)
非ゼロの数値を0で割ろうとするとゼロ除算例外が発行されます。
マスクされている場合、ゼロ除算例外は、ZEフラグを設定し、オペランドの符号を排他的論理和で符号化された無限大を返します。 マスクされていない場合、ZEフラグがセットされ、ソフトウェア例外ハンドラが呼び出され、オペランドは変更されません。
4.9.1.4 Numeric Overflow Exception (#O)
命令の丸められた結果が宛先オペランドに収まる最大許容有限値を超えると、プロセッサは浮動小数点数値オーバーフロー例外を発行します。
[IDMより掲載]
例外がマスクされている場合、OEフラグをセットし、現在の丸めモードにしたがって値が返される。
[IDMより掲載]
4.9.1.5 Numeric Underflow Exception (#U)
プロセッサは、(x87の精度制御を考慮に入れて)無限の指数での丸めの結果がゼロではなく、小さい場合はいつでも、潜在的な浮動小数点数値アンダーフロー条件を検出します。すなわち非ゼロで、宛先オペランドに収まる最小の正規化された有限値よりも小さい。
(以下省略)
4.9.1.6 Inexact-Result (Precision) Exception (#P)
演算結果が宛先フォーマットで正確に表現できない場合、不正確結果例外(精度例外とも呼ばれる)が発生します。たとえば、1/3の部分をバイナリ浮動小数点形式で正確に表すことはできません。
この例外は頻繁に発生し、丸めによって(通常許容される)精度が失われることを示します。 この例外は、正確な算術演算のみを実行する必要のあるアプリケーションでサポートされています。
丸められた結果は、ほとんどのアプリケーションでは一般的に満足できるので、この例外は通常マスクされます。
例外がマスクされていて、浮動小数点数値オーバーフロー例外と浮動小数点数値アンダーフロー条件が発行されていない場合、プロセッサはPEフラグをセットし、丸めた結果を返します。
例外がマスクされておらず、浮動小数点数値オーバーフロー例外と浮動小数点数値アンダーフロー条件が発行されていない場合、PEフラグをセットし丸めた値を宛先オペランドへ格納し、ソフトウェア例外ハンドラを呼び出します。
(以下省略)
4.9.2 Floating-Point Exception Priority
プロセッサは、所定の優先順位に従って例外を処理します。
(以下省略)
4.9.3 Typical Actions of a Floating-Point Exception Handler
浮動小数点例外ハンドラが呼び出された後、プロセッサは非浮動小数点例外を処理するのと同じ方法で例外を処理します。浮動小数点例外ハンドラは、通常オペレーティングシステムまたは実行環境ソフトウェアの一部であり、通常ユーザが登録した浮動小数点例外ハンドルを呼び出します。
例外ハンドラの典型的な動作は、メモリに状態情報を格納することです。 他の典型的な例外ハンドラのアクションには以下があります。
- 格納された状態情報を調べてエラーの性質を判断
- エラーの原因となった条件を修正するためのアクションの実行
- 例外フラグのクリア
- 中断したプログラムに戻り、通常の実行を再開
回復手続きを書く代わりに、例外ハンドラは以下を行うことができます:
- 後で表示または印刷するためのソフトウェア例外カウンタをインクリメント
- 診断情報(状態情報など)の印刷または表示
- プログラムの実行を停止
Chapter4まとめ
- Chapter4で基本データ型や数値型、文字列型、浮動小数点型や浮動小数点の例外について説明
- 64bitモードにはアドレスポインタにnear pointerとfar pointerがあり、far pointerはセグメントセレクタとオフセットを表現
- 浮動小数点例外をマスクするのとしないのとで動作が違う
まだ、読んでいてもイメージが湧かないところが多々あります。今後実際にアセンブリ言語などを触れていく中で再認識されていくと思いますので、今は前へ進みます。