1
0

ExceptionHandler.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  1. /*
  2. * ExceptionHandler.cpp
  3. * --------------------
  4. * Purpose: Code for handling crashes (unhandled exceptions) in OpenMPT.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "Mainfrm.h"
  11. #include "Mptrack.h"
  12. #include "AboutDialog.h"
  13. #include "InputHandler.h"
  14. #include "openmpt/sounddevice/SoundDevice.hpp"
  15. #include "Moddoc.h"
  16. #include <shlwapi.h>
  17. #include "ExceptionHandler.h"
  18. #include "../misc/WriteMemoryDump.h"
  19. #include "../common/version.h"
  20. #include "../common/mptFileIO.h"
  21. #include "../soundlib/mod_specifications.h"
  22. #include <atomic>
  23. OPENMPT_NAMESPACE_BEGIN
  24. // Write full memory dump instead of minidump.
  25. bool ExceptionHandler::fullMemDump = false;
  26. bool ExceptionHandler::stopSoundDeviceOnCrash = true;
  27. bool ExceptionHandler::stopSoundDeviceBeforeDump = false;
  28. // Delegate to system-specific crash processing once our own crash handler is
  29. // finished. This is useful to allow attaching a debugger.
  30. bool ExceptionHandler::delegateToWindowsHandler = false;
  31. // Allow debugging the unhandled exception filter. Normally, if a debugger is
  32. // attached, no exceptions are unhandled because the debugger handles them. If
  33. // debugExceptionHandler is true, an additional __try/__catch is inserted around
  34. // InitInstance(), ExitInstance() and the main message loop, which will call our
  35. // filter, which then can be stepped through in a debugger.
  36. bool ExceptionHandler::debugExceptionHandler = false;
  37. bool ExceptionHandler::useAnyCrashHandler = false;
  38. bool ExceptionHandler::useImplicitFallbackSEH = false;
  39. bool ExceptionHandler::useExplicitSEH = false;
  40. bool ExceptionHandler::handleStdTerminate = false;
  41. bool ExceptionHandler::handleMfcExceptions = false;
  42. static thread_local ExceptionHandler::Context *g_Context = nullptr;
  43. static LPTOP_LEVEL_EXCEPTION_FILTER g_OriginalUnhandledExceptionFilter = nullptr;
  44. static std::terminate_handler g_OriginalTerminateHandler = nullptr;
  45. static UINT g_OriginalErrorMode = 0;
  46. ExceptionHandler::Context *ExceptionHandler::SetContext(Context *newContext) noexcept
  47. {
  48. Context *oldContext = g_Context;
  49. g_Context = newContext;
  50. return oldContext;
  51. }
  52. static std::atomic<int> & g_CrashCount()
  53. {
  54. static std::atomic<int> s_CrashCount(0);
  55. return s_CrashCount;
  56. }
  57. static std::atomic<int> & g_TaintCountDriver()
  58. {
  59. static std::atomic<int> s_TaintCountDriver(0);
  60. return s_TaintCountDriver;
  61. }
  62. static std::atomic<int> & g_TaintCountPlugin()
  63. {
  64. static std::atomic<int> s_TaintCountPlugin(0);
  65. return s_TaintCountPlugin;
  66. }
  67. void ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason reason)
  68. {
  69. switch(reason)
  70. {
  71. case ExceptionHandler::TaintReason::Driver:
  72. g_TaintCountDriver().fetch_add(1);
  73. break;
  74. case ExceptionHandler::TaintReason::Plugin:
  75. g_TaintCountPlugin().fetch_add(1);
  76. break;
  77. default:
  78. MPT_ASSERT_NOTREACHED();
  79. break;
  80. }
  81. }
  82. enum DumpMode
  83. {
  84. DumpModeCrash = 0, // crash
  85. DumpModeWarning = 1, // assert
  86. DumpModeDebug = 2, // debug output (e.g. trace log)
  87. };
  88. struct CrashOutputDirectory
  89. {
  90. bool valid;
  91. mpt::PathString path;
  92. CrashOutputDirectory()
  93. : valid(true)
  94. {
  95. const mpt::PathString timestampDir = mpt::PathString::FromCString((CTime::GetCurrentTime()).Format(_T("%Y-%m-%d %H.%M.%S\\")));
  96. // Create a crash directory
  97. path = mpt::GetTempDirectory() + P_("OpenMPT Crash Files\\");
  98. if(!path.IsDirectory())
  99. {
  100. CreateDirectory(path.AsNative().c_str(), nullptr);
  101. }
  102. // Set compressed attribute in order to save disk space.
  103. // Debugging information should clutter the users computer as little as possible.
  104. // Performance is not important here.
  105. // Ignore any errors.
  106. SetFilesystemCompression(path);
  107. // Compression will be inherited by children directories and files automatically.
  108. path += timestampDir;
  109. if(!path.IsDirectory())
  110. {
  111. if(!CreateDirectory(path.AsNative().c_str(), nullptr))
  112. {
  113. valid = false;
  114. }
  115. }
  116. }
  117. };
  118. class DebugReporter
  119. {
  120. private:
  121. int crashCount;
  122. int taintCountDriver;
  123. int taintCountPlugin;
  124. bool stateFrozen;
  125. const DumpMode mode;
  126. const CrashOutputDirectory crashDirectory;
  127. bool writtenMiniDump;
  128. bool writtenTraceLog;
  129. int rescuedFiles;
  130. private:
  131. static bool FreezeState(DumpMode mode);
  132. static bool Cleanup(DumpMode mode);
  133. bool GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo);
  134. bool GenerateTraceLog();
  135. int RescueFiles();
  136. bool HasWrittenDebug() const { return writtenMiniDump || writtenTraceLog; }
  137. static void StopSoundDevice();
  138. public:
  139. DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo);
  140. ~DebugReporter();
  141. void ReportError(mpt::ustring errorMessage);
  142. };
  143. DebugReporter::DebugReporter(DumpMode mode, _EXCEPTION_POINTERS *pExceptionInfo)
  144. : crashCount(g_CrashCount().fetch_add(1) + 1)
  145. , taintCountDriver(g_TaintCountDriver().load())
  146. , taintCountPlugin(g_TaintCountPlugin().load())
  147. , stateFrozen(FreezeState(mode))
  148. , mode(mode)
  149. , writtenMiniDump(false)
  150. , writtenTraceLog(false)
  151. , rescuedFiles(0)
  152. {
  153. if(mode == DumpModeCrash || mode == DumpModeWarning)
  154. {
  155. writtenMiniDump = GenerateDump(pExceptionInfo);
  156. }
  157. if(mode == DumpModeCrash || mode == DumpModeWarning || mode == DumpModeDebug)
  158. {
  159. writtenTraceLog = GenerateTraceLog();
  160. }
  161. if(mode == DumpModeCrash || mode == DumpModeWarning)
  162. {
  163. rescuedFiles = RescueFiles();
  164. }
  165. }
  166. DebugReporter::~DebugReporter()
  167. {
  168. Cleanup(mode);
  169. }
  170. bool DebugReporter::GenerateDump(_EXCEPTION_POINTERS *pExceptionInfo)
  171. {
  172. return WriteMemoryDump(pExceptionInfo, (crashDirectory.path + P_("crash.dmp")).AsNative().c_str(), ExceptionHandler::fullMemDump);
  173. }
  174. bool DebugReporter::GenerateTraceLog()
  175. {
  176. return mpt::log::Trace::Dump(crashDirectory.path + P_("trace.log"));
  177. }
  178. static void SaveDocumentSafe(CModDoc *pModDoc, const mpt::PathString &filename)
  179. {
  180. __try
  181. {
  182. pModDoc->OnSaveDocument(filename);
  183. } __except(EXCEPTION_EXECUTE_HANDLER)
  184. {
  185. // nothing
  186. }
  187. }
  188. // Rescue modified files...
  189. int DebugReporter::RescueFiles()
  190. {
  191. int numFiles = 0;
  192. auto documents = theApp.GetOpenDocuments();
  193. for(auto modDoc : documents)
  194. {
  195. if(modDoc->IsModified())
  196. {
  197. if(numFiles == 0)
  198. {
  199. // Show the rescue directory in Explorer...
  200. CTrackApp::OpenDirectory(crashDirectory.path);
  201. }
  202. mpt::PathString filename;
  203. filename += crashDirectory.path;
  204. filename += mpt::PathString::FromUnicode(mpt::ufmt::val(++numFiles));
  205. filename += P_("_");
  206. filename += mpt::PathString::FromCString(modDoc->GetTitle()).SanitizeComponent();
  207. filename += P_(".");
  208. filename += mpt::PathString::FromUTF8(modDoc->GetSoundFile().GetModSpecifications().fileExtension);
  209. try
  210. {
  211. SaveDocumentSafe(modDoc, filename);
  212. } catch(...)
  213. {
  214. continue;
  215. }
  216. }
  217. }
  218. return numFiles;
  219. }
  220. void DebugReporter::ReportError(mpt::ustring errorMessage)
  221. {
  222. if(!crashDirectory.valid)
  223. {
  224. errorMessage += UL_("\n\n");
  225. errorMessage += UL_("Could not create the following directory for saving debug information and modified files to:\n");
  226. errorMessage += crashDirectory.path.ToUnicode();
  227. }
  228. if(HasWrittenDebug())
  229. {
  230. errorMessage += UL_("\n\n");
  231. errorMessage += UL_("Debug information has been saved to\n");
  232. errorMessage += crashDirectory.path.ToUnicode();
  233. }
  234. if(rescuedFiles > 0)
  235. {
  236. errorMessage += UL_("\n\n");
  237. if(rescuedFiles == 1)
  238. {
  239. errorMessage += UL_("1 modified file has been rescued, but it cannot be guaranteed that it is still intact.");
  240. } else
  241. {
  242. errorMessage += MPT_UFORMAT("{} modified files have been rescued, but it cannot be guaranteed that they are still intact.")(rescuedFiles);
  243. }
  244. }
  245. errorMessage += UL_("\n\n");
  246. errorMessage += MPT_UFORMAT("OpenMPT {} {} ({} ({}))")
  247. ( Build::GetVersionStringExtended()
  248. , mpt::OS::Windows::Name(mpt::OS::Windows::GetProcessArchitecture())
  249. , SourceInfo::Current().GetUrlWithRevision()
  250. , SourceInfo::Current().GetStateString()
  251. );
  252. errorMessage += UL_("\n\n");
  253. errorMessage += MPT_UFORMAT("Session error count: {}\n")(crashCount);
  254. if(taintCountDriver > 0 || taintCountPlugin > 0)
  255. {
  256. errorMessage += UL_("Process is in tainted state!\n");
  257. errorMessage += MPT_UFORMAT("Previously masked driver crashes: {}\n")(taintCountDriver);
  258. errorMessage += MPT_UFORMAT("Previously masked plugin crashes: {}\n")(taintCountPlugin);
  259. }
  260. errorMessage += UL_("\n");
  261. {
  262. mpt::SafeOutputFile sf(crashDirectory.path + P_("error.txt"), std::ios::binary, mpt::FlushMode::Full);
  263. mpt::ofstream& f = sf;
  264. f.imbue(std::locale::classic());
  265. f << mpt::replace(mpt::ToCharset(mpt::Charset::UTF8, errorMessage), std::string("\n"), std::string("\r\n"));
  266. }
  267. if(auto ih = CMainFrame::GetInputHandler(); ih != nullptr)
  268. {
  269. mpt::SafeOutputFile sf(crashDirectory.path + P_("last-commands.txt"), std::ios::binary, mpt::FlushMode::Full);
  270. mpt::ofstream &f = sf;
  271. f.imbue(std::locale::classic());
  272. const auto commandSet = ih->m_activeCommandSet.get();
  273. f << "Last commands:\n";
  274. for(size_t i = 0; i < ih->m_lastCommands.size(); i++)
  275. {
  276. CommandID id = ih->m_lastCommands[(ih->m_lastCommandPos + i) % ih->m_lastCommands.size()];
  277. if(id == kcNull)
  278. continue;
  279. f << mpt::afmt::val(id);
  280. if(commandSet)
  281. f << " (" << mpt::ToCharset(mpt::Charset::UTF8, commandSet->GetCommandText(id)) << ")";
  282. f << "\n";
  283. }
  284. }
  285. {
  286. mpt::SafeOutputFile sf(crashDirectory.path + P_("threads.txt"), std::ios::binary, mpt::FlushMode::Full);
  287. mpt::ofstream& f = sf;
  288. f.imbue(std::locale::classic());
  289. f << MPT_AFORMAT("current : {}")(mpt::afmt::hex0<8>(GetCurrentThreadId())) << "\r\n";
  290. f << MPT_AFORMAT("GUI : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n";
  291. f << MPT_AFORMAT("Audio : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindAudio))) << "\r\n";
  292. f << MPT_AFORMAT("Notify : {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindNotify))) << "\r\n";
  293. f << MPT_AFORMAT("WatchDir: {}")(mpt::afmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindWatchdir))) << "\r\n";
  294. }
  295. static constexpr struct { const mpt::uchar * section; const mpt::uchar * key; } configAnonymize[] =
  296. {
  297. { UL_("Version"), UL_("InstallGUID") },
  298. { UL_("Recent File List"), nullptr },
  299. };
  300. {
  301. mpt::SafeOutputFile sf(crashDirectory.path + P_("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full);
  302. mpt::ofstream& f = sf;
  303. f.imbue(std::locale::classic());
  304. if(theApp.GetpSettings())
  305. {
  306. SettingsContainer &settings = theApp.GetSettings();
  307. for(const auto &it : settings)
  308. {
  309. bool skipPath = false;
  310. for(const auto &path : configAnonymize)
  311. {
  312. if((path.key == nullptr && path.section == it.first.GetRefSection()) // Omit entire section
  313. || (path.key != nullptr && it.first == SettingPath(path.section, path.key))) // Omit specific key
  314. {
  315. skipPath = true;
  316. }
  317. }
  318. if(skipPath)
  319. {
  320. continue;
  321. }
  322. f
  323. << mpt::ToCharset(mpt::Charset::UTF8, it.first.FormatAsString() + U_(" = ") + it.second.GetRefValue().FormatValueAsString())
  324. << std::endl;
  325. }
  326. }
  327. }
  328. {
  329. const mpt::PathString crashStoredSettingsFilename = crashDirectory.path + P_("stored-mptrack.ini");
  330. CopyFile
  331. ( theApp.GetConfigFileName().AsNative().c_str()
  332. , crashStoredSettingsFilename.AsNative().c_str()
  333. , FALSE
  334. );
  335. IniFileSettingsContainer crashStoredSettings{crashStoredSettingsFilename};
  336. for(const auto &path : configAnonymize)
  337. {
  338. if(path.key)
  339. {
  340. crashStoredSettings.Write(SettingPath(path.section, path.key), SettingValue(mpt::ustring()));
  341. } else
  342. {
  343. crashStoredSettings.Remove(path.section);
  344. }
  345. }
  346. crashStoredSettings.Flush();
  347. }
  348. /*
  349. // This is very slow, we instead write active-settings.txt above.
  350. {
  351. IniFileSettingsBackend f(crashDirectory.path + P_("active-mptrack.ini"));
  352. if(theApp.GetpSettings())
  353. {
  354. SettingsContainer & settings = theApp.GetSettings();
  355. for(const auto &it : settings)
  356. {
  357. f.WriteSetting(it.first, it.second.GetRefValue());
  358. }
  359. }
  360. }
  361. */
  362. {
  363. mpt::SafeOutputFile sf(crashDirectory.path + P_("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full);
  364. mpt::ofstream& f = sf;
  365. f.imbue(std::locale::classic());
  366. f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(0));
  367. }
  368. {
  369. mpt::SafeOutputFile sf(crashDirectory.path + P_("about-components.txt"), std::ios::binary, mpt::FlushMode::Full);
  370. mpt::ofstream& f = sf;
  371. f.imbue(std::locale::classic());
  372. f << mpt::ToCharset(mpt::Charset::UTF8, CAboutDlg::GetTabText(1));
  373. }
  374. Reporting::Error(errorMessage, (mode == DumpModeWarning) ? "OpenMPT Warning" : "OpenMPT Crash", CMainFrame::GetMainFrame());
  375. }
  376. // Freezes the state as much as possible in order to avoid further confusion by
  377. // other (possibly still running) threads
  378. bool DebugReporter::FreezeState(DumpMode mode)
  379. {
  380. MPT_TRACE();
  381. // seal the trace log as early as possible
  382. mpt::log::Trace::Seal();
  383. if(mode == DumpModeCrash || mode == DumpModeWarning)
  384. {
  385. if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
  386. {
  387. // For fragile devices, always stop the device. Stop before the dumping if not in realtime context.
  388. if(!CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
  389. {
  390. StopSoundDevice();
  391. }
  392. } else
  393. {
  394. if(ExceptionHandler::stopSoundDeviceOnCrash && ExceptionHandler::stopSoundDeviceBeforeDump)
  395. {
  396. StopSoundDevice();
  397. }
  398. }
  399. }
  400. return true;
  401. }
  402. static void StopSoundDeviceSafe(CMainFrame *pMainFrame)
  403. {
  404. __try
  405. {
  406. if(pMainFrame->gpSoundDevice)
  407. {
  408. pMainFrame->gpSoundDevice->Close();
  409. }
  410. if(pMainFrame->m_NotifyTimer)
  411. {
  412. pMainFrame->KillTimer(pMainFrame->m_NotifyTimer);
  413. pMainFrame->m_NotifyTimer = 0;
  414. }
  415. } __except(EXCEPTION_EXECUTE_HANDLER)
  416. {
  417. // nothing
  418. }
  419. }
  420. void DebugReporter::StopSoundDevice()
  421. {
  422. CMainFrame* pMainFrame = CMainFrame::GetMainFrame();
  423. if(pMainFrame)
  424. {
  425. try
  426. {
  427. StopSoundDeviceSafe(pMainFrame);
  428. } catch(...)
  429. {
  430. // nothing
  431. }
  432. }
  433. }
  434. bool DebugReporter::Cleanup(DumpMode mode)
  435. {
  436. MPT_TRACE();
  437. if(mode == DumpModeCrash || mode == DumpModeWarning)
  438. {
  439. if(CMainFrame::GetMainFrame() && CMainFrame::GetMainFrame()->gpSoundDevice && CMainFrame::GetMainFrame()->gpSoundDevice->DebugIsFragileDevice())
  440. {
  441. // For fragile devices, always stop the device. Stop after the dumping if in realtime context.
  442. if(CMainFrame::GetMainFrame()->gpSoundDevice->DebugInRealtimeCallback())
  443. {
  444. StopSoundDevice();
  445. }
  446. } else
  447. {
  448. if(ExceptionHandler::stopSoundDeviceOnCrash && !ExceptionHandler::stopSoundDeviceBeforeDump)
  449. {
  450. StopSoundDevice();
  451. }
  452. }
  453. }
  454. return true;
  455. }
  456. // Different entry points for different situations in which we want to dump some information
  457. static bool IsCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
  458. {
  459. if (!pExceptionInfo)
  460. return false;
  461. if (!pExceptionInfo->ExceptionRecord)
  462. return false;
  463. if (pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
  464. return false;
  465. return true;
  466. }
  467. template <typename E>
  468. static const E * GetCxxException(_EXCEPTION_POINTERS *pExceptionInfo)
  469. {
  470. // https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
  471. struct info_a_t
  472. {
  473. DWORD bitmask; // Probably: 1=Const, 2=Volatile
  474. DWORD destructor; // RVA (Relative Virtual Address) of destructor for that exception object
  475. DWORD unknown;
  476. DWORD catchableTypesPtr; // RVA of instance of type "B"
  477. };
  478. struct info_c_t
  479. {
  480. DWORD someBitmask;
  481. DWORD typeInfo; // RVA of std::type_info for that type
  482. DWORD memberDisplacement; // Add to ExceptionInformation[1] in EXCEPTION_RECORD to obtain 'this' pointer.
  483. DWORD virtBaseRelated1; // -1 if no virtual base
  484. DWORD virtBaseRelated2; // ?
  485. DWORD objectSize; // Size of the object in bytes
  486. DWORD probablyCopyCtr; // RVA of copy constructor (?)
  487. };
  488. if(!pExceptionInfo)
  489. return nullptr;
  490. if (!pExceptionInfo->ExceptionRecord)
  491. return nullptr;
  492. if(pExceptionInfo->ExceptionRecord->ExceptionCode != 0xE06D7363u)
  493. return nullptr;
  494. #ifdef _WIN64
  495. if(pExceptionInfo->ExceptionRecord->NumberParameters != 4)
  496. return nullptr;
  497. #else
  498. if(pExceptionInfo->ExceptionRecord->NumberParameters != 3)
  499. return nullptr;
  500. #endif
  501. if(pExceptionInfo->ExceptionRecord->ExceptionInformation[0] != 0x19930520u)
  502. return nullptr;
  503. std::uintptr_t base_address = 0;
  504. #ifdef _WIN64
  505. base_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[3];
  506. #else
  507. base_address = 0;
  508. #endif
  509. std::uintptr_t obj_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[1];
  510. if(!obj_address)
  511. return nullptr;
  512. std::uintptr_t info_a_address = pExceptionInfo->ExceptionRecord->ExceptionInformation[2];
  513. if(!info_a_address)
  514. return nullptr;
  515. const info_a_t * info_a = reinterpret_cast<const info_a_t *>(info_a_address);
  516. std::uintptr_t info_b_offset = info_a->catchableTypesPtr;
  517. if(!info_b_offset)
  518. return nullptr;
  519. const DWORD * info_b = reinterpret_cast<const DWORD *>(base_address + info_b_offset);
  520. for(DWORD type = 1; type <= info_b[0]; ++type)
  521. {
  522. std::uintptr_t info_c_offset = info_b[type];
  523. if(!info_c_offset)
  524. continue;
  525. const info_c_t * info_c = reinterpret_cast<const info_c_t *>(base_address + info_c_offset);
  526. if(!info_c->typeInfo)
  527. continue;
  528. const std::type_info * ti = reinterpret_cast<const std::type_info *>(base_address + info_c->typeInfo);
  529. if(*ti != typeid(E))
  530. continue;
  531. const E * e = reinterpret_cast<const E *>(obj_address + info_c->memberDisplacement);
  532. return e;
  533. }
  534. return nullptr;
  535. }
  536. void ExceptionHandler::UnhandledMFCException(CException * e, const MSG * pMsg)
  537. {
  538. DebugReporter report(DumpModeCrash, nullptr);
  539. mpt::ustring errorMessage;
  540. if(e && dynamic_cast<CSimpleException*>(e))
  541. {
  542. TCHAR tmp[1024 + 1];
  543. MemsetZero(tmp);
  544. if(dynamic_cast<CSimpleException*>(e)->GetErrorMessage(tmp, static_cast<UINT>(std::size(tmp) - 1)) != 0)
  545. {
  546. tmp[1024] = 0;
  547. errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
  548. (mpt::ufmt::dec(pMsg ? pMsg->message : 0)
  549. , mpt::ToUnicode(CString(tmp))
  550. );
  551. } else
  552. {
  553. errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}': {}.")
  554. (mpt::ufmt::dec(pMsg ? pMsg->message : 0)
  555. , mpt::ToUnicode(CString(tmp))
  556. );
  557. }
  558. }
  559. else
  560. {
  561. errorMessage = MPT_UFORMAT("Unhandled MFC exception occurred while processming window message '{}'.")
  562. ( mpt::ufmt::dec(pMsg ? pMsg->message : 0)
  563. );
  564. }
  565. report.ReportError(errorMessage);
  566. }
  567. static void UnhandledExceptionFilterImpl(_EXCEPTION_POINTERS *pExceptionInfo)
  568. {
  569. DebugReporter report(DumpModeCrash, pExceptionInfo);
  570. mpt::ustring errorMessage;
  571. const std::exception * pE = GetCxxException<std::exception>(pExceptionInfo);
  572. if(g_Context)
  573. {
  574. if(!g_Context->description.empty())
  575. {
  576. errorMessage += MPT_UFORMAT("OpenMPT detected a crash in '{}'.\nThis is very likely not an OpenMPT bug. Please report the problem to the respective software author.\n")(g_Context->description);
  577. } else
  578. {
  579. errorMessage += MPT_UFORMAT("OpenMPT detected a crash in unknown foreign code.\nThis is likely not an OpenMPT bug.\n")();
  580. }
  581. }
  582. if(pE)
  583. {
  584. const std::exception & e = *pE;
  585. errorMessage += MPT_UFORMAT("Unhandled C++ exception '{}' occurred at address 0x{}: '{}'.")
  586. ( mpt::ToUnicode(mpt::Charset::ASCII, typeid(e).name())
  587. , mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
  588. , mpt::get_exception_text<mpt::ustring>(e)
  589. );
  590. } else
  591. {
  592. errorMessage += MPT_UFORMAT("Unhandled exception 0x{} at address 0x{} occurred.")
  593. ( mpt::ufmt::HEX0<8>(pExceptionInfo->ExceptionRecord->ExceptionCode)
  594. , mpt::ufmt::hex0<mpt::pointer_size*2>(reinterpret_cast<std::uintptr_t>(pExceptionInfo->ExceptionRecord->ExceptionAddress))
  595. );
  596. }
  597. report.ReportError(errorMessage);
  598. }
  599. LONG ExceptionHandler::UnhandledExceptionFilterContinue(_EXCEPTION_POINTERS *pExceptionInfo)
  600. {
  601. UnhandledExceptionFilterImpl(pExceptionInfo);
  602. // Disable the call to std::terminate() as that would re-renter the crash
  603. // handler another time, but with less information available.
  604. #if 0
  605. // MSVC implements calling std::terminate by its own UnhandledExeptionFilter.
  606. // However, we do overwrite it here, thus we have to call std::terminate
  607. // ourselves.
  608. if (IsCxxException(pExceptionInfo))
  609. {
  610. std::terminate();
  611. }
  612. #endif
  613. // Let a potential debugger handle the exception...
  614. return EXCEPTION_CONTINUE_SEARCH;
  615. }
  616. LONG ExceptionHandler::ExceptionFilter(_EXCEPTION_POINTERS *pExceptionInfo)
  617. {
  618. UnhandledExceptionFilterImpl(pExceptionInfo);
  619. // Let a potential debugger handle the exception...
  620. return EXCEPTION_EXECUTE_HANDLER;
  621. }
  622. static void mpt_unexpected_handler();
  623. static void mpt_terminate_handler();
  624. void ExceptionHandler::Register()
  625. {
  626. if(useImplicitFallbackSEH)
  627. {
  628. g_OriginalUnhandledExceptionFilter = ::SetUnhandledExceptionFilter(&UnhandledExceptionFilterContinue);
  629. }
  630. if(handleStdTerminate)
  631. {
  632. g_OriginalTerminateHandler = std::set_terminate(&mpt_terminate_handler);
  633. }
  634. }
  635. void ExceptionHandler::ConfigureSystemHandler()
  636. {
  637. #if (_WIN32_WINNT >= 0x0600)
  638. if(delegateToWindowsHandler)
  639. {
  640. //SetErrorMode(0);
  641. g_OriginalErrorMode = ::GetErrorMode();
  642. } else
  643. {
  644. g_OriginalErrorMode = ::SetErrorMode(::GetErrorMode() | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
  645. }
  646. #else // _WIN32_WINNT < 0x0600
  647. if(delegateToWindowsHandler)
  648. {
  649. g_OriginalErrorMode = ::SetErrorMode(0);
  650. } else
  651. {
  652. g_OriginalErrorMode = ::SetErrorMode(::SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
  653. }
  654. #endif // _WIN32_WINNT
  655. }
  656. void ExceptionHandler::UnconfigureSystemHandler()
  657. {
  658. ::SetErrorMode(g_OriginalErrorMode);
  659. g_OriginalErrorMode = 0;
  660. }
  661. void ExceptionHandler::Unregister()
  662. {
  663. if(handleStdTerminate)
  664. {
  665. std::set_terminate(g_OriginalTerminateHandler);
  666. g_OriginalTerminateHandler = nullptr;
  667. }
  668. if(useImplicitFallbackSEH)
  669. {
  670. ::SetUnhandledExceptionFilter(g_OriginalUnhandledExceptionFilter);
  671. g_OriginalUnhandledExceptionFilter = nullptr;
  672. }
  673. }
  674. static void mpt_terminate_handler()
  675. {
  676. DebugReporter(DumpModeCrash, nullptr).ReportError(U_("A C++ runtime crash occurred: std::terminate() called."));
  677. #if 1
  678. std::abort();
  679. #else
  680. if(g_OriginalTerminateHandler)
  681. {
  682. g_OriginalTerminateHandler();
  683. } else
  684. {
  685. std::abort();
  686. }
  687. #endif
  688. }
  689. #if defined(MPT_ASSERT_HANDLER_NEEDED)
  690. MPT_NOINLINE void AssertHandler(const mpt::source_location &loc, const char *expr, const char *msg)
  691. {
  692. DebugReporter report(msg ? DumpModeWarning : DumpModeCrash, nullptr);
  693. if(IsDebuggerPresent())
  694. {
  695. OutputDebugString(_T("ASSERT("));
  696. OutputDebugString(mpt::ToWin(mpt::Charset::ASCII, expr).c_str());
  697. OutputDebugString(_T(") failed\n"));
  698. DebugBreak();
  699. } else
  700. {
  701. mpt::ustring errorMessage;
  702. if(msg)
  703. {
  704. errorMessage = MPT_UFORMAT("Internal state inconsistency detected at {}({}). This is just a warning that could potentially lead to a crash later on: {} [{}].")
  705. ( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
  706. , loc.line()
  707. , mpt::ToUnicode(mpt::Charset::ASCII, msg)
  708. , mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
  709. );
  710. } else
  711. {
  712. errorMessage = MPT_UFORMAT("Internal error occurred at {}({}): ASSERT({}) failed in [{}].")
  713. ( mpt::ToUnicode(mpt::Charset::ASCII, loc.file_name() ? loc.file_name() : "")
  714. , loc.line()
  715. , mpt::ToUnicode(mpt::Charset::ASCII, expr)
  716. , mpt::ToUnicode(mpt::Charset::ASCII, loc.function_name() ? loc.function_name() : "")
  717. );
  718. }
  719. report.ReportError(errorMessage);
  720. }
  721. }
  722. #endif // MPT_ASSERT_HANDLER_NEEDED
  723. void DebugInjectCrash()
  724. {
  725. DebugReporter(DumpModeCrash, nullptr).ReportError(U_("Injected crash."));
  726. }
  727. void DebugTraceDump()
  728. {
  729. DebugReporter report(DumpModeDebug, nullptr);
  730. }
  731. OPENMPT_NAMESPACE_END