Отображение информации об отладке исключений для пользователей

StackOverflow https://stackoverflow.com/questions/1964478

Вопрос

В настоящее время я работаю над добавлением исключений и их обработкой в ​​свое приложение OSS.Исключения были общей идеей с самого начала, но я хотел найти хорошую структуру исключений и, честно говоря, немного лучше понять соглашения и идиомы обработки исключений C++, прежде чем начинать их использовать.У меня большой опыт работы с C#/.Net, Python и другими языками, использующими исключения.Мне эта идея не чужая (но далеко не мастер).

В C# и Python при возникновении необработанного исключения пользователь получает красивую трассировку стека и, как правило, множество очень полезно бесценная отладочная информация.Если вы работаете над приложением OSS, просить пользователей вставлять эту информацию в отчеты о проблемах...ну, скажем так, мне трудно без этого жить.В этом проекте C++ я получаю сообщение «Приложение вылетело» или от более информированных пользователей: «Я сделал X, Y и Z, а затем оно вылетело».Но мне также нужна эта отладочная информация!

Я уже (и с большим трудом) смирился с тем фактом, что никогда не увижу кросс-платформенного и кросс-компиляторного способа получения трассировки стека исключений C++, но я знаю, что могу получить имя функции и другие релевантная информация.

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

Естественно, я буду обрабатывать исключения внутри кода, когда смогу, но я не настолько наивен, чтобы думать, что не позволю паре проскользнуть (непреднамеренно, конечно).

Итак, я хочу обернуть мою основную точку входа внутри try блок с catch это создает специальный диалог, который информирует пользователя о том, что в приложении произошла ошибка, с более подробной информацией, предоставляемой, когда пользователь нажимает «Дополнительно», «Отладочная информация» или что-то еще.Он будет содержать строку из Diagnostic_information.Затем я мог бы поручить пользователям вставить эту информацию в отчеты о проблемах.

Но ноющее чутье подсказывает мне, что заключать все в блок try — действительно плохая идея.Неужели то, что я собираюсь сделать, глупо?Если да (и даже если это не так), какой лучший способ добиться того, чего я хочу?

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

Решение

Обертывание всего вашего кода в один try/catch Блок a-ok. Например, это не замедлит выполнение ничего внутри него. На самом деле, все мои программы имеют (код, похожий на) эту структуру:

int execute(int pArgc, char *pArgv[])
{
    // do stuff
}

int main(int pArgc, char *pArgv[])
{
    // maybe setup some debug stuff,
    // like splitting cerr to log.txt

    try
    {
        return execute(pArgc, pArgv);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Unhandled exception:\n" << e.what() << std::endl;
        // or other methods of displaying an error

        return EXIT_FAILURE;
    }
    catch (...)
    {
        std::cerr << "Unknown exception!" << std::endl;

        return EXIT_FAILURE;
    }
}

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

Поместить блок try/catch в main() можно, это не вызовет никаких проблем.В любом случае программа мертва из-за необработанного исключения.Однако это совершенно не поможет в вашем стремлении получить важнейшую трассировку стека.Эта информация просто сумасшедшая, когда блок catch перехватывает исключение.

Перехват исключения C++ также не будет очень полезен.Вероятность того, что программа завершится из-за исключения, полученного из std::Exception, довольно мала.Хотя это могло случиться.Гораздо более вероятно, что в приложении C/C++ смерть произойдет из-за аппаратных исключений, причем AccessViolation является номером один.Для их перехвата требуются ключевые слова __try и __Exception в вашем методе main().Опять же, доступно очень мало контекста, по сути, у вас есть только код исключения.AV также сообщает вам, какой именно участок памяти вызвал исключение.

Кстати, это не просто кроссплатформенная проблема: вы не можете получить хорошую трассировку стека ни на одной платформе.Не существует надежного способа обхода стека, существует слишком много оптимизаций (например, пропуск указателя кадра), которые делают это путешествие опасным.Это способ C/С++:сделайте это как можно быстрее, не оставив ни малейшего понятия, что произошло, когда он взорвется.

Что вам нужно сделать, так это отладить подобные проблемы с помощью C/C++.Вам необходимо создать минидамп.Это примерно аналогично старому «дампу ядра» — моментальному снимку образа процесса в момент возникновения исключения.Тогда вы фактически получили полный дамп ядра.Прогресс есть, сейчас это «мини», что в некоторой степени необходимо, потому что полный дамп ядра занимает около 2 гигабайт.На самом деле он очень хорошо работает для диагностики состояния программы.

