x86アセンブリ言語は、 x86クラスのプロセッサ用のオブジェクトコードを生成するために使用される低レベルプログラミング言語のファミリーです。この言語ファミリーの以前の進化は、1972年4月に発表されたIntel 8008マイクロプロセッサまで遡るCPUとの後方互換性を提供します。 [ 1 ] [ 2 ]アセンブリ言語であるため、アーキテクチャのマシンコード命令と密接に結びついており、ハードウェアを正確に制御できます。
x86アセンブリ言語では、基本的なCPU命令を表すためにニーモニックが使用され、生の機械語に比べてコードが人間にとって読みやすくなります。各機械語命令はオペコードであり、アセンブリではニーモニックに置き換えられます。[ 3 ]各ニーモニックは、算術計算、データ移動、制御フローの決定など、プロセッサによって実行される基本的な操作に対応します。アセンブリ言語は、パフォーマンスと効率が重要なアプリケーションで最も一般的に使用されます。これには、リアルタイム組み込みシステム、オペレーティングシステムカーネル、デバイスドライバなど があり、これらはすべてハードウェアリソースを直接操作する必要がある場合があります。
さらに、高級プログラミング言語のコンパイラは、コンパイル処理の中間段階としてアセンブリコードを生成することがあります。これにより、プロセッサが実行する最終的なマシンコードを生成する前に、アセンブリレベルでの最適化が可能になります。
x86アセンブリ言語の各命令は、多くの場合1つ以上のオペランドと組み合わされて、オペコードと呼ばれる1つ以上のバイトに変換されるニーモニックで表されます。たとえば、NOP命令はオペコード0x90に変換され、HLT命令は0xF4に変換されます。[ 3 ]ドキュメント化されたニーモニックのない潜在的なオペコードがあり、異なるプロセッサでは解釈が異なる場合があります。このようなオペコードを使用すると、プログラムが一貫性のない動作をしたり、一部のプロセッサで例外を生成したりする可能性があります。
x86アセンブリ言語には、 Intel構文とAT & T構文という2つの主要な構文ブランチがあります。[ 4 ] Intel構文はDOSおよびWindows環境で主流ですが、AT & T構文はUnix系システムで主流です。Unixは元々AT&Tベル研究所で開発されたためです。[ 5 ]以下は、 Intel構文とAT & T構文の主な違いの概要です。
多くの x86 アセンブラはIntel 構文を使用しており、FASM、MASM、NASM、TASM、YASMなどが含まれます。元々AT & T 構文を使用していたGNU アセンブラは、バージョン 2.10 以降、ディレクティブを介して両方の構文をサポートしています。[ 4 ] [ 6 ] [ 7 ] x86 用のAT & T 構文の癖として、 x87浮動小数点オペランドが逆になっていることが挙げられます。これは、元の AT&T アセンブラから引き継がれたバグです。[ 8 ].intel_syntax
AT&T構文は他のアーキテクチャでもほぼ共通しており(命令のオペランド順序は同じmov)、元々はPDP-11アセンブリ用に設計され、Unix系システムに継承されました。一方、Intel構文はx86アーキテクチャに固有のもので、x86プラットフォームの公式ドキュメントで使用されています。x86アーキテクチャより前に登場したIntel 8080も、命令の「宛先優先」順序を採用していますmov。[ 9 ]
ほとんどの x86 アセンブリ言語では、予約語は2 つの部分から構成されます。1 つはオペコードに変換されるニーモニック、もう 1 つはオペコードの単純な変換を超えてアセンブラ プログラムの機能にアクセスするディレクティブ (または「擬似命令」) です。前者の部分のリストについては、x86 命令リストを参照してください。後者の部分はアセンブラに大きく依存しており、Intel 構文のアセンブラには標準というものはありません。[ 10 ] AT&T 構文のアセンブラは、ディレクティブの命名方法を共有しています (すべてのディレクティブはドットで始まります。例: .ascii)、[ 11 ].asciiまた、やなどの基本的なディレクティブが.string広くサポートされています。[ 12 ] [ 13 ]
x86プロセッサには、プログラム実行中にバイナリデータとアドレスを格納するためのレジスタセットがあります。これらのレジスタは、汎用レジスタ、セグメントレジスタ、命令ポインタ、FLAGSレジスタ、および後のプロセッサモデルで導入されたさまざまな拡張レジスタに分類されます。各レジスタは、一般的な機能に加えて、特定の機能を持っています。[ 3 ]
これらのレジスタには慣習的な役割があるが、その使用方法は厳密には強制されない。プログラムは一般的に、これらのレジスタを他の目的に自由に利用できる。
XLAT。INとOUT命令も保持します。PUSHおよび操作中に自動的に更新されますPOP。MOVS(文字列移動など)命令は、SIを使用してメモリからデータを読み取ります。BXと同様に、インデックスとして使用できます。二重インデックスのために、BPまたはBXに追加することもできます。一般登録簿に加えて、以下のものも存在します。
x86レジスタはほとんどの命令で使用できます。例えば、Intel構文では次のようになります。
mov ax , 1234h ; 値 1234hex (4660d) をレジスタ AX にコピーしますmov bx , ax ; AXレジスタの値をBXレジスタにコピーしますx86アーキテクチャは、実環境および仮想環境の8086モードにおいて、メモリのアドレス指定にセグメンテーションと呼ばれるプロセスを使用します。これは、他の多くの環境で使用されているフラットメモリモデルとは異なります。セグメンテーションでは、メモリアドレスはセグメントとオフセットの2つの部分から構成されます。セグメントは64 KiB(64× 2¹⁰ )のアドレスグループの先頭を指し、オフセットはこの開始アドレスから目的のアドレスまでの距離を決定します。セグメントアドレス指定では、完全なメモリアドレスには2つのレジスタが必要です。1つはセグメントを保持するため、もう1つはオフセットを保持するためです。フラットアドレスに変換するには、セグメント値を4ビット左にシフト(2⁴または16を掛けることに相当)してからオフセットに加算して完全なアドレスを形成します。これにより、アドレスを巧みに選択することで64kの壁を突破できますが、プログラミングはかなり複雑になります。
リアルモード(保護モード)の場合、例えば、DSレジスタに16進数0xDEAD、DXレジスタに0xCAFEが格納されている場合、これら2つのレジスタはメモリのアドレスを指します。したがって、CPUはリアルモードで最大1,048,576バイト(1MiB)までアドレス指定できます。セグメント値とオフセット値を組み合わせることで、20ビットのアドレスが得られます。0xDEAD * 0x10 + 0xCAFE == 0xEB5CE
初期のIBM PCはプログラムのサイズを640KBに制限していたが、拡張されたメモリ仕様を用いてバンク切り替え方式を実装していた。しかし、Windowsなどの後のオペレーティングシステムが新しいプロセッサのより広いアドレス範囲を使用し、独自の仮想メモリ方式を実装するようになるにつれて、この方式は使われなくなった。
Intel 80286 から始まったプロテクトモードは、OS/2で利用されました。BIOSにアクセスできないことや、プロセッサをリセットせずにリアルモードに戻せないことなど、いくつかの欠点により、広く使用されることはありませんでした。[ 14 ]また、80286 はメモリのアドレス指定が 16 ビットセグメントに制限されていたため、一度にアクセスできるのは2 16バイト (64キロバイト) のみでした。80286 の拡張機能にアクセスするには、オペレーティングシステムがプロセッサをプロテクトモードに設定し、24 ビットアドレス指定を有効にして、2 24バイト (16メガバイト) のメモリを使用できるようにしました。
保護モードでは、セグメントセレクタは 3 つの部分に分けられます。13 ビットのインデックス、エントリがGDTまたはLDTにあるかどうかを決定するテーブルインジケータビット、および 2 ビットの要求された特権レベルです。x86メモリセグメンテーションを参照してください。
セグメントとオフセットを持つアドレスを参照する場合、セグメント:オフセットという表記が使用されます。したがって、上記の例では、フラットアドレス0xEB5CE は 0xDEAD:0xCAFE またはセグメントとオフセットレジスタのペア DS:DX として記述できます。
セグメントレジスタと汎用レジスタの特別な組み合わせの中には、重要なアドレスを指すものがあります。
Intel 80386 は、リアル モード、プロテクト モード、仮想モードの 3 つの動作モードを備えていました。80286で初めて搭載されたプロテクト モードは拡張され、80386 では最大 4 GBのメモリをアドレス指定できるようになりました。また、まったく新しい仮想 8086 モード ( VM86 ) により、リアル モードをほぼエミュレートする保護された環境で 1 つ以上のリアル モード プログラムを実行できるようになりましたが、一部のプログラムは互換性がありませんでした (通常はメモリ アドレス指定のトリックや未指定のオペ コードの使用が原因でした)。
80386の拡張保護モードの32ビットフラットメモリモデルは、 AMDが2003年にx86-64をリリースするまで、x86プロセッサファミリーにとって最も重要な機能変更だったと言えるでしょう。なぜなら、仮想メモリとシンプルなマルチタスク処理によって、DOSアプリケーションを含む多くのアプリケーションを同時に実行できるようになったため、保護モードに依存していたWindows 3.1の大規模な普及を促進したからです。
x86 プロセッサは、x86 コードに対して、リアル モード、プロテクト モード、ロング モード、仮想 86 モード、システム管理モードの 5 つの動作モードをサポートしており、これらのモードでは一部の命令が使用可能で、その他は使用できません。16 ビット x86 プロセッサ (8086、8088、80186、80188、80286) では、16 ビットの命令のサブセットが使用可能です。これらの命令は、すべての x86 プロセッサのリアル モードおよび 16 ビット プロテクト モード ( 80286以降) で使用可能で、プロテクト モードに関連する追加の命令も使用できます。80386 以降では、リアル モードを含むすべてのモードで 32 ビット命令 (後続の拡張機能を含む) も使用可能です。これらの CPU では、V86 モードと 32 ビット プロテクト モードが追加され、これらのモードで機能を管理するための追加の命令が提供されます。 SMMは、独自の特殊命令セットを備え、一部のIntel i386SL、i486以降のCPUで利用可能です。また、ロングモード(AMD Opteron以降)では、64ビット命令とより多くのレジスタが利用できます。命令セットは各モードで似ていますが、メモリのアドレッシングとワードサイズが異なるため、異なるプログラミング戦略が必要となります。
x86コードを実行できるモードは以下のとおりです。
プロセッサは電源投入直後からリアルモードで動作するため、オペレーティングシステムカーネルやその他のプログラムがリアルモード以外のモードで動作させたい場合は、明示的に別のモードに切り替える必要があります。モードの切り替えは、準備作業の後、プロセッサの制御レジスタの特定のビットを変更することで行われ、切り替え後に追加の設定が必要になる場合があります。
レガシーBIOSを搭載したコンピュータでは、BIOSとブートローダーはリアルモードで動作します。64ビットオペレーティングシステムのカーネルは、CPUをチェックしてロングモードに切り替え、その後、64ビットコードを実行する新しいカーネルモードスレッドを開始します。
UEFIを実行しているコンピュータでは、UEFIファームウェア(CSMと従来のオプションROMを除く)、UEFIブートローダー、およびUEFIオペレーティングシステムカーネルはすべてロングモードで実行されます。
一般的に、現代のx86命令セットの特徴は以下のとおりです。
ebp両方の使用について。32ビット モードでは7 個 (数えて) の一般的なレジスタすべて、64 ビット モードでは 15 個 (数えて) の一般的なレジスタすべてが、アキュムレータまたはアドレッシングにrbp自由に使用できますが、それらのほとんどは、特定の (多かれ少なかれ) 特殊な命令によって暗黙的に使用されます。そのため、影響を受けるレジスタは、そのような命令シーケンス中にアクティブである場合、一時的に保持 (通常はスタック) する必要があります。xchg、cmpxchg/ cmpxchg8b、、xaddおよびプレフィックスと組み合わせる整数命令lock)に対する特別なサポートが含まれています。x86アーキテクチャは、実行スタックpush機構をハードウェアでサポートしています。 、pop、などの命令はcall、ret適切に設定されたスタックとともに使用され、パラメータの受け渡し、ローカルデータ用の領域の割り当て、呼び出し復帰点の保存と復元を行います。retsize命令は、呼び出し先がパラメータによって占有されたスタック領域の解放を担当する、スペース効率の良い(そして高速な)呼び出し規約を実装するのに非常に役立ちます。
再帰プロシージャのローカルデータを保持するためのスタックフレームを設定する場合、いくつかの選択肢があります。高レベル命令(80186で導入)は、プロシージャのネスト深度引数とローカルサイズ引数の両方を受け取り、レジスタのより明示的な操作(; ;など)よりも高速になる可能性があります。高速か低速かは、特定のx86プロセッサの実装と、コンパイラ、プログラマ、または特定のプログラムコードで使用される呼び出し規約によって異なります。ほとんどのx86コードは、複数のメーカーのx86プロセッサと異なる世代のプロセッサで実行することを想定しているため、マイクロアーキテクチャとマイクロコードソリューション、およびゲートレベルとトランジスタレベルの設計の選択肢が大きく異なります。enterpush bp mov bp, sp sub sp, size
やなどの命令でも、アドレッシングモード(即値とベース+オフセットを含む)の全範囲が利用できるため、整数、浮動小数点、アドレスデータにスタックを直接使用することが容易になり、一部のRISCアーキテクチャ(より明示的なコールスタックの詳細が必要)と比較して、 ABI仕様とメカニズムが比較的シンプルに保たれます。pushpop
x86 アセンブリには、標準的な数学演算、、add、sub、neg(符号付き整数用)、 およびimul(符号なし整数用)、論理演算子、、、ビットシフト算術および論理演算、/ (符号付き整数用)、/ (符号なし整数用)、キャリーあり/なしの回転、/ 、/ 、および補数BCD算術命令、、があります。やなどの命令は、オペランドを変更せずにフラグを設定します。idivmuldivandorxornotsalsarshlshrrclrcrrolroraaaaaddaacmptest
x86アセンブリ言語には、スタックベースの浮動小数点演算ユニット(FPU)の命令が含まれています。FPUは、8086から80386まではオプションの独立したコプロセッサでしたが、80486シリーズではオンチップオプションとなり、Pentium以降の80486以降のすべてのIntel x86 CPUでは標準機能となっています。FPU命令には、加算、減算、否定、乗算、除算、剰余、平方根、整数切り捨て、小数切り捨て、2のべき乗によるスケーリングが含まれます。また、これらの演算には変換命令も含まれており、メモリから値をロードまたはストアする際に、2進化10進数、32ビット整数、64ビット整数、32ビット浮動小数点、64ビット浮動小数点、80ビット浮動小数点のいずれかの形式で使用できます(ロード時に、値は現在使用されている浮動小数点モードに変換されます)。 x86には、正弦、余弦、正接、逆正接、底が2のべき乗、底が2、10、またはeの対数など、多くの超越関数も含まれています。
命令のスタック レジスタからスタック レジスタへの形式は通常、またはであり、は と等価で、は 8 つのスタック レジスタ ( 、、 ...、) のいずれかです。整数と同様に、最初のオペランドは最初のソース オペランドとデスティネーション オペランドの両方です。と は、減算または除算を実行する前にソース オペランドを交換するため、特に注意が必要です。加算、減算、乗算、除算、ストア、および比較命令には、操作が完了した後にスタックのトップをポップする命令モードが含まれています。たとえば、 は計算 を実行し、スタックのトップから を削除するため、のスタックのトップにあった結果が になります。fop st, st(n)fop st(n), ststst(0)st(n)st(0)st(1)st(7)fsubrfdivrfaddp st(1), stst(1) = st(1) + st(0)st(0)st(1)st(0)
最新の x86 CPU にはSIMD命令が含まれており、これは、広い SIMD レジスタにエンコードされた多数の値に対して、ほぼ同じ操作を並列に実行します。さまざまな命令技術は、異なるレジスタ セットに対して異なる操作をサポートしていますが、全体として ( MMXからSSE4.2まで) は、整数または浮動小数点演算 (加算、減算、乗算、シフト、最小化、最大化、比較、除算、平方根) に関する一般的な計算を含みます。たとえば、 は、paddw mm0, mm1に 4 つの並列 16 ビット ( で示されるw) 整数加算 ( で示されるpadd)を実行mm0しmm1、結果を に格納しますmm0。ストリーミング SIMD 拡張(SSE) には、レジスタの最初の値のみが実際に変更される浮動小数点モードも含まれています ( SSE2で拡張)。その他の珍しい命令として、絶対差の合計( MPEGで行われるようなビデオ圧縮の動き推定に使用) や 16 ビット乗算累積命令 (ソフトウェア ベースのアルファ ブレンディングやデジタル フィルタリングに有用) などがあります。 SSE( SSE3以降)および3DNow!拡張機能には、浮動小数点値のペアを複素数のように扱うための加算および減算命令が含まれています。
これらの命令セットには、レジスタ内の値をシャッフル、挿入、抽出するための多数の固定サブワード命令も含まれています。さらに、整数レジスタとXMM(SSEで使用)/FPU(MMXで使用)レジスタ間でデータを移動するための命令もあります。
x86プロセッサには、即値オフセット、レジスタ、オフセット付きレジスタ、オフセットの有無にかかわらずスケーリングされたレジスタ、オプションのオフセットと別のスケーリングされたレジスタを使用してメモリをアドレッシングするための複雑なアドレッシングモードも含まれています。たとえば、セレクタからオフセットとして計算されたアドレスから32ビットのデータをロードし、レジスタに格納するmov eax, [Table + ebx + esi*4]単一の命令としてエンコードできます。一般に、x86プロセッサは、操作対象のレジスタのサイズに合わせたメモリをロードして使用できます。(SIMD命令には、ハーフロード命令も含まれています。)(Table + ebx + esi * 4)dseax
整数 ALU 命令を含むほとんどの 2 オペランド x86 命令は、標準的な「アドレッシング モードバイト」[ 15 ]を使用します。これはMOD-REG-R/M バイト と呼ばれることがよくあります。[ 16 ] [ 17 ] [ 18 ] 多くの 32 ビット x86 命令には、MOD-REG-R/M バイトに続くSIB アドレッシング モード バイトもあります。 [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ]
原則として、命令オペコードはアドレッシングモードバイトとは別であるため、これらの命令は直交性を持ち、どのオペコードも任意のアドレッシングモードと組み合わせることができます。しかし、x86命令セットは一般的に非直交性であると考えられています。これは、ほとんどの二進演算ではメモリ間操作ができず、他のオペコードには固定のアドレッシングモードがあり(アドレッシングモードバイトがない)、すべてのレジスタには優先的な用途があるためです。[ 23 ] [ 24 ]
x86命令セットには、文字列のロード、ストア、移動、スキャン、比較命令(、、、および)が含まれており、各lods操作は指定されたサイズ(8ビットバイトの場合は、16ビットワードの場合は、 32ビットダブルワードの場合は)まで実行され、その後、暗黙のアドレスレジスタ(の場合は、およびの場合は、およびの場合は両方)をインクリメント/デクリメントします(DF、方向フラグによって異なります)。ロード、ストア、スキャン操作の場合、暗黙のターゲット/ソース/比較レジスタは、またはレジスタ(サイズによって異なります)にあります。使用される暗黙のセグメントレジスタは、およびの場合です。またはレジスタはデクリメントカウンタとして使用され、カウンタがゼロに達するか、スキャンおよび比較の場合は、等価または不等価が検出されると操作が停止します。残念ながら、長年にわたり、これらの命令の一部のパフォーマンスは軽視され、場合によっては、より基本的な命令を使用してコーディングすることでより高速な結果が得られる可能性があります。ただし、IntelとAMDは一部の命令を更新しており、2025年現在、stosmovsscascmpsbwdsilodsdistosscasmovscmpsalaxeaxdssiesdicxecx中には非常に優れた性能を持つものもある。
スタックはメモリ領域であり、スタックにプッシュされた最後の項目を指すスタックポインタpushが関連付けられています。スタックポインタは、項目が追加される前にデクリメントされ、項目が削除された後にインクリメントされますpop。16 ビットモードでは、この暗黙のスタックポインタは SS:[SP] としてアドレス指定され、32 ビットモードでは SS:[ESP] 、64 ビットモードでは [RSP] としてアドレス指定されます。スタックポインタは、最後に格納された値を指し、そのサイズがプロセッサの動作モード (16、32、または 64 ビット) と一致し、push/ pop/ call/命令のデフォルトの幅に一致するという前提に基づいています。また、 / /にスタックフレームポインタを設定しながらスタックの最上位からデータを予約および削除するret命令とも含まれますenter。ただし、 / /レジスタへの直接設定、または加算と減算もサポートされているため、/命令は通常不要です。leavebpebprbpspesprspenterleave
このコードは、デバッグを容易にするためにコンパイラの最適化を無効にした場合の、高水準言語における典型的な関数の冒頭部分です。
push rbp ; 呼び出し関数のスタックフレームポインタ (rbp レジスタ) を保存します。mov rbp , rsp ; 呼び出し元のスタックの下に新しいスタックフレームを作成します。sub rsp , 32 ; この関数のローカル変数用に 32 バイトのスタック領域を予約します。; ローカル変数は rbp の下に配置され、rbp を基準として参照できます。; デバッグの容易さのためにはこれが最適ですが、最高のパフォーマンスを得るには rbp は全く使用せず、ローカル変数は rsp を基準として参照します。 ; コードの節約に加えて、rbp は他の用途に自由に使用できるためです。… … ; ただし、ここで rbp が変更された場合は、呼び出し元のためにその値を保持する必要があります。mov [ rbp - 8 ], rdx ; レジスタ rdx からローカル変数 (メモリ位置による) に書き込む例...は、単に以下と機能的に同等です。
32、0を入力してくださいスタックを操作するためのその他の命令には、pushfd(32ビット) / pushfq(64ビット) 命令、およびpopfd/popfqEFLAGS (32ビット) / RFLAGS (64ビット) レジスタの格納と取得命令が含まれます。
SIMD ロードまたはストアの値は、SIMD レジスタの隣接する位置にパックされているものとみなされ、リトルエンディアン順で順次整列されます。SSE のロードおよびストア命令の中には、正しく動作するために 16 バイトのアライメントを必要とするものがあります。SIMD 命令セットには、ロードを実行するもののレジスタをターゲットとしない「プリフェッチ」命令も含まれており、キャッシュへのロードに使用されます。SSE 命令セットには、非テンポラル ストア命令も含まれており、宛先が既にキャッシュされていない場合はキャッシュ割り当てを行わずにメモリに直接ストアを実行します (キャッシュされている場合は、通常のストアと同様に動作します)。
ほとんどの汎用整数命令および浮動小数点命令(ただしSIMD命令は除く)は、2番目のソースパラメータとして複素アドレスを1つのパラメータとして使用できます。整数命令は、宛先オペランドとして1つのメモリパラメータを受け入れることもできます。
x86アセンブリには無条件ジャンプ操作があり、jmp即値アドレス、レジスタ、または間接アドレスをパラメータとして受け取ることができます(ほとんどのRISCプロセッサは、ジャンプのためにリンクレジスタまたは短い即値変位のみをサポートしていることに注意してください)。
また、jz(ゼロでジャンプ)、jnz(ゼロ以外でジャンプ)、jg(符号付きより大きいでジャンプ)、(符号付きより小さいでジャンプ)、jl(符号なしより大きい/より大きいでジャンプ)、ja(符号なしより小さい/小さいでジャンプ)など、いくつかの条件付きジャンプもサポートされています。これらの条件付き操作は、 (E)FLAGSjbレジスタの特定のビットの状態に基づいています。多くの算術演算と論理演算は、その結果に応じてこれらのフラグを設定、クリア、または補数します。比較(compare) および命令は、オペランドの値を変更せずに、それぞれ減算またはビットごとの AND 演算を実行したかのようにフラグを設定します。また、(キャリーフラグをクリア) や(キャリーフラグを補数) など、フラグに直接作用する命令もあります。浮動小数点比較は、最終的に整数フラグに変換する必要がある命令または命令によって実行されます。cmptestclccmcfcomficom
ジャンプ操作には、オペランドのサイズに応じて3つの異なる形式があります。ショートジャンプは、現在の命令からの相対オフセットである8ビット符号付きオペランドを使用します。ニアジャンプはショートジャンプに似ていますが、16ビット符号付きオペランド(リアルモードまたはプロテクトモードの場合)または32ビット符号付きオペランド(32ビットプロテクトモードの場合のみ)を使用します。ファージャンプは、セグメントベース:オフセット値全体を絶対アドレスとして使用するものです。これらのそれぞれに、間接形式とインデックス形式もあります。
単純なジャンプ操作に加えて、callサブルーチン呼び出しとretサブルーチンからの復帰命令があります。サブルーチンに制御を移す前に、call命令の次の命令のオフセットアドレスをcallスタックにプッシュします。retスタックからこの値をポップし、そこにジャンプすることで、プログラムの制御フローをその部分に戻します。の場合far call、セグメントベースがプッシュされ、続いてオフセットがプッシュされますfar ret。オフセットをポップし、次にセグメントベースをポップして戻ります。
intまた、 ( interrupt )という同様の命令が 2 つあります。これは、現在の(E)FLAGSレジスタの値をスタックに保存し、次に を実行しますがfar call、アドレスの代わりに、割り込みベクタ、つまり割り込みハンドラ アドレスのテーブルへのインデックスを使用します。通常、割り込みハンドラは、呼び出し元のプログラムに操作の結果を返すために使用しない限り (ソフトウェアでは割り込みと呼ばれます)、使用する他のすべての CPU レジスタを保存します。対応する割り込みからの戻り命令は でiret、戻り後にフラグを復元します。上記で説明したタイプのソフト割り込みは、一部のオペレーティングシステムでシステムコールに使用され、ハード割り込みハンドラのデバッグにも使用できます。ハード割り込みは外部ハードウェア イベントによってトリガーされ、現在実行中のプログラムの状態が不明であるため、すべてのレジスタ値を保持する必要があります。保護モードでは、OS によって割り込みが設定され、タスク スイッチがトリガーされ、アクティブなタスクのすべてのレジスタが自動的に保存されます。
以下の例では、Microsoft MASM、NASM、その他多くのアセンブラで使用されている、いわゆるインテル構文を使用しています。(注:他にも、ソースオペランドとデスティネーションオペランドの順序が入れ替わっているなど、多くの違いがあるAT&T構文という代替形式もあります。) [ 25 ]
「Hello, world!」は、オペレーティングシステムの助けを借りずに出力できます。「call outchr」は、AL形式の文字をコンソールに出力するメカニズムを呼び出します。長さがゼロでない文字列は、ゼロバイトで終端する必要があります。なお、この例は16ビットIntel 8086用です。
hello: mov si , msg ; 文字列のアドレスを SI に格納cld ; SI をインクリメントする方向をクリアlodsb ; 最初の文字を AL にロードし、SI をインクリメントchrlp: call outchr ; 文字を AL に出力lodsb ; 次の文字を AL にロードし、SI をインクリメントor al , al ; ゼロ終端文字か? jnz chrlp ; そうでない場合は続行ret ; 呼び出し元に戻るmsg: db 'Hello, world!' , 0xa , 0x0 ; 出力する文字列ソフトウェア割り込み21h命令を使用してMS-DOSオペレーティングシステムを呼び出し、ディスプレイに出力します 。他のサンプルではlibcのC言語printf()ルーチンを使用して標準出力に書き込みます。この例ではIntel 8086と同様に16ビットモードを使用していることに注意してください。次の例は32ビットモードのIntel 386コードです。最新のコードは64ビットモードになります。[ 26 ]
.model small .stack 100h.data msg db 'Hello world!$'.code start: mov ah , 09h ; 8ビットレジスタ「ah」(レジスタaxの上位バイト)を9に設定し、; 下記で呼び出されるMS-DOSルーチンのサブファンクション番号を選択します。 ; ソフトウェア割り込みint 21hを介してメッセージを表示します。lea dx , msg ; msgのアドレスを取得し、そのアドレスを16ビットレジスタdxに格納します。int 21h ; 様々なMS-DOSルーチンがソフトウェア割り込み21hによって呼び出されます。 ; 必要なサブファンクションは上記のレジスタahに設定されています。mov ax , 4C00h ; MS-DOS のソフトウェアのサブファンクション番号にレジスタ ax を設定します。; サービス「プログラム終了」のために割り込み int 21h を設定します。int 21h ; この MS-DOS サービスを呼び出すと、プログラムが終了するため、戻りません。終了開始NASMへの注記:
; このプログラムは 32 ビット保護モードで実行されます。; ビルド: nasm -f elf -F stabs name.asm ; リンク: ld -o name name.o ; ; 64 ビットロングモードでは、64 ビットレジスタ (eax の代わりに rax、ebx の代わりに rbx など) を使用できます。; また、ビルドコマンドで "-f elf " を "-f elf64" に変更してください。; 64 ビットロングモードの場合、"lea rcx, str" はメッセージのアドレスになります。64 ビットレジスタ rcx に注意してください。; ; このプログラムは32ビット保護モードで実行されます。; gccはデフォルトで標準Cライブラリをリンクします。; ビルド: nasm -f elf -F stabs name.asm ; リンク: gcc -o name name.o ; ; 64 ビットロングモードでは、64 ビットレジスタ (eax の代わりに rax、ebx の代わりに rbx など) を使用できます。; また、ビルドコマンドで "-f elf " を "-f elf64" に変更してください。; global main ; 'main'は C 標準ライブラリに対してコンパイルされるため、定義する必要があります。 extern printf ; printf は別のオブジェクトモジュールで宣言されているため、外部シンボルの使用を宣言します。 ; リンカは後でこのシンボルを解決します。segment .data ; 初期化されたデータ文字列のセクションdb 'Hello world!' , 0Ah , 0 ; 改行文字 (10 ; 10 進数) とゼロバイトの 'NUL' 終端文字で終わるメッセージ文字列 ; 'string' は、'Hello, World' が格納されている開始アドレスを参照します。segment .text main: push string ; 'string' のアドレスをスタックにプッシュします。; これにより、esp が 4 バイト削減され、 ; 4 バイトのアドレス 'string' が新しい esp、つまりスタックの新しい最下位にメモリに格納されます。; これは printf() の引数になります。call printf ; C言語のprintf()関数を呼び出します。add esp , 4 ; スタックポインタを4バイト増やして、'push'前の状態に戻します。'push'によって4バイト減らされた状態です。ret ; 呼び出し元に戻ります。Cランタイムを使用するため、Cランタイムが想定する形式でmain()関数を定義します。exitを呼び出す代わりに、main関数から単純にreturnすることで、ランタイムにクリーンアップ処理を実行させます。
この例は最新の64ビットモードで動作しています。
; ビルド: nasm -f elf64 -F dwarf hello.asm ; リンク: ld -o hello hello.oDEFAULT REL ; デフォルトでは RIP 相対アドレス指定モードを使用するため、[foo] = [rel foo] となります。SECTION .rodata ; 読み取り専用データは、Windows の .rdata と同様に、GNU/Linux の .rodata セクションに格納する必要があります。Hello: db "Hello world!" , 10 ; バイト 10 = 改行 (ASCII LF) で終了します。len_Hello: equ $ - Hello ; NASM に長さをアセンブリ時定数として計算させます。 ; '$' 記号は 'ここ' を意味します。 write() は長さを引数にとるため、 ; ヌル終端の C スタイルの文字列は必要ありません。; C の場合は、puts() を使用します。セクション.textglobal _start _start: mov eax , 1 ; __NR_write システムコール番号 (Linux asm/unistd_64.h (x86_64) から取得) mov edi , 1 ; int fd = STDOUT_FILENO lea rsi , [ rel Hello ] ; x86-64 は RIP 相対 LEA を使用して静的アドレスをレジスタに格納しますmov rdx , len_Hello ; size_t count = len_Hello syscall ; write(1, Hello, len_Hello); カーネルを呼び出して実際にシステムコールを実行します;; 戻り値は RAX に格納されます。RCX と R11 もシステムコールによって上書きされますmov eax , 60 ; __NR_exit 呼び出し番号 (x86_64) はレジスタ eax に格納されます。xor edi , edi ; これにより edi と rdi がゼロになります。; この xor-self トリックは、レジスタをゼロにするための一般的な慣用表現であり、 ; 常に最も高速な方法です。; 32 ビットの値が edx などに格納されると、上位ビット 63:32 も; 常に自動的にゼロになります。 これにより、 ; 64 ビットのレジスタ全体を 32 ビットの値で埋める必要がある場合が非常に多いため、追加の命令でビットを設定する必要がなくなります。 ; これにより、ルーチンの終了ステータスが 0 (通常終了) に設定されます。syscall ; _exit(0)これを実行することで、straceプロセス内で余分なシステムコールが発生しないことを確認できます。printf 版では、libc の初期化や動的リンクのために、さらに多くのシステムコールが発生します。しかし、これは静的実行ファイルです。なぜなら、-pie オプションや共有ライブラリを使用せずに ld でリンクしたからです。ユーザー空間で実行される命令は、指定したものだけです。
$ strace ./hello > /dev/null # リダイレクトがない場合、プログラムの標準出力はstraceの標準エラー出力へのログと混ざります。これは通常問題ありません。execve("./hello", ["./hello"], 0x7ffc8b0b3570 /* 51 vars */) = 0 write(1, "Hello world!\n", 13) = 13 exit(0) = ? +++ 終了コード 0 +++x86アーキテクチャでは、比較にフラグが多用されます。2つのデータを比較すると、CPUは関連するフラグを設定します。その後、条件付きジャンプ命令を使用してフラグをチェックし、実行すべきコードに分岐できます。例:
cmp eax , ebx jne do_something ; ... do_something: ; ここで何かを実行する比較命令以外にも、フラグレジスタのビットを設定する算術命令やその他の命令が多数存在します。例えば、sub、test、add命令などがあり、他にも多くの命令があります。cmp + 条件付きジャンプなどの一般的な組み合わせは、内部的に「マクロ融合」によって1つのマイクロ命令(μ-op)に統合され、プロセッサが条件付きジャンプの方向(ジャンプか継続か)を予測できれば高速に実行されます。
フラグレジスタは、x86アーキテクチャにおいて特定の機能や実行モードをオン/オフするためにも使用されます。例えば、すべてのマスク可能な割り込みを無効にするには、次の命令を使用できます。
cliフラグレジスタには直接アクセスすることもできます。フラグレジスタの下位8ビットは、命令ahを使用してロードできます。フラグレジスタ全体は、命令、、(を含む)およびlahfを使用してスタックにロードしたり、スタックからロードしたりすることもできます。pushfd/pushfqpopfd/popfqintintoiret
x87浮動小数点演算サブシステムには、独自の独立した「フラグ」タイプのレジスタであるfpステータスワードも備わっています。1990年代には、このレジスタのフラグビットにアクセスするのは面倒で時間のかかる手順でしたが、最新のプロセッサでは、通常の条件付きジャンプ/分岐命令と直接組み合わせて使用できる「2つの浮動小数点値を比較する」命令があり、中間ステップは一切必要ありません。
命令ポインタipは、 16ビットモード、eip32ビットモード、および64ビットモードで呼び出されますrip。命令ポインタレジスタは、プロセッサが実行しようとする次の命令のアドレスを指します。16ビットモードまたは32ビットモードでは直接アクセスできませんが、次のようなシーケンスを記述することで、アドレスを(32ビットコードで)next_line格納できます。eax
call next_line next_line: pop eax命令ポインタへの書き込みは簡単です 。jmp命令は指定されたターゲットアドレスを命令ポインタに格納します。例えば、次のようなシーケンスを実行すると、の内容を(64ビットコードで)raxに格納します。rip
jmp rax64ビットモードでは、命令は命令ポインタを基準としてデータを参照できるため、命令ポインタの値を別のレジスタにコピーする必要性が少なくなります。