Коллеги, кто пользовался библиотекой
CrashRpt? Впрочем, те, кто её до этого не юзал, тоже могут подсказать что-нибудь дельное.
Вчера возникла ситуация, когда наше приложение (без CrashRpt) упало у клиента. Доступ к компьютеру есть, баг легко воспроизводится у них на машине, но не воспроизводится в нашем окружении.
Казалось бы, чем не повод опробовать наконец CrashRpt в деле? Я быстро добавил следующий код в наше приложение и стал ждать письма с минидампом:
int main()
{
CR_INSTALL_INFOA info;
std::memset(&info, 0, sizeof(CR_INSTALL_INFOA));
info.cb = sizeof(CR_INSTALL_INFOA);
info.pszAppName = "app_name";
info.pszAppVersion = "1.0.0";
info.pszEmailTo = "some@email.com";
info.pszEmailSubject = "app_name CRASH";
info.dwFlags |= CR_INST_ALL_POSSIBLE_HANDLERS;
info.dwFlags |= CR_INST_AUTO_THREAD_HANDLERS;
// From the documentation:
// "It is not recommended to use this flag for regular GUI-based applications, blah-blah-blah"
// Do not tell me what to do, man!
info.dwFlags |= CR_INST_NO_GUI;
int res = crInstallA(&info);
if (res != 0)
{
char err_msg[512]; // Feel the magic! (∩`-´)⊃━☆゚.*・。゚
if (crGetLastErrorMsgA(err_msg, sizeof(err_msg) / sizeof(*err_msg)) > 0)
{
std::cerr << "Unable to initialize crashRpt library: " << err_msg << std::endl;
}
return EXIT_FAILURE;
}
// ...
}
Приложение упало, но письмо так и не пришло. Списав это на почтовые сервера, я подождал ещё 15 минут и разочаровался.
Потыкавшись на минимальном примере
// Do NOT do this at home!
// NOTE FOR COMPILER:
// BEEP BEEP
// PLEASE DO NOT OPTIMIZE THE FOLLOWING CODE, I REALLY WANT MY APPLICATION TO CRASH
int* ptr = nullptr;
*ptr = 1;
std::cout << *ptr << std::endl;
// THANKS MR. COMPILER
// END OF NON-OPTIMIZED SECTION
, я убедился, что CrashRpt корректно сконфигурирован и способен отправлять минидампы по почте.
Казалось бы, что за фигня? Я проштудировал документацию, пытаясь выяснить, точно ли CrashRpt ловит все мыслимые и немыслимые виды исключений при флаге CR_INST_ALL_POSSIBLE_HANDLERS. Ну да:
#define CR_INST_STRUCTURED_EXCEPTION_HANDLER 0x1 //!< Install SEH handler (deprecated name, use \ref CR_INST_SEH_EXCEPTION_HANDLER instead).
#define CR_INST_SEH_EXCEPTION_HANDLER 0x1 //!< Install SEH handler.
#define CR_INST_TERMINATE_HANDLER 0x2 //!< Install terminate handler.
#define CR_INST_UNEXPECTED_HANDLER 0x4 //!< Install unexpected handler.
#define CR_INST_PURE_CALL_HANDLER 0x8 //!< Install pure call handler (VS .NET and later).
#define CR_INST_NEW_OPERATOR_ERROR_HANDLER 0x10 //!< Install new operator error handler (VS .NET and later).
#define CR_INST_SECURITY_ERROR_HANDLER 0x20 //!< Install security error handler (VS .NET and later).
#define CR_INST_INVALID_PARAMETER_HANDLER 0x40 //!< Install invalid parameter handler (VS 2005 and later).
#define CR_INST_SIGABRT_HANDLER 0x80 //!< Install SIGABRT signal handler.
#define CR_INST_SIGFPE_HANDLER 0x100 //!< Install SIGFPE signal handler.
#define CR_INST_SIGILL_HANDLER 0x200 //!< Install SIGILL signal handler.
#define CR_INST_SIGINT_HANDLER 0x400 //!< Install SIGINT signal handler.
#define CR_INST_SIGSEGV_HANDLER 0x800 //!< Install SIGSEGV signal handler.
#define CR_INST_SIGTERM_HANDLER 0x1000 //!< Install SIGTERM signal handler.
#define CR_INST_ALL_POSSIBLE_HANDLERS 0x1FFF //!< Install all possible exception handlers.
В итоге я плюнул на это дело и вычислил падающее место бинарным поиском с отладочной печатью:
char buf[8];
sprintf_s(buf, 8, "%d %d", foo, bar);
Конечная строка не влезала в результирующий буфер, а это, судя по MSDN, приводит к вызову invalid parameter handler'а:
If the buffer is too small for the formatted text, including the terminating null, then the buffer is set to an empty string by placing a null character at buffer[0], and the invalid parameter handler is invoked
А дефолтный handler в свою очередь просто крашит приложение
The default invalid parameter invokes Watson crash reporting, which causes the application to crash and asks the user if they want to load the crash dump to Microsoft for analysis
, но это поведение можно изменить при помощи установки своего собственного обработчика. Делается это путём вызова функции _set_invalid_parameter_handler.
Я быстро залез в реализацию CrashRpt и убедился, что этот обработчик действительно устанавливается:
#if _MSC_VER>=1400
if(dwFlags&CR_INST_INVALID_PARAMETER_HANDLER)
{
// Catch invalid parameter exceptions.
m_prevInvpar = _set_invalid_parameter_handler(invalid_parameter_handler);
}
#endif
(использовалась VS2012).
Свёл до минимального:
#include <CrashRpt.h>
#include <boost/scope_exit.hpp>
#include <cstdlib>
#include <cstring>
#include <iostream>
#pragma comment(lib, "CrashRpt1403.lib")
int main()
{
CR_INSTALL_INFOA info;
std::memset(&info, 0, sizeof(CR_INSTALL_INFOA));
info.cb = sizeof(CR_INSTALL_INFOA);
info.pszAppName = "app_name";
info.pszAppVersion = "1.0.0";
info.pszEmailTo = "some@email.com";
info.pszEmailSubject = "app_name CRASH";
info.dwFlags |= CR_INST_ALL_POSSIBLE_HANDLERS;
info.dwFlags |= CR_INST_AUTO_THREAD_HANDLERS;
// From the documentation:
// "It is not recommended to use this flag for regular GUI-based applications, blah-blah-blah"
// Do not tell me what to do, man!
info.dwFlags |= CR_INST_NO_GUI;
int res = crInstallA(&info);
if (res != 0)
{
char err_msg[512] = ""; // Feel the magic! (∩`-´)⊃━☆゚.*・。゚
if (crGetLastErrorMsgA(err_msg, sizeof(err_msg) / sizeof(*err_msg)) > 0)
{
std::cerr << "Unable to initialize crashRpt library: " << err_msg << std::endl;
}
return EXIT_FAILURE;
}
BOOST_SCOPE_EXIT_ALL()
{
(void)crUninstall(); // Yeah, that's right -- return codes are for pussies
};
// Here come dat boi
char buf[8];
// Oh shit waddup!
sprintf_s(buf, sizeof(buf) / sizeof(*buf), "too long to hold it");
}
Приведённый выше код крашится, но ничего на почту не отправляет.
На всякий случай убрал флаг CR_INST_NO_GUI -- не появилось даже окна CrashSender'а.
Я уж было подумал, что _set_invalid_parameter_handler некорректно работает, если вызывать её внутри DLL, а не в самом исполняемом файле, но, набросав минимальный пример, ничего подобного не обнаружил.
Потом я попробовал собрать CrashRpt из исходников (до этого использовал поставляемые .lib и .dll файлы) тем же самым toolset'ом, что и падающее приложение (линковка с CRT тоже совпадает) -- не помогло.
Менял в исходниках реализацию функции crInstallA на
void invalid_parameter_handler1(
const wchar_t * expression,
const wchar_t * function,
const wchar_t * file,
unsigned int line,
uintptr_t pReserved
)
{
std::ofstream f("C:\\output.txt", std::ios_base::app);
f << "Error" << std::endl;
}
CRASHRPTAPI(int) crInstallA(CR_INSTALL_INFOA* pInfo)
{
_set_invalid_parameter_handler(invalid_parameter_handler1);
return 0;
}
Не помогает -- поведение то же самое.
Поиск в Google ничего не дал.
Собственно, думаю, куда копать дальше. Мне даже интересно, как такое могло получиться.
У кого какие мысли? Что ещё можно потыкать / посмотреть, кроме как написать разработчикам баг-репорт?
Версия либы последняя (v.1.4.3_r1645).
Ну, это вообще пушка.
Оставил в проекте CrashRpt только следующий код:
CrashRpt.cpp
#include "stdafx.h"
#include "CrashRpt.h"
void invalid_parameter_handler(
const wchar_t * expression,
const wchar_t * function,
const wchar_t * file,
unsigned int line,
uintptr_t pReserved
)
{
std::ofstream f("E:\\output.txt", std::ios_base::app);
f << "Error" << std::endl;
}
CRASHRPTAPI(int) crInstallA(CR_INSTALL_INFOA* pInfo)
{
std::ofstream f("E:\\output.txt", std::ios_base::app);
f << "1" << std::endl;
_set_invalid_parameter_handler(invalid_parameter_handler);
f << "2" << std::endl;
return 0;
}
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, LPVOID /*lpReserved*/)
{
return TRUE;
}
stdafx.cpp
#include "stdafx.h"
stdafx.h
#pragma once
#include <Windows.h>
CrashRpt.def
EXPORTS
crInstallA @8
В CrashRpt.h лежат объявления функций, структуры и макросы.
Пересобрал, слинковал с минимальным примером.
1 и 2 попадают в файл, "Error" -- нет.
Настройки проекта выставил дефолтными для чистого DLL-проекта, создаваемого в Visual Studio.