6

アセンブリのさまざまな命令を見ていましたが、さまざまなオペランドとオペコードの長さがどのように決定されるかについて混乱しています。

それはあなたが経験から知っておくべきことですか、それともどのオペランド/演算子の組み合わせが何バイトを占めるかを知る方法がありますか?

例:

push %ebp ; takes up one byte
mov %esp, %ebp ; takes up two bytes

したがって、問題は次のとおりです。

与えられた命令を見て、そのオペコードが必要とするバイト数をどのように推測できますか?

4

6 に答える 6

8

命令のエンコードは非常に複雑であるため、データベースがない場合のx86の厳格なルールはありません(オペコード自体は1〜3バイトの範囲で変化する可能性があります)。インテル®64およびIA-32アーキテクチャーソフトウェア開発者マニュアル2Aドキュメント(第2章:命令フォーマット)を参照して、命令とそのオペランドがどのようにエンコードされているかを確認できます。

ここに画像の説明を入力してください

于 2010-12-31T03:39:52.203 に答える
8

それで、このトピックはあなたに興味を持っているように思われるので、私はあなたに概要を与えましょう。x86命令は、最大5つの部分で構成され、最大15バイトの長さです。

prefixes opcode operand displacement immediate

15バイトより長いエンコーディングを生成することは可能ですが、CPUはそれらを拒否します。オペコードを除く5つの部分はすべてオプションです。あなたは次のようにそれらの長さを見つけることができます:

  • 命令には、任意の数のレガシープレフィックスを付けることができます。これらは、 f0 lockf2 repnef3 repe2e cs36 ss3e ds26 es64 fs65 gs66オペランドサイズのオーバーライド、および67アドレスサイズのオーバーライドです。ただし、、、の1f0f2だけ、、、、、、、およびf3261つだけ2e363e6465一度に認識されます。各グループから複数のプレフィックスが提供されている場合、CPUの動作は異なります。VEXおよびEVEXでエンコードされた命令では、セグメントオーバーライドとアドレスサイズオーバーライドのレガシープレフィックスのみを使用できます。これは、他のプレフィックスがVEXおよびEVEXプレフィックスに含まれているためです。
  • ロングモード(およびそこでのみ)では、命令にすべてのレガシープレフィックスの直後にREXプレフィックスが含まれる場合があります。REXプレフィックスはtoの1つ40です4f。他のモードでは、これらのバイトはプレフィックスではなく命令であり、デコーダーはそれを考慮する必要があります。従来のプレフィックスと同様に、VEXまたはEVEXでエンコードされた命令にREXプレフィックスを付けることはできません。
  • バイトc4とは、いくつかの最新の命令をエンコードするために使用されるVEXプレフィックスc5を導入できます。ロングモードでは常に実行されますが、他のモードでは、後でバイトをチェックする必要があります。オペランドペアをエンコードする場合はmodr / mバイトとして解釈し、オペランドペアをエンコードする場合はVEXプレフィックス、それ以外の場合はまたはのオペコードとして解釈します。で始まるVEXプレフィックスは2バイト長で、3バイトです。VEXプレフィックスは、VEXエンコード命令で省略されている、およびオペコードプレフィックスもエンコードします。一般に、VEXプレフィックスの使用はオプションではないことに注意してください。たとえば、は(eg for )としてエンコードされますが、対応するレガシー命令(egr,rlesldsc4c50f0f 380f 3apdepVEX.NDS.LZ.F2.0F38.W0 F5 /rc4 e2 7b f5 c0pdep eax,eax,eaxf2 0f 38 f5 r/m32f2 0f 38 f5 c0for pdep eax,eax)は無効です。同じオペコードがVEXプレフィックス付きとなしで存在する可能性があり、2つは異なる意味を持つ可能性があることに注意してください。たとえば、0f 77is emmsbut VEX.128.0F.WIG 77(ie c5 f8 77)is vzeroupper
  • このバイトは、 AVX512命令をエンコードするために使用されるEVEXプレフィックス62を導入します。VEXプレフィックスと同様に、EVEXプレフィックスと命令を区別するために、次の数バイトをチェックする必要があります。EVEXプレフィックスは常に4バイトの長さで、VEXプレフィックスと同じようにオペコードの一部をエンコードします。bound

