Восстановить трассировку стека с номерами строк из отчета об ошибке пользователя в .net?

Во-первых, проблема: У меня есть несколько бесплатных проектов, и в качестве любого программного обеспечения они содержат ошибки. Некоторые пользователи, столкнувшись с ошибкой, присылают мне сообщения об ошибках со следами стека. Чтобы упростить поиск места неисправности, я хочу видеть номера строк в этих трассировках стека. Если приложение отправлено без файлов .pdb, тогда вся информация о линии теряется, поэтому в настоящее время все мои проекты, развернутые с файлами .pdb, и так созданные трассировки стека имеют эти числа. Но! Но я не хочу видеть эти файлы в дистрибутиве и хочу удалить все .pdb. Они путают пользователей, потребляют пространство в установщике и т.д.

Решение Delphi: Давным-давно, когда я был программистом delphi, я использовал следующую технику: при исключении мое приложение перемещается по стеку и собирает адреса. Затем, когда я получаю отчет об ошибке, я использовал инструмент, который восстанавливает правильную трассировку стека с именами функций и номерами строк на основе собранных адресов и соответствующих файлов символов, расположенных на моей машине.

Вопрос: Есть ли какой-либо lib, или техника или что-то подобное в .NET?

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

Вариант 1: MiniDumps. После многоголоджинга я нашел способ создать мини-дамп из кода и как воссоздать стек из управляемого мини-дампа.

Это решение, однако, необходимо перераспределить две дополнительные сборки (размером ~ 1 МБ), а мини-дампы занимают некоторое пространство, и пользователю неудобно отправлять их по электронной почте. Поэтому для моих целей сейчас это неприемлемо.

Вариант 2: благодаря отличительному признаку. Можно извлечь управляемое смещение IL для каждого кадра стека. Теперь проблема в том, как получить номера строк из .pdb на основе этих смещений. И что я нашел:

Используя этот инструмент, вы можете создавать файлы xml для каждой сборки релиза и помещать их в репозиторий. Когда исключение возникает на пользовательском компьютере, можно создать отформатированное сообщение об ошибке с смещениями IL. Затем пользователь отправляет это сообщение (очень маленькое) по почте. И, наконец, можно создать простой инструмент, который воссоздает полученный стек из отформатированного сообщения об ошибке.

Мне только интересно, почему никто другой не реализует такой инструмент? Я не считаю, что это интересно только мне.

Ответы

Ответ 1

Вы можете получить смещение последней инструкции MSIL из Exception с помощью System.Diagnostics.StackTrace:

// Using System.Diagnostics
static void Main(string[] args)
{
    try { ThrowError(); }
    catch (Exception e)
    {
        StackTrace st = new System.Diagnostics.StackTrace(e);
        string stackTrace = "";
        foreach (StackFrame frame in st.GetFrames())
        {
            stackTrace = "at " + frame.GetMethod().Module.Name + "." + 
                frame.GetMethod().ReflectedType.Name + "." 
                + frame.GetMethod().Name 
                + "  (IL offset: 0x" + frame.GetILOffset().ToString("x") + ")\n" + stackTrace;
        }
        Console.Write(stackTrace);
        Console.WriteLine("Message: " + e.Message);
    }
    Console.ReadLine();
}

static void ThrowError()
{
    DateTime myDateTime = new DateTime();
    myDateTime = new DateTime(2000, 5555555, 1); // won't work
    Console.WriteLine(myDateTime.ToString());
}

Вывод:

в ConsoleApplicationN.exe.Program.Main(смещение по IL: 0x7)
в ConsoleApplicationN.exe.Program.ThrowError(IL offset: 0x1b)
at mscorlib.dll.DateTime..ctor(смещение по IL: 0x9)
по адресу mscorlib.dll.DateTime.DateToTicks(смещение по IL: 0x61)
Параметры Message: Year, Month и Day описывают не представимые DateTime.

Затем вы можете использовать Reflector или ILSpy для интерпретации смещения:

.method private hidebysig static void ThrowError() cil managed
{
    .maxstack 4
    .locals init (
        [0] valuetype [mscorlib]System.DateTime myDateTime)
    L_0000: nop 
    L_0001: ldloca.s myDateTime
    L_0003: initobj [mscorlib]System.DateTime
    L_0009: ldloca.s myDateTime
    L_000b: ldc.i4 0x7d0
    L_0010: ldc.i4 0x54c563
    L_0015: ldc.i4.1 
    L_0016: call instance void [mscorlib]System.DateTime::.ctor(int32, int32, int32)
    L_001b: nop 
    L_001c: ldloca.s myDateTime
    L_001e: constrained [mscorlib]System.DateTime
    L_0024: callvirt instance string [mscorlib]System.Object::ToString()
    L_0029: call void [mscorlib]System.Console::WriteLine(string)
    L_002e: nop 
    L_002f: ret 
}

Вы знаете, что команда до 0x1b выбрала исключение. Легко найти код С# для этого:

 myDateTime = new DateTime(2000, 5555555, 1);

Теперь вы можете сопоставить код IL с кодом С#, но я думаю, что выигрыш будет слишком мал, и слишком много усилий (хотя может быть плагин с отражателем). Вы должны быть в порядке со смещением IL.

Ответ 2

Вы должны использовать Environment.FailFast, вызвать FailFast в Application.UnhandledException, и файл дампа будет создан для вас.

Из MSDN:

Метод FailFast записывает запись в журнал в журнал событий приложения Windows используя параметр message, создает дампа вашего приложения, а затем завершает текущий процесс.

Используйте метод FailFast вместо Выйти из приложения, если состояние вашего приложение повреждено без ремонта, и выполнение вашего приложения блоки try-finally и финализаторы будут коррумпированных ресурсов программы. Метод FailFast завершает текущий обрабатывать и выполнять любые Объекты CriticalFinalizerObject, но не выполняет никаких активных try-finally блоки или финализаторы.

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

Теперь открытие файла дампа немного сложно, Visual Studio не может обрабатывать управляемый файл дампа (исправленный в .NET 4.0), вы можете использовать WinDBG но вам нужно использовать SOS.

Ответ 3

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

Ответ 4

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

Сервер Microsoft Symbol Server будет индексировать символы отладки и хранить их для использования позднее, если у вас есть дамп сбоя, например, и нужны доступные символы. Вы можете указать либо Visual Studio, либо Windbg на свой собственный экземпляр сервера символов, как Microsoft, и он вытащит символы, необходимые для отладки эту версию вашего приложения (при условии, что вы указали символы с сервером символов перед отправкой).

Как только у вас появятся соответствующие символы для сборки, вы должны убедиться, что у вас есть соответствующие исходные файлы, которые принадлежат этой сборке.

Здесь находится Microsoft Source Server. Подобно тому, как Symbol Server индексирует символы, исходный сервер будет индексировать источник, чтобы убедиться, что у вас есть соответствующая версия исходного кода, принадлежащего сборке программного обеспечения.

Рабочие версии Контроль версий, Сервер Symbol и Source Server должны быть частью вашей стратегии управления конфигурацией программного обеспечения.

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

Джон Роббинс на Файлы PDB

Джон Роббинс на Исходный сервер

Просмотрите документацию WinDbg о том, как запустить сервер Symbol Server.