C# 如何防止和/或处理 StackOverflowException?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/206820/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 18:02:38  来源:igfitidea点击:

How do I prevent and/or handle a StackOverflowException?

c#.netstack-overflowxslcompiledtransform

提问by JohnnyM

I would like to either prevent or handle a StackOverflowExceptionthat I am getting from a call to the XslCompiledTransform.Transformmethod within an Xsl EditorI am writing. The problem seems to be that the user can write an Xsl scriptthat is infinitely recursive, and it just blows up on the call to the Transformmethod. (That is, the problem is not just the typical programmatic error, which is usually the cause of such an exception.)

我想阻止或处理我正在编写StackOverflowExceptionXslCompiledTransform.Transform方法调用中得到的Xsl Editor。问题似乎是用户可以编写一个Xsl script无限递归的方法,并且在调用该Transform方法时会崩溃。(也就是说,问题不仅仅是典型的程序错误,这通常是导致此类异常的原因。)

Is there a way to detect and/or limit how many recursions are allowed? Or any other ideas to keep this code from just blowing up on me?

有没有办法检测和/或限制允许的递归次数?或者有什么其他想法可以防止这段代码在我身上爆炸?

采纳答案by FlySwat

From Microsoft:

来自微软:

Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. Consequently, users are advised to write their code to detect and prevent a stack overflow. For example, if your application depends on recursion, use a counter or a state condition to terminate the recursive loop.

从 .NET Framework 2.0 版开始,一个 StackOverflowException 对象不能被 try-catch 块捕获,相应的进程默认终止。因此,建议用户编写他们的代码来检测和防止堆栈溢出。例如,如果您的应用程序依赖于递归,请使用计数器或状态条件来终止递归循环。

I'm assuming the exception is happening within an internal .NET method, and not in your code.

我假设异常发生在内部 .NET 方法中,而不是在您的代码中。

You can do a couple things.

你可以做几件事。

  • Write code that checks the xsl for infinite recursion and notifies the user prior to applying a transform (Ugh).
  • Load the XslTransform code into a separate process (Hacky, but less work).
  • 编写代码来检查 xsl 是否存在无限递归并在应用转换之前通知用户(呃)。
  • 将 XslTransform 代码加载到单独的进程中(Hacky,但工作量较少)。

You can use the Process class to load the assembly that will apply the transform into a separate process, and alert the user of the failure if it dies, without killing your main app.

您可以使用 Process 类加载将应用转换到单独进程的程序集,并在它终止时提醒用户失败,而不会终止您的主应用程序。

EDIT: I just tested, here is how to do it:

编辑:我刚刚测试过,这是如何做到的:

MainProcess:

主要流程:

// This is just an example, obviously you'll want to pass args to this.
Process p1 = new Process();
p1.StartInfo.FileName = "ApplyTransform.exe";
p1.StartInfo.UseShellExecute = false;
p1.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

p1.Start();
p1.WaitForExit();

if (p1.ExitCode == 1)    
   Console.WriteLine("StackOverflow was thrown");

ApplyTransform Process:

应用转换过程:

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        throw new StackOverflowException();
    }

    // We trap this, we can't save the process, 
    // but we can prevent the "ILLEGAL OPERATION" window 
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        if (e.IsTerminating)
        {
            Environment.Exit(1);
        }
    }
}

回答by Dmitry Dzygin

I would suggest creating a wrapper around XmlWriter object, so it would count amount of calls to WriteStartElement/WriteEndElement, and if you limit amount of tags to some number (f.e. 100), you would be able to throw a different exception, for example - InvalidOperation.

我建议在 XmlWriter 对象周围创建一个包装器,这样它就会计算对 WriteStartElement/WriteEndElement 的调用量,如果你将标签数量限制为某个数字(fe 100),你将能够抛出一个不同的异常,例如 -无效操作。

That should solve the problem in the majority of the cases

在大多数情况下,这应该可以解决问题

public class LimitedDepthXmlWriter : XmlWriter
{
    private readonly XmlWriter _innerWriter;
    private readonly int _maxDepth;
    private int _depth;