プレフィックスの後に、オペコードが続きます。元々、オペコードは常に1バイトでしたが、スペースが足りなくなったため、現在は1バイトまたは1バイトの前に、、、またはが付いて0f0f 38ます0f 3a。命令がVEXエンコードされている場合、これらのプレフィックスはありません。一部のプレフィックスは、エンコードされる命令を変更する場合があることに注意してください。たとえば、オペコード0f b8jmpe(IA-64モードに入る)ですが、そうでf3 0f b8はありませrepe jmpepopcnt

オペコードとプレフィックスによって、エンコードされる命令が決まります。これからは、ほとんどスムーズな航海です。命令によっては、modr/mバイトが続く場合があります。modr / mバイトとアドレスオーバーライドプレフィックスに応じて、sibバイトと1、2、または4つの変位バイトが続く場合があります。最後に、命令、オペランドサイズオーバーライドプレフィックス、およびREXプレフィックスに応じて、1、2、4、6、または8バイトのイミディエートバイトが続く場合があります。

これは、StackOverflowの回答の範囲で説明できることとほぼ同じです。だからTL;DR:それは本当に複雑です。

于 2017-08-21T16:59:53.223 に答える
4

用語:「オペコード」は、演算を選択する命令の一部であり、オペランドや、演算を変更する必須ではないプレフィックス(オペランドサイズなど)は含まれません。シェルコードについて話している人によってかなり頻繁に行われていますが、命令全体を参照するために「オペコード」を使用することは正しくありません。

それはあなたが経験から知っておくべきことですか

機械語を調べた経験、または特にコードサイズを最適化した経験があると、そうです。繰り返し調べたものを思い出し始め、asm行を調べて、命令の長さを知る方法を学びます。バイトがになるかを覚えずに。

オペランドのエンコード規則はオペコードに依存しないため、オペコードの長さと、オペランドのエンコードにModR/Mバイトを使用しない特殊な場合の短い形式を覚えておく必要があります。そして、オペランドのエンコード規則を個別に覚えておいてください。

個人的には、このようなコードゴルフの質問にx86マシンコードで答えるのが好きです。(x86 / x64マシンコードでのゴルフのヒントも参照してください)。私はNASMに書き込み、各命令の長さを計画/把握し、アセンブラーに実際のマシンコードの16進ダンプをリストとして生成させます。コードゴルフに役立つ短い命令については、最近、命令の長さについて間違っていたことを覚えていませんが、興味深いと思う詳細(x86命令セットなど)のメモリが十分にあるのは幸運です。またはたくさん使う。rorx(私はそれがどれくらいの長さであるかを見ようとしなければなりませんでした。)

マシンコードバイトを自分で入力することはありません。手作業でそれを行うには、マニュアルで各手順を調べる必要があります。x86にはPC相対アドレス指定用の短いエンコーディングがないため、マシンコード内で有用な定数(データを兼ねることができる)を見つけて作成することは重要ではないため、コードゴルフが数値を記憶することは一般的に有用ではありません命令エンコーディングの詳細。

パフォーマンスを最適化する場合、通常、他のすべてが等しい場合は小さい方が良いため、コードサイズ、特に配置を気にすることは間違いなくパフォーマンスの一部です。

または、どのオペランド/演算子の組み合わせが何バイトを占めるかを調べる方法はありますか?

これはマニュアルに詳しく記載されています。いくつかの特殊なケースの1バイト命令を除いて、オペランドのエンコードは(ほとんど)すべてで同じです。


ほとんどのx86命令のマシンコードエンコーディングは、次のパターンに従います( @Mehrdadの回答にあるIntelのより良い図バージョン):

[prefixes] opcode ModR/M [extra addressing-mode bytes] [immediate]

(明示的なオペランドのない命令には、ModR / Mバイトはなく、オペコードバイトのみがあります)。

