@@ -31,6 +31,9 @@ internal class DebugStackTrace : SentryStackTrace
31
31
private static readonly Regex RegexAsyncReturn = new ( @"^(.+`[0-9]+)\[\[" ,
32
32
RegexOptions . Compiled | RegexOptions . CultureInvariant ) ;
33
33
34
+ private static readonly Regex RegexNativeAOTInfo = new ( @"^(.+)\.([^.]+\(.*\)) ?\+ ?0x" ,
35
+ RegexOptions . Compiled | RegexOptions . CultureInvariant ) ;
36
+
34
37
internal DebugStackTrace ( SentryOptions options )
35
38
{
36
39
_options = options ;
@@ -123,11 +126,14 @@ private IEnumerable<SentryStackFrame> CreateFrames(StackTrace stackTrace, bool i
123
126
{
124
127
var frames = _options . StackTraceMode switch
125
128
{
126
- StackTraceMode . Enhanced => EnhancedStackTrace . GetFrames ( stackTrace ) . Select ( p => p as StackFrame ) ,
129
+ StackTraceMode . Enhanced => EnhancedStackTrace . GetFrames ( stackTrace ) . Select ( p => new RealStackFrame ( p ) ) ,
127
130
_ => stackTrace . GetFrames ( )
128
131
// error CS8619: Nullability of reference types in value of type 'StackFrame?[]' doesn't match target type 'IEnumerable<StackFrame>'.
129
132
#if NETCOREAPP3_0
130
133
. Where ( f => f is not null )
134
+ . Select ( p => new RealStackFrame ( p ! ) )
135
+ #else
136
+ . Select ( p => new RealStackFrame ( p ) )
131
137
#endif
132
138
} ;
133
139
@@ -153,109 +159,173 @@ private IEnumerable<SentryStackFrame> CreateFrames(StackTrace stackTrace, bool i
153
159
#endif
154
160
155
161
// Remove the frames until the call for capture with the SDK
156
- if ( firstFrame
157
- && isCurrentStackTrace
158
- && stackFrame . GetMethod ( ) is { } method
159
- && method . DeclaringType ? . AssemblyQualifiedName ? . StartsWith ( "Sentry" ) == true )
162
+ if ( firstFrame && isCurrentStackTrace )
160
163
{
161
- _options . LogDebug ( "Skipping initial stack frame '{0}'" , method . Name ) ;
162
- continue ;
164
+ string ? frameInfo = null ;
165
+ if ( stackFrame . GetMethod ( ) is { } method )
166
+ {
167
+ frameInfo = method . DeclaringType ? . AssemblyQualifiedName ;
168
+ }
169
+
170
+ // Native AOT currently only exposes some method info at runtime via ToString().
171
+ // See https://github.com/dotnet/runtime/issues/92869
172
+ if ( frameInfo is null && stackFrame . HasNativeImage ( ) )
173
+ {
174
+ frameInfo = stackFrame . ToString ( ) ;
175
+ }
176
+
177
+ if ( frameInfo ? . StartsWith ( "Sentry" ) is true )
178
+ {
179
+ _options . LogDebug ( "Skipping initial stack frame '{0}'" , frameInfo ) ;
180
+ continue ;
181
+ }
163
182
}
164
183
165
184
firstFrame = false ;
166
185
167
- yield return CreateFrame ( stackFrame ) ;
186
+ if ( CreateFrame ( stackFrame ) is { } frame )
187
+ {
188
+ yield return frame;
189
+ }
190
+ else
191
+ {
192
+ _options . LogDebug ( "Could not resolve stack frame '{0}'" , stackFrame . ToString ( ) ) ;
193
+ }
168
194
}
169
195
}
170
196
171
197
/// <summary>
172
- /// Create a <see cref="SentryStackFrame"/> from a <see cref="StackFrame"/>.
198
+ /// Native AOT implementation of CreateFrame.
199
+ /// Native frames have only limited method information at runtime (and even that can be disabled).
200
+ /// We try to parse that and also add addresses for server-side symbolication.
173
201
/// </summary>
174
- internal SentryStackFrame CreateFrame ( StackFrame stackFrame ) => InternalCreateFrame ( stackFrame , true ) ;
202
+ private SentryStackFrame ? TryCreateNativeAOTFrame ( IStackFrame stackFrame )
203
+ {
204
+ if ( ! stackFrame . HasNativeImage ( ) )
205
+ {
206
+ return null ;
207
+ }
208
+
209
+ var frame = ParseNativeAOTToString ( stackFrame . ToString ( ) ) ;
210
+ frame . ImageAddress = stackFrame . GetNativeImageBase ( ) ;
211
+ frame. InstructionAddress = stackFrame . GetNativeIP ( ) ;
212
+ return frame;
213
+ }
214
+
215
+ // Method info is currently only exposed by ToString(), see https://github.com/dotnet/runtime/issues/92869
216
+ // We only care about the case where the method is available (`StackTraceSupport` property is the default `true`):
217
+ // https://github.com/dotnet/runtime/blob/254230253da143a082f47cfaf8711627c0bf2faf/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs#L42
218
+ internal static SentryStackFrame ParseNativeAOTToString ( string info )
219
+ {
220
+ var frame = new SentryStackFrame ( ) ;
221
+ var match = RegexNativeAOTInfo. Match( info ) ;
222
+ if ( match is { Success : true, Groups . Count : 3 } )
223
+ {
224
+ frame . Module = match . Groups [ 1 ] . Value ;
225
+ frame. Function = match . Groups [ 2 ] . Value ;
226
+ }
227
+ return frame;
228
+ }
175
229
176
230
/// <summary>
177
231
/// Default the implementation of CreateFrame.
178
232
/// </summary>
179
- private SentryStackFrame InternalCreateFrame ( StackFrame stackFrame , bool demangle )
233
+ private SentryStackFrame ? TryCreateManagedFrame ( IStackFrame stackFrame )
180
234
{
235
+ if ( stackFrame . GetMethod ( ) is not { } method )
236
+ {
237
+ return null;
238
+ }
239
+
181
240
const string unknownRequiredField = "(unknown)" ;
182
- string ? projectPath = null ;
183
- var frame = new SentryStackFrame ( ) ;
184
- if ( stackFrame . GetMethod ( ) is { } method )
241
+ var frame = new SentryStackFrame
185
242
{
186
- frame . Module = method . DeclaringType ? . FullName ?? unknownRequiredField ;
187
- frame . Package = method . DeclaringType ? . Assembly . FullName ;
243
+ Module = method . DeclaringType ? . FullName ?? unknownRequiredField ,
244
+ Package = method . DeclaringType ? . Assembly . FullName
245
+ } ;
188
246
189
- if ( _options . StackTraceMode == StackTraceMode . Enhanced &&
190
- stackFrame is EnhancedStackFrame enhancedStackFrame )
191
- {
192
- var stringBuilder = new StringBuilder ( ) ;
193
- frame . Function = enhancedStackFrame . MethodInfo . Append ( stringBuilder , false ) . ToString ( ) ;
247
+ if ( stackFrame . Frame is EnhancedStackFrame enhancedStackFrame )
248
+ {
249
+ var stringBuilder = new StringBuilder ( ) ;
250
+ frame . Function = enhancedStackFrame . MethodInfo . Append ( stringBuilder , false ) . ToString ( ) ;
194
251
195
- if ( enhancedStackFrame . MethodInfo . DeclaringType is { } declaringType )
196
- {
197
- stringBuilder . Clear ( ) ;
198
- stringBuilder . AppendTypeDisplayName ( declaringType ) ;
199
-
200
- // Ben.Demystifier doesn't always include the namespace, even when fullName==true.
201
- // It's important that the module name always be fully qualified, so that in-app frame
202
- // detection works correctly.
203
- var module = stringBuilder . ToString ( ) ;
204
- frame . Module = declaringType . Namespace is { } ns && ! module . StartsWith ( ns )
205
- ? $ "{ ns } .{ module } "
206
- : module ;
207
- }
208
- }
209
- else
252
+ if ( enhancedStackFrame . MethodInfo . DeclaringType is { } declaringType )
210
253
{
211
- frame . Function = method . Name ;
254
+ stringBuilder. Clear ( ) ;
255
+ stringBuilder. AppendTypeDisplayName ( declaringType ) ;
256
+
257
+ // Ben.Demystifier doesn't always include the namespace, even when fullName==true.
258
+ // It's important that the module name always be fully qualified, so that in-app frame
259
+ // detection works correctly.
260
+ var module = stringBuilder. ToString( ) ;
261
+ frame . Module = declaringType . Namespace is { } ns && ! module. StartsWith ( ns )
262
+ ? $"{ns}.{module}"
263
+ : module;
212
264
}
265
+ }
266
+ else
267
+ {
268
+ frame. Function = method . Name ;
269
+ }
213
270
214
- // Originally we didn't skip methods from dynamic assemblies, so not to break compatibility:
215
- if ( _options . StackTraceMode != StackTraceMode . Original && method . Module . Assembly . IsDynamic )
216
- {
217
- frame . InApp = false ;
218
- }
271
+ // Originally we didn't skip methods from dynamic assemblies, so not to break compatibility:
272
+ if ( _options . StackTraceMode != StackTraceMode . Original && method . Module . Assembly . IsDynamic )
273
+ {
274
+ frame. InApp = false ;
275
+ }
219
276
220
- AttributeReader . TryGetProjectDirectory ( method . Module . Assembly , out projectPath ) ;
277
+ if ( AddDebugImage ( method . Module ) is { } moduleIdx && moduleIdx != DebugImageMissing )
278
+ {
279
+ frame. AddressMode = GetRelativeAddressMode ( moduleIdx ) ;
221
280
222
- if ( AddDebugImage ( method . Module ) is { } moduleIdx && moduleIdx != DebugImageMissing )
281
+ try
223
282
{
224
- frame . AddressMode = GetRelativeAddressMode ( moduleIdx ) ;
225
-
226
- try
227
- {
228
- var token = method . MetadataToken ;
229
- // The top byte is the token type, the lower three bytes are the record id.
230
- // See: https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms404456(v=vs.100)#metadata-token-structure
231
- var tokenType = token & 0xff000000 ;
232
- // See https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/cortokentype-enumeration
233
- if ( tokenType == 0x06000000 ) // CorTokenType.mdtMethodDef
234
- {
235
- frame . FunctionId = token & 0x00ffffff ;
236
- }
237
- }
238
- catch ( InvalidOperationException )
283
+ var token = method. MetadataToken;
284
+ // The top byte is the token type, the lower three bytes are the record id.
285
+ // See: https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/ms404456(v=vs.100)#metadata-token-structure
286
+ var tokenType = token & 0xff000000 ;
287
+ // See https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/cortokentype-enumeration
288
+ if ( tokenType = = 0x06000000 ) // CorTokenType.mdtMethodDef
239
289
{
240
- // method.MetadataToken may throw
241
- // see https://learn.microsoft.com/en-us/dotnet/api/system.reflection.memberinfo.metadatatoken?view=net-6.0
242
- _options . LogDebug ( "Could not get MetadataToken for stack frame {0} from {1}" , frame . Function , method . Module . Name ) ;
290
+ frame . FunctionId = token & 0x00ffffff ;
243
291
}
244
292
}
293
+ catch ( InvalidOperationException )
294
+ {
295
+ // method.MetadataToken may throw
296
+ // see https://learn.microsoft.com/en-us/dotnet/api/system.reflection.memberinfo.metadatatoken?view=net-6.0
297
+ _options. LogDebug( "Could not get MetadataToken for stack frame {0} from {1}" , frame . Function , method . Module . Name ) ;
298
+ }
245
299
}
246
300
247
- frame . ConfigureAppFrame ( _options ) ;
248
-
249
301
if ( stackFrame . GetFileName ( ) is { } frameFileName )
250
302
{
251
- if ( projectPath != null && frameFileName . StartsWith ( projectPath , StringComparison . OrdinalIgnoreCase ) )
303
+ if ( AttributeReader . TryGetProjectDirectory ( method . Module . Assembly ) is { } projectPath
304
+ && frameFileName . StartsWith ( projectPath , StringComparison . OrdinalIgnoreCase ) )
252
305
{
253
306
frame. AbsolutePath = frameFileName ;
254
307
frameFileName = frameFileName [ projectPath . Length ..] ;
255
308
}
256
309
frame. FileName = frameFileName ;
257
310
}
258
311
312
+ return frame;
313
+ }
314
+
315
+ /// <summary>
316
+ /// Create a <see cref="SentryStackFrame"/> from a <see cref="StackFrame"/>.
317
+ /// </summary>
318
+ internal SentryStackFrame ? CreateFrame ( IStackFrame stackFrame )
319
+ {
320
+ var frame = TryCreateManagedFrame( stackFrame ) ;
321
+ frame ??= TryCreateNativeAOTFrame ( stackFrame ) ;
322
+ if ( frame is null)
323
+ {
324
+ return null ;
325
+ }
326
+
327
+ frame. ConfigureAppFrame ( _options ) ;
328
+
259
329
// stackFrame.HasILOffset() throws NotImplemented on Mono 5.12
260
330
var ilOffset = stackFrame. GetILOffset( ) ;
261
331
if ( ilOffset ! = StackFrame . OFFSET_UNKNOWN )
@@ -270,19 +340,19 @@ private SentryStackFrame InternalCreateFrame(StackFrame stackFrame, bool demangl
270
340
}
271
341
272
342
var colNo = stackFrame . GetFileColumnNumber ( ) ;
273
- if ( lineNo > 0 )
343
+ if ( colNo > 0 )
274
344
{
275
345
frame . ColumnNumber = colNo ;
276
346
}
277
347
278
- if ( demangle && _options . StackTraceMode != StackTraceMode . Enhanced )
348
+ if ( stackFrame . Frame is not EnhancedStackFrame )
279
349
{
280
350
DemangleAsyncFunctionName ( frame ) ;
281
351
DemangleAnonymousFunction( frame ) ;
282
352
DemangleLambdaReturnType( frame ) ;
283
353
}
284
354
285
- if ( _options . StackTraceMode == StackTraceMode . Enhanced )
355
+ if ( stackFrame . Frame is EnhancedStackFrame )
286
356
{
287
357
// In Enhanced mode, Module (which in this case is the Namespace)
288
358
// is already prepended to the function, after return type.
0 commit comments