    public LimitedDepthXmlWriter(XmlWriter innerWriter): this(innerWriter, 100)
    {
    }

    public LimitedDepthXmlWriter(XmlWriter innerWriter, int maxDepth)
    {
        _maxDepth = maxDepth;
        _innerWriter = innerWriter;
    }

    public override void Close()
    {
        _innerWriter.Close();
    }

    public override void Flush()
    {
        _innerWriter.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return _innerWriter.LookupPrefix(ns);
    }

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        _innerWriter.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        _innerWriter.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        _innerWriter.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        _innerWriter.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        _innerWriter.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        _innerWriter.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        _innerWriter.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        _innerWriter.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        _depth--;

        _innerWriter.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        _innerWriter.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        _innerWriter.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        _innerWriter.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(string data)
    {
        _innerWriter.WriteRaw(data);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        _innerWriter.WriteRaw(buffer, index, count);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        _innerWriter.WriteStartAttribute(prefix, localName, ns);
    }

    public override void WriteStartDocument(bool standalone)
    {
        _innerWriter.WriteStartDocument(standalone);
    }

    public override void WriteStartDocument()
    {
        _innerWriter.WriteStartDocument();
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        if (_depth++ > _maxDepth) ThrowException();

        _innerWriter.WriteStartElement(prefix, localName, ns);
    }

    public override WriteState WriteState
    {
        get { return _innerWriter.WriteState; }
    }

    public override void WriteString(string text)
    {
        _innerWriter.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        _innerWriter.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        _innerWriter.WriteWhitespace(ws);
    }

    private void ThrowException()
    {
        throw new InvalidOperationException(string.Format("Result xml has more than {0} nested tags. It is possible that xslt transformation contains an endless recursive call.", _maxDepth));
    }
}

回答by jdehaan

With .NET 4.0 You can add the HandleProcessCorruptedStateExceptionsattribute from System.Runtime.ExceptionServices to the method containing the try/catch block. This really worked! Maybe not recommended but works.

在 .NET 4.0 中,您可以将HandleProcessCorruptedStateExceptionsSystem.Runtime.ExceptionServices 中的属性添加到包含 try/catch 块的方法中。这真的奏效了!也许不推荐但有效。

using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.ExceptionServices;

namespace ExceptionCatching
{
    public class Test
    {
        public void StackOverflow()
        {
            StackOverflow();
        }

        public void CustomException()
        {
            throw new Exception();
        }

        public unsafe void AccessViolation()
        {
            byte b = *(byte*)(8762765876);
        }
    }

    class Program
    {
        [HandleProcessCorruptedStateExceptions]
        static void Main(string[] args)
        {
            Test test = new Test();
            try {
                //test.StackOverflow();
                test.AccessViolation();
                //test.CustomException();
            }
            catch
            {
                Console.WriteLine("Caught.");
            }

            Console.WriteLine("End of program");

        }

    }      
}

回答by Shrike

If you application depends on 3d-party code (in Xsl-scripts) then you have to decide first do you want to defend from bugs in them or not. If you really want to defend then I think you should execute your logic which prone to external errors in separate AppDomains. Catching StackOverflowException is not good.

如果您的应用程序依赖于 3d 方代码(在 Xsl 脚本中),那么您必须首先决定是否要防御它们中的错误。如果您真的想捍卫,那么我认为您应该在单独的 AppDomains 中执行容易出现外部错误的逻辑。捕获 StackOverflowException 不好。

Check also this question.

也检查这个问题

回答by sharp12345

You can read up this property every few calls, Environment.StackTrace, and if the stacktrace exceded a specific threshold that you preset, you can return the function.

您可以每隔几次调用读取一次该属性Environment.StackTrace,如果堆栈跟踪超过您预设的特定阈值,您可以返回该函数。

You should also try to replace some recursive functions with loops.

您还应该尝试用循环替换一些递归函数。

回答by Fixation

