живая отладка переполнения стека - PullRequest
6 голосов
/ 20 апреля 2009

У меня есть приложение Windows Service с управляемым кодом, которое иногда дает сбой в работе из-за управляемого исключения StackOverFlowException. Я знаю это, потому что я запустил adplus в режиме сбоя и проанализировал сообщение об ошибке аварийного дампа с помощью SoS. Я даже подключил отладчик windbg и установил для него «исключение без обработки».

Моя проблема в том, что я не вижу ни одного из управляемых стеков или переключаюсь ни на один из потоков. Они все снесены к тому времени, когда отладчик ломается.

Я не эксперт по Windbg, и, кроме установки Visual Studio на работающей системе или использования удаленной отладки и отладки с использованием этого инструмента, у кого-нибудь есть какие-либо предложения относительно того, как я могу получить трассировку стека от обидчика нить?

Вот что я делаю.

! * Темы 1010 *

...

XXXX 11 27c 000000001b2175f0 b220 Отключено 00000000072c9058: 00000000072cad80 0000000019bdd3f0 0 UKN System.StackOverflowException (0000000000c010d0)

...

И в этот момент вы видите идентификатор XXXX, указывающий, что нить совершенно мертва.

Ответы [ 6 ]

8 голосов
/ 21 апреля 2009

Как только вы столкнулись с переполнением стека, вам почти не повезло в устранении проблемы - потеря пространства в стеке оставляет вашу программу в недетерминированном состоянии, поэтому вы не можете полагаться на любой информации в ней на тот момент - любая трассировка стека, которую вы пытаетесь получить, может быть повреждена и может легко указать вам неправильное направление. То есть, как только возникает StackOverflowException, уже слишком поздно.

Кроме того, согласно документации вы не можете поймать исключение StackOverflowException начиная с .Net 2.0, поэтому другие предложения окружить ваш код попыткой / отловом для этого, вероятно, не сработают. Это имеет смысл, учитывая побочные эффекты переполнения стека (я удивлен. Net когда-либо позволял вам это уловить).

Ваш единственный реальный вариант - заняться утомительным анализом кода, поиском чего-либо, что может вызвать переполнение стека, и установкой каких-либо маркеров, чтобы вы могли понять, где они происходят до они происходят. Например, очевидно, что сначала нужно начинать с любых рекурсивных методов, поэтому дайте им счетчик глубины и выведите свое собственное исключение , если они получат какое-то "необоснованное" значение, которое вы определили, таким образом, вы можете получить допустимая трассировка стека.

0 голосов
/ 20 сентября 2016

У меня есть класс RecursionChecker для такого рода вещей. Настоящим я отказываюсь от авторских прав на код ниже.

Он жалуется, если слишком часто выполняет проверку целевого объекта. Это не конец всему; петли могут вызвать ложные срабатывания, например. Этого можно избежать, сделав еще один вызов после опасного кода, сообщив контролеру, что он может уменьшить свой вызов recurse для целевого объекта. Это все еще не будет пуленепробиваемым.

Чтобы использовать это, я просто звоню

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;
  }
}
0 голосов
/ 05 декабря 2009

Взгляните на журнал отладки режима сбоя ADPLUS. Посмотрите, есть ли какие-либо нарушения прав доступа или истинные собственные исключения переполнения стека, возникающие до того, как выдается управляемое исключение StackOverflowException.

Я предполагаю, что в стеке потока есть исключение, которое вы перехватываете до выхода из потока.

Вы также можете использовать DebugDiag с www.iis.net, а затем установить правило сбоя и создать полный файл дампа для нарушений прав доступа (sxe av) и собственных исключений переполнения стека (sxe sov)

Спасибо, Аарон

0 голосов
/ 23 апреля 2009

Для чего стоит, начиная с .NET 4.0, Visual Studio (и любые отладчики, использующие API ICorDebug) получают возможность отлаживать мини-дампы. Это означает, что вы сможете загрузить аварийный дамп в отладчик VS на другом компьютере и увидеть управляемые стеки, аналогичные тем, которые вы подключили отладчик во время сбоя. См. PDC talk или Rick Byers 'blog для получения дополнительной информации. К сожалению, это не поможет вам решить проблему, но, возможно, в следующий раз вы столкнетесь с этой проблемой.

0 голосов
/ 20 апреля 2009

Один из вариантов - использовать блок try / catch на высоком уровне, а затем распечатать или записать трассировку стека, предоставленную исключением. Каждое исключение имеет свойство StackTrace, которое может сказать вам, откуда оно было выброшено. Это не позволит вам выполнять какую-либо интерактивную отладку, но даст вам возможность начать.

0 голосов
/ 20 апреля 2009

Можно ли обернуть ваш код в try-catch, который записывает в EventLog (или в файл, или что-то еще) и запустить эту разовую отладку?

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

Вы не сможете отлаживать, но вы получите трассировку стека.

...