如何在没有托管调试器的情况下检测.NET中的无限循环

时间:2014-04-14 06:55:35

标签: .net debugging infinite-loop

我们的客户在使用.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
 ....
        }

4 个答案:

答案 0 :(得分:4)

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

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中的其他线程   尝试使用该类被阻止。

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

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

感谢您的提示。该应用程序拥有自己的日志记录系统,涵盖了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();
        }
    }
}

答案 3 :(得分:0)

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

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