| File: System\Diagnostics\StackFrame.NativeAot.cs | Web Access |
| Project: src\runtime\src\coreclr\nativeaot\System.Private.CoreLib\src\System.Private.CoreLib.csproj (System.Private.CoreLib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime; using System.Runtime.CompilerServices; using System.Text; using Internal.Reflection.Augments; using Internal.Runtime.Augments; namespace System.Diagnostics { /// <summary> /// Stack frame represents a single frame in a stack trace; frames /// corresponding to methods with available symbolic information /// provide source file / line information. Some frames may provide IL /// offset information and / or MethodBase reflection information. /// There is no good reason for the methods of this class to be virtual. /// </summary> public partial class StackFrame { /// <summary> /// IP address representing this stack frame. /// </summary> private IntPtr _ipAddress; /// <summary> /// File info flag to use for stack trace-style formatting. /// </summary> private bool _needFileInfo; /// <summary> /// Will be true if we attempted to retrieve the associated MethodBase but couldn't. /// </summary> private bool _noMethodBaseAvailable; private bool _isStackTraceHidden; // If stack trace metadata is available, _methodOwningType is the namespace-qualified name of the owning type, // _methodName is the name of the method, _methodGenericArgs are generic arguments, and _methodSignature is the list of parameters // without braces. StackTrace will format this as `{_methodOwningType}.{_methodName}<{_genericArgs}>({_methodSignature}). // We keep this separate because StackFrame.ToString is defined as returning _methodName[{_genericArgs}]. // If stack trace metadata is unavailable, only _methodName is populated and contains the "{fileNameWithoutExtension}!<BaseAddress>+0x{rva:x}" private string _methodOwningType; private string _methodName; private string _methodGenericArgs; private string _methodSignature; /// <summary> /// Returns the method the frame is executing /// </summary> [RequiresUnreferencedCode("Metadata for the method might be incomplete or removed. Consider using " + nameof(DiagnosticMethodInfo) + "." + nameof(DiagnosticMethodInfo.Create) + " instead")] public virtual MethodBase? GetMethod() { TryInitializeMethodBase(); return _method; } internal bool TryGetMethodStartAddress(out IntPtr startAddress) { if (_ipAddress == IntPtr.Zero || _ipAddress == Exception.EdiSeparator) { startAddress = IntPtr.Zero; return false; } startAddress = _ipAddress - _nativeOffset; Debug.Assert(RuntimeImports.RhFindMethodStartAddress(_ipAddress) == startAddress); return true; } private bool TryInitializeMethodBase() { if (_noMethodBaseAvailable || _ipAddress == IntPtr.Zero || _ipAddress == Exception.EdiSeparator) return false; if (_method != null) return true; IntPtr methodStartAddress = _ipAddress - _nativeOffset; Debug.Assert(RuntimeImports.RhFindMethodStartAddress(_ipAddress) == methodStartAddress); _method = ReflectionAugments.GetMethodBaseFromStartAddressIfAvailable(methodStartAddress); if (_method == null) { _noMethodBaseAvailable = true; return false; } return true; } /// <summary> /// Constructs a StackFrame corresponding to a given IP address. /// </summary> internal StackFrame(IntPtr ipAddress, bool needFileInfo) { InitializeForIpAddress(ipAddress, needFileInfo); } /// <summary> /// Internal stack frame initialization based on IP address. /// </summary> private void InitializeForIpAddress(IntPtr ipAddress, bool needFileInfo) { _ipAddress = ipAddress; _needFileInfo = needFileInfo; if (_ipAddress == Exception.EdiSeparator) { _isLastFrameFromForeignExceptionStackTrace = true; } else if (_ipAddress != IntPtr.Zero) { IntPtr methodStartAddress = RuntimeImports.RhFindMethodStartAddress(ipAddress); _nativeOffset = (int)((nint)_ipAddress - (nint)methodStartAddress); _ilOffset = StackFrame.OFFSET_UNKNOWN; StackTraceMetadataCallbacks stackTraceCallbacks = RuntimeAugments.StackTraceCallbacksIfAvailable; if (stackTraceCallbacks != null) { _methodName = stackTraceCallbacks.TryGetMethodStackFrameInfo(methodStartAddress, _nativeOffset, needFileInfo, out _methodOwningType, out _methodGenericArgs, out _methodSignature, out _isStackTraceHidden, out _fileName, out _lineNumber); } if (_methodName == null) { // If we don't have precise information, try to map it at least back to the right module. string moduleFullFileName = RuntimeAugments.TryGetFullPathToApplicationModule(_ipAddress, out IntPtr moduleBase); // Without any callbacks or the ability to map ip correctly we better admit that we don't know if (string.IsNullOrEmpty(moduleFullFileName)) { _methodName = "<unknown>"; } else { ReadOnlySpan<char> fileNameWithoutExtension = Path.GetFileNameWithoutExtension(moduleFullFileName.AsSpan()); int rva = (int)(_ipAddress - moduleBase); _methodName = $"{fileNameWithoutExtension}!<BaseAddress>+0x{rva:x}"; } } } } /// <summary> /// Internal stack frame initialization based on frame index within the stack of the current thread. /// </summary> [MethodImplAttribute(MethodImplOptions.NoInlining)] private void BuildStackFrame(int frameIndex, bool needFileInfo) { const int SystemDiagnosticsStackDepth = 2; frameIndex += SystemDiagnosticsStackDepth; IntPtr[] frameArray = new IntPtr[frameIndex + 1]; int returnedFrameCount = RuntimeImports.RhGetCurrentThreadStackTrace(frameArray); int realFrameCount = (returnedFrameCount >= 0 ? returnedFrameCount : frameArray.Length); IntPtr ipAddress = (frameIndex < realFrameCount) ? frameArray[frameIndex] : IntPtr.Zero; InitializeForIpAddress(ipAddress, needFileInfo); } /// <summary> /// Return native IP address for this stack frame. /// </summary> internal IntPtr GetNativeIPAddress() { return _ipAddress; } /// <summary> /// Check whether method info is available. /// </summary> internal bool HasMethod() { return TryInitializeMethodBase(); } /// <summary> /// Format stack frame without MethodBase info. Return true if the stack info /// is valid and line information should be appended if available. /// </summary> private bool AppendStackFrameWithoutMethodBase(StringBuilder builder) { builder.Append(_methodName); if (_methodGenericArgs != null) { builder.Append('<'); builder.Append(_methodGenericArgs); builder.Append('>'); } return true; } /// <summary> /// Set rethrow marker. /// </summary> internal void SetIsLastFrameFromForeignExceptionStackTrace() { _isLastFrameFromForeignExceptionStackTrace = true; } /// <summary> /// Builds a representation of the stack frame for use in the stack trace. /// </summary> internal void AppendToStackTrace(StringBuilder builder) { if (_ipAddress != Exception.EdiSeparator) { if (!_isStackTraceHidden) { // Passing a default string for "at" in case SR.UsingResourceKeys() is true // as this is a special case and we don't want to have "Word_At" on stack traces. string word_At = SR.UsingResourceKeys() ? "at" : SR.Word_At; // We also want to pass in a default for inFileLineNumber. string inFileLineNum = SR.UsingResourceKeys() ? "in {0}:line {1}" : SR.StackTrace_InFileLineNumber; builder.Append(" ").Append(word_At).Append(' '); AppendCommonStringRepresenation(builder, allowFallback: true); if (_fileName != null) { // tack on " in c:\tmp\MyFile.cs:line 5" builder.Append(' '); builder.AppendFormat(CultureInfo.InvariantCulture, inFileLineNum, _fileName, _lineNumber); } builder.AppendLine(); } } if (_isLastFrameFromForeignExceptionStackTrace) { // Passing default for Exception_EndStackTraceFromPreviousThrow in case SR.UsingResourceKeys is set. builder.AppendLine(SR.UsingResourceKeys() ? "--- End of stack trace from previous location ---" : SR.Exception_EndStackTraceFromPreviousThrow); } } private void AppendCommonStringRepresenation(StringBuilder builder, bool allowFallback) { if (_methodOwningType != null) { builder.Append(_methodOwningType); builder.Append('.'); builder.Append(_methodName); if (_methodGenericArgs != null) { builder.Append('['); builder.Append(_methodGenericArgs); builder.Append(']'); } builder.Append('('); builder.Append(_methodSignature); builder.Append(')'); } else if (allowFallback) { Debug.Assert(_methodSignature == null); builder.Append(_methodName); } } internal string GetCrashInfoString() { StringBuilder sb = new StringBuilder(); AppendCommonStringRepresenation(sb, allowFallback: false); return sb.Length > 0 ? sb.ToString() : null; } } }