いけむランド

はてダからやってきました

ヒープとスタックは出会うべきではなかった

データがメモリへどのようにアロケーションされるかは OS や libc およびコンパイラに依存する。
Linux 2.6 の場合、どうもユーザ自身が limit でスタックの使用量を制限しないと、スタックもヒープも拡張し放題で、最終的に衝突してしまうことを許しているみたい。

Heap / stack overlap

  • Can heap and stack "collide"?
    • Heap grows up... stack grows down...
    • Collision or not: depends on process VM map
    • Two protections mechanisms at bottom of stack
      • Gap page(s): mappings forbidden
      • Guard page(s): PROT_NONE mapping
  • Linux 2.6
    • No gap, no guard page!
    • mmap() allocates close to bottom of stack if low mem (kernel>=2.6.9 ?)
    • Heap allocations use mmap if size>128K or if low memory condition
    • Thus heap and stack can be contiguous!

警告: Linux x86 では、メモリ使用率の設定を高くし過ぎないように注意してください。glibc はプロセスヒープがスレッドスタックよりも大きくなることを許可しており、その場合にサーバがクラッシュします。

ヒープが成長する時にスタックに衝突してしまった場合には malloc() が NULL を返したりすることで検出できるみたいだが、逆にスタックの成長を引き起こす関数呼び出しがヒープに衝突してしまっても、特にチェックしていないという雰囲気。
ちなみにこれを再現したプログラムはヒープを G 単位まで確保し、その後で再帰呼び出しを数万回くらい繰り返すような代物である。
見事に再帰呼び出し中のとある局所変数がヒープに置いてあったリストを破壊して、その後にそのリストのリンクをたどっている途中で SIGSEGV を発生させてくれました。最初はリストの実装が悪いのかかなり悩んじゃいました。


...というような情報を日本語で探してたんだけど、見当たらなかったのでここにメモしておくことにする。