x86オペコードは、最も一般的な命令、特に8086以降に存在する命令では1バイトです。後で追加される命令(たとえばbsf、 386など)は、エスケープバイトmovsx付きの2バイトのオペコードを使用することがよくあります。SOをぶらぶらしていると、8086について具体的に(特に)0f尋ねる質問がたくさん表示されます。emu8086これが、8086で使用できなかった命令について私が知っている主な理由です。履歴の詳細がない2バイトのオペコードを持つ命令を直接覚えておきたい場合は、まったく問題ありません。または、マニュアルで毎回調べてください:P

たとえば0f b6 c0 movzx eax,al、0F B6はのオペコードであり、C0はeaxを宛先(フィールド= 0)、ソースのレジスタダイレクトモード(上位2ビット= 11)、およびソースレジスタ(上位2ビット= 11)mov r32, r/m8としてエンコードするModR/Mバイトです。フィールド=0)。/ral/m

すべての例()にIntel構文を使用してmnemonic dst, src1 [,src2, ...]います。これは、IntelおよびAMDのマニュアルに記載されているものと一致するためです。AFAIK、AT&T構文を使用する詳細な命令エンコードマニュアルはありません。また、8086が持っていたものについて話すときでも、32ビットまたは64ビットの例を使用しています。もちろん、8086には16ビットのリアルモードしかありませんでしたが、64ビットモードでも同じオペコードとエンコーディングが使用されています(これは最近私たちが気にかけていることです)。


