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.

1084 lines
36 KiB

  1. //====== Copyright �, Valve Corporation, All rights reserved. =================
  2. //
  3. // Purpose: Defines the GC interface exposed to the host
  4. //
  5. //=============================================================================
  6. #include "stdafx.h"
  7. #include "winlite.h"
  8. #include "tier0/minidump.h"
  9. #include "tier1/interface.h"
  10. #include "appframework/iappsystemgroup.h"
  11. #include "filesystem.h"
  12. #include "vstdlib/cvar.h"
  13. #include "signal.h"
  14. #include "gcsdk/steamextra/rtime.h"
  15. #include "gcsdk/directory.h"
  16. #include "gcsdk/gcinterface.h"
  17. #define WINDOWS_LEAN_AND_MEAN
  18. #if !defined( _WIN32_WINNT )
  19. #define _WIN32_WINNT 0x0403
  20. #endif
  21. #include <windows.h>
  22. namespace GCSDK
  23. {
  24. static GCConVar cv_assert_minidump_window( "assert_minidump_window", "28800", "Size of the minidump window in seconds. Each unique assert will dump at most assert_max_minidumps_in_window times in this many seconds" );
  25. static GCConVar cv_assert_max_minidumps_in_window( "assert_max_minidumps_in_window", "5", "The amount of times each unique assert will write a dump in assert_minidump_window seconds" );
  26. static GCConVar enable_assert_minidumps( "enable_assert_minidumps", "1", "An emergency shutoff to prevent the recording or tracking of asserts" );
  27. static GCConVar filter_blank_lines( "filter_blank_lines", "1", "Prevents blank lines from being written or logged" );
  28. //-----------------------------------------------------------------------------
  29. // Purpose: Creates a global pointer to the interface and exposes it to the host
  30. //-----------------------------------------------------------------------------
  31. CGCInterface g_GCInterface;
  32. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CGCInterface, IGameCoordinator, GAMECOORDINATOR_INTERFACE_VERSION, g_GCInterface );
  33. int32 CGCInterface::CDisableAssertRateLimit::s_nDisabledCount = 0;
  34. // Force the linker to include this even though we're in a static lib
  35. void ForceIncludeGCInterface()
  36. {
  37. #pragma comment( linker, "/INCLUDE:" __FUNCDNAME__ )
  38. void *pUnused = &__g_CreateCGCInterfaceIGameCoordinator_reg;
  39. pUnused = NULL;
  40. #ifdef DEBUG
  41. // Adds a note for the deploy tool to not let it prop with a debug GCSDK
  42. printf( "is a debug binary" );
  43. #endif
  44. }
  45. //-----------------------------------------------------------------------------
  46. // Purpose: Overrides the spew func used by Msg and DMsg to print to the console
  47. //-----------------------------------------------------------------------------
  48. class CConsoleLoggingListener : public ILoggingListener
  49. {
  50. public:
  51. virtual void Log( const LoggingContext_t *pContext, const tchar *pMessage )
  52. {
  53. const char *pszFmt = ( sizeof( tchar ) == sizeof( char ) ) ? "%hs" : "%ls";
  54. switch ( pContext->m_Severity )
  55. {
  56. default:
  57. case LS_MESSAGE:
  58. EmitInfo( SPEW_CONSOLE, SPEW_ALWAYS, LOG_ALWAYS, pszFmt, pMessage );
  59. break;
  60. case LS_WARNING:
  61. EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, pszFmt, pMessage );
  62. break;
  63. case LS_ERROR:
  64. case LS_HIGHEST_SEVERITY:
  65. EmitError( SPEW_CONSOLE, pszFmt, pMessage );
  66. break;
  67. case LS_ASSERT:
  68. //if this assert is in a job, display the name of the job as well
  69. if ( ThreadInMainThread() && ( g_pJobCur != NULL ) )
  70. {
  71. pszFmt = ( sizeof( tchar ) == sizeof( char ) ) ? "[Job %s] %hs" : "[Job %s] %ls";
  72. EmitAssertError( SPEW_CONSOLE, pszFmt, g_pJobCur->GetName(), pMessage );
  73. }
  74. else
  75. {
  76. EmitAssertError( SPEW_CONSOLE, pszFmt, pMessage );
  77. }
  78. break;
  79. }
  80. }
  81. };
  82. static CNonFatalLoggingResponsePolicy s_NonFatalLoggingResponsePolicy;
  83. static CConsoleLoggingListener s_ConsoleLoggingListener;
  84. //-----------------------------------------------------------------------------
  85. // Purpose: Prints an assert to the console
  86. //-----------------------------------------------------------------------------
  87. class CGCAssertionFailureListener : public IAssertionFailureListener
  88. {
  89. public:
  90. CGCAssertionFailureListener( void )
  91. : IAssertionFailureListener( false )
  92. {
  93. }
  94. virtual void *AssertionFailure( const char *pFormattedMsg, const tchar *pchFile, int nLine, const tchar *pchFunction, const tchar *pchRawExpression, int nInstanceReportCount, AssertionType_t nType, bool bFatal ) OVERRIDE
  95. {
  96. if ( Plat_IsInDebugSession() )
  97. return NULL;
  98. bool bShouldWriteMinidump = false;
  99. GGCInterface()->RecordAssert( pchFile, nLine, pFormattedMsg, &bShouldWriteMinidump );
  100. return bShouldWriteMinidump ? this : NULL;
  101. }
  102. virtual void MiniDumpHandler( const MiniDumpHandlerData_t &HandlerData, const char *pFormattedMsg, const tchar *pchFile, int nLine, const tchar *pchFunction, const tchar *pchRawExpression, int nInstanceReportCount, AssertionType_t nType, bool bFatal ) OVERRIDE
  103. {
  104. //re-route to default minidump handler (treat it the same as a crash)
  105. CFmtStr minidumpNameToken( "assert_%s_%d", V_GetFileName( pchFile ), nLine );
  106. MiniDumpOptionalData_t optionalData( minidumpNameToken.Access() );
  107. MiniDumpHandlerData_t modifiableHandlerData( HandlerData );
  108. modifiableHandlerData.SetOptionalData( optionalData );
  109. //write to disk
  110. Tier0GenericMiniDumpHandlerEx( modifiableHandlerData, NULL, MINIDUMP_ADDITIONAL_FLAG_PRINT_MESSAGE );
  111. }
  112. };
  113. static CGCAssertionFailureListener sg_GCAssertionFailureHandler;
  114. static void ProtobufLogHandler( ::google::protobuf::LogLevel level, const char* filename, int line, const std::string& message )
  115. {
  116. EG_MSG( g_EGMessages, "Protobuf %s(%d): %s\n", filename, line, message.c_str() );
  117. AssertFatalMsg( level != google::protobuf::LOGLEVEL_FATAL, "Fatal protobuf assert %s(%d): %s", filename, line, message.c_str() );
  118. }
  119. //-----------------------------------------------------------------------------
  120. // Purpose: Initializes the underlying libraries
  121. //-----------------------------------------------------------------------------
  122. static class CGCAppSystemGroup : public CAppSystemGroup
  123. {
  124. public:
  125. CGCAppSystemGroup() {}
  126. void SetPath ( const char *pchBinaryPath ) { m_sBinaryPath = pchBinaryPath; }
  127. // Implementation of IAppSystemGroup
  128. virtual bool Create() OVERRIDE
  129. {
  130. AppModule_t cvarModule = LoadModule( VStdLib_GetICVarFactory() );
  131. AddSystem( cvarModule, CVAR_INTERFACE_VERSION );
  132. AppSystemInfo_t appSystems[] =
  133. {
  134. { "filesystem_stdio.dll", FILESYSTEM_INTERFACE_VERSION },
  135. { "", "" } // Required to terminate the list
  136. };
  137. CUtlVector<CUtlString> vecFullPaths;
  138. AppSystemInfo_t *pSystem = appSystems;
  139. while( pSystem->m_pModuleName[0] != '\0' )
  140. {
  141. CUtlString &strNewPath = vecFullPaths[ vecFullPaths.AddToTail() ];
  142. strNewPath.Format( "%s%s%s", m_sBinaryPath.Get(), CORRECT_PATH_SEPARATOR_S, pSystem->m_pModuleName );
  143. pSystem->m_pModuleName = strNewPath.Get();
  144. pSystem++;
  145. }
  146. return AddSystems( appSystems );
  147. }
  148. virtual bool PreInit() OVERRIDE
  149. {
  150. CreateInterfaceFn factory = GetFactory();
  151. ConnectTier1Libraries( &factory, 1 );
  152. ConnectTier2Libraries( &factory, 1 );
  153. if( !g_pFullFileSystem )
  154. return false;
  155. if ( !g_pCVar )
  156. return false;
  157. ConVar_Register();
  158. return true;
  159. }
  160. virtual void PostShutdown() OVERRIDE
  161. {
  162. ConVar_Unregister();
  163. DisconnectTier2Libraries();
  164. DisconnectTier1Libraries();
  165. }
  166. virtual void Destroy() OVERRIDE {}
  167. // this should never be called
  168. virtual int Main( ) OVERRIDE { return -1; }
  169. private:
  170. CUtlString m_sBinaryPath;
  171. } g_gcAppSystemGroup;
  172. //-----------------------------------------------------------------------------
  173. // Purpose: Gets the global instance
  174. //-----------------------------------------------------------------------------
  175. CGCInterface *GGCInterface()
  176. {
  177. return &g_GCInterface;
  178. }
  179. //-----------------------------------------------------------------------------
  180. // Purpose: Constructor
  181. //-----------------------------------------------------------------------------
  182. CGCInterface::CGCInterface()
  183. : m_pGCHost( NULL )
  184. , m_pGC( NULL )
  185. , m_pGCDirProcess( NULL )
  186. , m_nAppID( k_uAppIdInvalid )
  187. , m_eUniverse( k_EUniverseInvalid )
  188. , m_bDevMode( false )
  189. , m_ullGID( 0 )
  190. , m_bLogCaptureEnabled( false )
  191. , m_nVersion( 0 )
  192. , m_hParentProcess( NULL )
  193. {
  194. }
  195. //-----------------------------------------------------------------------------
  196. // Purpose: Destructor
  197. //-----------------------------------------------------------------------------
  198. CGCInterface::~CGCInterface()
  199. {
  200. m_BlockEmitStrings.PurgeAndDeleteElements();
  201. ClearAssertInfo();
  202. delete m_pGC;
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Purpose: Gets the actual GC referred to by the interface
  206. //-----------------------------------------------------------------------------
  207. IGameCoordinator *CGCInterface::GetGC()
  208. {
  209. return m_pGC;
  210. }
  211. //-----------------------------------------------------------------------------
  212. // Purpose: Returns true if the GC is running in a dev environment
  213. //-----------------------------------------------------------------------------
  214. bool CGCInterface::BIsDevMode() const
  215. {
  216. return m_bDevMode;
  217. }
  218. //-----------------------------------------------------------------------------
  219. // Purpose: Gets the GC's appID
  220. //-----------------------------------------------------------------------------
  221. AppId_t CGCInterface::GetAppID() const
  222. {
  223. return m_nAppID;
  224. }
  225. //-----------------------------------------------------------------------------
  226. // Purpose: Gets the directory gc.dll is running in
  227. //-----------------------------------------------------------------------------
  228. const char *CGCInterface::GetGCDLLPath() const
  229. {
  230. return m_sGCDLLPath;
  231. }
  232. //-----------------------------------------------------------------------------
  233. // Purpose: Reads the config KV from the disk
  234. //-----------------------------------------------------------------------------
  235. bool CGCInterface::BReadConfigDirectory( KeyValuesAD& configValues )
  236. {
  237. // Read the config file
  238. const char *pchBaseConfigName = NULL;
  239. switch( GetUniverse() )
  240. {
  241. case k_EUniversePublic: pchBaseConfigName = "gcconfig_public.vdf"; break;
  242. case k_EUniverseBeta: pchBaseConfigName = "gcconfig_beta.vdf"; break;
  243. case k_EUniverseInternal: pchBaseConfigName = "gcconfig_internal.vdf"; break;
  244. case k_EUniverseDev: pchBaseConfigName = "gcconfig_dev.vdf"; break;
  245. }
  246. if( !pchBaseConfigName || !configValues->LoadFromFile( g_pFullFileSystem, pchBaseConfigName, "CONFIG" ) )
  247. {
  248. GCSDK::EmitError( SPEW_GC, "Unable to read config file: %s. Aborting.\n", pchBaseConfigName ? pchBaseConfigName : "unknown universe specified" );
  249. return false;
  250. }
  251. //load up our directory
  252. if ( !GDirectory()->BInit( configValues->FindKey( "directory" ) ) )
  253. {
  254. GCSDK::EmitError( SPEW_GC, "Unable to find 'directory' key within config file %s.\n", pchBaseConfigName );
  255. return false;
  256. }
  257. return true;
  258. }
  259. bool CGCInterface::BReadConvars( KeyValuesAD& configValues )
  260. {
  261. //load the standard global convars
  262. InitConVars( configValues->FindKey( "convars" ) );
  263. //we can't load more if we don't have a directory as we don't know our GC type
  264. if( !m_pGCDirProcess )
  265. {
  266. AssertMsg( false, "Attempted to read console variables without any GC type specified" );
  267. return false;
  268. }
  269. //get convars for this specific configuration name
  270. InitConVars( configValues->FindKey( CFmtStr( "%s-process-convars", m_pGCDirProcess->GetName() ) ) );
  271. //now load the GC type specific convars. Note that these can stomp, so we must do all of them in sequence
  272. for( uint32 nInstance = 0; nInstance < m_pGCDirProcess->GetTypeInstanceCount(); nInstance++ )
  273. {
  274. const char* pszTypeName = GDirectory()->GetNameForGCType( m_pGCDirProcess->GetTypeInstance( nInstance )->GetType() );
  275. InitConVars( configValues->FindKey( CFmtStr( "%s-type-convars", pszTypeName ) ) );
  276. }
  277. //see if they have a special config associated with this GC
  278. if( m_pGCDirProcess->GetConfig( ) )
  279. {
  280. const char* pszAdditionalConvars = m_pGCDirProcess->GetConfig()->GetString( "convars", NULL );
  281. if( pszAdditionalConvars )
  282. {
  283. //now load the convars that are specific to this instance
  284. InitConVars( configValues->FindKey( CFmtStr( "%s-convars", pszAdditionalConvars ) ) );
  285. }
  286. }
  287. if ( k_EUniverseDev != GetUniverse() )
  288. {
  289. // See if there's a convar override file
  290. KeyValuesAD pkvSavedConvars( "convars" );
  291. if( pkvSavedConvars->LoadFromFile( g_pFullFileSystem, CFmtStr( "%u_%s_%s_savedconvars.vdf", GetAppID(), m_pGCDirProcess->GetName(), PchNameFromEUniverse( GetUniverse() ) ), "CONFIG" ) )
  292. {
  293. InitConVars( pkvSavedConvars );
  294. }
  295. else
  296. {
  297. EmitInfo( SPEW_GC, SPEW_ALWAYS, LOG_ALWAYS, "Unable to read saved convars file. Continuing with defaults.\n" );
  298. }
  299. }
  300. return true;
  301. }
  302. //-----------------------------------------------------------------------------
  303. // Purpose: Sets the values of convars from the given KV
  304. //-----------------------------------------------------------------------------
  305. void CGCInterface::InitConVars( KeyValues *pkvConvars )
  306. {
  307. // init all the convars
  308. if( !pkvConvars )
  309. return;
  310. FOR_EACH_VALUE( pkvConvars, pkvVar )
  311. {
  312. if ( !pkvVar->GetString() )
  313. {
  314. EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, "variable %s missing value, skipping\n", pkvVar->GetName() );
  315. }
  316. ConVar *pVar = NULL;
  317. const char *pchSuffix = V_strrchr( pkvVar->GetName(), '_' );
  318. if ( NULL != pchSuffix && 0 == V_strcmp( pchSuffix, CFmtStr( "_%u", GetAppID() ) ) )
  319. {
  320. pVar = g_pCVar->FindVar( pkvVar->GetName() );
  321. }
  322. else
  323. {
  324. pVar = g_pCVar->FindVar( CFmtStr( "%s_%u", pkvVar->GetName(), GetAppID() ) );
  325. }
  326. if ( !pVar )
  327. {
  328. EmitWarning( SPEW_CONSOLE, SPEW_ALWAYS, "config file references unknown convar %s\n", pkvVar->GetName() );
  329. }
  330. else
  331. {
  332. pVar->SetValue( pkvVar->GetString() );
  333. }
  334. }
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Purpose: Writes the current non-default convars to disk
  338. //-----------------------------------------------------------------------------
  339. bool CGCInterface::BSaveConvars()
  340. {
  341. //do nothing if we haven't loaded the directory
  342. if( !m_pGCDirProcess )
  343. return false;
  344. // copy all the non-default convars to the config
  345. KeyValuesAD pkvConvars( "convars" );
  346. ICvar::Iterator iter( g_pCVar );
  347. for ( iter.SetFirst(); iter.IsValid(); iter.Next() )
  348. {
  349. const ConCommandBase *pCommand = iter.Get();
  350. const GCConVar *pVar = dynamic_cast<const GCConVar *>( pCommand );
  351. if( pVar && 0 != Q_strcmp( pVar->GetString(), pVar->GetDefault() ) )
  352. {
  353. KeyValues *pkvVar = pkvConvars->FindKey( pVar->GetBaseName(), true );
  354. pkvVar->SetStringValue( pVar->GetString() );
  355. }
  356. }
  357. return pkvConvars->SaveToFile( g_pFullFileSystem, CFmtStr( "%u_%s_%s_savedconvars.vdf", GetAppID(), m_pGCDirProcess->GetName(), PchNameFromEUniverse( GetUniverse() ) ), "CONFIG" );
  358. }
  359. //-----------------------------------------------------------------------------
  360. // Purpose: Construct a Steam ID for a client, given an account ID
  361. //-----------------------------------------------------------------------------
  362. CSteamID CGCInterface::ConstructSteamIDForClient( AccountID_t unAccountID ) const
  363. {
  364. return CSteamID( unAccountID, m_eUniverse, k_EAccountTypeIndividual );
  365. }
  366. //-----------------------------------------------------------------------------
  367. void CGCInterface::ClearAssertWindowCounts()
  368. {
  369. FOR_EACH_DICT_FAST( m_dictAsserts, nCurrFile )
  370. {
  371. FOR_EACH_VEC( *m_dictAsserts[ nCurrFile ], nCurrAssert )
  372. {
  373. ( *m_dictAsserts[ nCurrFile ] )[ nCurrAssert ]->m_nWindowFired = 0;
  374. }
  375. }
  376. }
  377. //-----------------------------------------------------------------------------
  378. void CGCInterface::ClearAssertInfo()
  379. {
  380. FOR_EACH_DICT_FAST( m_dictAsserts, nCurrAssert )
  381. {
  382. m_dictAsserts[ nCurrAssert ]->PurgeAndDeleteElements();
  383. }
  384. m_dictAsserts.PurgeAndDeleteElements();
  385. }
  386. //-----------------------------------------------------------------------------
  387. // Purpose: Records an assert and optionally passes back if we should write
  388. // a minidump based on it
  389. //-----------------------------------------------------------------------------
  390. void CGCInterface::RecordAssert( const char *pchFile, int nLine, const char *pchMessage, bool *pbShouldWriteMinidump )
  391. {
  392. //assume we are not writing a dump by default
  393. if( pbShouldWriteMinidump )
  394. *pbShouldWriteMinidump = false;
  395. //handle an emergency disable of asserts
  396. if( !enable_assert_minidumps.GetBool() )
  397. return;
  398. //get our entry in our map
  399. int iDict = m_dictAsserts.Find( pchFile );
  400. if ( !m_dictAsserts.IsValidIndex( iDict ) )
  401. {
  402. iDict = m_dictAsserts.Insert( pchFile, new CUtlVector< AssertInfo_t* > );
  403. }
  404. CUtlVector< AssertInfo_t* > &vecAsserts = *m_dictAsserts[iDict];
  405. //see if we have an entry for this line already
  406. AssertInfo_t* pAssert = NULL;
  407. FOR_EACH_VEC( vecAsserts, nCurrAssert )
  408. {
  409. if( ( uint32 )nLine == vecAsserts[ nCurrAssert ]->m_nLine )
  410. {
  411. pAssert = vecAsserts[ nCurrAssert ];
  412. break;
  413. }
  414. }
  415. //one wasn't already in the list, so we need to create and insert it
  416. if( !pAssert )
  417. {
  418. pAssert = new AssertInfo_t;
  419. pAssert->m_nLine = nLine;
  420. pAssert->m_sMsg = pchMessage;
  421. pAssert->m_nWindowFired = 0;
  422. pAssert->m_nTotalFired = 0;
  423. pAssert->m_nTotalRecorded = 0;
  424. vecAsserts.AddToTail( pAssert );
  425. //also, remove any newlines from the asserts. The default assert inserts them and this creates problems for a lot of the exporting of the data from SQL into Excel
  426. pAssert->m_sMsg = pAssert->m_sMsg.Replace( '\n', ' ' );
  427. }
  428. //update our stats
  429. pAssert->m_nTotalFired++;
  430. pAssert->m_nWindowFired++;
  431. //remove any recorded asserts that are older than our window, so that we can record new asserts
  432. int nStale = 0;
  433. CUtlVector< RTime32 >& vecTimes = pAssert->m_vRecordTimes;
  434. const RTime32 nStaleTime = CRTime::RTime32TimeCur() - (uint32)cv_assert_minidump_window.GetInt();
  435. while ( ( nStale < vecTimes.Count() ) && ( vecTimes[nStale] < nStaleTime ) )
  436. {
  437. nStale++;
  438. }
  439. vecTimes.RemoveMultipleFromHead( nStale );
  440. //see if we have room in how many asserts we want to track, if so, we want to record this assert
  441. if ( ( vecTimes.Count() < cv_assert_max_minidumps_in_window.GetInt() ) || ( CDisableAssertRateLimit::s_nDisabledCount > 0 ) )
  442. {
  443. vecTimes.AddToTail( CRTime::RTime32TimeCur() );
  444. pAssert->m_nTotalRecorded++;
  445. if( pbShouldWriteMinidump )
  446. *pbShouldWriteMinidump = true;
  447. }
  448. }
  449. //flag indicating whether or not we should force a crash if we encounter an exit
  450. static bool g_bCrashIfExitDetected = false;
  451. //callback handler registered to force a crash on exit conditions so we can track when/why the GC ever exits
  452. static void GCForceCrash( bool bForceCrash )
  453. {
  454. if( bForceCrash )
  455. {
  456. //we just want to initiate a crash, so that we can get a call stack
  457. int* pForceCrash = NULL;
  458. *pForceCrash = 100;
  459. }
  460. }
  461. static void ExitHandler() { GCForceCrash( g_bCrashIfExitDetected ); }
  462. static void AbortHandler( int ) { GCForceCrash( true ); }
  463. static void PureCallHandler() { GCForceCrash( true ); }
  464. static void InvalidCRTParamHandler(const wchar_t* expression,
  465. const wchar_t* function,
  466. const wchar_t* file,
  467. unsigned int line,
  468. uintptr_t pReserved) { GCForceCrash( true ); }
  469. static void InstallExceptionHandlers( bool bCrashOnNormalExit )
  470. {
  471. //don't crash on exit while in dev universe
  472. g_bCrashIfExitDetected = bCrashOnNormalExit;
  473. Plat_CollectMiniDumpsForFatalErrors();
  474. //and register one with the at exit handler
  475. atexit( ExitHandler );
  476. //and register an abort handler
  477. signal( SIGABRT, AbortHandler );
  478. //CRT invalid parameter handler
  479. _set_invalid_parameter_handler( InvalidCRTParamHandler );
  480. //Pure virtual function call handler
  481. _set_purecall_handler( PureCallHandler );
  482. MiniDumpRegisterForUnhandledExceptions();
  483. }
  484. //-----------------------------------------------------------------------------
  485. // Purpose: Loads the config, figures out what GC we should be running, and
  486. // creates it
  487. //-----------------------------------------------------------------------------
  488. bool CGCInterface::BAsyncInit( uint32 unAppID, const char *pchDebugName, int iGCIndex, IGameCoordinatorHost *pHost )
  489. {
  490. //called to handle registration of exception handlers so that we will always crash rather than an unexpected termination
  491. InstallExceptionHandlers( pHost->GetUniverse() != k_EUniverseDev );
  492. // Make sure we can't deploy debug GCs outside the dev environment
  493. #ifdef _DEBUG
  494. if ( pHost->GetUniverse() != k_EUniverseDev )
  495. {
  496. pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS,
  497. CFmtStr( "The GC for App %u is a debug binary. Shutting down.\n", unAppID ) );
  498. return false;
  499. }
  500. #endif
  501. //report if we are 64 or 32 bit for easier tracking during transition
  502. COMPILE_TIME_ASSERT( sizeof( tchar ) == sizeof( char ) );
  503. pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, CFmtStr( "Initializing %d bit GC, Dir Index %d, PID:%u\n", ( uint32 )( sizeof( void* ) * 8 ), iGCIndex, GetCurrentProcessId() ).Access() );
  504. pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, CFmtStr1024( "Command Line: %s\n", Plat_GetCommandLine() ).Access() );
  505. CommandLine()->CreateCmdLine( Plat_GetCommandLine() );
  506. //get our machine name
  507. {
  508. char szMachineName[ MAX_COMPUTERNAME_LENGTH + 1 ];
  509. DWORD nBufferSize = ARRAYSIZE( szMachineName );
  510. GetComputerName( szMachineName, &nBufferSize );
  511. m_sMachineName = szMachineName;
  512. }
  513. //open our a handle to our parent
  514. {
  515. uint32 nParentPID = MAX( 0, CommandLine()->ParmValue( "-parentpid", 0 ) );
  516. if( nParentPID == 0 )
  517. {
  518. pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, "Parent process ID was not specified via -parentpid, unable to get information about the launching process\n" );
  519. }
  520. else
  521. {
  522. m_hParentProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, nParentPID );
  523. if( !m_hParentProcess )
  524. {
  525. pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, "Unable to open the parent process with read access. Unable to get information about the launching process\n" );
  526. }
  527. }
  528. }
  529. static bool s_bInitCalled = false;
  530. if ( s_bInitCalled )
  531. {
  532. pHost->EmitSpew( SPEW_GC.GetName(), SPEW_ERROR, SPEW_ALWAYS, LOG_ALWAYS, "BInit called twice on the game IGameCoordinator" );
  533. return false;
  534. }
  535. s_bInitCalled = true;
  536. // Set basic variables
  537. m_pGCHost = pHost;
  538. m_nAppID = unAppID;
  539. m_sDebugName = pchDebugName;
  540. m_eUniverse = (EUniverse)m_pGCHost->GetUniverse();
  541. // Initialize core systems
  542. CRTime::UpdateRealTime();
  543. RandomSeed( CRTime::RTime32TimeCur() );
  544. // Gets the path our dll is loaded from
  545. HMODULE hModuleGC;
  546. char rgchGCModuleFile[MAX_PATH+1] = ".\\";
  547. char rgchGCModulePath[MAX_PATH+1] = ".\\";
  548. if ( ::GetModuleHandleExA( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCSTR)&g_GCInterface, &hModuleGC ) )
  549. {
  550. ::GetModuleFileNameA( hModuleGC, rgchGCModuleFile, MAX_PATH );
  551. V_strcpy_safe( rgchGCModulePath, rgchGCModuleFile );
  552. if ( char *pSlash = strrchr( rgchGCModulePath, '\\' ) )
  553. pSlash[1] = 0;
  554. }
  555. // Full path to GC.DLL (with final slash) is now in rgchGCModulePath
  556. m_sGCDLLPath = rgchGCModulePath;
  557. CFmtStr sContentPath = rgchGCModulePath;
  558. CFmtStr sBinaryPath = rgchGCModulePath;
  559. if( Q_stristr( rgchGCModulePath, "bin\\gc\\x64" ) != NULL )
  560. {
  561. Q_MakeAbsolutePath( sContentPath.Access(), FMTSTR_STD_LEN, "..\\..\\..", rgchGCModulePath );
  562. Q_MakeAbsolutePath( sBinaryPath.Access(), FMTSTR_STD_LEN, "..\\..\\..\\..\\bin\\x64", rgchGCModulePath );
  563. m_bDevMode = true;
  564. m_sDevBinaryName = rgchGCModuleFile;
  565. }
  566. else if( Q_stristr( rgchGCModulePath, "bin\\gc" ) != NULL )
  567. {
  568. Q_MakeAbsolutePath( sContentPath.Access(), FMTSTR_STD_LEN, "..\\..", rgchGCModulePath );
  569. Q_MakeAbsolutePath( sBinaryPath.Access(), FMTSTR_STD_LEN, "..\\..\\..\\bin", rgchGCModulePath );
  570. m_bDevMode = true;
  571. m_sDevBinaryName = rgchGCModuleFile;
  572. }
  573. else
  574. {
  575. //launch through standard GC, so try and extract the version from our path (not a great solution, should extend interface so that
  576. //the GCH provides us with the version it expects). The format is ....\vNNN\ so try and extract that
  577. CUtlString sGCPath = rgchGCModulePath;
  578. sGCPath.StripTrailingSlash();
  579. if ( const char *pSlash = strrchr( sGCPath.Get(), '\\' ) )
  580. {
  581. //skip over the slash, and verify that we have a 'v' before the version
  582. if( tolower( pSlash[ 1 ] ) == 'v' )
  583. {
  584. //grab the version number
  585. m_nVersion = ( uint32 )max( 0, atoi( pSlash + 2 ) );
  586. }
  587. }
  588. }
  589. // Starts logging
  590. LoggingSystem_PushLoggingState();
  591. LoggingSystem_SetLoggingResponsePolicy( &s_NonFatalLoggingResponsePolicy );
  592. LoggingSystem_RegisterLoggingListener( &s_ConsoleLoggingListener );
  593. // Select folder and prefix for dumps (using just the dir index for now, but update this later once we have the name
  594. Tier0GenericMiniDumpHandler_SetFilenamePrefix( CFmtStr( "dumps\\gc%d_idx%d", unAppID, iGCIndex ) );
  595. // Make sure dialogs don't come up and hang the process in production
  596. if ( !m_bDevMode )
  597. {
  598. Plat_EnableHeadlessMode();
  599. }
  600. RegisterAssertionFailureListener( &sg_GCAssertionFailureHandler );
  601. // Initialize GIDs
  602. m_ullGID = 0;
  603. m_ullGID = (uint64)iGCIndex << 56; // 8 bits of process id
  604. m_ullGID |= (uint64)CRTime::RTime32TimeCur() << 24; // 32 bits of UTC time in seconds
  605. // 24 bits/second of incremental counter space
  606. // This system assumes there are less than 256 GCs. Make sure of that
  607. AssertMsg( iGCIndex >= 0 && iGCIndex < 256, "iGCIndex out of range. There can only be 256 GC processes for an app" );
  608. if ( iGCIndex < 0 || iGCIndex >= 256 )
  609. return false;
  610. // Make sure the protobuf library won't exitprocess without dumping
  611. ::google::protobuf::SetLogHandler( ProtobufLogHandler );
  612. g_gcAppSystemGroup.SetPath( sBinaryPath );
  613. if( g_gcAppSystemGroup.Startup() < 0 )
  614. return false;
  615. g_pFullFileSystem->AddSearchPath( sContentPath, "GAME" );
  616. g_pFullFileSystem->AddSearchPath( rgchGCModulePath, "CONFIG" ); // config files go with gc.dll
  617. // load the config file first thing so that we can use it for all the other startup code
  618. KeyValuesAD configKeys( "config" );
  619. if( !BReadConfigDirectory( configKeys ) )
  620. {
  621. return false;
  622. }
  623. // Find this GC in the config and create it
  624. m_pGCDirProcess = GDirectory()->GetProcess( iGCIndex );
  625. if ( NULL == m_pGCDirProcess )
  626. {
  627. GCSDK::EmitError( SPEW_CONSOLE, "Failed to start GC for index %d.\n", iGCIndex );
  628. return false;
  629. }
  630. CDirectory::GCFactory_t pfnFactory = GDirectory()->GetFactoryForProcessType( m_pGCDirProcess->GetProcessType() );
  631. if ( NULL == pfnFactory )
  632. {
  633. GCSDK::EmitError( SPEW_CONSOLE, "Failed to start GC for index %d (type %s). Got a NULL factory function, likely missing registration for this type\n", iGCIndex, m_pGCDirProcess->GetProcessType() );
  634. return false;
  635. }
  636. //now that we have more information about which GC we are, update our minidump name to reflect this
  637. Tier0GenericMiniDumpHandler_SetFilenamePrefix( CFmtStr( "dumps\\gc%d_%s", unAppID, m_pGCDirProcess->GetName() ) );
  638. //now that we know our GC type, we can actually load up our convars (which are dependent on this info)
  639. if( !BReadConvars( configKeys ) )
  640. return false;
  641. // Init the GC. Not passing along the host because the interface layer owns it
  642. // and chooses what to expose
  643. m_pGC = pfnFactory( m_pGCDirProcess );
  644. return m_pGC->BAsyncInit( unAppID, pchDebugName, iGCIndex, NULL );
  645. }
  646. //-----------------------------------------------------------------------------
  647. // Purpose: Generates a number that's guaranteed unique across all GC processes
  648. // for this app. It is also guaranteed to have never been used by previous
  649. // processes.
  650. //-----------------------------------------------------------------------------
  651. GID_t CGCInterface::GenerateGID()
  652. {
  653. return ++m_ullGID;
  654. }
  655. //we have the GC index encoded in the high bits when we init the gid, so just extract that
  656. uint32 CGCInterface::GetGCDirIndexFromGID( GID_t gid )
  657. {
  658. return ( uint32 )( gid >> 56 );
  659. }
  660. //-----------------------------------------------------------------------------
  661. // Purpose: Gets the universe the GC is currently running in
  662. //-----------------------------------------------------------------------------
  663. EUniverse CGCInterface::GetUniverse() const
  664. {
  665. // Gets the current universe
  666. return m_eUniverse;
  667. }
  668. //-----------------------------------------------------------------------------
  669. // Purpose: Wrappers for GCHost functions that allow GCInterface to hook the calls
  670. //-----------------------------------------------------------------------------
  671. bool CGCInterface::BProcessSystemMessage( uint32 unGCSysMsgType, const void *pubData, uint32 cubData )
  672. {
  673. AssertMsg( unGCSysMsgType != 0, "Message sent without an ID. Did you use the wrong constructor?" );
  674. //track this message that we are sending (always just strip off the protobuff flag so it works with all message types)
  675. g_theMessageList.TallySendMessage( unGCSysMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_System );
  676. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  677. {
  678. VPROF_BUDGET( "GCHost - ProcessSystemMessage", VPROF_BUDGETGROUP_STEAM );
  679. return m_pGCHost->BProcessSystemMessage( unGCSysMsgType, pubData, cubData );
  680. }
  681. }
  682. //-----------------------------------------------------------------------------
  683. bool CGCInterface::BSendMessageToClient( uint64 ullSteamID, uint32 unMsgType, const void *pubData, uint32 cubData )
  684. {
  685. AssertMsg( unMsgType != 0, "Message sent without an ID. Did you use the wrong constructor?" );
  686. //sanity check on our side that we are sending with a valid steam ID. Useful to catch message failures on the GC side since otherwise it must be caught in the GCH side
  687. if( ullSteamID == k_steamIDNil.ConvertToUint64() )
  688. {
  689. AssertMsg( false, "Message %d sent to invalid steam ID. This message will not be processed.", unMsgType & ~k_EMsgProtoBufFlag );
  690. return false;
  691. }
  692. //track this message that we are sending (always just strip off the protobuff flag so it works with all message types)
  693. g_theMessageList.TallySendMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_Client );
  694. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  695. {
  696. VPROF_BUDGET( "GCHost - SendMessageToClient", VPROF_BUDGETGROUP_STEAM );
  697. return m_pGCHost->BSendMessageToClient( ullSteamID, unMsgType, pubData, cubData );
  698. }
  699. }
  700. //-----------------------------------------------------------------------------
  701. bool CGCInterface::BSendMessageToGC( int iGCServerIDTarget, uint32 unMsgType, const void *pubData, uint32 cubData )
  702. {
  703. AssertMsg( unMsgType != 0, "Message sent without an ID. Did you use the wrong constructor?" );
  704. //track this message that we are sending (always just strip off the protobuff flag so it works with all message types)
  705. g_theMessageList.TallySendMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_GC );
  706. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  707. {
  708. VPROF_BUDGET( "GCHost - SendMessageToGC", VPROF_BUDGETGROUP_STEAM );
  709. return m_pGCHost->BSendMessageToGC( iGCServerIDTarget, unMsgType, pubData, cubData );
  710. }
  711. }
  712. //-----------------------------------------------------------------------------
  713. void CGCInterface::AddBlockEmitString( const char* pszStr, bool bBlockConsole, bool bBlockLog )
  714. {
  715. BlockString_t* pStr = new BlockString_t;
  716. pStr->m_sStr = pszStr;
  717. pStr->m_bBlockConsole = bBlockConsole;
  718. pStr->m_bBlockLog = bBlockLog;
  719. m_BlockEmitStrings.AddToTail( pStr );
  720. }
  721. //-----------------------------------------------------------------------------
  722. void CGCInterface::ClearBlockEmitStrings()
  723. {
  724. m_BlockEmitStrings.PurgeAndDeleteElements();
  725. }
  726. //-----------------------------------------------------------------------------
  727. void CGCInterface::EmitSpew( const char *pchGroupName, SpewType_t spewType, int iSpewLevel, int iLevelLog, const char *pchMsg )
  728. {
  729. //see if this output is being squelched by going through our blocked string
  730. FOR_EACH_VEC( m_BlockEmitStrings, nStr )
  731. {
  732. //if our output contains the blocked string, turn off the severity for that level
  733. const BlockString_t* pStr = m_BlockEmitStrings[ nStr ];
  734. if( V_stristr( pchMsg, pStr->m_sStr ) != NULL )
  735. {
  736. if( pStr->m_bBlockConsole )
  737. iSpewLevel = SPEW_NEVER;
  738. if( pStr->m_bBlockLog )
  739. iLevelLog = LOG_NEVER;
  740. }
  741. }
  742. //see if this is just a blank line
  743. if( filter_blank_lines.GetBool() && pchMsg )
  744. {
  745. bool bIsBlankLine = true;
  746. for( const char* pszCurrChar = pchMsg; *pszCurrChar; pszCurrChar++ )
  747. {
  748. if( !V_isspace( *pszCurrChar ) )
  749. {
  750. bIsBlankLine = false;
  751. break;
  752. }
  753. }
  754. if( bIsBlankLine )
  755. {
  756. return;
  757. }
  758. }
  759. if ( m_bLogCaptureEnabled )
  760. {
  761. m_vecLogCapture[m_vecLogCapture.AddToTail()].Set( pchMsg );
  762. }
  763. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  764. {
  765. VPROF_BUDGET( "GCHost - EmitMessage", VPROF_BUDGETGROUP_STEAM );
  766. m_pGCHost->EmitSpew( pchGroupName, spewType, iSpewLevel, iLevelLog, pchMsg );
  767. }
  768. }
  769. //-----------------------------------------------------------------------------
  770. void CGCInterface::AsyncSQLQuery( IGCSQLQuery *pQuery, int eSchemaCatalog )
  771. {
  772. VPROF_BUDGET( "GCHost", VPROF_BUDGETGROUP_STEAM );
  773. {
  774. VPROF_BUDGET( "GCHost - SQLQuery", VPROF_BUDGETGROUP_STEAM );
  775. m_pGCHost->AsyncSQLQuery( pQuery, eSchemaCatalog );
  776. }
  777. }
  778. //-----------------------------------------------------------------------------
  779. void CGCInterface::SetStartupComplete( bool bSuccess )
  780. {
  781. m_pGCHost->StartupComplete( bSuccess );
  782. }
  783. //-----------------------------------------------------------------------------
  784. void CGCInterface::SetShutdownComplete()
  785. {
  786. m_pGCHost->ShutdownComplete();
  787. }
  788. //-----------------------------------------------------------------------------
  789. // Purpose: Passthrough implementations of the rest of the interface
  790. //-----------------------------------------------------------------------------
  791. void CGCInterface::Unload()
  792. {
  793. if ( m_pGC )
  794. m_pGC->Unload();
  795. g_gcAppSystemGroup.Shutdown();
  796. }
  797. //-----------------------------------------------------------------------------
  798. bool CGCInterface::BAsyncShutdown()
  799. {
  800. bool bResult = false;
  801. if ( m_pGC )
  802. bResult = m_pGC->BAsyncShutdown();
  803. //if they have requested a shutdown, go ahead and allow exit
  804. g_bCrashIfExitDetected = false;
  805. return bResult;
  806. }
  807. //-----------------------------------------------------------------------------
  808. bool CGCInterface::BMainLoopOncePerFrame( uint64 ulLimitMicroseconds )
  809. {
  810. if ( !m_pGC )
  811. return false;
  812. else
  813. return m_pGC->BMainLoopOncePerFrame( ulLimitMicroseconds );
  814. }
  815. //-----------------------------------------------------------------------------
  816. bool CGCInterface::BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds )
  817. {
  818. if ( !m_pGC )
  819. return false;
  820. else
  821. return m_pGC->BMainLoopUntilFrameCompletion( ulLimitMicroseconds );
  822. }
  823. //-----------------------------------------------------------------------------
  824. void CGCInterface::HandleMessageFromClient( uint64 ullSenderID, uint32 unMsgType, void *pubData, uint32 cubData )
  825. {
  826. if ( NULL == pubData || 0 == cubData )
  827. {
  828. EG_ERROR( g_EGMessages, "Received invalid message from user %s. MessageID: %u pubData: %p cubData: %u\n", CSteamID( ullSenderID ).Render(), unMsgType, pubData, cubData );
  829. }
  830. else if ( m_pGC )
  831. {
  832. g_theMessageList.TallyReceiveMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_Client );
  833. m_pGC->HandleMessageFromClient( ullSenderID, unMsgType, pubData, cubData );
  834. }
  835. }
  836. //-----------------------------------------------------------------------------
  837. void CGCInterface::HandleMessageFromSystem( uint32 unGCSysMsgType, void *pubData, uint32 cubData )
  838. {
  839. if ( NULL == pubData || 0 == cubData )
  840. {
  841. EG_ERROR( g_EGMessages, "Received invalid message from system. MessageID: %u pubData: %p cubData: %u\n", unGCSysMsgType, pubData, cubData );
  842. }
  843. else if ( m_pGC )
  844. {
  845. g_theMessageList.TallyReceiveMessage( unGCSysMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_System );
  846. m_pGC->HandleMessageFromSystem( unGCSysMsgType, pubData, cubData );
  847. }
  848. }
  849. //-----------------------------------------------------------------------------
  850. void CGCInterface::HandleMessageFromGC( int iGCServerIDSender, uint32 unMsgType, void *pubData, uint32 cubData )
  851. {
  852. if ( NULL == pubData || 0 == cubData )
  853. {
  854. EG_ERROR( g_EGMessages, "Received invalid message from GC. MessageID: %u pubData: %p cubData: %u\n", iGCServerIDSender, pubData, cubData );
  855. }
  856. else if ( m_pGC )
  857. {
  858. g_theMessageList.TallyReceiveMessage( unMsgType & ~k_EMsgProtoBufFlag, cubData, eMsgSource_GC );
  859. m_pGC->HandleMessageFromGC( iGCServerIDSender, unMsgType, pubData, cubData );
  860. }
  861. }
  862. //-----------------------------------------------------------------------------
  863. void CGCInterface::StartLogCapture()
  864. {
  865. m_bLogCaptureEnabled = true;
  866. }
  867. //-----------------------------------------------------------------------------
  868. void CGCInterface::EndLogCapture()
  869. {
  870. m_bLogCaptureEnabled = false;
  871. }
  872. //-----------------------------------------------------------------------------
  873. const CUtlVector<CUtlString> *CGCInterface::GetLogCapture()
  874. {
  875. return &m_vecLogCapture;
  876. }
  877. //-----------------------------------------------------------------------------
  878. void CGCInterface::ClearLogCapture()
  879. {
  880. m_vecLogCapture.RemoveAll();
  881. }
  882. }
  883. //For the GC, we want to force a crash when we get stack corruption errors so
  884. // that we can analyze the dumps and fix the problem. To disable this behavior,
  885. // comment out the function below.
  886. // TEMP: Disabled on VS2013+ because I didn't know how to fix the linking problems
  887. #if defined( _MSC_VER ) && ( _MSC_VER < 1800 )
  888. extern "C"
  889. {
  890. #if defined (_X86_)
  891. __declspec(noreturn) void __cdecl __report_gsfailure(void)
  892. #else /* defined (_X86_) */
  893. __declspec(noreturn) void __cdecl __report_gsfailure(ULONGLONG StackCookie)
  894. #endif /* defined (_X86_) */
  895. {
  896. MiniDumpOptionalData_t optionalData( _T("stack_corruption") );
  897. InvokeMiniDumpHandler( NULL, &optionalData );
  898. static uint32* pNull = NULL;
  899. *pNull = 0;
  900. }
  901. }
  902. #endif