web-dev-qa-db-ja.com

関数呼び出しのスタックの構造

私は、楽しみと利益のためのスタックのスマッシングに関するAleph Oneの論文を読んでいます。私は書き留めましたexample1.c彼の論文から、私のシステムでスタックがどのように見えるかを確認するために少し修正しました。

Intel i5 M 480のVM=でUbuntu(64ビット)を実行しています。

この論文では、スタックは次のような構造になるとしています。また、Wordのサイズは4バイトであるとも述べています。 Wordのサイズを確認したところ、「long対応」ではない64ビットOSでは、Wordのサイズが32ビットまたは4バイトのままであることがわかりました。

enter image description here

ただし、カスタムコードを実行すると:

void function(int a, int b, int c) {
    char buffer1[5];
    char buffer2[10];
    memset(buffer1, "\xaa", sizeof(buffer1));
}

void main() {
    function(1, 2, 3);
}

紙と同じスタック構造が得られません。はい、私はこの論文が1998年に発行されたことを知っていますが、スタック構造が大幅に変更されたという記事はインターネット上で見つかりませんでした。スタックは次のようになります(スタックを誤って解釈した場合に備えて、確認のためにGDBスクリーンショットもアップロードしています)。

    Lower memory                                                Higher memory
    -------------------------------------------------------------------------   
    | int c   | int b   | int a   | buffer1  | buffer2  | RBP     | RET     |
    | 4 bytes | 4 bytes | 4 bytes | 16 bytes | 16 bytes | 8 bytes | 8 bytes |
    -------------------------------------------------------------------------

enter image description hereenter image description here

今私の質問のために:

  1. スタック構造が変更されたのはなぜですか?
  2. Buffer1とbuffer2に追加されたスペースとは何ですか?論文によると、割り当てられるのは8バイトと12バイトのみです。ただし、buffer2は追加の6バイトを取得し、その後で初めてbuffer1が開始され、buffer1にも16バイトが割り当てられます。ここで何か不足していますか?保護機構としてスラックスペースが与えられていると読んだのですが、これですか?

[編集]

X64 ABIによると、スタックは常に8バイト境界で整列されます(17ページ、脚注番号9)。

したがって、パラメータと2つの文字バッファがスタックにプッシュされると、スタックが占める合計スペースは48バイト(または0x30バイト)になります。これは、3つの整数で合計8バイト、合計で24バイト、バッファ1で8バイト、バッファ2で16バイトであるため、GDBスクリーンショットで裏付けられた合計48バイトに到達します。

ただし、GDBスタックは、整数(a、b、c)がそれぞれ4バイトしか割り当てられていないことも示しています。バッファは、プログラムで宣言されたサイズも占めます(明らかに)。これが、たるみが見える理由ですか?

スラック= 48-(5 + 10 + 4 + 4 + 4)= 21バイト

私は正しいですか?

3
Torcellite

違いは主にABIの違いによって説明されます。

  • 紙はx86(32ビット)で何が起こるかを説明しています

  • x86_64を使用しています。

X86nでは x86 ABI が使用されます

  • パラメータは、呼び出し元によってスタック上に渡されます。つまり、上部の3つの値はa、b、cになります。

  • Call命令はIP(retフィールド)をプッシュします。

  • 通常、呼び出し元のフレームポインターは、呼び出し先(sfpフィールド)によってプッシュされます。

  • これらのフィールドはすべて、ABIによってこのアーキテクチャに固定されています。

  • スタックの下部(ローカル変数、保存されたレジスタなど)はABIによって修正されず、コンパイラはそれをどのように使用するかを決定できます。

低メモリ高メモリ
 ------------------------------------ ------------------------------------- 
 | buffer2 | buffer2 | sfp | ret | a | b | c | 
 ------------------------------------------- ------------------------------ 
 <------- )- -----> <-(2)-> <--------------------(1)----------- -------> 
 
(1):呼び出し先が呼び出し先にプッシュし、ABIが修正しました。
(2):呼び出し先がプッシュし、修正しましたABIによって。
技術的には、これはx86の場合と同様にオプションですが、一般的に使用されます。
(3):呼び出し先が任意の順序でプッシュします。

X86_64では、---(AMD-64/x86_64 ABIが使用されます。

  • パラメータは通常渡されますレジスタによって:スタックで見つけたabc変数は、呼び出し先(おそらく最適化を有効にしなかったため)。これが、スタック内で戻りアドレスよりも低い理由です。これは、コンパイラーがそれらを任意の順序で自由に配置できる場合(そしてそれらをスタック上に置く必要がない場合)を意味します。

  • さらに、コードは通常フレームポインターでコンパイルされません:Push rbp; mov rbp, rspは通常省略されます。

低メモリ高メモリ
 ------------------------------------ ------------------------------------- 
 | int c | int b | int a | buffer1 | buffer2 | RBP | RET | 
 | 4バイト| 4バイト| 4バイト| 16バイト| 16バイト| 8バイト| 8バイト| 
 ------------------------------------------ ------------------------------- 
 <---------- 2)--------------------------------------------> <- -(1)-> 
 
(1):呼び出し先が呼び出し先にプッシュし、ABIが修正します。
(2):呼び出し先がプッシュします任意の順序。

要約すれば:

注:Windows x86_64 ABIは異なります。

いくつかの参照:

8
ysdx