再帰呼び出しが異なるスタック深度でStackOverflowを引き起こすのはなぜですか?
質問
私は、C#コンパイラによってテールコールがどのように処理されるかをハンズオンで把握しようとしていました。
(回答 彼らはそうではありません。 しかし、その 64 ビット JIT (複数可) は TCE (テールコールエリミネーション) を実行します。 制限事項の適用 .)
そこで、再帰的な呼び出しを使って小さなテストを書きました。
StackOverflowException
がプロセスを停止させるまでに何回呼び出されたかを表示します。
class Program
{
static void Main(string[] args)
{
Rec();
}
static int sz = 0;
static Random r = new Random();
static void Rec()
{
sz++;
//uncomment for faster, more imprecise runs
//if (sz % 100 == 0)
{
//some code to keep this method from being inlined
var zz = r.Next();
Console.Write("{0} Random: {1}\r", sz, zz);
}
//uncommenting this stops TCE from happening
//else
//{
// Console.Write("{0}\r", sz);
//}
Rec();
}
ちょうどいいタイミングで、SO Exception on any ofでプログラムが終了します。
- 最適化ビルド」オフ (デバッグまたはリリースのいずれか)
- ターゲット: x86
- ターゲット AnyCPU + "Prefer 32 bit"(これはVS 2012で新しくなったもので、初めて見ました。 詳細はこちら .)
- コード内の一見無害なブランチ (コメントされた 'else' ブランチを参照)。
逆に、'Optimize build' ON + (Target = x64 or AnyCPU with 'Prefer 32bit' OFF (64bit CPU)) を使用すると、TCE が発生し、カウンターは永遠に回転し続けます (OK, it arguably spins) 。 ダウン その値がオーバーフローするたびに回転します)。
しかし、私は説明できない動作に気づきました。
で
StackOverflowException
の場合:それは決して(?
まさに
で発生することはありません。以下は、いくつかの 32 ビット実行の出力です (リリース ビルド)。
51600 Random: 1778264579
Process is terminated due to StackOverflowException.
51599 Random: 1515673450
Process is terminated due to StackOverflowException.
51602 Random: 1567871768
Process is terminated due to StackOverflowException.
51535 Random: 2760045665
Process is terminated due to StackOverflowException.
そしてDebugビルド。
28641 Random: 4435795885
Process is terminated due to StackOverflowException.
28641 Random: 4873901326 //never say never
Process is terminated due to StackOverflowException.
28623 Random: 7255802746
Process is terminated due to StackOverflowException.
28669 Random: 1613806023
Process is terminated due to StackOverflowException.
スタックサイズは一定で ( のデフォルトは 1 MB です。 ). スタックフレームのサイズは一定です。
では、スタックの深さが(時には自明でないほど)変化するのは、何が原因なのでしょうか。
StackOverflowException
がヒットしたときのスタックの深さの変化 (時に非自明) を説明できるでしょうか?
最新情報
Hans Passant は、次のような問題を提起しています。
Console.WriteLine
は、P/Invoke、interop、そしておそらく非決定論的ロックに触れています。
そこで、コードをこんな風に簡略化しました。
class Program
{
static void Main(string[] args)
{
Rec();
}
static int sz = 0;
static void Rec()
{
sz++;
Rec();
}
}
デバッガを使わずにRelease/32bit/Optimization ONで動かしてみました。プログラムがクラッシュした時にデバッガを付けてカウンタの値を確認しています。
すると、それは 今も は何度か実行しても同じではありません。(あるいは私のテストに欠陥があるか)
更新: クロージャ
fejesjocoに提案されたように、私はASLR (Address space layout randomization) について調べました。
これは、スタックの位置やそのサイズなど、プロセスのアドレス空間におけるさまざまなものをランダムにすることにより、バッファ オーバーフロー攻撃による (たとえば) 特定のシステム コールの正確な位置の発見を困難にするセキュリティ技術です。
理論は良さそうですね。実践してみましょう!
これをテストするために、私はこのタスクに特化したMicrosoftのツールを使用しました。
EMET (The Enhanced Mitigation Experience Toolkit、強化された緩和経験ツールキット)
. これは、システム レベルまたはプロセス レベルで ASLR フラグ (およびその他多数) を設定することができます。
(また
システム全体、レジストリをハックする代替手段
という選択肢もありますが、私は試していません)
また、このツールの効果を検証するために、以下のことを発見しました。 プロセス エクスプローラー は、プロセスの [プロパティ] ページで ASLR フラグの状態をきちんと報告しています。今日まで一度も見たことがありませんでした :)
理論的には、EMET は単一のプロセスに対して ASLR フラグを (再) 設定することができます。実際には、何も変わらないようでした (上の画像参照)。
しかし、私はシステム全体の ASLR を無効にし、(1 回のリブート後に)実際に、SO の例外が常に同じスタック深度で発生することを最終的に確認することができました。
ボーナス
ASLR関連、古いニュースでは Chrome はどのように破壊されたのか
解決方法は?
私はそれがかもしれないと思う ASLR が働いているのではないかと思います。この理論を検証するために DEP をオフにすることができます。
メモリ情報を確認するためのC#ユーティリティクラスはこちらをご覧ください。 https://stackoverflow.com/a/8716410/552139
ちなみにこのツールで、スタックサイズの最大値と最小値の差は約2KiB、つまり半ページ分であることがわかりました。変な話ですね。
更新しました。OK、今、私は自分が正しいことを知りました。半ページの理論を追ったところ、Windows での ASLR 実装を検証したこのドキュメントを見つけました。 http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf
引用元
スタックが配置されると、最初のスタックポインタはさらにランダムな減少量によって ランダムな減少量によってランダム化されます。最初のオフセットは 半ページ (2,048 バイト) までの範囲で選択されます。
そして、これが質問の答えです。ASLR は、初期スタックの 0 バイトから 2048 バイトの間をランダムに取り去ります。
関連
-
[解決済み] メンバー '<メンバー名>' にインスタンス参照でアクセスできない
-
[解決済み] エンティティタイプ <type> は、現在のコンテキストのモデルの一部ではありません。
-
[解決済み】C#はJavaのcharAt()と同等?)
-
[解決済み】ソケットのアドレス(プロトコル/ネットワークアドレス/ポート)は、通常1つしか使用できない?
-
[解決済み】「...は'型'であり、与えられたコンテキストでは有効ではありません」を解決するにはどうすればよいですか?(C#)
-
[解決済み】ファイルへの読み書きの際に共有違反のIOExceptionが発生する C#
-
[解決済み】URLから画像をダウンロードする方法
-
[解決済み】ユーザー設定値を別のユーザー設定値で設定する
-
[解決済み] なぜC#は汎用属性型を禁止しているのですか?
-
[解決済み] Math.Round(2.5)はなぜ3でなく2を返すのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み】Ajax処理で「無効なJSONプリミティブ」と表示される件
-
[解決済み】ここで「要求URIに一致するHTTPリソースが見つかりませんでした」となるのはなぜですか?
-
[解決済み】文字列が有効な DateTime " format dd/MM/yyyy " として認識されなかった。
-
[解決済み] 'IEnumerable<SelectListItem>' 型の ViewData アイテムで、キーが国であるものは存在しない。
-
[解決済み】Socket.Selectがエラー "An operation was attempted on something that is not a socket" を返す。
-
[解決済み】MetadataException: 指定されたメタデータ・リソースをロードできない
-
[解決済み】 C# 条件演算子エラー 代入、call、increment、decrement、await、new object 式のみ文として使用可能です。
-
[解決済み】Visual Studioの「32ビットを優先する」設定の目的と実際の動作は?
-
[解決済み] なぜ.NET/C#は末尾再帰を最適化しないのですか?