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です)。
では、このスタックマップはどのように割り当てられるのでしょうか。
スタックメモリ制限が割り当てられていないようです(とにかく、無制限のスタックではできませんでした)。 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のマニュアルページを参照してください)。
Linuxカーネル4.2
rlim[RLIMIT_STACK]
_を使用します gerlimit(RLIMIT_STACK)
acct_stack_growth
_を呼び出すチェーンを開始する割り込みハンドラーです。最小限のテストプログラム
次に、最小限の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上。