私は学校のプロジェクトからいくつかの古いコードを見ています、そして私のラップトップでそれをコンパイルしようとしたとき、私はいくつかの問題に遭遇しました。もともとは古い32ビットバージョンのgcc用に書かれていました。とにかく、私はアセンブリの一部を64ビット互換のコードに変換しようとしていて、いくつかの問題にぶつかりました。
元のコードは次のとおりです。
pusha
pushl %ds
pushl %es
pushl %fs
pushl %gs
pushl %ss
pusha
は64ビットモードでは無効です。では、64ビットモードでx86_64アセンブリでこれを行う適切な方法は何でしょうか?
pusha
が64ビットモードで無効である理由がなければならないので、すべてのレジスタを手動でプッシュするのは良い考えではないかもしれないと感じています。
この種のことを行う既存のコードから学びます。例えば:
SAVE_ARGS_IRQ
を検索): entry_64.SINTR_Push
を検索): privregs.hIDT_VEC
を検索): exception.S (NetBSDの vector.S と同様)実際、AMD64にはPUSHA
が存在しないため、regを「手動でプッシュ」することが唯一の方法です。 AMD64は、この点で一意ではありません。ほとんどの非x86 CPUは、ある時点でレジスタごとの保存/復元も必要とします。
ただし、参照されているソースコードを詳しく調べると、すべての割り込みハンドラがレジスタセット全体を保存/復元する必要があるわけではないため、最適化の余地があります。
AMDは、64ビットx86拡張機能を開発したときに、REX
プレフィックスの新しいオペコードとその他の新しい命令を追加するためのスペースを必要としていました。彼らはいくつかのオペコードの意味をそれらの新しい命令に変えました。
いくつかの指示は、既存の指示の単なる短縮形であるか、そうでなければ必要ありませんでした。 PUSHA
は犠牲者の1人でした。彼らがPUSHA
を禁止した理由は明らかではありませんが、新しい命令オペコードと重複しているようには見えません。おそらく、それらは将来の使用のためにPUSHA
およびPOPA
オペコードとして予約されています。これらは完全に冗長であり、高速ではなく、コード内で重要な頻度で発生しないためです。
PUSHA
の順序は、命令エンコーディングの順序でした:eax
、ecx
、edx
、ebx
、esp
、ebp
、esi
、edi
。冗長にesp
をプッシュしたことに注意してください!プッシュされたデータを見つけるには、esp
を知る必要があります。
コードを64ビットから変換する場合、PUSHA
コードはとにかく良くないので、新しいレジスタをr8
からr15
にプッシュするように更新する必要があります。また、はるかに大きなSSE状態、xmm8
からxmm15
までを保存および復元する必要があります。それらを壊そうとしていると仮定します。
割り込みハンドラコードが単にCコードに転送するスタブである場合は、すべてのレジスタを保存する必要はありません。 Cコンパイラは、rbx
、rbp
、rsi
、rdi
、およびr12
からr15
を保持するコードを生成すると想定できます。 rax
、rcx
、rdx
、およびr8
からr11
までを保存および復元するだけで済みます。 (注:Linuxまたはその他のSystem V ABIプラットフォームでは、コンパイラーはrbx
、rbp
、r12
-r15
を保持し、rsi
およびrdi
clobberedを期待できます)。
セグメントレジスタはロングモードでは値を保持しません(割り込みスレッドが32ビット互換モードで実行されている場合は、ughoavgfhwのおかげでセグメントレジスタを保持する必要があります)。実際には、ロングモードでほとんどのセグメンテーションが削除されましたが、FS
は、スレッドローカルデータのベースアドレスとして使用するオペレーティングシステム用に予約されています。レジスタ値自体は重要ではありません。FS
とGS
のベースは、MSR 0xC0000100
と0xC0000101
を介して設定されます。 FS
を使用しないと仮定すると、心配する必要はありません。Cコードによってアクセスされるスレッドローカルデータは、ランダムスレッドのTLSを使用している可能性があることに注意してください。 Cランタイムライブラリは一部の機能にTLSを使用するため、注意してください(例:strtokは通常TLSを使用します)。
値をFS
またはGS
にロードすると(ユーザーモードでも)、FSBASE
またはGSBASE
MSRが上書きされます。一部のオペレーティングシステムはGS
を「プロセッサローカル」ストレージとして使用するため(各CPUの構造体へのポインタを持つ方法が必要です)、ユーザーモードでGS
をロードすることによって混乱しない場所に保持する必要があります。この問題を解決するために、GSBASE
レジスタ用に予約された2つのMSRがあります。1つはアクティブで、もう1つは非表示です。カーネルモードでは、カーネルのGSBASE
は通常のGSBASE
MSRに保持され、ユーザーモードベースは他の(非表示の)GSBASE
MSRにあります。カーネルモードからユーザーモードコンテキストにコンテキストを切り替えるとき、およびユーザーモードコンテキストを保存してカーネルモードに入るとき、コンテキストスイッチコードはSWAPGS命令を実行する必要があります。これにより、表示と非表示のGSBASE
MSRの値が交換されます。カーネルのGSBASE
はユーザーモードの他のMSRで安全に非表示になっているため、ユーザーモードコードはGSBASE
に値をロードすることによってカーネルのGS
を破壊することはできません。 CPUがカーネルモードに戻ると、コンテキスト保存コードはSWAPGS
を実行し、カーネルのGSBASE
を復元します。
pusha
は冗長であるため、64ビットモードでは無効です。各レジスタを個別にプッシュすることは、まさに行うべきことです。
こんにちはそれはそれを行う正しい方法ではないかもしれませんが、次のようなマクロを作成することができます
.macro pushaq
Push %rax
Push %rcx
Push %rdx
Push %rbx
Push %rbp
Push %rsi
Push %rdi
.endm # pushaq
そして
.macro popaq
pop %rdi
pop %rsi
pop %rbp
pop %rbx
pop %rdx
pop %rcx
pop %rax
.endm # popaq
必要に応じて、最終的に他のr8-15レジスタを追加します