オペレーティングシステムは、スタックまたはその他のアイテム用に固定量の有効仮想スペースを予約しますか?大きなローカル変数を使用するだけでスタックオーバーフローを生成できますか?
私は私の仮説をテストするために小さな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
マイコンピュータから印刷してください10240
。
ご覧のとおり、どちらの場合もスタックサイズがulimit -s
指定されたサイズより大きいです。そして、ローカル変数が大きくなるとスタックも大きくなります。スタックの上部は3〜5kBほど少ない&a[0]
(私が知っている限り、赤い領域は128Bです)。
では、このスタックマップはどのように割り当てられますか?
答え1
スタックメモリ制限が割り当てられていないようです(とにかく無限にスタックすることはできません)。https://www.kernel.org/doc/Documentation/vm/overcommit-accounting説明する:
C言語スタックは暗黙のmremapを介して増加します。絶対的な保証を望んでエッジの近くに実行するには、必要と思われる最大サイズにスタックをマッピングする必要があります。一般的なスタック使用の場合、これは重要ではありませんが、実際に興味がある場合は極端なケースです。
ただし、スタックマッピングはコンパイラのターゲットになります(オプションがある場合)。
編集:x84_64 Debianシステムでいくつかのテストを実行した後(Stack Overflowに従ってstrace
)、システムコールなしでスタックが大きくなることがわかりました。したがって、これはカーネルがプロセスで明示的mmap
に成長せずに自動的に成長することを意味します(上記では「暗黙的に」という意味)。mremap
これを確認する詳細を見つけるのは難しいです。私はお勧めしますLinux仮想メモリマネージャについてメルゴーマンの地音。私の考えに対する答えはセクション4.6.1にあります。ページエラー処理、例外は、「地域は無効ですが、拡張可能領域(スタックなど)の横にあります。」とそのアクションは、「領域の拡張とページの割り当て」です。 D.5.2も参照してください。拡張スタック。
Linuxメモリ管理に関するその他の参考資料(スタックにはほとんどありません):
- メモリに関するよくある質問
- すべてのプログラマがメモリについて知っておくべきこと著者:ウリヒドレーパー
編集2:この実装には欠点があります。極端な場合、スタックが制限より大きい場合でも、スタックヒープの衝突が検出されない可能性があります。これは、スタック内の変数への書き込みが割り当てられたヒープメモリで終了する可能性があるためです。この場合、ページエラーは発生せず、カーネルはスタックを拡張する必要があることはわかりません。ディスカッションの私の例を参照してください。GNU/Linuxでの自動スタックの競合私はgcc-helpのリストから始めました。これを防ぐには、コンパイラは関数が呼び出されたときにいくつかのコードを追加する必要があります。これはGCCを介して行うことができます-fstack-check
(詳細については、Ian Lance Taylorの回答とGCCのマニュアルページを参照)。
答え2
Linuxカーネル4.2
- mm/mmap.c#acct_stack_growthセグフォルトが発生するかどうかを決定します。
rlim[RLIMIT_STACK]
POSIX対応物を使用します。gerlimit(RLIMIT_STACK)
- アーチ/x86/mm/fault.c#do_page_fault最終的に呼び出すチェーンを開始する割り込みハンドラです。
acct_stack_growth
- アーチ/x86/entry/entry_64.Sページフォルトハンドラを設定します。このセクションを理解するには、ページ付けについて知っておく必要があります。x86ページングはどのように機能しますか? |スタックオーバーフロー
最小テストプログラム
その後、最小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
。それは素晴らしいでしょう。
関連:
答え3
デフォルトでは、最大スタックサイズはプロセスあたり8 MBで構成されていますが、
次のコマンドを使用して変更できますulimit
。
デフォルト値の表示(kB):
$ ulimit -s
8192
無制限に設定:
ulimit -s unlimited
現在のシェル、サブシェル、およびそのサブプロセスに影響します。
(ulimit
シェル組み込みコマンドです)
次のコマンドを使用すると、使用している物理スタックアドレスの範囲を表示できます。
cat /proc/$PID/maps | grep -F '[stack]'
Linuxの場合。