7

マネージド コードの Windows サービス アプリケーションがあり、マネージド StackOverFlowException が原因で運用環境で時折クラッシュします。adplus をクラッシュ モードで実行し、SoS を使用してクラッシュ ダンプの事後分析を行ったので、これはわかっています。私はwindbgデバッガーをアタッチして、「ハンドルされていない例外に行く」ように設定しました。

私の問題は、マネージド スタックが表示されないか、スレッドに切り替えられないことです。デバッガーが壊れるまでに、それらはすべて取り壊されています。

私は Windbg の専門家ではありません。ライブ システムに Visual Studio をインストールしたり、リモート デバッグを使用したり、そのツールを使用してデバッグしたりすること以外に、問題のあるスレッドからスタック トレースを取得する方法について何か提案はありますか?

これが私がやっていることです。

!スレッド

...

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

...

この時点で、スレッドが完全に停止していることを示す XXXX ID が表示されます。

4

6 に答える 6

8

スタック オーバーフローが発生すると、問題をデバッグすることはほとんどできなくなります。スタック スペースを使い果たすと、プログラムが非決定論的な状態のままになるため、その中の情報に依存することはできません。その点-取得しようとするスタックトレースは破損している可能性があり、間違った方向に簡単に向く可能性があります. つまり、StackOverflowException が発生してからでは手遅れです。

また、ドキュメントによると、 .Net 2.0 以降では StackOverflowException をキャッチできないため、コードを try/catch で囲むという他の提案はおそらく機能しません。スタック オーバーフローの副作用を考えると、これは完全に理にかなっています (.Net でこれをキャッチできることに驚きました)。

唯一の現実的な選択肢は、コードを分析するという退屈な作業に従事し、スタック オーバーフローを引き起こす可能性のあるものを探し、何らかのマーカーを配置して、発生するに発生する場所を把握できるようにすることです。たとえば、明らかに、再帰メソッドは最初に開始する場所であるため、深さカウンターを与え、定義した「不当な」値に達した場合は独自の例外をスローします。これにより、実際に有効なスタックトレースを取得できます。

于 2009-04-20T20:03:52.053 に答える
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;
  }
}
于 2016-09-20T13:12:00.783 に答える
0

(またはファイルなど) にtry-catch書き込む でコードをラップし、このデバッグを 1 回限り実行するオプションはありますか?EventLog

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

デバッグはできませんが、スタック トレースは取得できます。

于 2009-04-20T19:18:45.283 に答える
0

選択肢の 1 つは、高いレベルで try/catch ブロックを使用してから、例外によって提供されるスタック トレースを出力またはログに記録することです。すべての例外には、StackTraceどこからスローされたかを示すプロパティがあります。これではインタラクティブなデバッグを行うことはできませんが、開始する場所が得られるはずです。

于 2009-04-20T19:44:52.757 に答える
0

その価値のために、.NET 4.0 以降では、Visual Studio (およびICorDebugAPI に依存するすべてのデバッガー) でミニダンプをデバッグする機能が得られます。これは、別のコンピューターの VS デバッガーにクラッシュ ダンプを読み込んで、クラッシュ時にデバッガーをアタッチした場合と同様に、マネージド スタックを確認できることを意味します。詳細については、PDC トークまたはRick Byers のブログを参照してください。残念ながら、これは当面の問題の解決には役立ちませんが、次回この問題に遭遇したときに役立つでしょう。

于 2009-04-22T21:33:15.740 に答える