3

一部の関数では、元のスタックが変更されないようにスタックを切り替える必要があります。そのために、以下のように2つのマクロを作成しました。

#define SAVE_STACK()    __asm__ __volatile__ ( "mov %%rsp, %0; mov %1, %%rsp" : \
"=m" (saved_sp) : "m" (temp_sp) );
#define RESTORE_STACK() __asm__ __volatile__ ( "mov %0, %%rsp" : \
"=m" (saved_sp) );

ここで、 temp_spsaved_spはスレッドローカル変数です。temp_spは、使用する仮のスタックを指します。元のスタックを変更しないようにしたい関数の場合、SAVE_STACKを最初に配置し、RESTORE_STACKを最後に配置します。たとえば、このように。

int some_func(int param1, int param2)
{
 int a, b, r;
 SAVE_STACK();
 // Function Body here
 .....................
 RESTORE_STACK();
 return r;
}

今私の質問は、このアプローチがうまくいくかどうかです。x86(64ビット)では、ローカル変数とパラメーターはrbpレジスターを介してアクセスされ、それに応じてrspは関数プロローグで減算され、関数エピローグで追加されて元の値に戻るまで変更されません。したがって、ここでは問題はありません。

ただし、コンテキストスイッチと信号が存在する場合にこれが正しいかどうかはわかりません。(Linuxの場合)。また、関数がインライン化されている場合、または末尾呼び出しの最適化(呼び出しの代わりにjmpが使用されている場合)が適用されている場合、これが正しいかどうかもわかりません。このアプローチに問題や副作用がありますか?

4

2 に答える 2

5

あなたが上に示したコードで、私は次の破損を考えることができます:

  1. x86 / x64では、GCCは適切と判断した場合、プロローグ/エピローグを使用して関数を「デコ」します。これを停止することはできません(ARMのように、__attribute__((__naked__))プロローグ/エピローグなしでコードを強制的に作成します。スタックフレームを設定しない場合もあります)。 。そのため、スタックを切り替える前に
    、 スタックを割り当てたり、スタックメモリの場所への参照を作成したりする可能性があります。さらに悪いことに、コンパイラの選択により、スタックを切り替える前にそのようなアドレスが不揮発性レジスタに入れられると、2つの場所(変更したstackpointer-relativeとother-reg-relative)にエイリアスされる可能性があります。それは同じです)。

  2. 繰り返しになりますが、x86 / x64では、ABIは、スタックフレームが割り当てられていないリーフ関数(「レッドゾーン」)の最適化を提案していますが、関数は終了の「下」にある128バイトのスタックを使用できます。メモリバッファがこれを考慮に入れていない限り、予期しないオーバーランが発生する可能性があります。

  3. シグナルは代替スタックで処理され(を参照sigaltstack())、独自のスタックスイッチングを行うと、シグナルハンドラー内からコードを呼び出せなくなる可能性があります。それは間違いなく再入可能ではなくなり、「スタックの場所」を取得する場所/方法によっては、間違いなくスレッドセーフではなくなります。

一般に、特定のコードを別のスタックで実行する場合は、次のいずれかを実行しないでください。

  • 別のスレッドで実行します(すべてのスレッドが異なるスタックを取得します)?
  • たとえば、トリガーSIGUSR1して、シグナルハンドラー(別のスタックを使用するように構成できます)でコードを実行しますか?
  • makecontext()/を介して実行しますswapcontext()(マンページの例を参照)?

編集:

「2つのプロセスのメモリを比較したい」と言うので、そのためのさまざまな方法があります。特に、外部プロセスのトレース-「デバッガ」(自分で作成したプロセスで、必要ptrace()なものを制御するために使用できます)をアタッチします。監視し、トレースするものに代わってブレークポイント/チェックポイントなどを処理させ、必要な検証を実行します)。検査するコードを変更する必要がないため、これもより柔軟になります。

于 2012-01-11T17:25:49.633 に答える
1

-fomit-frame-pointerデフォルトでオンになっています。最適化を無効にしてコンパイルする予定がない限り、プロローグ/エピローグを除いて関数がRSPに触れないという仮定は非常に破られています。

を使用した場合でも-O3 -fno-omit-frame-pointer、コンパイラはRSPを使用して引数やローカルにアクセスすることはありませんが、RSPを移動する場合があります。たとえばalloc、/ C99 VLA、または6つを超える引数を持つ関数(より正確には、レジスタに収まらない引数を持つ関数)を呼び出すと、すべてRSPが移動します。(コンパイラーが選択した戦略によっては、関数の呼び出しでmovストアを使用する場合があります。)

また、関数が呼び出し保存登録の保存を早期終了の可能性がなくなるまで遅らせる「シュリンクラップ」最適化は、コンパイラが保存/復元の準備ができる前にスタックスイッチが発生することを意味する可能性があります。また、復元が遅すぎたり早すぎたりする可能性があります。(これはamsによるコメントで言及されました。)

于 2022-01-05T09:30:17.483 に答える