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.

573 lines
14 KiB

  1. //========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "tier0/platform.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( _PS3 )
  12. #include "ps3/ps3_console.h"
  13. #elif defined( _WIN32 )
  14. #include <windows.h>
  15. #elif POSIX
  16. char *GetCommandLine();
  17. #endif
  18. #include "resource.h"
  19. #include "tier0/valve_on.h"
  20. #include "tier0/threadtools.h"
  21. #include "tier0/icommandline.h"
  22. // NOTE: This has to be the last file included!
  23. #include "tier0/memdbgon.h"
  24. class CDialogInitInfo
  25. {
  26. public:
  27. const tchar *m_pFilename;
  28. int m_iLine;
  29. const tchar *m_pExpression;
  30. };
  31. class CAssertDisable
  32. {
  33. public:
  34. tchar m_Filename[512];
  35. // If these are not -1, then this CAssertDisable only disables asserts on lines between
  36. // these values (inclusive).
  37. int m_LineMin;
  38. int m_LineMax;
  39. // Decremented each time we hit this assert and ignore it, until it's 0.
  40. // Then the CAssertDisable is removed.
  41. // If this is -1, then we always ignore this assert.
  42. int m_nIgnoreTimes;
  43. CAssertDisable *m_pNext;
  44. };
  45. #ifdef _WIN32
  46. static HINSTANCE g_hTier0Instance = 0;
  47. #endif
  48. static bool g_bAssertsEnabled = true;
  49. static CAssertDisable *g_pAssertDisables = NULL;
  50. #if ( defined( _WIN32 ) && !defined( _X360 ) )
  51. static int g_iLastLineRange = 5;
  52. static int g_nLastIgnoreNumTimes = 1;
  53. #endif
  54. #if defined( _X360 ) || defined( _PS3 )
  55. static int g_VXConsoleAssertReturnValue = -1;
  56. #endif
  57. // Set to true if they want to break in the debugger.
  58. static bool g_bBreak = false;
  59. static CDialogInitInfo g_Info;
  60. static bool g_bDisableAsserts = false;
  61. // -------------------------------------------------------------------------------- //
  62. // Internal functions.
  63. // -------------------------------------------------------------------------------- //
  64. #if defined(_WIN32) && !defined(STATIC_TIER0)
  65. BOOL WINAPI DllMain(
  66. HINSTANCE hinstDLL, // handle to the DLL module
  67. DWORD fdwReason, // reason for calling function
  68. LPVOID lpvReserved // reserved
  69. )
  70. {
  71. g_hTier0Instance = hinstDLL;
  72. return true;
  73. }
  74. #endif
  75. static bool IsDebugBreakEnabled()
  76. {
  77. static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-debugbreak") ) != NULL );
  78. return bResult;
  79. }
  80. static bool AssertStack()
  81. {
  82. static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-assertstack") ) != NULL );
  83. return bResult;
  84. }
  85. static bool AreAssertsDisabled()
  86. {
  87. static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-noassert") ) != NULL );
  88. return bResult || g_bDisableAsserts;
  89. }
  90. static bool AllAssertOnce()
  91. {
  92. static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-assertonce") ) != NULL );
  93. return bResult;
  94. }
  95. static bool AreAssertsEnabledInFileLine( const tchar *pFilename, int iLine )
  96. {
  97. CAssertDisable **pPrev = &g_pAssertDisables;
  98. CAssertDisable *pNext;
  99. for ( CAssertDisable *pCur=g_pAssertDisables; pCur; pCur=pNext )
  100. {
  101. pNext = pCur->m_pNext;
  102. if ( _tcsicmp( pFilename, pCur->m_Filename ) == 0 )
  103. {
  104. // Are asserts disabled in the whole file?
  105. bool bAssertsEnabled = true;
  106. if ( pCur->m_LineMin == -1 && pCur->m_LineMax == -1 )
  107. bAssertsEnabled = false;
  108. // Are asserts disabled on the specified line?
  109. if ( iLine >= pCur->m_LineMin && iLine <= pCur->m_LineMax )
  110. bAssertsEnabled = false;
  111. if ( !bAssertsEnabled )
  112. {
  113. // If this assert is only disabled for the next N times, then countdown..
  114. if ( pCur->m_nIgnoreTimes > 0 )
  115. {
  116. --pCur->m_nIgnoreTimes;
  117. if ( pCur->m_nIgnoreTimes == 0 )
  118. {
  119. // Remove this one from the list.
  120. *pPrev = pNext;
  121. delete pCur;
  122. continue;
  123. }
  124. }
  125. return false;
  126. }
  127. }
  128. pPrev = &pCur->m_pNext;
  129. }
  130. return true;
  131. }
  132. CAssertDisable* CreateNewAssertDisable( const tchar *pFilename )
  133. {
  134. CAssertDisable *pDisable = new CAssertDisable;
  135. pDisable->m_pNext = g_pAssertDisables;
  136. g_pAssertDisables = pDisable;
  137. pDisable->m_LineMin = pDisable->m_LineMax = -1;
  138. pDisable->m_nIgnoreTimes = -1;
  139. _tcsncpy( pDisable->m_Filename, g_Info.m_pFilename, sizeof( pDisable->m_Filename ) - 1 );
  140. pDisable->m_Filename[ sizeof( pDisable->m_Filename ) - 1 ] = 0;
  141. return pDisable;
  142. }
  143. void IgnoreAssertsInCurrentFile()
  144. {
  145. CreateNewAssertDisable( g_Info.m_pFilename );
  146. }
  147. CAssertDisable* IgnoreAssertsNearby( int nRange )
  148. {
  149. CAssertDisable *pDisable = CreateNewAssertDisable( g_Info.m_pFilename );
  150. pDisable->m_LineMin = g_Info.m_iLine - nRange;
  151. pDisable->m_LineMax = g_Info.m_iLine - nRange;
  152. return pDisable;
  153. }
  154. #if ( defined( _WIN32 ) && !defined( _X360 ) )
  155. INT_PTR CALLBACK AssertDialogProc(
  156. HWND hDlg, // handle to dialog box
  157. UINT uMsg, // message
  158. WPARAM wParam, // first message parameter
  159. LPARAM lParam // second message parameter
  160. )
  161. {
  162. switch( uMsg )
  163. {
  164. case WM_INITDIALOG:
  165. {
  166. #ifdef TCHAR_IS_WCHAR
  167. SetDlgItemTextW( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression );
  168. SetDlgItemTextW( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename );
  169. #else
  170. SetDlgItemText( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression );
  171. SetDlgItemText( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename );
  172. #endif
  173. SetDlgItemInt( hDlg, IDC_LINE_CONTROL, g_Info.m_iLine, false );
  174. SetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, g_iLastLineRange, false );
  175. SetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, g_nLastIgnoreNumTimes, false );
  176. // Center the dialog.
  177. RECT rcDlg, rcDesktop;
  178. GetWindowRect( hDlg, &rcDlg );
  179. GetWindowRect( GetDesktopWindow(), &rcDesktop );
  180. SetWindowPos(
  181. hDlg,
  182. HWND_TOP,
  183. ((rcDesktop.right-rcDesktop.left) - (rcDlg.right-rcDlg.left)) / 2,
  184. ((rcDesktop.bottom-rcDesktop.top) - (rcDlg.bottom-rcDlg.top)) / 2,
  185. 0,
  186. 0,
  187. SWP_NOSIZE );
  188. }
  189. return true;
  190. case WM_COMMAND:
  191. {
  192. switch( LOWORD( wParam ) )
  193. {
  194. case IDC_IGNORE_FILE:
  195. {
  196. IgnoreAssertsInCurrentFile();
  197. EndDialog( hDlg, 0 );
  198. return true;
  199. }
  200. // Ignore this assert N times.
  201. case IDC_IGNORE_THIS:
  202. {
  203. BOOL bTranslated = false;
  204. UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, &bTranslated, false );
  205. if ( bTranslated && value > 1 )
  206. {
  207. CAssertDisable *pDisable = IgnoreAssertsNearby( 0 );
  208. pDisable->m_nIgnoreTimes = value - 1;
  209. g_nLastIgnoreNumTimes = value;
  210. }
  211. EndDialog( hDlg, 0 );
  212. return true;
  213. }
  214. // Always ignore this assert.
  215. case IDC_IGNORE_ALWAYS:
  216. {
  217. IgnoreAssertsNearby( 0 );
  218. EndDialog( hDlg, 0 );
  219. return true;
  220. }
  221. case IDC_IGNORE_NEARBY:
  222. {
  223. BOOL bTranslated = false;
  224. UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, &bTranslated, false );
  225. if ( !bTranslated || value < 1 )
  226. return true;
  227. IgnoreAssertsNearby( value );
  228. EndDialog( hDlg, 0 );
  229. return true;
  230. }
  231. case IDC_IGNORE_ALL:
  232. {
  233. g_bAssertsEnabled = false;
  234. EndDialog( hDlg, 0 );
  235. return true;
  236. }
  237. case IDC_BREAK:
  238. {
  239. g_bBreak = true;
  240. EndDialog( hDlg, 0 );
  241. return true;
  242. }
  243. }
  244. case WM_KEYDOWN:
  245. {
  246. // Escape?
  247. if ( wParam == 2 )
  248. {
  249. // Ignore this assert.
  250. EndDialog( hDlg, 0 );
  251. return true;
  252. }
  253. }
  254. }
  255. return true;
  256. }
  257. return FALSE;
  258. }
  259. static HWND g_hBestParentWindow;
  260. static BOOL CALLBACK ParentWindowEnumProc(
  261. HWND hWnd, // handle to parent window
  262. LPARAM lParam // application-defined value
  263. )
  264. {
  265. if ( IsWindowVisible( hWnd ) )
  266. {
  267. DWORD procID;
  268. GetWindowThreadProcessId( hWnd, &procID );
  269. if ( procID == (DWORD)lParam )
  270. {
  271. g_hBestParentWindow = hWnd;
  272. return FALSE; // don't iterate any more.
  273. }
  274. }
  275. return TRUE;
  276. }
  277. static HWND FindLikelyParentWindow()
  278. {
  279. // Enumerate top-level windows and take the first visible one with our processID.
  280. g_hBestParentWindow = NULL;
  281. EnumWindows( ParentWindowEnumProc, GetCurrentProcessId() );
  282. return g_hBestParentWindow;
  283. }
  284. #endif
  285. // -------------------------------------------------------------------------------- //
  286. // Interface functions.
  287. // -------------------------------------------------------------------------------- //
  288. // provides access to the global that turns asserts on and off
  289. PLATFORM_INTERFACE bool AreAllAssertsDisabled()
  290. {
  291. return !g_bAssertsEnabled;
  292. }
  293. PLATFORM_INTERFACE void SetAllAssertsDisabled( bool bAssertsDisabled )
  294. {
  295. g_bAssertsEnabled = !bAssertsDisabled;
  296. }
  297. PLATFORM_INTERFACE bool ShouldUseNewAssertDialog()
  298. {
  299. static bool bMPIWorker = ( _tcsstr( Plat_GetCommandLine(), _T("-mpi_worker") ) != NULL );
  300. if ( bMPIWorker )
  301. {
  302. return false;
  303. }
  304. #ifdef DBGFLAG_ASSERTDLG
  305. return true; // always show an assert dialog
  306. #else
  307. return Plat_IsInDebugSession(); // only show an assert dialog if the process is being debugged
  308. #endif // DBGFLAG_ASSERTDLG
  309. }
  310. PLATFORM_INTERFACE bool DoNewAssertDialog( const tchar *pFilename, int line, const tchar *pExpression )
  311. {
  312. LOCAL_THREAD_LOCK();
  313. if ( AreAssertsDisabled() )
  314. return false;
  315. // If they have the old mode enabled (always break immediately), then just break right into
  316. // the debugger like we used to do.
  317. if ( IsDebugBreakEnabled() )
  318. return true;
  319. // Have ALL Asserts been disabled?
  320. if ( !g_bAssertsEnabled )
  321. return false;
  322. // Has this specific Assert been disabled?
  323. if ( !AreAssertsEnabledInFileLine( pFilename, line ) )
  324. return false;
  325. // Now create the dialog.
  326. g_Info.m_pFilename = pFilename;
  327. g_Info.m_iLine = line;
  328. g_Info.m_pExpression = pExpression;
  329. if ( AssertStack() )
  330. {
  331. IgnoreAssertsNearby( 0 );
  332. // @TODO: add-back callstack spew support
  333. Warning( "%s (%d) : Assertion callstack...(NOT IMPLEMENTED IN NEW LOGGING SYSTEM.)\n", pFilename, line );
  334. // Warning_SpewCallStack( 10, "%s (%d) : Assertion callstack...\n", pFilename, line );
  335. return false;
  336. }
  337. if( AllAssertOnce() )
  338. {
  339. IgnoreAssertsNearby( 0 );
  340. }
  341. g_bBreak = false;
  342. #if defined( _X360 )
  343. char cmdString[XBX_MAX_RCMDLENGTH];
  344. // Before calling VXConsole, init the global variable that receives the result
  345. g_VXConsoleAssertReturnValue = -1;
  346. // Message VXConsole to pop up a PC-side Assert dialog
  347. _snprintf( cmdString, sizeof(cmdString), "Assert() 0x%.8x File: %s\tLine: %d\t%s",
  348. &g_VXConsoleAssertReturnValue, pFilename, line, pExpression );
  349. XBX_SendRemoteCommand( cmdString, false );
  350. // We sent a synchronous message, so g_xbx_dbgVXConsoleAssertReturnValue should have been overwritten by now
  351. if ( g_VXConsoleAssertReturnValue == -1 )
  352. {
  353. // VXConsole isn't connected/running - default to the old behaviour (break)
  354. g_bBreak = true;
  355. }
  356. else
  357. {
  358. // Respond to what the user selected
  359. switch( g_VXConsoleAssertReturnValue )
  360. {
  361. case ASSERT_ACTION_IGNORE_FILE:
  362. IgnoreAssertsInCurrentFile();
  363. break;
  364. case ASSERT_ACTION_IGNORE_THIS:
  365. // Ignore this Assert once
  366. break;
  367. case ASSERT_ACTION_BREAK:
  368. // Break on this Assert
  369. g_bBreak = true;
  370. break;
  371. case ASSERT_ACTION_IGNORE_ALL:
  372. // Ignore all Asserts from now on
  373. g_bAssertsEnabled = false;
  374. break;
  375. case ASSERT_ACTION_IGNORE_ALWAYS:
  376. // Ignore this Assert from now on
  377. IgnoreAssertsNearby( 0 );
  378. break;
  379. case ASSERT_ACTION_OTHER:
  380. default:
  381. // Error... just break
  382. XBX_Error( "DoNewAssertDialog: invalid Assert response returned from VXConsole - breaking to debugger" );
  383. g_bBreak = true;
  384. break;
  385. }
  386. }
  387. #elif defined( _PS3 )
  388. // There are a few ways to handle this sort of assert behavior with the PS3 / Target Manager API.
  389. // One is to use a DebuggerBreak per usual, and then SNProcessContinue in the TMAPI to make
  390. // the game resume after a breakpoint. (You can use snIsDebuggerPresent() to determine if
  391. // the debugger is attached, although really it doesn't matter here.)
  392. // This doesn't work because the DebuggerBreak() is actually an interrupt op, and so Continue()
  393. // won't continue past it -- you need to do that from inside the ProDG debugger itself.
  394. // Another is to wait on a mutex here and then trip it from the TMAPI, but there isn't
  395. // a clean way to trip sync primitives from TMAPI.
  396. // Another way is to suspend the thread here and have TMAPI resume it.
  397. // The simplest way is to spin-wait on a shared variable that you expect the
  398. // TMAPI to poke into memory. I'm trying that.
  399. char cmdString[XBX_MAX_RCMDLENGTH];
  400. // Before calling VXConsole, init the global variable that receives the result
  401. g_VXConsoleAssertReturnValue = -1;
  402. // Message VXConsole to pop up a PC-side Assert dialog
  403. _snprintf( cmdString, sizeof(cmdString), "Assert() 0x%.8x File: %s\tLine: %d\t%s",
  404. &g_VXConsoleAssertReturnValue, pFilename, line, pExpression );
  405. XBX_SendRemoteCommand( cmdString, false );
  406. if ( g_pValvePS3Console->IsConsoleConnected() )
  407. {
  408. // DebuggerBreak();
  409. while ( g_VXConsoleAssertReturnValue == -1 )
  410. {
  411. ThreadSleep( 1000 );
  412. }
  413. // assume that the VX has poked the return value
  414. // Respond to what the user selected
  415. switch( g_VXConsoleAssertReturnValue )
  416. {
  417. case ASSERT_ACTION_IGNORE_FILE:
  418. IgnoreAssertsInCurrentFile();
  419. break;
  420. case ASSERT_ACTION_IGNORE_THIS:
  421. // Ignore this Assert once
  422. break;
  423. case ASSERT_ACTION_BREAK:
  424. // Break on this Assert
  425. g_bBreak = true;
  426. break;
  427. case ASSERT_ACTION_IGNORE_ALL:
  428. // Ignore all Asserts from now on
  429. g_bAssertsEnabled = false;
  430. break;
  431. case ASSERT_ACTION_IGNORE_ALWAYS:
  432. // Ignore this Assert from now on
  433. IgnoreAssertsNearby( 0 );
  434. break;
  435. case ASSERT_ACTION_OTHER:
  436. default:
  437. // nothing.
  438. break;
  439. }
  440. }
  441. else if ( g_pValvePS3Console->IsDebuggerPresent() )
  442. {
  443. g_bBreak = true;
  444. }
  445. else
  446. {
  447. // ignore the assert
  448. }
  449. #elif defined( POSIX )
  450. fprintf(stderr, "%s %i %s\n", pFilename, line, pExpression);
  451. if ( getenv( "RAISE_ON_ASSERT" ) )
  452. {
  453. DebuggerBreak();
  454. g_bBreak = true;
  455. }
  456. #elif defined( _WIN32 )
  457. if ( !g_hTier0Instance || !ThreadInMainThread() )
  458. {
  459. int result = MessageBox( NULL, pExpression, "Assertion Failed", MB_SYSTEMMODAL | MB_CANCELTRYCONTINUE );
  460. if ( result == IDCANCEL )
  461. {
  462. IgnoreAssertsNearby( 0 );
  463. }
  464. else if ( result == IDCONTINUE )
  465. {
  466. g_bBreak = true;
  467. }
  468. }
  469. else
  470. {
  471. HWND hParentWindow = FindLikelyParentWindow();
  472. DialogBox( g_hTier0Instance, MAKEINTRESOURCE( IDD_ASSERT_DIALOG ), hParentWindow, AssertDialogProc );
  473. }
  474. #endif
  475. return g_bBreak;
  476. }