わかりましたので、開発中のOS用のアセンブラを書いています。私はすべてのmov命令を持っていますが、callやjmpなどの命令を実装したいと思っています。私は本当に良いドキュメントを持っていないので、NASM によって生成されたマシン コードを調べて、オペコードなどを見つけています。call のオペコードが何であるかを確認したかったので、最初からラベルで始まるコードをコンパイルしました。呼び出しオペコードの後のアドレスは 00 00 00 00 だと思っていましたが、FB FF FF FF でした。シンボルに関係があると思ったので、call 0x000000 でコードをコンパイルして何が起こったのかを確認したところ、アドレスはまったく同じ (0xFBFFFFFF) でした。誰かが私にこれを説明できますか?私は混乱しています。
3 に答える
逆アセンブルしている実際のコードを表示すると便利です。ほとんどの場合、その数値はリトルエンディアンの負のオフセットです。2 の補数で 0xFFFFFFFB = -5。書きましたか:
Label: call Label
call が 4 バイトの相対オフセットを持つ 1 バイトのオペコードである場合、それは理にかなっています。
32 ビット ユーザー モード x86 コードでの CALL の最も一般的な形式はCALL rel32
、オペランドのポイントと次の命令のアドレスを "呼び出す" です。これは近相対呼び出しです。
参考までに、絶対呼び出しを使用することは可能ですが、エンコーディングは 5 バイトではなく 6 バイトです。エンコーディングは になりますFF 14 XX XX XX XX
。余分なバイト ( 14
) は、即値として使用される即値変位を読み取るよう命令に指示します。ただし、これは、プログラムがロードされるモジュール ベースに応じて書き直す必要があります。相対呼び出しは、再配置時に注意する必要はありません。
これがどのように機能するかを視覚化するために、命令が実行されると、次のことが起こります。
EIP
(命令ポインター) は、次の命令を指すようにインクリメントされます。- そのアドレスは (戻りアドレスを提供するために) スタックにプッシュされます。
- 即値 (たとえば、rel32 値) が に追加され
EIP
、 - 次の命令は
[EIP]
通常どおり (新しい) から読み取られます。
この命令をエンコードすると、次のようになりますE8 XX XX XX XX
。このことから、命令の長さが 5 バイトであることがわかります。
は命令の長さだけインクリメントされるためEIP
、呼び出しは命令の開始から 5 バイト後のポイントに相対的になります。したがって、CALL 0x00000000 命令の相対アドレスが 0x00000000 である場合、 から 5 を引く必要がありEIP
ます。アセンブラが絶対アドレスを相対アドレスに変換しました。
オフセットは負になる場合があります。また、x86 アドレスはリトルエンディアンであることを思い出してください。したがって、命令はE8 FB FF FF FF
です。
興味深いことに、この特定の命令の結果は、例外 ( ) が生成されるEIP+5
まで継続的にスタックにプッシュされます。#SS(0)
call
多くの場合、さまざまな形式で提供されます。たとえば、絶対アドレスを使用してジャンプするものや、現在のアドレスに相対的にジャンプするものなどです。これは相対的なものである可能性がありますが、4 バイトはおそらく直接のオフセットではありません。
疑問がある場合、特にアセンブラを実装する場合は、マニュアルまたはデータシートを参照してください。