I had a stackoverflow today and i read some of your posts and decided to help out the Garbage Collecter.

我今天有一个 stackoverflow,我阅读了你的一些帖子,并决定帮助垃圾收集器。

I used to have a near infinite loop like this:

我曾经有一个像这样的近乎无限循环:

    class Foo
    {
        public Foo()
        {
            Go();
        }

        public void Go()
        {
            for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
            {
                byte[] b = new byte[1]; // Causes stackoverflow
            }
        }
    }

Instead let the resource run out of scope like this:

而是让资源像这样超出范围:

class Foo
{
    public Foo()
    {
        GoHelper();
    }

    public void GoHelper()
    {
        for (float i = float.MinValue; i < float.MaxValue; i+= 0.000000000000001f)
        {
            Go();
        }
    }

    public void Go()
    {
        byte[] b = new byte[1]; // Will get cleaned by GC
    }   // right now
}

It worked for me, hope it helps someone.

它对我有用,希望它可以帮助某人。

回答by atlaste

NOTEThe question in the bounty by @WilliamJockusch and the original question are different.

This answer is about StackOverflow's in the general case of third-party libraries and what you can/can't do with them. If you're looking about the special case with XslTransform, see the accepted answer.

注意@WilliamJockusch 悬赏中的问题与原始问题不同。

这个答案是关于 StackOverflow 在第三方库的一般情况下以及你可以/不能用它们做什么。如果您正在查看 XslTransform 的特殊情况,请参阅已接受的答案。



Stack overflows happen because the data on the stack exceeds a certain limit (in bytes). The details of how this detection works can be found here.

堆栈溢出发生是因为堆栈上的数据超过了某个限制(以字节为单位)。可以在此处找到有关此检测工作原理的详细信息。

I'm wondering if there is a general way to track down StackOverflowExceptions. In other words, suppose I have infinite recursion somewhere in my code, but I have no idea where. I want to track it down by some means that is easier than stepping through code all over the place until I see it happening. I don't care how hackish it is.

我想知道是否有一种通用的方法来追踪 StackOverflowExceptions。换句话说,假设我的代码中某处有无限递归,但我不知道在哪里。我想通过某种方式追踪它,这比在我看到它发生之前在整个地方单步执行代码更容易。我不在乎它有多黑。

As I mentioned in the link, detecting a stack overflow from static code analysis would require solving the halting problem which is undecidable. Now that we've established that there is no silver bullet, I can show you a few tricks that I think helps track down the problem.

正如我在链接中提到的,从静态代码分析中检测堆栈溢出需要解决无法确定的停机问题。既然我们已经确定没有灵丹妙药,我可以向您展示一些我认为有助于追踪问题的技巧。

I think this question can be interpreted in different ways, and since I'm a bit bored :-), I'll break it down into different variations.

我认为这个问题可以有不同的解释,因为我有点无聊:-),我会把它分解成不同的变体。

Detecting a stack overflow in a test environment

在测试环境中检测堆栈溢出

Basically the problem here is that you have a (limited) test environment and want to detect a stack overflow in an (expanded) production environment.

基本上,这里的问题是您有一个(有限的)测试环境,并且想要检测(扩展的)生产环境中的堆栈溢出。

Instead of detecting the SO itself, I solve this by exploiting the fact that the stack depth can be set. The debugger will give you all the information you need. Most languages allow you to specify the stack size or the max recursion depth.

我没有检测 SO 本身,而是通过利用可以设置堆栈深度的事实来解决这个问题。调试器将为您提供所需的所有信息。大多数语言允许您指定堆栈大小或最大递归深度。

Basically I try to force a SO by making the stack depth as small as possible. If it doesn't overflow, I can always make it bigger (=in this case: safer) for the production environment. The moment you get a stack overflow, you can manually decide if it's a 'valid' one or not.

基本上我试图通过使堆栈深度尽可能小来强制 SO。如果它没有溢出,我总是可以为生产环境使它更大(=在这种情况下:更安全)。堆栈溢出的那一刻,您可以手动确定它是否是“有效”的。