Intelの命令セットref。マニュアル(SDM vol.2)には、1、2、3バイトのオペコード(付録A.3)のオペコードマップがあるため、オペコードエンコーディングの選択にいくつかのパターンが見られます。または、特定の手順については、そのマニュアルの完全な説明とともにリストされているエンコーディングを参照してください。( https://github.com/HJLebbink/asm-dude/wikihttp://felixcloutier.com/x86/のように、命令ごとに1ページの優れたオンライン抜粋も参照してください。HJLebbinkのページでは、各命令にタグが付けられています。が導入されたためadd、は8086、新しい形式のシフトの場合は386、およびの場合は386が表示されますmovzx

shlまたはのような一部の1オペランド命令は、ModR/Mバイトのフィールドを追加のオペコードビットとしてnot使用することに注意してください。/rまた、イミディエートのあるほとんどの命令は、/rフィールドをオペコードビットとして使用するため、依然として破壊的です。 imul r32, r/m32, imm32(386)はこのルールの例外であり、両方のオペランドに即時で完全なModR/Mバイトを使用します。(ModR / Mはレジスタまたはメモリオペランドにのみ信号を送ることができることに注意してください。のエンコーディングadd r/m32, imm8は、オペコードを使用してイミディエートがあることを示します。ただし、メインのオペコードバイトは複数の命令で共有されるため、/rフィールドはオペコードの一部として使用されます。そのため私たちは持っていませんadd r/m32, r32, imm8。しかし、ADD / SUBの場合lea ecx, [rax + 1]、コピーアンドアドとして使用できます。)


オペランドエンコーディング:

イミディエートオペランドを持つほとんどの命令は、レジスタ/メモリソースバージョンと同じ長さに、イミディエートをエンコードするためのバイトを加えたものです。イミディエイトはimm8またはimm32のいずれかであるため、-128..127からの値はよりコンパクトです。(16ビットモードでは、imm8またはimm16のいずれかです)。

ModR / Mバイトは、レジスタダイレクト、または変位のない最も単純な1レジスタアドレッシングモードに必要なすべてです。(を除く[esp])。add eax, ecxと同じように、2バイトの長さですadd eax, [ecx]。インデックス付きアドレッシングモード(およびesp/rspをベースレジスタとするモード)には、SIB(スケール/インデックス/ベース)バイトが必要です。

アドレッシングモードでの一定の変位には、ModR / M +オプションのSIBに加えて、追加の1バイトまたは4バイト(符号拡張されたdisp8またはdisp32)が必要です。

disp8を使用するAVX512EVEXは、disp8をベクトル幅でスケーリングするため、わずかvaddps zmm31, zmm30, [rsi + 256]7バイト(4バイトEVX + opcode = 0x58 + modrm + disp8)ですが、vaddps zmm31, zmm30, [rsi + 16]11バイトです。+1664の倍数。ただし、xmmレジスタを使用した同じ命令で。を使用できますdisp8

詳細については、Intelのマニュアルを参照してください。


最も一般的な指示の特別な短縮形

コードサイズを節約するために、8086(およびそれ以降のx86)は、いくつかの非常に一般的な命令に対してModR/Mバイトのない特別なエンコーディングを提供します。命令がこれらのいずれでもない場合は、ModR/Mバイトを使用します

  • add / adc / sub / cmp / test / and / or / xor/etc。レジスタと同じサイズのイミディエートを持つAL/AX/EAX。例:and eax, imm32(5バイト)またはand al,imm8(2バイト)。and eax, imm8ただし、 ;の特別なエンコーディングはありません。それでも3バイトのand r/m32, imm8エンコーディングを使用する必要があります。8ビットデータを操作する場合、特に部分レジスタのストールやパフォーマンスの問題を引き起こす誤った依存関係をal回避したり心配したりしない場合は、コードサイズに非常に適しています。
  • カウントが1のshift/rotate:8086にはimm8が回転せず、暗黙の1によって、または暗黙の1によってのみ回転するため、暗黙の場合のclようなオペコードがあります。shl r/m32,11

    imm8エンコーディングを使用すると、パフォーマンスに影響があります。実行するまでimm8がゼロかどうかをチェックしないため、P6ファミリでストールが発生する可能性があります。ただし、 Skylakeを含むSandybridgeファミリでは、rol r32,1短縮形は2 uopsであるのに対し、rol r32, imm8(imm8が1の場合でも)1です。短い形式は、rcl r32,1imm8よりもはるかに高速です。(Skylakeでは3 uops対8)。

また、レジスタが命令バイトの下位3ビットでエンコードされ、これらの命令のレジスタオペランド形式を1バイト短くするために、8バイトのオペコードコーディングスペースを効果的に使用するものもあります。

  • mov r8, imm8:一般的なmov r/m8, imm8エンコーディングでは3バイトではなく2バイト。
  • mov r32, imm32:の6バイトではなく5バイトmov r/m32, imm32。おもしろい事実:x86-64では、短い形式のオペコードのREX.W = 1バージョンが、64ビットのイミディエートを使用できる唯一の命令です。10バイトmov r64, imm64。REX.W = 1バージョンのr/m32オペコードは引き続き32ビットのイミディエート(通常のように符号拡張)を使用するため、mov rax, -1その方法でエンコードするのが最適で、5バイトに対して7バイトを使用しますmov eax,-1。(または、コードサイズを最適化する場合は、CPUレジスタのすべてのビットを効率的に1に設定するも参照してください。)
  • push/popレジスタ、1バイト対pop r/m32エンコーディングの2バイト。
  • push/popセグメントレジスタ(FS / GS以外)。これらのar/m16エンコーディングはありませんが。
  • inc r32/ dec r32(16/32ビットモードのみ:0x4Xバイトはx86-64のREXプレフィックスであるためinc eax、2バイトinc r/m32エンコーディングを使用する必要があります)。
  • xchg eax, reg:これはどこ0x90 nopから来たのか:の短い形式xchg eax,eax(または16ビットモードの場合xchg ax,ax)。x86-64では、90nopもそうではありませんxchg eax,eax。これは、EAXをRAXにゼロ拡張するためです。代わりに、独自の命令セットの手動エントリがあります。

    xchg reg,regコンパイラによって使用されることはなく、通常は3mov命令より高速ではないため、将来の拡張に役立つ7つのオペコードバイトがあれば便利です。(またはnop、別のオペコードに移動された場合は8 ...)。アキュムレータが「より特別」だった8086では、より便利でした。たとえばcbw、ALをAXに符号拡張することmovsxが、存在しなかったための唯一の(良い)方法でした。そして、1-オペランドmul/のみimulが利用可能でした。

xchg eax, r32それでもコードゴルフには最適です。たとえば、8バイトのx8632ビットマシンコードのGCDです。さまざまなコードサイズのトリックについては、他のコードゴルフの回答も参照してください(主にパフォーマンスを犠牲にして、それがコードゴルフのポイントです)。

r/m32これは、エンコーディングも含む命令の1バイトの特殊なケースをすべてカバーしていると思います。


この答えは網羅的であることを意味するものではありません。最近の指示についてはあまり話していませんが、まれな指示の場合は特別な場合がたくさんあります。REXプレフィックスまたはオペランドサイズプレフィックスが必要な場合のルールは非常に簡単です。より一般的なルールは次のとおりです。

  • SSE1 / SSE3ABCps命令には2バイトのオペコード(0F xx)があります
  • SSE2整数/倍精度命令には、通常3バイトのオペコード(66 0F xxまたは同様のもの)があります。
  • SSSE3 / SSE4.x命令には、4バイトのオペコード(3つの必須プレフィックス)があります

SSEバージョンがSSE3以前であり、2番目のソースレジスタが「ハイ」レジスタ(xmm / ymm8-15)でない場合、 VEXコード化命令は2バイトのVEXプレフィックスを使用できます。同じ命令のXMMバージョンとYMMバージョンは、常に同じサイズです。(ただし、上位の半分をゼロにする必要がない場合は、明示的なymmではなく暗黙的なゼロ拡張を使用したxmmを使用してください。

vpxor  ymm8,ymm8,ymm5    ; 2-byte VEX
vpxor  ymm7,ymm7,ymm8    ; 3-byte VEX
vpxor  ymm7,ymm8,ymm7    ; 2-byte VEX

したがって、3バイトのVEXを必要とせずに、宛先または最初のソースとして「高」レジスタを使用できますが、2番目のソース(全体で3番目のオペランド)としては使用できません。可換演算の場合、low8を2番目のソースとして配置することでサイズを節約できます。

vblendvpsのような4オペランドの命令の場合、4番目のオペランドは。にエンコードされることに注意してくださいimm8。したがって、必要なVEXプレフィックスのサイズに影響を与えるのは、最後のオペランドではなく、依然として3番目のオペランド(2番目のソース)です。ただし、SSE4.1であるため、プレフィックスフィールドのエンコーディングblendvpsを表すには、常に3バイトのVEXプレフィックスが必要です。66.0F3A

于 2018-02-19T19:26:40.073 に答える
2

オペコードの長さは、(少なくとも)2つの基準を念頭に置いて作成されています

  • オペコードの周波数(プログラムで頻繁に使用される場合は1バイト、可能であれば)
  • オペコードが機能するために必要な情報(絶対アドレスが必要な場合、コードを一意のバイトにエンコードすることはできません)

また、

  • 初期の8088から最新のIntelプロセッサ(30年)までの間に、多くの新しい命令が作成され、256の値全体が予約されていたため、プログラムに頻繁に表示されるものの、1バイトにコーディングできなかったものもあります。

別の回答(具体的にはコードのサイズをリストしている)で提供されているリンクに加えて、プロセッサーの履歴も参照してください。

于 2010-12-31T03:47:43.663 に答える
1

通常、これは、アセンブリ言語でプログラミングするときに、ある命令から次の命令へと知っておく必要のあることではありません。重要な場合(特定のコードを制約されたスペースに収めようとしている場合など)は、アセンブラーからのリスト出力、または逆アセンブリリストを確認できます。

于 2010-12-31T03:41:53.013 に答える
1

私の6510組立日から、答えは通常、オペランドアドレスとオフセットに関係していました。6510のオペコードは常に1バイトでした。アドレスは常に2バイトでした。オペコードに1つのアドレスが必要な場合、合計サイズは3バイトであることがわかりました。2つのアドレスが指定された場合、合計サイズは5バイトであることがわかりました。

オフセットに関しては、彼らが占めるスペースは枝の長さに依存していました。したがって、これを考慮してください:

bne FooBar

「Foobar」オフセットが128バイト未満のアドレスを指している場合、オペランドは1バイトでした。オフセットがそれを超えるアドレスを指している場合は、完全なアドレスが必要でした。完全なアドレスはもはやオフセットではなく、もちろんアドレスは2バイトを占めていました。

したがって、この後者の場合、オペコード+オペランドに2バイトまたは3バイトが必要かどうかを判断するのは簡単ではないかもしれません。

ですから、私は推測します、時々あなたは言うことができます、そして他の時にはそれはそれほど明白ではありません。

于 2010-12-31T03:47:13.263 に答える