web-dev-qa-db-ja.com

割り込みサービスルーチンのためにx86_64にレジスタを保存する方法は?

私は学校のプロジェクトからいくつかの古いコードを見ています、そして私のラップトップでそれをコンパイルしようとしたとき、私はいくつかの問題に遭遇しました。もともとは古い32ビットバージョンのgcc用に書かれていました。とにかく、私はアセンブリの一部を64ビット互換のコードに変換しようとしていて、いくつかの問題にぶつかりました。

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

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

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

pushaが64ビットモードで無効である理由がなければならないので、すべてのレジスタを手動でプッシュするのは良い考えではないかもしれないと感じています。

24
Mr. Shickadance

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

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

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

8
FrankH.

AMDは、64ビットx86拡張機能を開発したときに、REXプレフィックスの新しいオペコードとその他の新しい命令を追加するためのスペースを必要としていました。彼らはいくつかのオペコードの意味をそれらの新しい命令に変えました。

いくつかの指示は、既存の指示の単なる短縮形であるか、そうでなければ必要ありませんでした。 PUSHAは犠牲者の1人でした。彼らがPUSHAを禁止した理由は明らかではありませんが、新しい命令オペコードと重複しているようには見えません。おそらく、それらは将来の使用のためにPUSHAおよびPOPAオペコードとして予約されています。これらは完全に冗長であり、高速ではなく、コード内で重要な頻度で発生しないためです。

PUSHAの順序は、命令エンコーディングの順序でした:eaxecxedxebxespebpesiedi。冗長にespをプッシュしたことに注意してください!プッシュされたデータを見つけるには、espを知る必要があります。

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

割り込みハンドラコードが単にCコードに転送するスタブである場合は、すべてのレジスタを保存する必要はありません。 Cコンパイラは、rbxrbprsirdi、およびr12からr15を保持するコードを生成すると想定できます。 raxrcxrdx、およびr8からr11までを保存および復元するだけで済みます。 (注:Linuxまたはその他のSystem V ABIプラットフォームでは、コンパイラーはrbxrbpr12-r15を保持し、rsiおよびrdiclobberedを期待できます)

セグメントレジスタはロングモードでは値を保持しません(割り込みスレッドが32ビット互換モードで実行されている場合は、ughoavgfhwのおかげでセグメントレジスタを保持する必要があります)。実際には、ロングモードでほとんどのセグメンテーションが削除されましたが、FSは、スレッドローカルデータのベースアドレスとして使用するオペレーティングシステム用に予約されています。レジスタ値自体は重要ではありません。FSGSのベースは、MSR 0xC00001000xC0000101を介して設定されます。 FSを使用しないと仮定すると、心配する必要はありません。Cコードによってアクセスされるスレッドローカルデータは、ランダムスレッドのTLSを使用している可能性があることに注意してください。 Cランタイムライブラリは一部の機能にTLSを使用するため、注意してください(例:strtokは通常TLSを使用します)。

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

18
doug65536

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

7
user200783

こんにちはそれはそれを行う正しい方法ではないかもしれませんが、次のようなマクロを作成することができます

.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レジスタを追加します

3
baz