В Windows это начинается с вызова SetUnhandledExceptionFilter(), вы предоставляете указатель функции обратного вызова на функцию, которая будет запускаться, когда ваша программа завершится из-за необработанного исключения.Любое исключение, C++ и SEH.Ваш следующий ресурс — dbghelp.dll, доступный в пакете «Средства отладки для Windows».У него есть точка входа MiniDumpWriteDump(), она создает минидамп.

Как только вы получите файл, созданный MiniDumpWriteDump(), вы уже в восторге.Вы можете загрузить файл .dmp в Visual Studio, как будто это проект.Нажмите F5, и VS некоторое время работает, пытаясь загрузить файлы .pdb для загружаемых в процессе DLL.Вам нужно настроить сервер символов, это очень важно получить хорошие трассировки стека.Если все работает, вы получите «прерывание отладки» именно в том месте, где было создано исключение».Со трассировкой стека.

Что нужно сделать, чтобы все работало гладко:

  • Используйте сервер сборки для создания двоичных файлов.Символы отладки (файлы .pdb) необходимо отправить на сервер символов, чтобы они были легко доступны при отладке минидампа.
  • Настройте отладчик так, чтобы он мог находить символы отладки для всех модулей.Вы можете получить символы отладки для Windows от Microsoft, символы для вашего кода должны поступать с сервера символов, упомянутого выше.
  • Напишите код для перехвата необработанного исключения и создания минидампа.Я упомянул SetUnhandledExceptionFilter(), но код, создающий минидамп, не должен находиться в программе, в которой произошел сбой.Шансы на то, что он сможет успешно записать минидамп, довольно малы, состояние программы не определено.Лучше всего запустить «охранный» процесс, который следит за именованным мьютексом.Ваш фильтр исключений может установить мьютекс, охранник может создать минидамп.
  • Создайте способ передачи минидампа с компьютера клиента на ваш.Для этого мы используем сервис Amazon S3: терабайты по разумной цене.
  • Подключите обработчик минидампа к вашей базе данных отладки.Мы используем Jira, у нее есть веб-сервис, который позволяет нам сверять сбойную корзину с базой данных более ранних сбоев с той же «подписью».Если он уникален или в нем недостаточно совпадений, мы просим код менеджера сбоев загрузить минидамп на Amazon и создать запись в базе данных ошибок.

Ну, это то, что я сделал для компании, в которой работаю.Сработало очень хорошо, это снизило частоту аварийных сегментов с тысяч до десятков.Личное сообщение создателям компонента ffdshow с открытым исходным кодом:Я ненавижу тебя страстно.Но вы больше не приводите к сбою в работе нашего приложения!Педерасты.

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

Имейте в виду, что уже есть обработчик исключений, обертывающий ваш поток, предоставленный ОС (и еще один, я думаю, еще один). Возможно, вам придется передать определенные исключения этим обработчикам, чтобы получить правильное поведение. В некоторых архитектурах доступ к неправильно повышенным данным обрабатывается обработчиком исключений. Итак, вы можете захотеть получить особый случай EXCEPTION_DATATYPE_MISALIGNMENT И пусть он переходит к обработчику исключений на более высоком уровне.

Я включаю регистры, версию приложения и номер сборки, тип исключений и дамп стека в шестнадцатеричном, аннотированном с именами модулей и смещениями для значений шестнадцатеристики, которые могут быть адресами кода. Обязательно включите номер версии и номер сборки/дату вашего EXE.

Вы также можете использовать VirtualQuery Чтобы превратить значения стека в «Modulename+смещение» довольно легко. И это, в сочетании с файлом .map часто сообщает вам, где вы разбились.

Я обнаружил, что мог бы научить бета -тестеры довольно легко отправлять свой текст, но в первые дни я получил изображение диалога ошибок, а не текст. Я думаю, что это потому, что многие пользователи не знают, что вы можете щелкнуть правой кнопкой мыши на любом управлении редактированием, чтобы получить меню с «Select All» и «Copy». Если бы я собирался сделать это снова, я бы добавил кнопку, которая скопировала этот текст в буфер обмена, чтобы ее можно было легко вставить в электронное письмо.

Еще лучше, если вы хотите перейти к проблеме получить кнопку «Отчет о ошибке отправки», но просто предоставив пользователям возможность довести текст в свои собственные электронные письма, приводит к большую часть пути и не поднимает красные флаги о "какой информации я делюсь с ними?"

Фактически, Boost :: Diagnostic_information была разработана специально для использования в «глобальном» блоке Catch (...), чтобы отобразить информацию об исключениях, которые не должны были его достигать. Однако обратите внимание, что строка, возвращаемая Boost :: Diagnostic_information, не удобна для пользователя.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top