实时调试堆栈溢出

时间:2009-04-20 18:52:27

标签: c# crash windbg stack-overflow sos

我有一个托管代码Windows服务应用程序,由于托管的StackOverFlowException,它偶尔会在生产中崩溃。我知道这是因为我在崩溃模式下运行了adplus并使用SoS分析了故障转储。我甚至连接了windbg调试器并将其设置为“未处理的异常”。

我的问题是,我看不到任何托管堆栈或切换到任何线程。在调试器中断时,它们都被拆除了。

我不是Windbg专家,而且,除了在实时系统上安装Visual Studio或使用该工具进行远程调试和调试外,是否有人建议如何从违规行为中获取堆栈跟踪线程?

这就是我正在做的事情。

  

!线程

     

...

     

XXXX 11 27c 000000001b2175f0 b220已禁用00000000072c9058:00000000072cad80 0000000019bdd3f0 0 Ukn System.StackOverflowException(0000000000c010d0)

     

...

此时你会看到XXXX ID表明线程已经死了。

6 个答案:

答案 0 :(得分:8)

一旦你遇到堆栈溢出,你就很难调试问题 - 吹掉你的堆栈空间会使你的程序处于非确定状态,所以你不能依赖任何其中的信息 - 您尝试获取的任何堆栈跟踪可能已损坏,并且可能轻易指向错误的方向。即,一旦发生StackOverflowException,就太晚了。

此外,根据the documentation,您无法从.Net 2.0开始捕获StackOverflowException,因此使用try / catch围绕代码的其他建议可能无效。考虑到堆栈溢出的副作用(这让我很惊讶.Net曾经允许你抓住它),这非常有意义。

你唯一真正的选择是参与分析代码,寻找可能导致堆栈溢出的任何东西,并添加某种标记,以便你可以在之前了解它们 / em>他们发生了。例如,显然任何递归方法都是第一个开始的,所以给它们一个深度计数器和抛出你自己的异常如果它们达到你定义的某个“不合理”的值,那么你实际上可以获得有效的堆栈跟踪。

答案 1 :(得分:0)

使用try-catch写入EventLog(或文件,或其他)并用一次性运行此调试的代码来包装代码是一种选择吗?

try { ... } catch(SOE) { EventLog.Write(...); throw; }

您将无法进行调试,但您将获得堆栈跟踪。

答案 2 :(得分:0)

您有一个选择是在高级别使用try / catch块,然后打印或记录异常提供的堆栈跟踪。每个异常都有一个StackTrace属性,可以告诉您它被抛出的位置。这不会让你做任何交互式调试,但它应该给你一个开始的地方。

答案 3 :(得分:0)

从.NET 4.0开始,Visual Studio(以及任何依赖ICorDebug api的调试器)获得了调试minidump的能力。这意味着您可以将故障转储加载到另一台计算机上的VS调试器中,并查看托管堆栈,如果您在崩溃时附加了调试器。有关详细信息,请参阅PDC talkRick Byers' blog。不幸的是,这对你手头的问题没有帮助,但也许下次你会遇到这个问题。

答案 4 :(得分:0)

查看ADPLUS崩溃模式调试日志。 在抛出托管的StackOverflowException之前,查看是否存在任何访问冲突或真正的本机堆栈溢出异常。

我的猜测是线程堆栈上有一个异常,你会在线程退出之前进行冷捕获。

您还可以使用www.iis.net中的DebugDiag,然后设置崩溃规则并为Access Violations(sxe av)和Stack Overflow本机异常(sxe sov)创建完整转储文件

谢谢, 亚伦

答案 5 :(得分:0)

我有一个RecursionChecker类用于此类事情。我特此声明对以下代码的版权不予承认。

如果它发现自己经常检查目标对象,它就会抱怨。这不是万能的;例如,循环可能导致误报。可以通过在危险代码之后进行另一次调用来避免这种情况,告诉检查器它可以减少对目标对象的递归调用。它仍然不会是防弹的。

