Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

616 lines
15 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #include "pch_tier0.h"
  7. #include "tier0/valve_off.h"
  8. #ifdef _X360
  9. #include "xbox/xbox_console.h"
  10. #include "xbox/xbox_vxconsole.h"
  11. #elif defined( _WIN32 )
  12. #include <windows.h>
  13. #elif defined( POSIX )
  14. #include <stdlib.h>
  15. #endif
  16. #include "resource.h"
  17. #include "tier0/valve_on.h"
  18. #include "tier0/threadtools.h"
  19. #if defined( POSIX )
  20. #include <dlfcn.h>
  21. #endif
  22. #if defined( LINUX ) || defined( USE_SDL )
  23. // We lazily load the SDL shared object, and only reference functions if it's
  24. // available, so this can be included on the dedicated server too.
  25. #include "SDL.h"
  26. typedef int ( SDLCALL FUNC_SDL_ShowMessageBox )( const SDL_MessageBoxData *messageboxdata, int *buttonid );
  27. #endif
  28. class CDialogInitInfo
  29. {
  30. public:
  31. const tchar *m_pFilename;
  32. int m_iLine;
  33. const tchar *m_pExpression;
  34. };
  35. class CAssertDisable
  36. {
  37. public:
  38. tchar m_Filename[512];
  39. // If these are not -1, then this CAssertDisable only disables asserts on lines between
  40. // these values (inclusive).
  41. int m_LineMin;
  42. int m_LineMax;
  43. // Decremented each time we hit this assert and ignore it, until it's 0.
  44. // Then the CAssertDisable is removed.
  45. // If this is -1, then we always ignore this assert.
  46. int m_nIgnoreTimes;
  47. CAssertDisable *m_pNext;
  48. };
  49. #ifdef _WIN32
  50. static HINSTANCE g_hTier0Instance = 0;
  51. #endif
  52. static bool g_bAssertsEnabled = true;
  53. static CAssertDisable *g_pAssertDisables = NULL;
  54. #if ( defined( _WIN32 ) && !defined( _X360 ) )
  55. static int g_iLastLineRange = 5;
  56. static int g_nLastIgnoreNumTimes = 1;
  57. #endif
  58. #if defined( _X360 )
  59. static int g_VXConsoleAssertReturnValue = -1;
  60. #endif
  61. // Set to true if they want to break in the debugger.
  62. static bool g_bBreak = false;
  63. static CDialogInitInfo g_Info;
  64. // -------------------------------------------------------------------------------- //
  65. // Internal functions.
  66. // -------------------------------------------------------------------------------- //
  67. #if defined(_WIN32) && !defined(STATIC_TIER0)
  68. extern "C" BOOL APIENTRY MemDbgDllMain( HMODULE hDll, DWORD dwReason, PVOID pvReserved );
  69. BOOL WINAPI DllMain(
  70. HINSTANCE hinstDLL, // handle to the DLL module
  71. DWORD fdwReason, // reason for calling function
  72. LPVOID lpvReserved // reserved
  73. )
  74. {
  75. g_hTier0Instance = hinstDLL;
  76. #ifdef DEBUG
  77. MemDbgDllMain( hinstDLL, fdwReason, lpvReserved );
  78. #endif
  79. return true;
  80. }
  81. #endif
  82. static bool IsDebugBreakEnabled()
  83. {
  84. static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-debugbreak") ) != NULL ) || \
  85. ( _tcsstr( Plat_GetCommandLine(), _T("-raiseonassert") ) != NULL ) || \
  86. getenv( "RAISE_ON_ASSERT" );
  87. return bResult;
  88. }
  89. static bool AreAssertsDisabled()
  90. {
  91. static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-noassert") ) != NULL );
  92. return bResult;
  93. }
  94. static bool AreAssertsEnabledInFileLine( const tchar *pFilename, int iLine )
  95. {
  96. CAssertDisable **pPrev = &g_pAssertDisables;
  97. CAssertDisable *pNext;
  98. for ( CAssertDisable *pCur=g_pAssertDisables; pCur; pCur=pNext )
  99. {
  100. pNext = pCur->m_pNext;
  101. if ( _tcsicmp( pFilename, pCur->m_Filename ) == 0 )
  102. {
  103. // Are asserts disabled in the whole file?
  104. bool bAssertsEnabled = true;
  105. if ( pCur->m_LineMin == -1 && pCur->m_LineMax == -1 )
  106. bAssertsEnabled = false;
  107. // Are asserts disabled on the specified line?
  108. if ( iLine >= pCur->m_LineMin && iLine <= pCur->m_LineMax )
  109. bAssertsEnabled = false;
  110. if ( !bAssertsEnabled )
  111. {
  112. // If this assert is only disabled for the next N times, then countdown..
  113. if ( pCur->m_nIgnoreTimes > 0 )
  114. {
  115. --pCur->m_nIgnoreTimes;
  116. if ( pCur->m_nIgnoreTimes == 0 )
  117. {
  118. // Remove this one from the list.
  119. *pPrev = pNext;
  120. delete pCur;
  121. continue;
  122. }
  123. }
  124. return false;
  125. }
  126. }
  127. pPrev = &pCur->m_pNext;
  128. }
  129. return true;
  130. }
  131. CAssertDisable* CreateNewAssertDisable( const tchar *pFilename )
  132. {
  133. CAssertDisable *pDisable = new CAssertDisable;
  134. pDisable->m_pNext = g_pAssertDisables;
  135. g_pAssertDisables = pDisable;
  136. pDisable->m_LineMin = pDisable->m_LineMax = -1;
  137. pDisable->m_nIgnoreTimes = -1;
  138. _tcsncpy( pDisable->m_Filename, g_Info.m_pFilename, sizeof( pDisable->m_Filename ) - 1 );
  139. pDisable->m_Filename[ sizeof( pDisable->m_Filename ) - 1 ] = 0;
  140. return pDisable;
  141. }
  142. void IgnoreAssertsInCurrentFile()
  143. {
  144. CreateNewAssertDisable( g_Info.m_pFilename );
  145. }
  146. CAssertDisable* IgnoreAssertsNearby( int nRange )
  147. {
  148. CAssertDisable *pDisable = CreateNewAssertDisable( g_Info.m_pFilename );
  149. pDisable->m_LineMin = g_Info.m_iLine - nRange;
  150. pDisable->m_LineMax = g_Info.m_iLine - nRange;
  151. return pDisable;
  152. }
  153. #if ( defined( _WIN32 ) && !defined( _X360 ) )
  154. INT_PTR CALLBACK AssertDialogProc(
  155. HWND hDlg, // handle to dialog box
  156. UINT uMsg, // message
  157. WPARAM wParam, // first message parameter
  158. LPARAM lParam // second message parameter
  159. )
  160. {
  161. switch( uMsg )
  162. {
  163. case WM_INITDIALOG:
  164. {
  165. #ifdef TCHAR_IS_WCHAR
  166. SetDlgItemTextW( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression );
  167. SetDlgItemTextW( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename );
  168. #else
  169. SetDlgItemText( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression );
  170. SetDlgItemText( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename );
  171. #endif
  172. SetDlgItemInt( hDlg, IDC_LINE_CONTROL, g_Info.m_iLine, false );
  173. SetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, g_iLastLineRange, false );
  174. SetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, g_nLastIgnoreNumTimes, false );
  175. // Center the dialog.
  176. RECT rcDlg, rcDesktop;
  177. GetWindowRect( hDlg, &rcDlg );
  178. GetWindowRect( GetDesktopWindow(), &rcDesktop );
  179. SetWindowPos(
  180. hDlg,
  181. HWND_TOP,
  182. ((rcDesktop.right-rcDesktop.left) - (rcDlg.right-rcDlg.left)) / 2,
  183. ((rcDesktop.bottom-rcDesktop.top) - (rcDlg.bottom-rcDlg.top)) / 2,
  184. 0,
  185. 0,
  186. SWP_NOSIZE );
  187. }
  188. return true;
  189. case WM_COMMAND:
  190. {
  191. switch( LOWORD( wParam ) )
  192. {
  193. case IDC_IGNORE_FILE:
  194. {
  195. IgnoreAssertsInCurrentFile();
  196. EndDialog( hDlg, 0 );
  197. return true;
  198. }
  199. // Ignore this assert N times.
  200. case IDC_IGNORE_THIS:
  201. {
  202. BOOL bTranslated = false;
  203. UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, &bTranslated, false );
  204. if ( bTranslated && value > 1 )
  205. {
  206. CAssertDisable *pDisable = IgnoreAssertsNearby( 0 );
  207. pDisable->m_nIgnoreTimes = value - 1;
  208. g_nLastIgnoreNumTimes = value;
  209. }
  210. EndDialog( hDlg, 0 );
  211. return true;
  212. }
  213. // Always ignore this assert.
  214. case IDC_IGNORE_ALWAYS:
  215. {
  216. IgnoreAssertsNearby( 0 );
  217. EndDialog( hDlg, 0 );
  218. return true;
  219. }
  220. case IDC_IGNORE_NEARBY:
  221. {
  222. BOOL bTranslated = false;
  223. UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, &bTranslated, false );
  224. if ( !bTranslated || value < 1 )
  225. return true;
  226. IgnoreAssertsNearby( value );
  227. EndDialog( hDlg, 0 );
  228. return true;
  229. }
  230. case IDC_IGNORE_ALL:
  231. {
  232. g_bAssertsEnabled = false;
  233. EndDialog( hDlg, 0 );
  234. return true;
  235. }
  236. case IDC_BREAK:
  237. {
  238. g_bBreak = true;
  239. EndDialog( hDlg, 0 );
  240. return true;
  241. }
  242. }
  243. case WM_KEYDOWN:
  244. {
  245. // Escape?
  246. if ( wParam == 2 )
  247. {
  248. // Ignore this assert.
  249. EndDialog( hDlg, 0 );
  250. return true;
  251. }
  252. }
  253. }
  254. return true;
  255. }
  256. return FALSE;
  257. }
  258. static HWND g_hBestParentWindow;
  259. static BOOL CALLBACK ParentWindowEnumProc(
  260. HWND hWnd, // handle to parent window
  261. LPARAM lParam // application-defined value
  262. )
  263. {
  264. if ( IsWindowVisible( hWnd ) )
  265. {
  266. DWORD procID;
  267. GetWindowThreadProcessId( hWnd, &procID );
  268. if ( procID == (DWORD)lParam )
  269. {
  270. g_hBestParentWindow = hWnd;
  271. return FALSE; // don't iterate any more.
  272. }
  273. }
  274. return TRUE;
  275. }
  276. static HWND FindLikelyParentWindow()
  277. {
  278. // Enumerate top-level windows and take the first visible one with our processID.
  279. g_hBestParentWindow = NULL;
  280. EnumWindows( ParentWindowEnumProc, GetCurrentProcessId() );
  281. return g_hBestParentWindow;
  282. }
  283. #endif // ( defined( _WIN32 ) && !defined( _X360 ) )
  284. // -------------------------------------------------------------------------------- //
  285. // Interface functions.
  286. // -------------------------------------------------------------------------------- //
  287. // provides access to the global that turns asserts on and off
  288. DBG_INTERFACE bool AreAllAssertsDisabled()
  289. {
  290. return !g_bAssertsEnabled;
  291. }
  292. DBG_INTERFACE void SetAllAssertsDisabled( bool bAssertsDisabled )
  293. {
  294. g_bAssertsEnabled = !bAssertsDisabled;
  295. }
  296. #if defined( LINUX ) || defined( USE_SDL )
  297. SDL_Window *g_SDLWindow = NULL;
  298. DBG_INTERFACE void SetAssertDialogParent( struct SDL_Window *window )
  299. {
  300. g_SDLWindow = window;
  301. }
  302. DBG_INTERFACE struct SDL_Window * GetAssertDialogParent()
  303. {
  304. return g_SDLWindow;
  305. }
  306. #endif
  307. DBG_INTERFACE bool ShouldUseNewAssertDialog()
  308. {
  309. static bool bMPIWorker = ( _tcsstr( Plat_GetCommandLine(), _T("-mpi_worker") ) != NULL );
  310. if ( bMPIWorker )
  311. {
  312. return false;
  313. }
  314. #ifdef DBGFLAG_ASSERTDLG
  315. return true; // always show an assert dialog
  316. #else
  317. return Plat_IsInDebugSession(); // only show an assert dialog if the process is being debugged
  318. #endif // DBGFLAG_ASSERTDLG
  319. }
  320. #if defined( POSIX )
  321. #include <execinfo.h>
  322. static void SpewBacktrace()
  323. {
  324. void *buffer[ 16 ];
  325. int nptrs = backtrace( buffer, ARRAYSIZE( buffer ) );
  326. if ( nptrs )
  327. {
  328. char **strings = backtrace_symbols(buffer, nptrs);
  329. if ( strings )
  330. {
  331. for ( int i = 0; i < nptrs; i++)
  332. {
  333. const char *module = strrchr( strings[ i ], '/' );
  334. module = module ? ( module + 1 ) : strings[ i ];
  335. printf(" %s\n", module );
  336. }
  337. free( strings );
  338. }
  339. }
  340. }
  341. #endif
  342. DBG_INTERFACE bool DoNewAssertDialog( const tchar *pFilename, int line, const tchar *pExpression )
  343. {
  344. LOCAL_THREAD_LOCK();
  345. if ( AreAssertsDisabled() )
  346. return false;
  347. // Have ALL Asserts been disabled?
  348. if ( !g_bAssertsEnabled )
  349. return false;
  350. // Has this specific Assert been disabled?
  351. if ( !AreAssertsEnabledInFileLine( pFilename, line ) )
  352. return false;
  353. // Assert not suppressed. Spew it, and optionally a backtrace.
  354. #if defined( POSIX )
  355. if( isatty( STDERR_FILENO ) )
  356. {
  357. #define COLOR_YELLOW "\033[1;33m"
  358. #define COLOR_GREEN "\033[1;32m"
  359. #define COLOR_RED "\033[1;31m"
  360. #define COLOR_END "\033[0m"
  361. fprintf(stderr, COLOR_YELLOW "ASSERT:" COLOR_END " " COLOR_RED "%s" COLOR_GREEN ":%i:" COLOR_END " " COLOR_RED "%s" COLOR_END "\n",
  362. pFilename, line, pExpression);
  363. if ( getenv( "POSIX_ASSERT_BACKTRACE" ) )
  364. {
  365. SpewBacktrace();
  366. }
  367. }
  368. else
  369. #endif
  370. {
  371. fprintf(stderr, "ASSERT: %s:%i: %s\n", pFilename, line, pExpression);
  372. }
  373. // If they have the old mode enabled (always break immediately), then just break right into
  374. // the debugger like we used to do.
  375. if ( IsDebugBreakEnabled() )
  376. return true;
  377. // Now create the dialog. Just return true for old-style debug break upon failure.
  378. g_Info.m_pFilename = pFilename;
  379. g_Info.m_iLine = line;
  380. g_Info.m_pExpression = pExpression;
  381. g_bBreak = false;
  382. #if defined( _X360 )
  383. char cmdString[XBX_MAX_RCMDLENGTH];
  384. // Before calling VXConsole, init the global variable that receives the result
  385. g_VXConsoleAssertReturnValue = -1;
  386. // Message VXConsole to pop up a PC-side Assert dialog
  387. _snprintf( cmdString, sizeof(cmdString), "Assert() 0x%.8x File: %s\tLine: %d\t%s",
  388. &g_VXConsoleAssertReturnValue, pFilename, line, pExpression );
  389. XBX_SendRemoteCommand( cmdString, false );
  390. // We sent a synchronous message, so g_xbx_dbgVXConsoleAssertReturnValue should have been overwritten by now
  391. if ( g_VXConsoleAssertReturnValue == -1 )
  392. {
  393. // VXConsole isn't connected/running - default to the old behaviour (break)
  394. g_bBreak = true;
  395. }
  396. else
  397. {
  398. // Respond to what the user selected
  399. switch( g_VXConsoleAssertReturnValue )
  400. {
  401. case ASSERT_ACTION_IGNORE_FILE:
  402. IgnoreAssertsInCurrentFile();
  403. break;
  404. case ASSERT_ACTION_IGNORE_THIS:
  405. // Ignore this Assert once
  406. break;
  407. case ASSERT_ACTION_BREAK:
  408. // Break on this Assert
  409. g_bBreak = true;
  410. break;
  411. case ASSERT_ACTION_IGNORE_ALL:
  412. // Ignore all Asserts from now on
  413. g_bAssertsEnabled = false;
  414. break;
  415. case ASSERT_ACTION_IGNORE_ALWAYS:
  416. // Ignore this Assert from now on
  417. IgnoreAssertsNearby( 0 );
  418. break;
  419. case ASSERT_ACTION_OTHER:
  420. default:
  421. // Error... just break
  422. XBX_Error( "DoNewAssertDialog: invalid Assert response returned from VXConsole - breaking to debugger" );
  423. g_bBreak = true;
  424. break;
  425. }
  426. }
  427. #elif defined( _WIN32 )
  428. if ( !ThreadInMainThread() )
  429. {
  430. int result = MessageBox( NULL, pExpression, "Assertion Failed", MB_SYSTEMMODAL | MB_CANCELTRYCONTINUE );
  431. if ( result == IDCANCEL )
  432. {
  433. IgnoreAssertsNearby( 0 );
  434. }
  435. else if ( result == IDCONTINUE )
  436. {
  437. g_bBreak = true;
  438. }
  439. }
  440. else
  441. {
  442. HWND hParentWindow = FindLikelyParentWindow();
  443. DialogBox( g_hTier0Instance, MAKEINTRESOURCE( IDD_ASSERT_DIALOG ), hParentWindow, AssertDialogProc );
  444. }
  445. #elif defined( POSIX )
  446. static FUNC_SDL_ShowMessageBox *pfnSDLShowMessageBox = NULL;
  447. if( !pfnSDLShowMessageBox )
  448. {
  449. #ifdef OSX
  450. void *ret = dlopen( "libSDL2-2.0.0.dylib", RTLD_LAZY );
  451. #else
  452. void *ret = dlopen( "libSDL2-2.0.so.0", RTLD_LAZY );
  453. #endif
  454. if ( ret )
  455. { pfnSDLShowMessageBox = ( FUNC_SDL_ShowMessageBox * )dlsym( ret, "SDL_ShowMessageBox" ); }
  456. }
  457. if( pfnSDLShowMessageBox )
  458. {
  459. int buttonid;
  460. char text[ 4096 ];
  461. SDL_MessageBoxData messageboxdata = { 0 };
  462. const char *DefaultAction = Plat_IsInDebugSession() ? "Break" : "Corefile";
  463. SDL_MessageBoxButtonData buttondata[] =
  464. {
  465. { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, IDC_BREAK, DefaultAction },
  466. { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, IDC_IGNORE_THIS, "Ignore" },
  467. { 0, IDC_IGNORE_FILE, "Ignore This File" },
  468. { 0, IDC_IGNORE_ALWAYS, "Always Ignore" },
  469. { 0, IDC_IGNORE_ALL, "Ignore All Asserts" },
  470. };
  471. _snprintf( text, sizeof( text ), "File: %s\nLine: %i\nExpr: %s\n", pFilename, line, pExpression );
  472. text[ sizeof( text ) - 1 ] = 0;
  473. messageboxdata.window = g_SDLWindow;
  474. messageboxdata.title = "Assertion Failed";
  475. messageboxdata.message = text;
  476. messageboxdata.numbuttons = ARRAYSIZE( buttondata );
  477. messageboxdata.buttons = buttondata;
  478. int Ret = ( *pfnSDLShowMessageBox )( &messageboxdata, &buttonid );
  479. if( Ret == -1 )
  480. {
  481. buttonid = IDC_BREAK;
  482. }
  483. switch( buttonid )
  484. {
  485. default:
  486. case IDC_BREAK:
  487. // Break on this Assert
  488. g_bBreak = true;
  489. break;
  490. case IDC_IGNORE_THIS:
  491. // Ignore this Assert once
  492. break;
  493. case IDC_IGNORE_FILE:
  494. IgnoreAssertsInCurrentFile();
  495. break;
  496. case IDC_IGNORE_ALWAYS:
  497. // Ignore this Assert from now on
  498. IgnoreAssertsNearby( 0 );
  499. break;
  500. case IDC_IGNORE_ALL:
  501. // Ignore all Asserts from now on
  502. g_bAssertsEnabled = false;
  503. break;
  504. }
  505. }
  506. else
  507. {
  508. // Couldn't SDL it up
  509. g_bBreak = true;
  510. }
  511. #else
  512. // No dialog mode on this platform
  513. g_bBreak = true;
  514. #endif
  515. return g_bBreak;
  516. }