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