私は、楽しみと利益のためのスタックのスマッシングに関するAleph Oneの論文を読んでいます。私は書き留めましたexample1.c
彼の論文から、私のシステムでスタックがどのように見えるかを確認するために少し修正しました。
Intel i5 M 480のVM=でUbuntu(64ビット)を実行しています。
この論文では、スタックは次のような構造になるとしています。また、Wordのサイズは4バイトであるとも述べています。 Wordのサイズを確認したところ、「long対応」ではない64ビットOSでは、Wordのサイズが32ビットまたは4バイトのままであることがわかりました。
ただし、カスタムコードを実行すると:
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 |
-------------------------------------------------------------------------
今私の質問のために:
[編集]
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バイト
私は正しいですか?
違いは主に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が使用されます。
パラメータは通常渡されますレジスタによって:スタックで見つけたa
、b
、c
変数は、呼び出し先(おそらく最適化を有効にしなかったため)。これが、スタック内で戻りアドレスよりも低い理由です。これは、コンパイラーがそれらを任意の順序で自由に配置できる場合(そしてそれらをスタック上に置く必要がない場合)を意味します。
さらに、コードは通常フレームポインターでコンパイルされません: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):呼び出し先がプッシュします任意の順序。
要約すれば:
X86では、パラメーターは呼び出し元によってスタックにプッシュされます:スタック上の位置はABIによって固定されます。それらは発信者によってプッシュされたであるため、戻りアドレスよりも高くなっています。
X86_64では、パラメーターはレジスターに渡されますです(パラメーターが多すぎる場合を除く)。呼び出し先は、必要に応じてスタックに自由にプッシュできます。それらは呼び出し先によってプッシュされますなので、スタック上の戻りアドレスよりも低くなります(ローカル変数、保存されたレジスターと混在させることができます)。
両方のABIで、%rbp
フレームベースはオプションですが、通常はx86で使用され、x86_64では使用されないことがよくあります。
注:Windows x86_64 ABIは異なります。
いくつかの参照:
---(x86_64のスタックレイアウト ;