To do this, pass the stack size (in our case: a small value) to a Thread parameter, and see what happens. The default stack size in .NET is 1 MB, we're going to use a way smaller value:

为此,请将堆栈大小(在我们的示例中:一个小值)传递给 Thread 参数,然后看看会发生什么。.NET 中的默认堆栈大小为 1 MB,我们将使用更小的值:

class StackOverflowDetector
{
    static int Recur()
    {
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        int depth = 1 + Recur();
    }

    static void Main(string[] args)
    {
        Thread t = new Thread(Start, 1);
        t.Start();
        t.Join();
        Console.WriteLine();
        Console.ReadLine();
    }
}

Note: we're going to use this code below as well.

注意:我们也将在下面使用此代码。

Once it overflows, you can set it to a bigger value until you get a SO that makes sense.

一旦溢出,您可以将其设置为更大的值,直到获得有意义的 SO。

Creating exceptions before you SO

在您 SO 之前创建例外

The StackOverflowExceptionis not catchable. This means there's not much you can do when it has happened. So, if you believe something is bound to go wrong in your code, you can make your own exception in some cases. The only thing you need for this is the current stack depth; there's no need for a counter, you can use the real values from .NET:

StackOverflowException不开捕。这意味着当它发生时你无能为力。因此,如果您认为代码中一定会出错,您可以在某些情况下创建自己的异常。为此,您唯一需要的是当前堆栈深度;不需要计数器,您可以使用 .NET 中的真实值:

class StackOverflowDetector
{
    static void CheckStackDepth()
    {
        if (new StackTrace().FrameCount > 10) // some arbitrary limit
        {
            throw new StackOverflowException("Bad thread.");
        }
    }

    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
        Console.WriteLine();
        Console.ReadLine();
    }
}

Note that this approach also works if you are dealing with third-party components that use a callback mechanism. The only thing required is that you can intercept somecalls in the stack trace.

请注意,如果您正在处理使用回调机制的第三方组件,则此方法也适用。唯一需要的是您可以拦截堆栈跟踪中的一些调用。

Detection in a separate thread

在单独的线程中检测

You explicitly suggested this, so here goes this one.

你明确提出了这一点,所以这里是这个。

You can try detecting a SO in a separate thread.. but it probably won't do you any good. A stack overflow can happen fast, even before you get a context switch. This means that this mechanism isn't reliable at all... I wouldn't recommend actually using it. It was fun to build though, so here's the code :-)

您可以尝试在单独的线程中检测 SO.. 但它可能对您没有任何好处。堆栈溢出可能会很快发生,甚至在您获得上下文切换之前。这意味着这种机制根本不可靠......我不建议实际使用它。不过构建起来很有趣,所以这是代码:-)

class StackOverflowDetector
{
    static int Recur()
    {
        Thread.Sleep(1); // simulate that we're actually doing something :-)
        int variable = 1;
        return variable + Recur();
    }

    static void Start()
    {
        try
        {
            int depth = 1 + Recur();
        }
        catch (ThreadAbortException e)
        {
            Console.WriteLine("We've been a {0}", e.ExceptionState);
        }
    }

    static void Main(string[] args)
    {
        // Prepare the execution thread
        Thread t = new Thread(Start);
        t.Priority = ThreadPriority.Lowest;

        // Create the watch thread
        Thread watcher = new Thread(Watcher);
        watcher.Priority = ThreadPriority.Highest;
        watcher.Start(t);

        // Start the execution thread
        t.Start();
        t.Join();

        watcher.Abort();
        Console.WriteLine();
        Console.ReadLine();
    }

    private static void Watcher(object o)
    {
        Thread towatch = (Thread)o;

        while (true)
        {
            if (towatch.ThreadState == System.Threading.ThreadState.Running)
            {
                towatch.Suspend();
                var frames = new System.Diagnostics.StackTrace(towatch, false);
                if (frames.FrameCount > 20)
                {
                    towatch.Resume();
                    towatch.Abort("Bad bad thread!");
                }
                else
                {
                    towatch.Resume();
                }
            }
        }
    }
}