要使用它,我只需要打电话

public void DangerousMethod() {
  RecursionChecker.Check(someTargetObjectThatWillBeTheSameIfWeReturnHereViaRecursion);
  // recursion-risky code here.
}

这是RecursionChecker类:​​

/// <summary>If you use this class frequently from multiple threads, expect a lot of blocking. In that case,
/// might want to make this a non-static class and have an instance per thread.</summary>
public static class RecursionChecker
{
  #if DEBUG
  private static HashSet<ReentrancyInfo> ReentrancyNotes = new HashSet<ReentrancyInfo>();
  private static object LockObject { get; set; } = new object();
  private static void CleanUp(HashSet<ReentrancyInfo> notes) {
    List<ReentrancyInfo> deadOrStale = notes.Where(info => info.IsDeadOrStale()).ToList();
    foreach (ReentrancyInfo killMe in deadOrStale) {
      notes.Remove(killMe);
    }
  }
  #endif
  public static void Check(object target, int maxOK = 10, int staleMilliseconds = 1000)
  {
    #if DEBUG
    lock (LockObject) {
      HashSet<ReentrancyInfo> notes = RecursionChecker.ReentrancyNotes;
      foreach (ReentrancyInfo note in notes) {
        if (note.HandlePotentiallyRentrantCall(target, maxOK)) {
          break;
        }
      }
      ReentrancyInfo newNote = new ReentrancyInfo(target, staleMilliseconds);
      newNote.HandlePotentiallyRentrantCall(target, maxOK);
      RecursionChecker.CleanUp(notes);
      notes.Add(newNote);
    }
    #endif
  }
}

以下助手类:

internal class ReentrancyInfo
{
  public WeakReference<object> ReentrantObject { get; set;}
  public object GetReentrantObject() {
    return this.ReentrantObject?.TryGetTarget();
  }
  public DateTime LastCall { get; set;}
  public int StaleMilliseconds { get; set;}
  public int ReentrancyCount { get; set;}
  public bool IsDeadOrStale() {
    bool r = false;
    if (this.LastCall.MillisecondsBeforeNow() > this.StaleMilliseconds) {
      r = true;
    } else if (this.GetReentrantObject() == null) {
      r = true;
    }
    return r;
  }
  public ReentrancyInfo(object reentrantObject, int staleMilliseconds = 1000)
  {
    this.ReentrantObject = new WeakReference<object>(reentrantObject);
    this.StaleMilliseconds = staleMilliseconds;
    this.LastCall = DateTime.Now;
  }
  public bool HandlePotentiallyRentrantCall(object target, int maxOK) {
    bool r = false;
    object myTarget = this.GetReentrantObject();
    if (target.DoesEqual(myTarget)) {
      DateTime last = this.LastCall;
      int ms = last.MillisecondsBeforeNow();
      if (ms > this.StaleMilliseconds) {
        this.ReentrancyCount = 1;
      }
      else {
        if (this.ReentrancyCount == maxOK) {
          throw new Exception("Probable infinite recursion");
        }
        this.ReentrancyCount++;
      }
    }
    this.LastCall = DateTime.Now;
    return r;
  }
}

public static class DateTimeAdditions
{
  public static int MillisecondsBeforeNow(this DateTime time) {
    DateTime now = DateTime.Now;
    TimeSpan elapsed = now.Subtract(time);
    int r;
    double totalMS = elapsed.TotalMilliseconds;
    if (totalMS > int.MaxValue) {
      r = int.MaxValue;
    } else {
      r = (int)totalMS;
    }
    return r;
  }
}

public static class WeakReferenceAdditions {
  /// <summary> returns null if target is not available. </summary>
  public static TTarget TryGetTarget<TTarget> (this WeakReference<TTarget> reference) where TTarget: class 
  {
    TTarget r = null;
    if (reference != null) {
      reference.TryGetTarget(out r);
    }
    return r;
  }
}