別のキャラクターと比較したいキャラクターを含むメモリの場所があります(スタックの一番上にないので、それだけpop
ではできません)。比較できるように、メモリの場所の内容を参照するにはどうすればよいですか?
基本的に、構文的にどのように行うのですか。
別のキャラクターと比較したいキャラクターを含むメモリの場所があります(スタックの一番上にないので、それだけpop
ではできません)。比較できるように、メモリの場所の内容を参照するにはどうすればよいですか?
基本的に、構文的にどのように行うのですか。
そしてもちろん、Intel と AMD のマニュアルには、ModRM (およびオプションの SIB と disp8/disp32 バイト) のエンコーディングの詳細に関するセクション全体があり、何がエンコード可能で、なぜ制限が存在するのかが明確になります。
参照: AT&T(GNU) 構文と NASM 構文の表 (間接ジャンプ/呼び出しを含む、さまざまなアドレス指定モード)。この回答の下部にあるリンクのコレクションも参照してください。
x86 (32 および 64 ビット) には、いくつかのアドレス指定モードから選択できます。それらはすべて次の形式です。
[base_reg + index_reg*scale + displacement] ; or a subset of this
[RIP + displacement] ; or RIP-relative: 64bit only. No index reg is allowed
(scale は 1、2、4、または 8 で、displacement は符号付き 32 ビット定数です)。 他のすべての形式 (RIP 相対を除く) は、1 つまたは複数の component を除外した this のサブセットです。これは、たとえばindex_reg
、アクセスするためにゼロにする必要がないことを意味します。[rsi]
asm source codeでは、どの順序で記述しても問題ありません。問題なく[5 + rax + rsp + 15*4 + MY_ASSEMBLER_MACRO*2]
動作します。(定数に関するすべての計算はアセンブル時に行われるため、単一の定数変位が得られます。)
レジスタはすべて互いに同じサイズである必要があります。また、追加のプレフィックス バイトが必要な別の address-size を使用しない限り、現在のモードと同じサイズになります。レジスタの上位 32 ビットを無視したいx32 ABI (ロング モードの ILP32)以外では、ナロー ポインタはめったに役に立ちませんmovsxd
。 64 ビットのポインター幅。
たとえば、配列インデックスとして使用するal
場合は、ポインター幅にゼロ拡張または符号拡張する必要があります。rax
(バイトレジスタをいじる前に上位ビットをゼロにすることが可能な場合があり、これはこれを達成するための良い方法です。)
制限は、アセンブリ言語の場合と同様に、マシンコードでエンコードできるものを反映しています。スケール係数は 2 ビットのシフト カウントです。ModRM (およびオプションの SIB) バイトは、最大 2 つのレジスタをエンコードできますが、それ以上はエンコードできません。また、レジスタを減算するモードはなく、加算のみを行うモードはありません。任意のレジスターをベースにすることができます。ESP/RSP 以外の任意のレジスターをインデックスにすることができます。SIB ベースとして許可されていない rbpを参照してください。[rsp]
なぜ常にSIBバイトが必要なのかなど、エンコーディングの詳細について。
一般的なケースのすべての可能なサブセットは、を使用するものを除いて、エンコード可能ですe/rsp*scale
(スタック メモリへのポインターを常に保持する「通常の」コードでは明らかに役に立ちませんesp
)。
通常、エンコーディングのコードサイズは次のとおりです。
[-128 to +127]
は、よりコンパクトなdisp8
エンコーディングを使用できるため、 disp32
.ModRM は常に存在し、そのビットは SIB も存在するかどうかを通知します。disp8/disp32 についても同様です。コードサイズの例外:
[reg*scale]
それ自体は、32ビットの変位(もちろんゼロにすることもできます)でのみエンコードできます。lea eax, [rdx*2]
賢いアセンブラはasをエンコードすることでこれを回避しますlea eax, [rdx + rdx]
が、そのトリックは 2 倍のスケーリングでしか機能しません。いずれにしても、ModRM に加えて SIB バイトが必要です。
e/rbp
またはr13
をディスプレースメント バイトなしでベース レジスタとしてエンコードすることは不可能であるため、[ebp]
としてエンコードされ[ebp + byte 0]
ます。ベース レジスタとしての非置換エンコーディングはebp
、代わりにベース レジスタがないことを意味します (たとえば の場合[disp + reg*scale]
)。
[e/rsp]
インデックス レジスタがない場合でも、SIB バイトが必要です。(変位があるかどうか)。代わりに指定する mod/rm エンコーディングは[rsp]
、SIB バイトがあることを意味します。
特殊なケースの詳細については、Intel の ref マニュアルの表 2-5 とその周辺のセクションを参照してください。(これらは 32 ビット モードと 64 ビット モードで同じです。RIP 相対エンコーディングを追加しても、REX プレフィックスがなくても、他のエンコーディングと競合しませんでした。)
パフォーマンスのために、通常、より小さな x86 マシン コードを取得するためだけに余分な命令を費やす価値はありません。uop キャッシュを備えた Intel CPU では、L1 I$ よりも小さく、より貴重なリソースです。融合ドメインの uops を最小限に抑えることは、通常、より重要です。
(この質問には MASM というタグが付けられましたが、この回答の一部は、特に x86-64 RIP 相対アドレッシングで異なる場所で、NASM の Intel 構文のバージョンについて説明しています。AT&T 構文はカバーされていませんが、同じ構文の単なる別の構文であることに注意してください。機械語であるため、制限は同じです。)
この表は、可能なアドレッシング モードのハードウェア エンコーディングと正確には一致しません。これは、ラベル (グローバルまたは静的データなど) の使用と小さな定数変位の使用を区別しているためです。そこで、ハードウェア アドレッシング モード + シンボルのリンカ サポートについて説明します。
(注: 通常、movzx eax, byte [esi]
またはmovsx
ソースがバイトである場合に必要ですが、mov al, byte_src
アセンブルし、古いコードでは一般的であり、EAX/RAX の下位バイトにマージされます。GCCが部分レジスタを使用しない理由と分離方法を参照してください。 64 ビット レジスタ内のバイトおよびワード配列要素)
がある場合int*
、バイト オフセットの代わりに要素インデックスがある場合、多くの場合、スケール ファクターを使用して配列要素のサイズでインデックスをスケーリングします。(バイト オフセットまたはポインターを使用して、コード サイズの理由からインデックス付きアドレッシング モードを回避し、場合によっては特にマイクロ フュージョンを損なう可能性がある Intel CPU でのパフォーマンスを優先します)。しかし、他のこともできます。にポインターが
ある場合char array*
esi
:
mov al, esi
: 無効、アセンブルしません。角かっこがなければ、それはまったく負荷ではありません。レジスタが同じサイズではないため、エラーです。
mov al, [esi]
が指すバイト、つまりarray[0]
orをロードします*array
。
mov al, [esi + ecx]
ロードしますarray[ecx]
。
mov al, [esi + 10]
ロードしますarray[10]
。
mov al, [esi + ecx*8 + 200]
荷重array[ecx*8 + 200]
mov al, [global_array + 10]
から読み込みますglobal_array[10]
。64 ビット モードでは、これは RIP 相対アドレスにすることができます。DEFAULT REL
を常に使用する代わりに、デフォルトで RIP 相対アドレスを生成するには、NASM を使用することをお勧めします[rel global_array + 10]
。MASM はデフォルトでこれを行うと思います。RIP 相対アドレスを持つインデックス レジスタを直接使用する方法はありません。通常の方法はlea rax, [global_array]
mov al, [rax + rcx*8 + 10]
、または同様です。
x86-64 GAS Intel-syntax の "[RIP + _a]" のような RIP 相対変数参照はどのように機能しますか?を参照してください。詳細と、 GAS .intel_syntax
、 NASM 、および GAS AT&T 構文の構文については、 を参照してください。
mov al, [global_array + ecx + edx*2 + 10]
global_array[ecx + edx*2 + 10]
もちろん、単一のレジスタで静的/グローバル配列にインデックスを付けることができます。2 つの別個のレジスタを使用する 2D 配列も可能です。(2、4、または 8 以外の倍率の場合は、追加の命令で 1 つを事前にスケーリングします)。global_array + 10
計算はリンク時に行われることに注意してください。オブジェクト ファイル (アセンブラ出力、リンカ入力) は、リンカに +10 を最終絶対アドレスに追加して、実行可能ファイル (リンカ出力) に正しい 4 バイト ディスプレースメントを挿入するように通知します。これが、アセンブル時定数ではないリンク時定数(シンボル アドレスなど)に任意の式を使用できない理由です。
64 ビット モードでは、パーツglobal_array
の 32 ビット絶対アドレスとして が引き続き必要です。これは、位置依存の Linux 実行可能ファイルまたは largeaddressaware=no Windows でのみ機能します。disp32
mov al, 0ABh
ロードではなく、命令内に格納された即時定数です。0
(アセンブラがそれがシンボルではなく定数であることを認識できるように、プレフィックス a が必要であることに注意してください。一部のアセンブラは も受け入れます0xAB
が、一部のアセンブラは を受け入れません0ABh
:詳細を参照してください)。
シンボルを即値定数として使用して、アドレスをレジスタに取得できます。
mov esi, global_array
a にアセンブルします。mov esi, imm32
mov esi, OFFSET global_array
同じことをするために必要です。mov esi, global_array
ロードにアセンブル: mov esi, dword [global_array]
.64 ビット モードでは、シンボル アドレスをレジスタに格納する標準的な方法は、RIP 相対 LEA です。構文はアセンブラによって異なります。MASM はデフォルトでそれを行います。NASM にはdefault rel
ディレクティブ、またはが必要[rel global_array]
です。GAS は、すべてのアドレッシング モードで明示的にそれを必要とします。 関数またはラベルのアドレスを GNU Assembler のレジスタにロードする方法。 mov r64, imm64
通常、64 ビットの絶対アドレス指定でもサポートされますが、通常は最も遅いオプションです (コード サイズがフロントエンドのボトルネックになります)。 mov rdi, format_string
/call printf
通常は NASM で動作しますが、効率的ではありません。
アドレスを(現在の位置からの rel32 オフセットとしてではなく) 32 ビットの絶対値として表すことができる場合の最適化としては、mov reg, imm32
32 ビット コードと同様に最適です。(Linux の非 PIE 実行可能ファイルまたは LargeAddressAware=no の Windows)。ただし、32 ビット モードでは効率的でlea eax, [array]
はないことに注意してください。1 バイトのコード サイズ (ModRM + 絶対 disp32) を浪費し、 のように多くの実行ポートで実行できませんmov eax, imm32
。32 ビット モードには、RIP 相対アドレッシングがありません。
OS X はすべてのコードを下位 32 ビット以外のアドレスにロードするため、32 ビットの絶対アドレス指定は使用できないことに注意してください。実行可能ファイルには位置に依存しないコードは必要ありませんが、64 ビットの絶対アドレス指定は RIP 相対よりも効率が悪いため、必要になる場合もあります。 macho64 オブジェクト ファイル形式は、Linux ELF のように 32 ビット絶対アドレスの再配置をサポートしていません。どこでもコンパイル時の 32 ビット定数としてラベル名を使用しないようにしてください。[global_array + constant]
RIP 相対アドレッシング モードにアセンブルできるため、有効なアドレスのようなもので問題ありません。ただし[global_array + rcx]
、RIP は他のレジスタと一緒に使用できないため、許可されていないためglobal_array
、32 ビット ディスプレースメント (これは 64b に符号拡張されます)。
これらのアドレス指定モードのすべてを使用しLEA
て、有効なアドレスであるかどうかに関係なく、 フラグに影響を与えないというボーナスで整数計算を行うことができます。アドレス/ポインターではない値に LEA を使用していますか?
[esi*4 + 10]
通常、LEA でのみ有用です (変位が小さな定数ではなく記号である場合を除きます)。機械語では、scaled-register だけのエンコードは存在しないため、32 ビット ディスプレースメントに対して 4 バイトのゼロを使用し[esi*4]
て にアセンブルする必要があります。[esi*4 + 0]
短い mov + shl の代わりに 1 つの命令でコピー + シフトすることは、通常、特にデコードされた uop キャッシュを備えた CPU では、コードサイズよりも uop スループットの方がボトルネックになるため、依然として価値があることがよくあります。
mov al, fs:[esi]
(NASM 構文)のようなセグメントオーバーライドを指定できます。セグメント オーバーライドは、通常のエンコーディングの前にプレフィックス バイトを追加するだけです。他のすべては、同じ構文で同じままです。
RIP 相対アドレッシングでセグメント オーバーライドを使用することもできます。32 ビットの絶対アドレス指定は、RIP 相対よりもエンコードに 1 バイト多いためmov eax, fs:[0]
、既知の絶対アドレスを生成する相対変位を使用して最も効率的にエンコードできます。つまり、RIP+rel32 = 0 になるように rel32 を選択します。YASM は でこれを行いますmov ecx, [fs: rel 0]
が、NASM は常に disp32 絶対アドレス指定を使用し、rel
指定子を無視します。MASM やガスはテストしていません。
オペランドのサイズがあいまいな場合(たとえば、即値オペランドとメモリ オペランドを含む命令)、byte
/ word
/ dword
/qword
を使用して次のように指定します。
mov dword [rsi + 10], 123 ; NASM
mov dword ptr [rsi + 10], 123 ; MASM and GNU .intex_syntax noprefix
movl $123, 10(%rsi) # GNU(AT&T): operand size from mnemonic suffix
NASM 構文の有効なアドレスについては yasm のドキュメントを参照してください。また、ウィキペディアの x86 エントリのアドレッシング モードに関するセクションも参照してください。
wiki ページには、16 ビット モードで何が許可されているかが記載されています。32 ビット アドレッシング モードの別の「チート シート」を次に示します。
16 ビット アドレス サイズでは SIB バイトを使用できないため、1 および 2 レジスタ アドレッシング モードはすべて単一の mod/rm バイトにエンコードされます。reg1
BX または BP にreg2
することができ、SI または DI にすることができます (または、これら 4 つのレジスタのいずれかを単独で使用することもできます)。スケーリングは利用できません。16 ビット コードは、これを含む多くの理由で時代遅れであり、必要がなければ学ぶ価値はありません。
アドレス サイズ プレフィックスが使用される場合、16 ビットの制限が 32 ビット コードに適用されることに注意してください。ソースレジスタの上位ビットのガベージは効果がないため、設定lea eax, [edx + ecx*2]
します。ax = dx + cx*2
16bit のアドレッシング モードのより詳細なガイドもあります。16 ビットのアドレッシング モードのセットは限られています (少数のレジスタのみが有効で、スケール ファクターはありません)。 32 ビット モード。
これらの多くは上記にもリンクされていますが、すべてではありません。