Run this in the debugger and have fun of what happens.

在调试器中运行它并享受发生的事情的乐趣。

Using the characteristics of a stack overflow

使用堆栈溢出的特性

Another interpretation of your question is: "Where are the pieces of code that could potentially cause a stack overflow exception?". Obviously the answer of this is: all code with recursion. For each piece of code, you can then do some manual analysis.

您的问题的另一种解释是:“可能导致堆栈溢出异常的代码片段在哪里?”。显然,答案是:所有代码都带有递归。对于每一段代码,您可以进行一些手动分析。

It's also possible to determine this using static code analysis. What you need to do for that is to decompile all methods and figure out if they contain an infinite recursion. Here's some code that does that for you:

也可以使用静态代码分析来确定这一点。为此,您需要做的是反编译所有方法并确定它们是否包含无限递归。这里有一些代码可以为您做到这一点:

// A simple decompiler that extracts all method tokens (that is: call, callvirt, newobj in IL)
internal class Decompiler
{
    private Decompiler() { }

    static Decompiler()
    {
        singleByteOpcodes = new OpCode[0x100];
        multiByteOpcodes = new OpCode[0x100];
        FieldInfo[] infoArray1 = typeof(OpCodes).GetFields();
        for (int num1 = 0; num1 < infoArray1.Length; num1++)
        {
            FieldInfo info1 = infoArray1[num1];
            if (info1.FieldType == typeof(OpCode))
            {
                OpCode code1 = (OpCode)info1.GetValue(null);
                ushort num2 = (ushort)code1.Value;
                if (num2 < 0x100)
                {
                    singleByteOpcodes[(int)num2] = code1;
                }
                else
                {
                    if ((num2 & 0xff00) != 0xfe00)
                    {
                        throw new Exception("Invalid opcode: " + num2.ToString());
                    }
                    multiByteOpcodes[num2 & 0xff] = code1;
                }
            }
        }
    }

    private static OpCode[] singleByteOpcodes;
    private static OpCode[] multiByteOpcodes;

    public static MethodBase[] Decompile(MethodBase mi, byte[] ildata)
    {
        HashSet<MethodBase> result = new HashSet<MethodBase>();

        Module module = mi.Module;

        int position = 0;
        while (position < ildata.Length)
        {
            OpCode code = OpCodes.Nop;

            ushort b = ildata[position++];
            if (b != 0xfe)
            {
                code = singleByteOpcodes[b];
            }
            else
            {
                b = ildata[position++];
                code = multiByteOpcodes[b];
                b |= (ushort)(0xfe00);
            }

            switch (code.OperandType)
            {
                case OperandType.InlineNone:
                    break;
                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    position += 1;
                    break;
                case OperandType.InlineVar:
                    position += 2;
                    break;
                case OperandType.InlineBrTarget:
                case OperandType.InlineField:
                case OperandType.InlineI:
                case OperandType.InlineSig:
                case OperandType.InlineString:
                case OperandType.InlineTok:
                case OperandType.InlineType:
                case OperandType.ShortInlineR:
                    position += 4;
                    break;
                case OperandType.InlineR:
                case OperandType.InlineI8:
                    position += 8;
                    break;
                case OperandType.InlineSwitch:
                    int count = BitConverter.ToInt32(ildata, position);
                    position += count * 4 + 4;
                    break;

                case OperandType.InlineMethod:
                    int methodId = BitConverter.ToInt32(ildata, position);
                    position += 4;
                    try
                    {
                        if (mi is ConstructorInfo)
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), Type.EmptyTypes));
                        }
                        else
                        {
                            result.Add((MethodBase)module.ResolveMember(methodId, mi.DeclaringType.GetGenericArguments(), mi.GetGenericArguments()));
                        }
                    }
                    catch { } 
                    break;


                default:
                    throw new Exception("Unknown instruction operand; cannot continue. Operand type: " + code.OperandType);
            }
        }
        return result.ToArray();
    }
}

