!@# Мистика, да и только! Или как я краш пытаюсь отловить
От: b0r3d0m  
Дата: 31.08.16 12:17
Оценка:
Коллеги, кто пользовался библиотекой 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).
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.