web-dev-qa-db-ja.com

Linuxでスタック割り当てはどのように機能しますか?

OSは、スタックなどのために一定量の有効な仮想スペースを予約しますか?大きなローカル変数を使用するだけでスタックオーバーフローを生成できますか?

私は自分の仮定をテストするために小さなCプログラムを書きました。 X86-64 CentOS 6.5で実行されています。

_#include <string.h>
#include <stdio.h>
int main()
{
    int n = 10240 * 1024;
    char a[n];
    memset(a, 'x', n);
    printf("%x\n%x\n", &a[0], &a[n-1]);
    getchar();
    return 0;
}
_

プログラムを実行すると_&a[0] = f0ceabe0_と_&a[n-1] = f16eabdf_が得られます

Procマップはスタックを示します:7ffff0cea000-7ffff16ec000. (10248 * 1024B)

次に_n = 11240 * 1024_を増やしてみました

プログラムを実行すると_&a[0] = b6b36690_と_&a[n-1] = b763068f_が得られます

Procマップはスタックを示します:7fffb6b35000-7fffb7633000. (11256 * 1024B)

_ulimit -s_は、PCで_10240_を印刷します。

ご覧のとおり、どちらの場合でも、スタックサイズは_ulimit -s_が与えるサイズよりも大きくなっています。そして、スタックはローカル変数が大きくなるにつれて大きくなります。スタックのトップは、どういうわけか_&a[0]_から3-5kB離れています(赤いゾーンは128Bです)。

では、このスタックマップはどのように割り当てられるのでしょうか。

18
Amos

スタックメモリ制限が割り当てられていないようです(とにかく、無制限のスタックではできませんでした)。 https://www.kernel.org/doc/Documentation/vm/overcommit-accounting 言う:

C言語スタックの増加は、暗黙のmremapを実行します。絶対的な保証が必要でEdgeの近くで実行する場合は、必要と思われる最大サイズのスタックをマップする必要があります。典型的なスタックの使用では、これはそれほど重要ではありませんが、本当に気にかけているのであれば、それはコーナーケースです

ただし、スタックのmmappingはコンパイラーの目標です(そのオプションがある場合)。

編集:x84_64 Debianマシンでいくつかのテストを行った後、(straceによると)システムコールなしでスタックが大きくなることがわかりました。つまり、これはカーネルが自動的に拡大することを意味します(これが上記の「暗黙的」の意味です)。つまり、プロセスから明示的なmmap/mremapを使用しません。

これを確認する詳細情報を見つけるのは非常に困難でした。 Mel Gormanによる The Linux Virtual Memory Managerの理解 をお勧めします。答えはセクション4.6.1Handling a Page Faultであると仮定しますが、「Region not valid、side side of the Expandable Region of the Stack "および対応するアクション"領域を拡張してページを割り当てる "。 D.5.2スタックの拡張も参照してください。

Linuxメモリ管理に関するその他の参考資料(ただし、スタックについてはほとんど何もありません):

編集2:この実装には欠点があります:コーナーケースでは、スタックが制限よりも大きい場合でも、スタックヒープの衝突が検出されないことがあります!その理由は、スタック内の変数への書き込みが割り当てられたヒープメモリで終了する可能性があるためです。その場合、ページフォールトは発生せず、カーネルはスタックを拡張する必要があることを認識できません。ディスカッションの私の例を参照してください GNU/Linuxでのサイレントスタックヒープの衝突 gcc-helpリストから始めました。これを回避するには、コンパイラーが関数呼び出し時にコードを追加する必要があります。これは-fstack-check for GCC(詳細については、Ian Lance Taylorの返信とGCCのマニュアルページを参照してください)。

14
vinc17

Linuxカーネル4.2

最小限のテストプログラム

次に、最小限のNASM 64ビットプログラムでテストできます。

_global _start
_start:
    sub rsp, 0x7FF000
    mov [rsp], rax
    mov rax, 60
    mov rdi, 0
    syscall
_

ASLRをオフにし、環境変数がスタックに配置されて領域を占有するので、環境変数を削除してください。

_echo 0 | Sudo tee /proc/sys/kernel/randomize_va_space
env -i ./main.out
_

制限は、私の_ulimit -s_(私にとっては8MiB)をわずかに下回るところです。これは、環境に加えて最初にスタックに置かれる余分なSystem V指定データが原因であるように見えます: アセンブリのLinux 64コマンドラインパラメーター|スタックオーバーフロー

これに真剣に取り組んでいる場合は、TODO 最小限のinitrdイメージを作成 がスタックトップから書き込みを開始してダウンし、次に QEMU + GDBで実行 します。スタックアドレスを出力するループにdprintfを置き、_acct_stack_growth_にブレークポイントを置きます。それは栄光でしょう。

関連:

デフォルトでは、最大スタックサイズはプロセスごとに8MBに設定されています。
しかしulimitを使用して変更できます:

デフォルトをkBで表示:

$ ulimit -s
8192

無制限に設定:

ulimit -s unlimited

現在のシェルとサブシェル、およびそれらの子プロセスに影響します。
ulimitはシェル組み込みコマンドです)

使用中の実際のスタックアドレス範囲を表示できます。
cat /proc/$PID/maps | grep -F '[stack]'
Linux上。

2
Volker Siegel