class StackOverflowDetector
{
    // This method will be found:
    static int Recur()
    {
        CheckStackDepth();
        int variable = 1;
        return variable + Recur();
    }

    static void Main(string[] args)
    {
        RecursionDetector();
        Console.WriteLine();
        Console.ReadLine();
    }

    static void RecursionDetector()
    {
        // First decompile all methods in the assembly:
        Dictionary<MethodBase, MethodBase[]> calling = new Dictionary<MethodBase, MethodBase[]>();
        var assembly = typeof(StackOverflowDetector).Assembly;

        foreach (var type in assembly.GetTypes())
        {
            foreach (var member in type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance).OfType<MethodBase>())
            {
                var body = member.GetMethodBody();
                if (body!=null)
                {
                    var bytes = body.GetILAsByteArray();
                    if (bytes != null)
                    {
                        // Store all the calls of this method:
                        var calls = Decompiler.Decompile(member, bytes);
                        calling[member] = calls;
                    }
                }
            }
        }

        // Check every method:
        foreach (var method in calling.Keys)
        {
            // If method A -> ... -> method A, we have a possible infinite recursion
            CheckRecursion(method, calling, new HashSet<MethodBase>());
        }
    }

Now, the fact that a method cycle contains recursion, is by no means a guarantee that a stack overflow will happen - it's just the most likely precondition for your stack overflow exception. In short, this means that this code will determine the pieces of code where a stack overflow canoccur, which should narrow down most code considerably.

现在,方法循环包含递归的事实绝不是保证会发生堆栈溢出 - 这只是堆栈溢出异常最有可能的先决条件。简而言之,这意味着此代码将确定可能发生堆栈溢出的代码段,这将大大缩小大多数代码的范围。

Yet other approaches

还有其他方法

There are some other approaches you can try that I haven't described here.

您可以尝试其他一些方法,但我没有在此处描述。

  1. Handling the stack overflow by hosting the CLR process and handling it. Note that you still cannot 'catch' it.
  2. Changing all IL code, building another DLL, adding checks on recursion. Yes, that's quite possible (I've implemented it in the past :-); it's just difficult and involves a lot of code to get it right.
  3. Use the .NET profiling API to capture all method calls and use that to figure out stack overflows. For example, you can implement checks that if you encounter the same method X times in your call tree, you give a signal. There's a project herethat will give you a head start.
  1. 通过托管 CLR 进程并处理它来处理堆栈溢出。请注意,您仍然无法“抓住”它。
  2. 更改所有 IL 代码,构建另一个 DLL,添加递归检查。是的,这很有可能(我过去已经实现了:-);这很困难,并且需要大量代码才能使其正确。
  3. 使用 .NET 分析 API 来捕获所有方法调用并使用它来找出堆栈溢出。例如,您可以实施检查,如果您在调用树中遇到相同的方法 X 次,您就发出一个信号。有一个项目在这里会给你一个良好的开端。

回答by Jeremy Thompson

This answer is for @WilliamJockusch.

这个答案适用于@WilliamJockusch。

I'm wondering if there is a general way to track down StackOverflowExceptions. In other words, suppose I have infinite recursion somewhere in my code, but I have no idea where. I want to track it down by some means that is easier than stepping through code all over the place until I see it happening. I don't care how hackish it is. For example, It would be great to have a module I could activate, perhaps even from another thread, that polled the stack depth and complained if it got to a level I considered "too high." For example, I might set "too high" to 600 frames, figuring that if the stack were too deep, that has to be a problem. Is something like that possible. Another example would be to log every 1000th method call within my code to the debug output. The chances this would get some evidence of the overlow would be pretty good, and it likely would not blow up the output too badly. The key is that it cannot involve writing a check wherever the overflow is happening. Because the entire problem is that I don't know where that is. Preferrably the solution should not depend on what my development environment looks like; i.e, it should not assumet that I am using C# via a specific toolset (e.g. VS).

