2009-04-20 12 views
6

Ich habe einen verwalteten Code Windows-Dienstanwendung, die gelegentlich in der Produktion wegen einer verwalteten StackOverFlowException abstürzt. Ich weiß das, weil ich adplus im Crash-Modus laufen lasse und den Absturz nach dem Tod mit SoS analysiert habe. Ich habe sogar den windbg Debugger angehängt und auf "go unhandled exception" gesetzt.Live-Debugging eines Stack-Überlaufs

Mein Problem ist, ich kann keine der verwalteten Stacks sehen oder zu einem der Threads wechseln. Sie sind alle bis zum Zeitpunkt des Debuggers abgerissen.

Ich bin kein Windbg-Experte, und, kurz vor der Installation von Visual Studio auf dem Live-System oder mit Remote-Debugging und Debugging mit diesem Tool, hat jemand irgendwelche Vorschläge, wie ich eine Stack-Trace aus dem bekommen kann anstößiger Thread?

Hier ist, was ich tue. !

fädelt

...

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

...

Und an dieser Stelle sehen Sie die XXXX ID, die anzeigt, dass der Thread ziemlich tot ist.

Antwort

8

Sobald Sie einen Stack-Überlauf getroffen haben, haben Sie ziemlich viel Glück für das Debuggen des Problems - bläst Ihr Stack-Raum Ihr Programm in einem nicht-deterministischen Zustand, so können Sie sich nicht auf verlassen von den Informationen, die darin enthalten sind - jeder Stack-Trace, den Sie zu erhalten versuchen, ist möglicherweise beschädigt und kann Sie leicht in die falsche Richtung weisen. Dh, sobald die StackOverflowException auftritt, ist es zu spät.

Auch, nach the documentation können Sie eine StackOverflowException ab .Net 2.0 abfangen, so dass die anderen Vorschläge, Ihren Code mit einem try/catch dafür zu umgeben, wahrscheinlich nicht funktionieren. Das macht durchaus Sinn, wenn man die Nebeneffekte eines Stack-Überlaufs betrachtet (ich bin überrascht. Net hat es dir jemals erlaubt es zu fangen).

Ihre einzige wirkliche Option ist in der Langwierigkeit der Analyse des Codes, auf der Suche nach etwas zu greifen, die möglicherweise einen Stapelüberlauf verursachen könnte, und in einer Art Marker setzen, so dass Sie eine Vorstellung bekommen, wo sie vor treten sie auftreten. ZB sind offensichtlich alle rekursiven Methoden der erste Ort, um zu beginnen, also geben Sie ihnen einen Tiefenzähler und werfen Sie Ihre eigene Ausnahme, wenn sie zu irgendeinem "unvernünftigen" Wert kommen, den Sie definieren, auf diese Art können Sie tatsächlich eine gültige Stapelspur erhalten.

+1

Das ist interessant. Ich hatte nicht einmal bemerkt, dass sich das geändert hatte. Das letzte Mal, als ich eines davon hatte, war, als ich die Eigenschaft/den Member-Getter vertippte und die endlosen rekursiven Aufrufe bekam (und ich konnte sie damals fangen und debuggen). +1 für das tatsächliche Lesen der neuesten Dokumentation. –

0

Ist es eine Option, Ihren Code mit einer try-catch zu umhüllen, die an die EventLog (oder Datei oder was auch immer) schreibt und diesen Debug-One-Off ausführt?

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

Sie können nicht debuggen, aber Sie würden die Stapelüberwachung erhalten.

0

Eine Option, die Sie haben, besteht darin, einen try/catch-Block auf einer hohen Ebene zu verwenden und dann den durch die Ausnahme bereitgestellten Stack-Trace zu drucken oder zu protokollieren. Jede Ausnahme hat eine StackTrace Eigenschaft, die Ihnen sagen kann, wo sie geworfen wurde. Dies ermöglicht Ihnen kein interaktives Debugging, aber es sollte Ihnen einen Platz zum Starten geben.

+0

ich dieses seltsame Gefühl von Déjà-vu hatte gerade ... :) –

+0

Heh, ich lese gerade deine Antwort noch einmal und sehe deinen Standpunkt: P. Naja, wahrscheinlich lohnt es sich, explizit zu sein, dass Ausnahmen den Stack haben, aus dem sie geworfen wurden, falls es nicht offensichtlich ist –

0

Für was es wert ist, in .NET 4.0, Visual Studio (und alle Debugger, die auf der ICorDebug API angewiesen sind) erhalten die Fähigkeit, Minidumps zu debuggen. Dies bedeutet, dass Sie in der Lage sind, den Absturzabbild in den VS-Debugger auf einem anderen Computer zu laden und die verwalteten Stapel ähnlich zu sehen, wenn Sie zum Zeitpunkt des Absturzes einen Debugger angehängt hätten. Weitere Informationen finden Sie unter PDC talk oder Rick Byers' blog. Leider wird dir das bei dem vorliegenden Problem nicht helfen, aber vielleicht wirst du das nächste Mal auf dieses Problem stoßen.

0

Werfen Sie einen Blick auf Ihr ADPLUS Crash Mode Debug Log. Überprüfen Sie, ob Zugriffsverletzungen oder echte systemeigene Stack Overflow-Ausnahmen auftreten, bevor die verwaltete StackOverflowException ausgelöst wird.

Meine Vermutung ist, dass es eine Ausnahme auf dem Stack des Threads gibt, die Sie cold fangen, bevor der Thread beendet wird.

Sie könnten auch DebugDiag von www.iis.net verwenden und dann einen Crash-Regelsatz und eine vollständige Speicherabbilddatei für Zugriffsverletzungen (sxe av) und Stack-Überlauf nativen Ausnahmen (sxe sov)

Dank erstellen, Aaron

0

Ich habe eine RecursionChecker-Klasse für diese Art von Sache. Ich verwerfe hiermit das Urheberrecht auf den folgenden Code.

Es beschwert sich, wenn es findet, die Kontrolle für das Zielobjekt zu oft zu treffen. Es ist kein All-End-All; Schleifen können beispielsweise zu Fehlalarmen führen. Man könnte dies vermeiden, indem man nach dem riskanten Code einen weiteren Aufruf erhält und dem Prüfer mitteilt, dass er seinen Rekursaufruf für das Zielobjekt dekrementieren kann. Es wäre immer noch nicht kugelsicher.

, es zu benutzen, ich nenne nur

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

Hier ist die RecursionChecker Klasse:

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

Hilfsklassen unter:

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; 
    } 
} 
Verwandte Themen