为什么递归调用StackOverflow的原因在不同的堆栈深度? [英] Why does a recursive call cause StackOverflow at different stack depths?

查看:684
本文介绍了为什么递归调用StackOverflow的原因在不同的堆栈深度?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图找出动手如何尾调用由C#编译器来处理。

I was trying to figure out hands-on how tail calls are handled by the C# compiler.

(答案:​​<一href="http://stackoverflow.com/questions/491376/why-doesnt-net-c-optimize-for-tail-call-recursion">They're并非如此。但 64位JIT(S)的会做的TCE(尾调用消除)的有限制。)

(Answer: They're not. But the 64bit JIT(s) WILL do TCE (tail call elimination). Restrictions apply.)

因此​​,使用该打印多少次它被调用之前的 StackOverflowException 终止进程的递归调用我写了一个小测试。

So I wrote a small test using a recursive call that prints how many times it gets called before the StackOverflowException kills the process.

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();
    }

果然不出所料,有这么异常的任何程序结束

  • 在优化构建OFF(Debug或释放)
  • 目标:86
  • 目标:值为anycpu +preFER 32位(这是新的VS 2012和我第一次看到它<一个。 href="http://stackoverflow.com/questions/12066638/what-is-the-purpose-of-the-$p$pfer-32-bit-setting-in-visual-studio-2012-and-how">More这里。)
  • 在code一些看似无害的分支(见注释'其他'分支)。
    • 'Optimize build' OFF (either Debug or Release)
    • Target: x86
    • Target: AnyCPU + "Prefer 32 bit" (this is new in VS 2012 and the first time I saw it. More here.)
    • Some seemingly innocuous branch in the code (see commented 'else' branch).

    相反,采用优化构建ON +(目标= 64或值为anycpu与'preFER 32位OFF(在64位CPU)),三氯乙烯发生,计数器仍继续旋转起来永远的(好吧,它可以说是旋转的每一次它的价值溢出)。

    Conversely, using 'Optimize build' ON + (Target = x64 or AnyCPU with 'Prefer 32bit' OFF (on a 64bit CPU)), TCE happens and the counter keeps spinning up forever (ok, it arguably spins down each time its value overflows).

    但是我注意到一个现象我无法解释 StackOverflowException 情况:(?),它永远不会发生在的完全的相同栈深度。这里有几个32位运行,发布版本的输出:

    But I noticed a behaviour I can't explain in the StackOverflowException case: it never (?) happens at exactly the same stack depth. Here are the outputs of a few 32-bit runs, Release build:

    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.
    

    和调试版本:

    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 的)。堆栈帧的大小是不变的。

    The stack size is constant (defaults to 1 MB). The stack frames' sizes are constant.

    那么,如何解释当 StackOverflowException 命中堆栈深度的(有时不平凡的)变化?

    So then, what can account for the (sometimes non-trivial) variation of stack depth when the StackOverflowException hits?

    汉斯帕桑特提出了 Console.WriteLine 的问题,触及的P / Invoke,互操作和可能的不确定性锁定。

    Hans Passant raises the issue of Console.WriteLine touching P/Invoke, interop and possibly non-deterministic locking.

    所以,我简化了code到这一点:

    So I simplified the code to this:

    class Program
    {
        static void Main(string[] args)
        {
            Rec();
        }
        static int sz = 0;
        static void Rec()
        {
            sz++;
            Rec();
        }
    }
    

    我在Release / 32位/优化冉它没有一个调试器。当程序崩溃,我附加调试和检查计数器的值。

    I ran it in Release/32bit/Optimization ON without a debugger. When the program crashes, I attach the debugger and check the value of the counter.

    和它的还是的不是几个运行相同。 (或者我的测试是有缺陷的。)

    And it still isn't the same on several runs. (Or my test is flawed.)

    所建议的fejesjoco,我看着ASLR(地址空间布局随机化)。

    As suggested by fejesjoco, I looked into ASLR (Address space layout randomization).

    这是一种安全的技术,使得它很难对缓冲区溢出攻击,找到(例如)特定的系统调用precise位置,通过随机各种事物的进程地址空间,包括堆栈中的位置,显然,其大小。

    It's a security technique that makes it hard for buffer overflow attacks to find the precise location of (e.g.) specific system calls, by randomizing various things in the process address space, including the stack position and, apparently, its size.

    的理论听起来不错。让我们把它付诸实践!

    为了测试这个,我用具体的任务,微软的工具: EMET或增强的缓解体验工具包。它允许设置ASLR标志(和更多)在一个系统或进程级别。
    (还有一个<一href="http://stackoverflow.com/questions/9560993/how-do-you-disable-aslr-address-space-layout-randomization-on-windows-7-x64">system-wide,注册表黑客替代,我没有尝试)

    In order to test this, I used a Microsoft tool specific for the task: EMET or The Enhanced Mitigation Experience Toolkit. It allows setting the ASLR flag (and a lot more) on a system- or process-level.
    (There is also a system-wide, registry hacking alternative that I didn't try)

    为了验证工具的有效性,我还发现,进程浏览器妥报告ASLR标志的状态的过程中的属性页面。从来没有看到直到今天:)

    In order to verify the effectiveness of the tool, I also discovered that Process Explorer duly reports the status of the ASLR flag in the 'Properties' page of the process. Never saw that until today :)

    从理论上讲,EMET可以(重新)设置ASLR标志一个单一的过程。在实践中,它似乎没有任何改变(见上图)。

    Theoretically, EMET can (re)set the ASLR flag for a single process. In practice, it didn't seem to change anything (see above image).

    不过,我禁用ASLR整个系统,并(重新启动一次后)我终于可以证实的确,SO例外现在总是发生在同一个堆栈的深度。

    However, I disabled ASLR for the entire system and (one reboot later) I could finally verify that indeed, the SO exception now always happens at the same stack depth.

    ASLR相关,在老年消息:<一href="http://9to5google.com/2012/03/07/vupen-conquers-chrome-at-pwn2own-security-conference-google-must-pay-cash-reward-for-french-teams-hack/">How Chrome浏览器得到了PWNED

    ASLR-related, in older news: How Chrome got pwned

    推荐答案

    我想这可能是 ASLR 的工作。您可以关闭DEP来验证这一理论。

    I think it may be ASLR at work. You can turn off DEP to test this theory.

    在这里看到一个C#实用类来检查内存的信息: http://stackoverflow.com/a/8716410/552139

    See here for a C# utility class to check memory information: http://stackoverflow.com/a/8716410/552139

    顺便说一下,用这个工具,我发现的最大和最小堆栈大小之间的差大约是2 KIB,这是半页。这是奇怪的。

    By the way, with this tool, I found that the difference between the maximum and minimum stack size is around 2 KiB, which is half a page. That's weird.

    更新:OK,现在我知道我是对的。我也跟着上半页的理论,并发现这个文档的检查Windows中的ASLR实现:<一href="http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf">http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf

    Update: OK, now I know I'm right. I followed up on the half-page theory, and found this doc that examines the ASLR implementation in Windows: http://www.symantec.com/avcenter/reference/Address_Space_Layout_Randomization.pdf

    报价:

    在堆栈已经被放置,初始堆栈指针是进一步   由一个随机递减量随机化。初始偏移   选择为高达半页(2048字节)

    Once the stack has been placed, the initial stack pointer is further randomized by a random decremental amount. The initial offset is selected to be up to half a page (2,048 bytes)

    这是回答你的问题。 ASLR带走0和2048字节的初始堆栈随机之间。

    And this is the answer to your question. ASLR takes away between 0 and 2048 bytes of your initial stack randomly.

    这篇关于为什么递归调用StackOverflow的原因在不同的堆栈深度?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