29

学校のプロジェクトの古いコードを見ていて、ラップトップでコンパイルしようとすると、いくつかの問題が発生しました。これは元々、古い 32 ビット バージョンの gcc 用に作成されたものです。とにかく、アセンブリの一部を 64 ビット互換コードに変換しようとしていたところ、いくつか問題が発生しました。

元のコードは次のとおりです。

pusha
pushl   %ds
pushl   %es
pushl   %fs
pushl   %gs
pushl   %ss

pusha64 ビット モードでは無効です。それでは、64ビットモードでx86_64アセンブリでこれを行う適切な方法は何でしょうか?

が 64 ビット モードで有効にならないのには理由pushaがあるはずなので、すべてのレジスタを手動でプッシュするのは得策ではないのではないかと感じています。

4

4 に答える 4

22

REXAMDは、64 ビット x86 拡張機能を開発したときに、プレフィックス用の新しいオペコードやその他の新しい命令を追加する余地が必要でした。一部のオペコードの意味を新しい命令に変更しました。

命令のいくつかは、既存の命令の単なる短縮形であるか、そうでなければ必要ではありませんでした。PUSHA被害者の一人でした。なぜ禁止されたのかは明らかPUSHAではありませんが、新しい命令オペコードと重複しているようには見えません。おそらく、それらは将来の使用のために予約されているPUSHAオペコードPOPAです。これらは完全に冗長であり、これ以上高速ではなく、重要なほどコード内で頻繁に発生しないためです。

の順序は、PUSHA命令エンコードの順序でした: eax, ecx, edx, ebx, esp, ebp, . 重複してプッシュしたことに注意してください! プッシュされたデータを見つけるために知っておく必要があります!esiediespesp

コードを 64 ビットから変換している場合、PUSHAコードはとにかく良くないため、更新して新しいレジスタをプッシュする必要がありr8ますr15。また、はるかに大きな SSE 状態を保存して復元する必要がありxmm8ますxmm15。あなたがそれらを破壊しようとしていると仮定します。

割り込みハンドラ コードが C コードに転送される単なるスタブである場合、すべてのレジスタを保存する必要はありません。rbxC コンパイラは、rbprsirdi、およびr12thruを保持するコードを生成すると想定できますr15rax、、、およびthrurcxを保存して復元するだけで済みます。(注: Linux またはその他の System V ABI プラットフォームでは、コンパイラは, , -保持しますrdxr8r11rbxrbpr12r15rsirdi

セグメント レジスタはロング モードでは値を保持しません (中断されたスレッドが 32 ビット互換モードで実行されている場合は、セグメント レジスタを保存する必要があります。ughoavgfhw に感謝します)。実際には、ロング モードでのセグメンテーションの大部分を取り除きましたがFS、オペレーティング システムがスレッド ローカル データのベース アドレスとして使用するために予約されています。レジスタ値自体は重要ではなく、 と のベースはMSRFSGSによって設定され0xC0000100ます0xC0000101。C コードによってアクセスされるスレッド ローカル データは、任意のスレッドの TLS を使用している可能性があることをFS覚えておいてください。C ランタイム ライブラリは一部の機能に TLS を使用するため、注意してください (例: 通常、strtok は TLS を使用します)。

FSまたはGS(ユーザー モードであっても) に値をロードすると、FSBASEまたはGSBASEMSR が上書きされます。一部のオペレーティング システムは「プロセッサ ローカル」ストレージとして使用するため (各 CPU の構造体へのポインターを保持する方法が必要です)、ユーザー モードでのGS読み込みによって破壊されない場所にそれを保持する必要があります。GSこの問題を解決するために、GSBASEレジスター用に 2 つの MSR が予約されています。1 つはアクティブで、もう 1 つは非表示です。カーネル モードでは、カーネルGSBASEは通常のGSBASEMSR に保持され、ユーザー モード ベースは別の (非表示) にあります。GSBASEMSR。カーネル モードからユーザー モード コンテキストにコンテキストを切り替えるとき、およびユーザー モード コンテキストを保存してカーネル モードに入るときに、コンテキスト切り替えコードは SWAPGS 命令を実行する必要があります。これにより、可視 MSR と非表示GSBASEMSR の値が交換されます。カーネルGSBASEはユーザー モードの他の MSR に安全に隠されているため、ユーザー モード コードはGSBASE、値を にロードしてカーネルを上書きすることはできませんGS。CPU がカーネル モードに再び入ると、コンテキスト保存コードが実行SWAPGSされ、カーネルのGSBASE.

于 2013-01-23T18:01:17.527 に答える
9

pusha冗長であるため、64 ビット モードでは無効です。各レジスタを個別にプッシュすることは、まさに行うべきことです。

于 2011-07-26T22:32:33.003 に答える
9

この種のことを行う既存のコードから学びます。例えば:

PUSHA実際、AMD64 にはレジストリが存在しないため、regs を「手動でプッシュ」することが唯一の方法です。AMD64 はこの点で独特ではありません。ほとんどの非 x86 CPU では、ある時点でレジスタごとの保存/復元も必要です。

しかし、参照されているソースコードを詳しく調べると、すべての割り込みハンドラがレジスタ セット全体を保存/復元する必要があるわけではないことがわかります。そのため、最適化の余地があります。

于 2011-07-28T10:17:19.487 に答える