Вопрос

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

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

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

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

!нити

...

XXXX 11 27c 000000001b2175f0 b220 Отключен 00000000072c9058:00000000072cad80 0000000019bdd3f0 0 в системе Ukn.Исключение StackOverflowException (0000000000c010d0)

...

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

Как бы то ни было, начиная с .NET 4.0, Visual Studio (и любые отладчики, использующие ICorDebug api) получить возможность отладки минидампов.Это означает, что вы сможете загрузить дамп сбоя в отладчик VS на другом компьютере и просмотреть управляемые стеки так же, как если бы вы подключили отладчик во время сбоя.См. PDC разговор или Блог Рика Байерса Чтобы получить больше информации.К сожалению, это не поможет вам решить возникшую проблему, но, возможно, это поможет вам в следующий раз, когда вы столкнетесь с этой проблемой.

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

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

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

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

У меня есть класс 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;
  }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top