【问题标题】:How do I prevent and/or handle a StackOverflowException?如何防止和/或处理 StackOverflowException?
【发布时间】:2010-09-17 10:26:30
【问题描述】:

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

有没有办法检测和/或限制允许的递归次数?或者有什么其他想法可以防止这段代码对我产生影响?

【问题讨论】:

  • @William Jockusch 您可以采取的一种方法是编写/修改分析器以在收到调用通知时监控堆栈的大小。 CLRProfiler 的ProfilerCallback::_LogCallTrace 可能是一个不错的起点,但它似乎不是一件容易的事。通过 David Browman 的博客链接到分析器源下载:blogs.msdn.com/b/davbr/archive/2011/02/01/…
  • 赏金是有问题的,至少可以这么说。请参阅meta.stackoverflow.com/questions/296486/… 进行讨论。

标签: c# .net stack-overflow xslcompiledtransform


【解决方案1】:

来自微软:

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

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

你可以做几件事。

  • 编写代码检查 xsl 的无限递归,并在应用转换之前通知用户 (Ugh)。
  • 将 XslTransform 代码加载到单独的进程中(Hacky,但工作量较少)。

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

编辑:我刚刚测试过,这是怎么做的:

主进程:

// 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 过程:

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

【讨论】:

  • 捕获代码抛出的 StackOverflowException 和运行时抛出的异常之间存在细微差别。处理你抛出的堆栈溢出是完全可以的。处理运行时版本是非常不同的。
  • 我最初没有明白你的意思,但是在我向我的测试代码添加递归循环之后,我明白你的意思了。在这种情况下,您无法捕获 UnhandledException ......但是,将其拆分为单独的进程将防止应用程序死机,从而给用户带来轻微的不便。
【解决方案2】:

我建议围绕 XmlWriter 对象创建一个包装器,以便计算对 WriteStartElement/WriteEndElement 的调用次数,如果您将标签数量限制为某个数字(fe 100),您将能够抛出不同的异常,例如 - InvalidOperation。

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

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

