5
5
using System . Collections . Generic ;
6
6
using System . Diagnostics ;
7
7
using System . IO ;
8
+ using System . Runtime . InteropServices ;
8
9
using System . Linq ;
9
10
using System . Text ;
10
11
using System . Text . Json ;
11
12
using System . Text . Json . Serialization ;
12
13
13
14
namespace Microsoft . Diagnostics . DebugServices . Implementation
14
15
{
16
+ public sealed class CrashInfoModuleService : ICrashInfoModuleService
17
+ {
18
+ private readonly IServiceProvider Services ;
19
+
20
+ public CrashInfoModuleService ( IServiceProvider services )
21
+ {
22
+ Services = services ?? throw new ArgumentNullException ( nameof ( services ) ) ;
23
+ }
24
+ public ICrashInfoService Create ( ModuleEnumerationScheme moduleEnumerationScheme )
25
+ {
26
+ return CreateCrashInfoServiceFromModule ( Services , moduleEnumerationScheme ) ;
27
+ }
28
+
29
+ private static bool CreateCrashInfoServiceForModule ( IModule module , out ICrashInfoService crashInfoService )
30
+ {
31
+ crashInfoService = null ;
32
+ if ( module == null || module . IsManaged )
33
+ {
34
+ return false ;
35
+ }
36
+ if ( ( module as IExportSymbols ) ? . TryGetSymbolAddress ( CrashInfoService . DOTNET_RUNTIME_DEBUG_HEADER_NAME , out ulong headerAddr ) != true )
37
+ {
38
+ Trace . TraceInformation ( $ "CrashInfoService: { CrashInfoService . DOTNET_RUNTIME_DEBUG_HEADER_NAME } export not found in module { module . FileName } ") ;
39
+ return false ;
40
+ }
41
+ IMemoryService memoryService = module . Services . GetService < IMemoryService > ( ) ;
42
+ if ( ! memoryService . Read < uint > ( ref headerAddr , out uint headerValue ) ||
43
+ headerValue != CrashInfoService . DOTNET_RUNTIME_DEBUG_HEADER_COOKIE ||
44
+ ! memoryService . Read < ushort > ( ref headerAddr , out ushort majorVersion ) || majorVersion < 3 || // .NET 8 and later
45
+ ! memoryService . Read < ushort > ( ref headerAddr , out ushort minorVersion ) )
46
+ {
47
+ Trace . TraceInformation ( $ "CrashInfoService: .NET 8+ { CrashInfoService . DOTNET_RUNTIME_DEBUG_HEADER_NAME } not found in module { module . FileName } ") ;
48
+ return false ;
49
+ }
50
+
51
+ Trace . TraceInformation ( $ "CrashInfoService: Found { CrashInfoService . DOTNET_RUNTIME_DEBUG_HEADER_NAME } in module { module . FileName } with version { majorVersion } .{ minorVersion } ") ;
52
+
53
+ if ( ! memoryService . Read < uint > ( ref headerAddr , out uint flags ) ||
54
+ memoryService . PointerSize != ( flags == 0x1 ? 8 : 4 ) ||
55
+ ! memoryService . Read < uint > ( ref headerAddr , out uint _ ) ) // padding
56
+ {
57
+ Trace . TraceError ( $ "CrashInfoService: Failed to read DotNetRuntimeDebugHeader flags or padding in module { module . FileName } ") ;
58
+ return false ;
59
+ }
60
+
61
+ Trace . TraceInformation ( $ "CrashInfoService: Target is { memoryService . PointerSize * 8 } -bit") ;
62
+
63
+ headerAddr += ( uint ) memoryService . PointerSize ; // skip DebugEntries array
64
+
65
+ // read GlobalEntries array
66
+ if ( ! memoryService . ReadPointer ( ref headerAddr , out ulong globalEntryArrayAddress ) || globalEntryArrayAddress == 0 )
67
+ {
68
+ Trace . TraceError ( $ "CrashInfoService: Unable to read GlobalEntry array") ;
69
+ return false ;
70
+ }
71
+ uint maxGlobalEntries = ( majorVersion >= 4 /*.NET 9 or later*/ ) ? CrashInfoService . MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET9_PLUS : CrashInfoService . MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET8 ;
72
+ for ( int i = 0 ; i < maxGlobalEntries ; i ++ )
73
+ {
74
+ if ( ! memoryService . ReadPointer ( ref globalEntryArrayAddress , out ulong globalNameAddress ) || globalNameAddress == 0 ||
75
+ ! memoryService . ReadPointer ( ref globalEntryArrayAddress , out ulong globalValueAddress ) || globalValueAddress == 0 ||
76
+ ! memoryService . ReadAnsiString ( CrashInfoService . MAX_GLOBAL_ENTRY_NAME_CHARS , globalNameAddress , out string globalName ) )
77
+ {
78
+ break ; // no more global entries
79
+ }
80
+
81
+ if ( ! string . Equals ( globalName , "g_CrashInfoBuffer" , StringComparison . Ordinal ) )
82
+ {
83
+ continue ; // not the crash info buffer
84
+ }
85
+
86
+ ulong triageBufferAddress = globalValueAddress ;
87
+ Span < byte > buffer = new byte [ CrashInfoService . MAX_CRASHINFOBUFFER_SIZE ] ;
88
+ if ( memoryService . ReadMemory ( triageBufferAddress , buffer , out int bytesRead ) && bytesRead > 0 && bytesRead <= CrashInfoService . MAX_CRASHINFOBUFFER_SIZE )
89
+ {
90
+ // truncate the buffer to the null terminated string in the buffer
91
+ int nullTerminatorIndex = buffer . IndexOf ( ( byte ) 0 ) ;
92
+ if ( nullTerminatorIndex >= 0 )
93
+ {
94
+ buffer = buffer . Slice ( 0 , nullTerminatorIndex ) ;
95
+ Trace . TraceInformation ( $ "CrashInfoService: Found g_CrashInfoBuffer in module { module . FileName } with size { buffer . Length } bytes") ;
96
+ if ( buffer . Length > 0 )
97
+ {
98
+ crashInfoService = CrashInfoService . Create ( 0 , buffer , module . Services . GetService < IModuleService > ( ) ) ;
99
+ return true ;
100
+ }
101
+ else
102
+ {
103
+ Trace . TraceError ( $ "CrashInfoService: g_CrashInfoBuffer is empty in module { module . FileName } ") ;
104
+ }
105
+ }
106
+ else
107
+ {
108
+ Trace . TraceError ( $ "CrashInfoService: g_CrashInfoBuffer is not null terminated in module { module . FileName } ") ;
109
+ }
110
+ }
111
+ else
112
+ {
113
+ Trace . TraceError ( $ "CrashInfoService: ReadMemory({ triageBufferAddress } ) failed in module { module . FileName } ") ;
114
+ }
115
+ break ;
116
+ }
117
+ return false ;
118
+ }
119
+
120
+ private static unsafe ICrashInfoService CreateCrashInfoServiceFromModule ( IServiceProvider services , ModuleEnumerationScheme moduleEnumerationScheme )
121
+ {
122
+ if ( moduleEnumerationScheme == ModuleEnumerationScheme . None )
123
+ {
124
+ return null ;
125
+ }
126
+
127
+ if ( moduleEnumerationScheme == ModuleEnumerationScheme . EntryPointModule ||
128
+ moduleEnumerationScheme == ModuleEnumerationScheme . EntryPointAndEntryPointDllModule )
129
+ {
130
+ // if the above did not locate the crash info service, then look for the DotNetRuntimeDebugHeader
131
+ IModule entryPointModule = services . GetService < IModuleService > ( ) . EntryPointModule ;
132
+ if ( entryPointModule == null )
133
+ {
134
+ Trace . TraceError ( "CrashInfoService: No entry point module found" ) ;
135
+ return null ;
136
+ }
137
+ if ( CreateCrashInfoServiceForModule ( entryPointModule , out ICrashInfoService crashInfoService ) )
138
+ {
139
+ return crashInfoService ;
140
+ }
141
+ if ( moduleEnumerationScheme == ModuleEnumerationScheme . EntryPointAndEntryPointDllModule )
142
+ {
143
+ // if the entry point module did not have the crash info service, then look for a library with the same name as the entry point
144
+ string fileName = entryPointModule . FileName ;
145
+ OSPlatform osPlatform = entryPointModule . Target . OperatingSystem ;
146
+ if ( fileName == null )
147
+ {
148
+ Trace . TraceError ( "CrashInfoService: Entry point module has no file name" ) ;
149
+ return null ;
150
+ }
151
+ if ( osPlatform == OSPlatform . Windows )
152
+ {
153
+ if ( fileName . EndsWith ( ".exe" , StringComparison . OrdinalIgnoreCase ) )
154
+ {
155
+ fileName = fileName . Substring ( 0 , fileName . Length - 4 ) + ".dll" ; // look for a dll with the same name
156
+ }
157
+ else
158
+ {
159
+ Trace . TraceError ( $ "CrashInfoService: Unexpected entry point module file name { fileName } ") ;
160
+ }
161
+ }
162
+ else if ( osPlatform == OSPlatform . Linux )
163
+ {
164
+ fileName += ".so" ; // look for a .so with the same name
165
+ }
166
+ else if ( osPlatform == OSPlatform . OSX )
167
+ {
168
+ fileName += ".dylib" ; // look for a .dylib with the same name
169
+ }
170
+ else
171
+ {
172
+ Trace . TraceError ( $ "CrashInfoService: Unsupported operating system { osPlatform } ") ;
173
+ return null ;
174
+ }
175
+ foreach ( IModule speculativeAppModule in services . GetService < IModuleService > ( ) . GetModuleFromModuleName ( fileName ) )
176
+ {
177
+ if ( CreateCrashInfoServiceForModule ( speculativeAppModule , out crashInfoService ) )
178
+ {
179
+ return crashInfoService ;
180
+ }
181
+ }
182
+ }
183
+ }
184
+ else if ( moduleEnumerationScheme == ModuleEnumerationScheme . All )
185
+ {
186
+ // enumerate all modules and look for the crash info service
187
+ foreach ( IModule module in services . GetService < IModuleService > ( ) . EnumerateModules ( ) )
188
+ {
189
+ if ( CreateCrashInfoServiceForModule ( module , out ICrashInfoService crashInfoService ) )
190
+ {
191
+ return crashInfoService ;
192
+ }
193
+ }
194
+ }
195
+ else
196
+ {
197
+ Trace . TraceError ( $ "CrashInfoService: Unknown module enumeration scheme { moduleEnumerationScheme } ") ;
198
+ }
199
+
200
+ return null ;
201
+ }
202
+ }
203
+
15
204
public class CrashInfoService : ICrashInfoService
16
205
{
17
206
/// <summary>
@@ -24,6 +213,13 @@ public class CrashInfoService : ICrashInfoService
24
213
/// </summary>
25
214
public const uint FAST_FAIL_EXCEPTION_DOTNET_AOT = 0x48 ;
26
215
216
+ public const uint MAX_CRASHINFOBUFFER_SIZE = 8192 ;
217
+ public const uint MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET8 = 6 ;
218
+ public const uint MAX_GLOBAL_ENTRIES_ARRAY_SIZE_NET9_PLUS = 8 ;
219
+ public const uint MAX_GLOBAL_ENTRY_NAME_CHARS = 32 ;
220
+ public const uint DOTNET_RUNTIME_DEBUG_HEADER_COOKIE = 0x48444e44 ; // 'DNDH'
221
+ public const string DOTNET_RUNTIME_DEBUG_HEADER_NAME = "DotNetRuntimeDebugHeader" ;
222
+
27
223
private sealed class CrashInfoJson
28
224
{
29
225
[ JsonPropertyName ( "version" ) ]
0 commit comments