【问题标题】:How to detect infinite loop in .NET without managed debugger如何在没有托管调试器的情况下检测 .NET 中的无限循环
【发布时间】:2014-05-28 00:57:35
【问题描述】:

我们的一位客户在使用 .NET 编写的应用程序时遇到问题。它在进程开始后消耗整个 CPU,没有任何窗口可显示。它看起来像一个无限循环。

问题是,这个客户使用的是 Windows 7 Home Edition,这就是为什么我无法通过远程托管调试器连接到应用程序。

我可以创建带有一些调试模式的特殊版本。我认为我可以在进程的请求中运行一个线程,大约一分钟后调用 Debugger.Break 并以某种方式记录线程和堆栈帧。我不确定是否可以在所有线程上记录堆栈帧。

你有过这种不带调试器的调试经验吗?

更新

经过两天的努力,我终于找到了问题所在。这是坏的 ATI 显卡驱动程序,导致了无限循环。因为有些组件使用 OpenGL。通过迭代当前进程的线程,将它们挂起并写入堆栈跟踪,很容易发现问题。我最终用来发现错误的代码如下:

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;


    public class DebuggerContext
    {
        public int TimeIntervalMs { get; set; }
        public Thread InfiniteLoopThread { get; set; }
    }

    public static class InfiniteLogger
    {
        public static void LogInfniteLoopAfterTimeInterval(int timeIntervalMs)
        {
            Thread watchdog = new Thread(Watchdog);

            DebuggerContext context = new DebuggerContext()
            {
                InfiniteLoopThread = Thread.CurrentThread,
                TimeIntervalMs = timeIntervalMs,
            };

            watchdog.Start(context);
        }

        static void Watchdog(object threadContext)
        {
            DebuggerContext context = (DebuggerContext)threadContext;
            Thread.Sleep(context.TimeIntervalMs);

            LogThread(context.InfiniteLoopThread);

            var threads = Process.GetCurrentProcess().Threads;

            foreach (var thread in threads)
            {
                if (thread == Thread.CurrentThread)
                    continue;

                LogThread(context.InfiniteLoopThread);
            }
        }

        private static void LogThread(Thread thread)
        {
            thread.Suspend();           

            StackTrace stackTrace = new StackTrace(thread, true);

            string log = "The infinite loop is in: " + StackTraceToString(stackTrace);

            Trace.WriteLine(log);

            thread.Resume();
        }

        /// <summary>
        /// http://stackoverflow.com/questions/51768/print-stack-trace-information-from-c-sharp
        /// </summary>
        /// <returns></returns>
        static string StackTraceToString(StackTrace stackTrace)
        {
            StringBuilder sb = new StringBuilder(256);
            var frames = stackTrace.GetFrames();
            for (int i = 0; i < frames.Length; i++) /* Ignore current StackTraceToString method...*/
            {
                var currFrame = frames[i];
                var method = currFrame.GetMethod();
                var line = currFrame.GetFileLineNumber();
                var file = currFrame.GetFileName();
                var column = currFrame.GetFileColumnNumber();

                sb.AppendLine(string.Format("{0}:[{1},{2}], {3}.{4}",
                    file,
                    line,
                    column,
                    method.ReflectedType != null ? method.ReflectedType.Name : string.Empty,
                    method.Name
                    ));
            }
            return sb.ToString();
        }
    }

 [STAThread]
        static void Main(string[] args)
        {
            InfiniteLogger.LogInfniteLoopAfterTimeInterval(30 * 1000); //log thread state after 30 seconds after start
 ....
        }

【问题讨论】:

  • 您检查应用程序日志了吗?也许根本不用调试就可以解决问题。
  • 使用 mdbg 或 ProcessExplorer。
  • 这个工具很棒。不幸的是,当我选择所需的进程时,它在客户的计算机上崩溃了。
  • 是的,我检查了日志。没有什么有趣的...

标签: .net debugging infinite-loop


【解决方案1】:

如果您有所有正在运行的线程的列表,则可以打印堆栈帧,但不推荐

StackTrace 有一个过时 构造函数 -- StackTrace Constructor (Thread, Boolean)

所以你可以这样做:

foreach (var thread in list)
{
    thread.Suspend();
    Console.WriteLine(thread.ManagedThreadId);
    System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(thread, true);
    Console.WriteLine(trace);
}