我想知道是否有一种通用的方法来追踪 StackOverflowExceptions。换句话说,假设我的代码中某处有无限递归,但我不知道在哪里。我想通过某种方式追踪它,这比在我看到它发生之前在整个地方单步执行代码更容易。我不在乎它有多黑。例如,如果有一个我可以激活的模块,甚至可能来自另一个线程,它会轮询堆栈深度并抱怨它是否达到我认为“太高”的水平,那就太好了。例如,我可能将“太高”设置为 600 帧,认为如果堆栈太深,那一定是一个问题。这样的事情有可能吗。另一个示例是将我的代码中的每 1000 次方法调用记录到调试输出中。这将获得一些证据表明过低的机会非常好,而且它可能不会太严重地破坏输出。关键是它不能涉及在发生溢出的任何地方写检查。因为整个问题是我不知道那在哪里。最好的解决方案不应该依赖于我的开发环境是什么样的;即,它不应假定我通过特定工具集(例如 VS)使用 C#。

It sounds like you're keen to hear some debugging techniques to catch this StackOverflow so I thought I would share a couple for you to try.

听起来您很想听到一些调试技术来捕捉这个 StackOverflow,所以我想我会分享一些供您尝试。

1. Memory Dumps.

1. 内存转储。

Pro's: Memory Dumps are a sure fire way to work out the cause of a Stack Overflow. A C# MVP & I worked together troubleshooting a SO and he went on to blog about it here.

专业人士:内存转储是确定堆栈溢出原因的可靠方法。AC# MVP 和我一起对 SO 进行故障排除,他继续在此处发表有关它的博客。

This method is the fastest way to track down the problem.

此方法是查找问题的最快方法。

This method wont require you to reproduce problems by following steps seen in logs.

此方法不需要您按照日志中看到的步骤重现问题。

Con's: Memory Dumps are very large and you have to attach AdPlus/procdump the process.

反对的:内存转储是非常大的,你必须附加ADPlus的/ procdump的过程。

2. Aspect Orientated Programming.

2. 面向方面的编程。

Pro's: This is probably the easiest way for you to implement code that checks the size of the call stack from any method without writing code in every method of your application. There are a bunch of AOP Frameworksthat allow you to Intercept before and after calls.

专业人士:这可能是您实现代码的最简单方法,该代码可以检查任何方法的调用堆栈大小,而无需在应用程序的每个方法中编写代码。有很多AOP 框架允许您在调用前后进行拦截。

Will tell you the methods that are causing the Stack Overflow.

会告诉你导致堆栈溢出的方法。

Allows you to check the StackTrace().FrameCountat the entry and exit of all methods in your application.

允许您检查StackTrace().FrameCount应用程序中所有方法的入口和出口。

Con's: It will have a performance impact - the hooks are embedded into the IL for every method and you cant really "de-activate" it out.

缺点:它会对性能产生影响——每个方法的钩子都嵌入到 IL 中,你不能真正“停用”它。

It somewhat depends on your development environment tool set.

这在某种程度上取决于您的开发环境工具集。

3. Logging User Activity.

3. 记录用户活动。

A week ago I was trying to hunt down several hard to reproduce problems. I posted this QA User Activity Logging, Telemetry (and Variables in Global Exception Handlers). The conclusion I came to was a really simple user-actions-logger to see how to reproduce problems in a debugger when any unhandled exception occurs.

一周前,我试图寻找几个难以重现的问题。我发布了这篇 QA User Activity Logging, Telemetry (and Variables in Global Exception Handlers)。我得出的结论是一个非常简单的用户操作记录器,用于查看在发生任何未处理的异常时如何在调试器中重现问题。

Pro's: You can turn it on or off at will (ie subscribing to events).

优点:您可以随意打开或关闭它(即订阅事件)。

Tracking the user actions doesn't require intercepting every method.

跟踪用户操作不需要拦截每个方法。

You can count the number of events methods are subscribed too far more simply than with AOP.

与使用 AOP 相比,您可以更简单地计算订阅方法的事件数量。