【讨论】:

    【解决方案3】:

    在 .NET 4.0 中,您可以将 System.Runtime.ExceptionServices 中的 HandleProcessCorruptedStateExceptions 属性添加到包含 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");
    
            }
    
        }      
    }
    

    【讨论】:

    • HandleProcessCorruptedStateExceptions 不允许您处理 StackOverflowException。
    【解决方案4】:

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

    还要检查这个question

    【讨论】:

    • 在新的 AppDomain 中运行代码不会阻止整个进程被 StackOverflowException 删除。从 v2 及更高版本开始,在 .NET 中无法捕获此类异常。
    • 在控制 .NET 运行时托管时,可以将 StackOverflowException 的处理更改为仅卸载 AppDomain 而不会停止进程(这是默认情况下发生的)。
    【解决方案5】:

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

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

    【讨论】:

    • 他如何在 XSLT 中做到这一点?引发此异常的是 XslCompiledTransform。
    【解决方案6】:

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

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

        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
                }
            }
        }
    

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

    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
    }
    

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

    【讨论】:

    • 对于编写需要大量堆栈空间(字节数组在堆栈上分配)的方法而言,这是一个很好的解决方案,每次调用后都会清除堆栈。但是,不是垃圾收集器,而是堆栈展开(在退出 Go 方法后)导致堆栈内存再次可用。 GC 只适用于堆内存。
    • 出于多种原因,我决定对这个答案投反对票。首先,它不会导致堆栈溢出。您在堆上分配一个字节数组,它只是简单地收集。其次,在第一个代码的情况下,编译器足够聪明,可以确定您没有使用b,并且它没有任何副作用需要删除。我很确定第二个代码将被内联程序拾取,因为它是如此微不足道的主体,从而产生完全相同的代码。第三,第一个赋值的范围是循环的 '{'..'}' 内容。真的,代码唯一要做的就是永远循环。
    • 有点不相关,但无限循环的良好编程习惯是使用while(true){},而不是一些基于float 的模糊for 循环实现。
    【解决方案7】:

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

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


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

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

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

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

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

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

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

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

    为此,将堆栈大小(在我们的例子中:一个小值)传递给 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();
        }
    }
    

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

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

    在您这样做之前创建例外

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

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

    在单独的线程中检测

    你明确建议了这个,所以这里是这个。

    您可以尝试在单独的线程中检测 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();
                    }
                }
            }
        }
    }
    

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

    利用堆栈溢出的特性

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

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

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

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

    还有其他方法

    您可以尝试其他一些我没有在这里描述的方法。

    1. 通过托管 CLR 进程并对其进行处理来处理堆栈溢出。请注意,您仍然无法“抓住”它。
    2. 更改所有 IL 代码,构建另一个 DLL,添加递归检查。是的,这很有可能(我过去已经实现了:-);这很困难,并且需要大量代码才能正确完成。
    3. 使用 .NET 分析 API 捕获所有方法调用并使用它来确定堆栈溢出。例如,您可以实施检查,如果您在调用树中遇到 X 次相同的方法,您会发出信号。有一个项目clrprofiler 可以让您抢占先机。

    【讨论】:

      【解决方案8】:

      此答案适用于@WilliamJockusch。

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

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

      1。内存转储。

      专业人士:内存转储是找出堆栈溢出原因的可靠方法。一个 C# MVP 和我一起解决了一个 SO,他继续写博客 here

      这种方法是追查问题的最快方法。

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

      缺点:内存转储非常大,你必须附加 AdPlus/procdump 进程。

      2。面向方面的编程。

      专业人士:这可能是您实现代码的最简单方法,该代码可以从任何方法检查调用堆栈的大小,而无需在应用程序的每个方法中编写代码。有一堆AOP Frameworks可以让你在通话前后拦截。

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

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

      缺点:它会对性能产生影响 - 每个方法的钩子都嵌入到 IL 中,您无法真正“取消激活”它。

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

      3。记录用户活动。

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

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

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

      您也可以计算方法订阅的事件数量比使用 AOP 简单得多

      日志文件相对较小,主要关注重现问题所需执行的操作。

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

      缺点:不适合 Windows 服务而且我确信对于网络应用程序有更好的类似工具

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

      要求您手动逐步检查日志以重现问题,而不是通过内存转储,您可以从中获取并立即进行调试。

       


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

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

      【讨论】:

      • 在我的情况下,似乎最简单的事情就是逐步完成,直到找到它,这就是我所做的。一点都不令人满意,但确实如此。无论如何,这很容易成为回答我问题的最佳尝试;因此赏金。
      【解决方案9】:

      看起来,除了启动另一个进程之外,似乎没有任何方法可以处理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);
                      }
                  }
              }
          }
      }
      

      但是,如果您最终使用了分离进程解决方案,我建议您使用 Process.ExitedProcess.StandardOutput 并自行处理错误,以便为您的用户提供更好的体验。

      【讨论】:

        【解决方案10】:

        @WilliamJockusch,如果我正确理解了您的担忧,则不可能(从数学的角度)总是识别无限递归,因为这意味着解决Halting problem。要解决它,您需要Super-recursive algorithm(例如Trial-and-error predicates)或可以hypercompute 的机器(在following section 中解释了一个示例-可作为预览-this book)。

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

        • 在给定时间您还剩下多少堆栈内存
        • 您的递归方法在给定时间需要多少堆栈内存用于特定输出。

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

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

        【讨论】:

        • 我明白这一点。但实际上,如果堆栈在 n 帧处溢出,则应该有可能在堆栈命中(例如)n-10 帧时中断。
        • 是的,当然。拥有有限的内存,您可以做到这一点。一旦你有了内存数据(知道剩下多少内存/帧就足够了),你可以根据情况暂停或继续。至于实现(显然)还有许多其他细节需要考虑,但可以知道堆栈何时快满。
        • 作为一个实际问题,您也可以近似决定,以防您无法访问堆栈但只能访问代码,但这是另一回事。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-06-07
        • 2011-12-19
        • 2015-05-21
        • 2011-04-02
        • 1970-01-01
        • 2020-11-02
        相关资源
        最近更新 更多