请阅读 MSDN 的备注部分,因为当你挂起一个线程时,你可能会导致死锁...

重要提示

不要使用这个构造函数。已经过时了, 并且没有推荐的替代方案。当你挂起一个线程时, 你无法知道它正在执行什么代码,并且死锁 很容易发生。例如,如果您暂停一个线程,而它 在安全权限评估期间持有锁,其他线程在 AppDomain 可能被阻止。如果您暂停一个线程,而它是 执行类构造函数,AppDomain 中的其他线程 尝试使用该类被阻止。

我同意其他人的观点,您最好采用日志方式,而不是尝试暂停和打印堆栈..

【讨论】:

【解决方案2】:

在我看来,最好开始构建一个日志结构,在 Windows 日志(或者可能是一个文件)中写入您的程序在某个时刻正在执行的指令。 您可以将此作为选项,以便您可以制作生产可执行文件并在您的每个客户端上使用这种类型的日志记录。

无论如何,如果您有兴趣记录所有线程的堆栈跟踪,这个答案可能对您有用Print thread stack of all threads of a process

【讨论】:

    【解决方案3】:

    感谢您的提示。该应用程序有自己的日志记录系统,涵盖了 99% 的情况,但您无法记录所有内容。最后我设法做了一个简单的应用程序来证明无限循环日志记录确实有效:

    using System;
    using System.Diagnostics;
    using System.Text;
    using System.Threading;
    
    namespace InfiniteLoopDebug
    {
        public class DebuggerContext
        {
            public int TimeIntervalMs { get; set; }
            public Thread InfiniteLoopThread { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                LogInfniteLoopAfterTimeInterval(1200);
    
                WorkerMethod();
            }
    
            static void WorkerMethod()
            {
    
                int a = 0;
                int b = 1;
                while (true) //infinite loop
                {
                    int c = a + b;
                    b = a;            
                }
            }
    
            static void LogInfniteLoopAfterTimeInterval(int timeIntervalMs)
            {
                Thread watchdog = new Thread(Watchdog);
    
                DebuggerContext context = new DebuggerContext()
                {
                    InfiniteLoopThread = Thread.CurrentThread,
                    TimeIntervalMs = timeIntervalMs,
                };
    
                watchdog.Start(context);
            }
    
            static void Watchdog(object threadContext)
            {
                DebuggerContext context = (DebuggerContext)threadContext;
                Thread.Sleep(context.TimeIntervalMs);
    
                context.InfiniteLoopThread.Suspend();
    
                //Thread.Sleep(100);
    
                StackTrace stackTrace = new StackTrace(context.InfiniteLoopThread, true);
    
                string log = "The infinite loop is in: " +StackTraceToString(stackTrace);
    
                Trace.WriteLine( log);
                Console.WriteLine(log);
    
                context.InfiniteLoopThread.Resume();
            }
    
            /// <summary>
            /// http://stackoverflow.com/questions/51768/print-stack-trace-information-from-c-sharp
            /// </summary>
            /// <returns></returns>
            static public string StackTraceToString(StackTrace stackTrace)
            {
                StringBuilder sb = new StringBuilder(256);
                var frames = stackTrace.GetFrames();
                for (int i = 0; i < frames.Length; i++) /* Ignore current StackTraceToString method...*/
                {
                    var currFrame = frames[i];
                    var method = currFrame.GetMethod();
                    var line = currFrame.GetFileLineNumber();
                    var file = currFrame.GetFileName();
                    var column = currFrame.GetFileColumnNumber();
    
                    sb.AppendLine(string.Format("{0}:[{1},{2}], {3}.{4}",
                        file,
                        line,
                        column,
                        method.ReflectedType != null ? method.ReflectedType.Name : string.Empty,
                        method.Name
                        ));
                }
                return sb.ToString();
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      虽然这样做可能是可行的,但我认为更好的方法是将日志记录添加到应用程序中。始终将日志记录语句添加到您的应用程序中,并在需要时以可配置的方式启用日志记录。

      使用现有的日志记录框架(例如 Log4Net)- 他们可以选择在不同的步骤中启用日志记录。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-04
        • 2013-08-22
        • 1970-01-01
        • 2016-09-11
        • 2017-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多