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

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

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

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

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

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

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

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

4 ответа

Решение

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

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() можно, проблем не возникает. Программа в любом случае не работает. Тем не менее, в вашем стремлении получить полезную трассировку стека не поможет. Эта информация - gonzo, когда блок catch перехватывает исключение.

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

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

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

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

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

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

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

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

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

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

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

Вы также можете использовать VirtualQuery превратить значения стека в "ModuleName+Offset" довольно легко. И это в сочетании с файлом.MAP часто скажет вам, где именно вы потерпели крах.

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

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

Фактически, boost:: диагностика_информация была специально разработана для использования в "глобальном" блоке catch(...) для отображения информации об исключениях, которые не должны были его достичь. Тем не менее, обратите внимание, что строка, возвращаемая boost:: диагностика_информация не является удобной для пользователя.

Другие вопросы по тегам