The log files are relatively small and focus on what actions you need to perform to reproduce the problem.

日志文件相对较小,侧重于您需要执行哪些操作来重现问题。

It can help you to understand how users are using your application.

它可以帮助您了解用户如何使用您的应用程序。

Con's: Isn't suited to a Windows Service and I'm sure there are better tools like this for web apps.

缺点:不适合 Windows 服务,我相信有更好的工具可以用于 Web 应用程序

Doesn't necessarilytell you the methods that cause the Stack Overflow.

并不一定告诉你,导致堆栈溢出的方法。

Requires you to step through logs manually reproducing problems rather than a Memory Dump where you can get it and debug it straight away.

需要您手动遍历日志来重现问题,而不是通过内存转储来获取并立即调试。

 

 



Maybe you might try all techniques I mention above and some that @atlaste posted and tell us which one's you found were the easiest/quickest/dirtiest/most acceptable to run in a PROD environment/etc.

也许您可能会尝试我上面提到的所有技术以及@atlaste 发布的一些技术,并告诉我们您发现哪一个是在 PROD 环境中运行的最简单/最快/最脏/最可接受的技术/等等。

Anyway good luck tracking down this SO.

无论如何,祝你好运追踪这个 SO。

回答by Nick Mertin

By the looks of it, apart from starting another process, there doesn't seem to be any way of handling a StackOverflowException. Before anyone else asks, I tried using AppDomain, but that didn't work:

从它的外观来看,除了启动另一个进程外,似乎没有任何方法可以处理StackOverflowException. 在其他人问之前,我尝试使用AppDomain,但这没有用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

namespace StackOverflowExceptionAppDomainTest
{
    class Program
    {
        static void recrusiveAlgorithm()
        {
            recrusiveAlgorithm();
        }
        static void Main(string[] args)
        {
            if(args.Length>0&&args[0]=="--child")
            {
                recrusiveAlgorithm();
            }
            else
            {
                var domain = AppDomain.CreateDomain("Child domain to test StackOverflowException in.");
                domain.ExecuteAssembly(Assembly.GetEntryAssembly().CodeBase, new[] { "--child" });
                domain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) =>
                {
                    Console.WriteLine("Detected unhandled exception: " + e.ExceptionObject.ToString());
                };
                while (true)
                {
                    Console.WriteLine("*");
                    Thread.Sleep(1000);
                }
            }
        }
    }
}

If you do end up using the separate-process solution, however, I would recommend using Process.Exitedand Process.StandardOutputand handle the errors yourself, to give your users a better experience.

如果你最终使用单独的线程解决方案,但是,我会建议使用Process.Exited,并Process.StandardOutput和自己处理这些错误,给用户更好的体验。

回答by Gentian Kasa

@WilliamJockusch, if I understood correctly your concern, it's not possible (from a mathematical point of view) to alwaysidentify an infinite recursion as it would mean to solve the Halting problem. To solve it you'd need a Super-recursive algorithm(like Trial-and-error predicatesfor example) or a machine that can hypercompute(an example is explained in the following section- available as preview - of this book).

@WilliamJockusch,如果我正确理解了您的担忧,则不可能(从数学的角度来看)总是确定无限递归,因为这意味着解决停机问题。为了解决这个问题,你需要一个超级递归算法(例如试错谓词)或一台可以进行超计算的机器(一个例子在本书的下一节中解释- 作为预览提供)。

From a practical point of view, you'd have to know:

从实际的角度来看,你必须知道:

  • How much stack memory you have left at the given time
  • How much stack memory your recursive method will need at the given time for the specific output.
  • 您在给定时间还剩下多少堆栈内存
  • 您的递归方法在给定时间为特定输出需要多少堆栈内存。

Keep in mind that, with the current machines, this data is extremely mutable due to multitasking and I haven't heard of a software that does the task.

请记住,对于当前的机器,由于多任务处理,这些数据非常易变,而且我还没有听说过可以完成这项任务的软件。

Let me know if something is unclear.

如果有不清楚的地方,请告诉我。