MiniDumper.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. #include <windows.h>
  2. #include <stdio.h>
  3. #include <assert.h>
  4. #include <time.h>
  5. #include <tchar.h>
  6. #include <dbghelp.h>
  7. #include "miniDumper.h"
  8. #ifdef UNICODE
  9. #define _tcssprintf wsprintf
  10. #define tcsplitpath _wsplitpath
  11. #else
  12. #define _tcssprintf sprintf
  13. #define tcsplitpath _splitpath
  14. #endif
  15. const int USER_DATA_BUFFER_SIZE = 4096;
  16. //-----------------------------------------------------------------------------
  17. // GLOBALS
  18. //-----------------------------------------------------------------------------
  19. CMiniDumper* CMiniDumper::s_pMiniDumper = NULL;
  20. LPCRITICAL_SECTION CMiniDumper::s_pCriticalSection = NULL;
  21. // Based on dbghelp.h
  22. typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess,
  23. DWORD dwPid,
  24. HANDLE hFile,
  25. MINIDUMP_TYPE DumpType,
  26. CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
  27. CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
  28. CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
  29. //-----------------------------------------------------------------------------
  30. // Name: CMiniDumper()
  31. // Desc: Constructor
  32. //-----------------------------------------------------------------------------
  33. CMiniDumper::CMiniDumper( bool bPromptUserForMiniDump )
  34. {
  35. // Our CMiniDumper should act alone as a singleton.
  36. assert( !s_pMiniDumper );
  37. s_pMiniDumper = this;
  38. m_bPromptUserForMiniDump = bPromptUserForMiniDump;
  39. // The SetUnhandledExceptionFilter function enables an application to
  40. // supersede the top-level exception handler of each thread and process.
  41. // After calling this function, if an exception occurs in a process
  42. // that is not being debugged, and the exception makes it to the
  43. // unhandled exception filter, that filter will call the exception
  44. // filter function specified by the lpTopLevelExceptionFilter parameter.
  45. ::SetUnhandledExceptionFilter( unhandledExceptionHandler );
  46. // Since DBGHELP.dll is not inherently thread-safe, making calls into it
  47. // from more than one thread simultaneously may yield undefined behavior.
  48. // This means that if your application has multiple threads, or is
  49. // called by multiple threads in a non-synchronized manner, you need to
  50. // make sure that all calls into DBGHELP.dll are isolated via a global
  51. // critical section.
  52. s_pCriticalSection = new CRITICAL_SECTION;
  53. if( s_pCriticalSection )
  54. InitializeCriticalSection( s_pCriticalSection );
  55. }
  56. //-----------------------------------------------------------------------------
  57. // Name: ~CMiniDumper()
  58. // Desc: Destructor
  59. //-----------------------------------------------------------------------------
  60. CMiniDumper::~CMiniDumper( void )
  61. {
  62. if( s_pCriticalSection )
  63. {
  64. DeleteCriticalSection( s_pCriticalSection );
  65. delete s_pCriticalSection;
  66. }
  67. }
  68. //-----------------------------------------------------------------------------
  69. // Name: unhandledExceptionHandler()
  70. // Desc: Call-back filter function for unhandled exceptions
  71. //-----------------------------------------------------------------------------
  72. LONG CMiniDumper::unhandledExceptionHandler( _EXCEPTION_POINTERS *pExceptionInfo )
  73. {
  74. if( !s_pMiniDumper )
  75. return EXCEPTION_CONTINUE_SEARCH;
  76. return s_pMiniDumper->writeMiniDump( pExceptionInfo );
  77. }
  78. //-----------------------------------------------------------------------------
  79. // Name: setMiniDumpFileName()
  80. // Desc:
  81. //-----------------------------------------------------------------------------
  82. void CMiniDumper::setMiniDumpFileName( void )
  83. {
  84. time_t currentTime;
  85. time( &currentTime );
  86. _tcssprintf( m_szMiniDumpPath,
  87. _T( "%s%s.%ld.dmp" ),
  88. m_szAppPath,
  89. m_szAppBaseName,
  90. currentTime );
  91. }
  92. //-----------------------------------------------------------------------------
  93. // Name: getImpersonationToken()
  94. // Desc: The method acts as a potential workaround for the fact that the
  95. // current thread may not have a token assigned to it, and if not, the
  96. // process token is received.
  97. //-----------------------------------------------------------------------------
  98. bool CMiniDumper::getImpersonationToken( HANDLE* phToken )
  99. {
  100. *phToken = NULL;
  101. if( !OpenThreadToken( GetCurrentThread(),
  102. TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
  103. TRUE,
  104. phToken) )
  105. {
  106. if( GetLastError() == ERROR_NO_TOKEN )
  107. {
  108. // No impersonation token for the current thread is available.
  109. // Let's go for the process token instead.
  110. if( !OpenProcessToken( GetCurrentProcess(),
  111. TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES,
  112. phToken) )
  113. return false;
  114. }
  115. else
  116. return false;
  117. }
  118. return true;
  119. }
  120. //-----------------------------------------------------------------------------
  121. // Name: enablePrivilege()
  122. // Desc: Since a MiniDump contains a lot of meta-data about the OS and
  123. // application state at the time of the dump, it is a rather privileged
  124. // operation. This means we need to set the SeDebugPrivilege to be able
  125. // to call MiniDumpWriteDump.
  126. //-----------------------------------------------------------------------------
  127. BOOL CMiniDumper::enablePrivilege( LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld )
  128. {
  129. BOOL bOk = FALSE;
  130. TOKEN_PRIVILEGES tp;
  131. tp.PrivilegeCount = 1;
  132. tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  133. bOk = LookupPrivilegeValue( 0, pszPriv, &tp.Privileges[0].Luid );
  134. if( bOk )
  135. {
  136. DWORD cbOld = sizeof(*ptpOld);
  137. bOk = AdjustTokenPrivileges( hToken, FALSE, &tp, cbOld, ptpOld, &cbOld );
  138. }
  139. return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
  140. }
  141. //-----------------------------------------------------------------------------
  142. // Name: restorePrivilege()
  143. // Desc:
  144. //-----------------------------------------------------------------------------
  145. BOOL CMiniDumper::restorePrivilege( HANDLE hToken, TOKEN_PRIVILEGES* ptpOld )
  146. {
  147. BOOL bOk = AdjustTokenPrivileges(hToken, FALSE, ptpOld, 0, NULL, NULL);
  148. return ( bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()) );
  149. }
  150. //-----------------------------------------------------------------------------
  151. // Name: writeMiniDump()
  152. // Desc:
  153. //-----------------------------------------------------------------------------
  154. LONG CMiniDumper::writeMiniDump( _EXCEPTION_POINTERS *pExceptionInfo )
  155. {
  156. LONG retval = EXCEPTION_CONTINUE_SEARCH;
  157. m_pExceptionInfo = pExceptionInfo;
  158. HANDLE hImpersonationToken = NULL;
  159. if( !getImpersonationToken( &hImpersonationToken ) )
  160. return FALSE;
  161. // You have to find the right dbghelp.dll.
  162. // Look next to the EXE first since the one in System32 might be old (Win2k)
  163. HMODULE hDll = NULL;
  164. TCHAR szDbgHelpPath[MAX_PATH];
  165. if( GetModuleFileName( NULL, m_szAppPath, _MAX_PATH ) )
  166. {
  167. TCHAR *pSlash = _tcsrchr( m_szAppPath, '\\' );
  168. if( pSlash )
  169. {
  170. _tcscpy_s( m_szAppBaseName, pSlash + 1);
  171. *(pSlash+1) = 0;
  172. }
  173. _tcscpy_s( szDbgHelpPath, m_szAppPath );
  174. _tcscat_s( szDbgHelpPath, _T("DBGHELP.DLL") );
  175. hDll = ::LoadLibrary( szDbgHelpPath );
  176. }
  177. if( hDll == NULL )
  178. {
  179. // If we haven't found it yet - try one more time.
  180. hDll = ::LoadLibrary( _T("DBGHELP.DLL") );
  181. }
  182. LPCTSTR szResult = NULL;
  183. if( hDll )
  184. {
  185. // Get the address of the MiniDumpWriteDump function, which writes
  186. // user-mode mini-dump information to a specified file.
  187. MINIDUMPWRITEDUMP MiniDumpWriteDump =
  188. (MINIDUMPWRITEDUMP)::GetProcAddress( hDll, "MiniDumpWriteDump" );
  189. if( MiniDumpWriteDump != NULL )
  190. {
  191. TCHAR szScratch[USER_DATA_BUFFER_SIZE];
  192. setMiniDumpFileName();
  193. // Ask the user if he or she wants to save a mini-dump file...
  194. _tcssprintf( szScratch,
  195. _T("There was an unexpected error:\n\nWould you ")
  196. _T("like to create a mini-dump file?\n\n%s " ),
  197. m_szMiniDumpPath);
  198. // Create the mini-dump file...
  199. HANDLE hFile = ::CreateFile( m_szMiniDumpPath,
  200. GENERIC_WRITE,
  201. FILE_SHARE_WRITE,
  202. NULL,
  203. CREATE_ALWAYS,
  204. FILE_ATTRIBUTE_NORMAL,
  205. NULL );
  206. if( hFile != INVALID_HANDLE_VALUE )
  207. {
  208. _MINIDUMP_EXCEPTION_INFORMATION ExInfo;
  209. ExInfo.ThreadId = ::GetCurrentThreadId();
  210. ExInfo.ExceptionPointers = pExceptionInfo;
  211. ExInfo.ClientPointers = NULL;
  212. // We need the SeDebugPrivilege to be able to run MiniDumpWriteDump
  213. TOKEN_PRIVILEGES tp;
  214. BOOL bPrivilegeEnabled = enablePrivilege( SE_DEBUG_NAME, hImpersonationToken, &tp );
  215. BOOL bOk;
  216. // DBGHELP.dll is not thread-safe, so we need to restrict access...
  217. EnterCriticalSection( s_pCriticalSection );
  218. {
  219. // Write out the mini-dump data to the file...
  220. bOk = MiniDumpWriteDump( GetCurrentProcess(),
  221. GetCurrentProcessId(),
  222. hFile,
  223. MiniDumpNormal,
  224. &ExInfo,
  225. NULL,
  226. NULL );
  227. }
  228. LeaveCriticalSection( s_pCriticalSection );
  229. // Restore the privileges when done
  230. if( bPrivilegeEnabled )
  231. restorePrivilege( hImpersonationToken, &tp );
  232. if( bOk )
  233. {
  234. szResult = NULL;
  235. retval = EXCEPTION_EXECUTE_HANDLER;
  236. }
  237. else
  238. {
  239. _tcssprintf( szScratch,
  240. _T("Failed to save the mini-dump file to '%s' (error %d)"),
  241. m_szMiniDumpPath,
  242. GetLastError() );
  243. szResult = szScratch;
  244. }
  245. ::CloseHandle( hFile );
  246. }
  247. else
  248. {
  249. _tcssprintf( szScratch,
  250. _T("Failed to create the mini-dump file '%s' (error %d)"),
  251. m_szMiniDumpPath,
  252. GetLastError() );
  253. szResult = szScratch;
  254. }
  255. }
  256. else
  257. {
  258. szResult = _T( "Call to GetProcAddress failed to find MiniDumpWriteDump. ")
  259. _T("The DBGHELP.DLL is possibly outdated." );
  260. }
  261. }
  262. else
  263. {
  264. szResult = _T( "Call to LoadLibrary failed to find DBGHELP.DLL." );
  265. }
  266. if( szResult && m_bPromptUserForMiniDump )
  267. ::MessageBox( NULL, szResult, NULL, MB_OK );
  268. TerminateProcess( GetCurrentProcess(), 0 );
  269. return retval;
  270. }