// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Text; using System.Reflection; namespace System.Diagnostics { /// /// There is no good reason for the methods of this class to be virtual. /// public partial class StackFrame { /// /// Reflection information for the method if available, null otherwise. /// private MethodBase? _method; /// /// Native offset of the current instruction within the current method if available, /// OFFSET_UNKNOWN otherwise. /// private int _nativeOffset; /// /// IL offset of the current instruction within the current method if available, /// OFFSET_UNKNOWN otherwise. /// private int _ilOffset; /// /// Source file name representing the current code location if available, null otherwise. /// private string? _fileName; /// /// Line number representing the current code location if available, 0 otherwise. /// private int _lineNumber; /// /// Column number representing the current code location if available, 0 otherwise. /// private int _columnNumber; /// /// This flag is set to true when the frame represents a rethrow marker. /// private bool _isLastFrameFromForeignExceptionStackTrace; private void InitMembers() { _nativeOffset = OFFSET_UNKNOWN; _ilOffset = OFFSET_UNKNOWN; } /// /// Constructs a StackFrame corresponding to the active stack frame. /// public StackFrame() { InitMembers(); BuildStackFrame(StackTrace.METHODS_TO_SKIP, false); } /// /// Constructs a StackFrame corresponding to the active stack frame. /// public StackFrame(bool needFileInfo) { InitMembers(); BuildStackFrame(StackTrace.METHODS_TO_SKIP, needFileInfo); } /// /// Constructs a StackFrame corresponding to a calling stack frame. /// public StackFrame(int skipFrames) { InitMembers(); BuildStackFrame(skipFrames + StackTrace.METHODS_TO_SKIP, false); } /// /// Constructs a StackFrame corresponding to a calling stack frame. /// public StackFrame(int skipFrames, bool needFileInfo) { InitMembers(); BuildStackFrame(skipFrames + StackTrace.METHODS_TO_SKIP, needFileInfo); } /// /// Constructs a "fake" stack frame, just containing the given file /// name and line number. Use when you don't want to use the /// debugger's line mapping logic. /// public StackFrame(string? fileName, int lineNumber) { InitMembers(); BuildStackFrame(StackTrace.METHODS_TO_SKIP, false); _fileName = fileName; _lineNumber = lineNumber; } /// /// Constructs a "fake" stack frame, just containing the given file /// name, line number and column number. Use when you don't want to /// use the debugger's line mapping logic. /// public StackFrame(string? fileName, int lineNumber, int colNumber) : this (fileName, lineNumber) { _columnNumber = colNumber; } /// /// Constant returned when the native or IL offset is unknown /// public const int OFFSET_UNKNOWN = -1; internal bool IsLastFrameFromForeignExceptionStackTrace => _isLastFrameFromForeignExceptionStackTrace; /// /// Returns the method the frame is executing /// public virtual MethodBase? GetMethod() { return _method; } /// /// Returns the offset from the start of the native (jitted) code for the /// method being executed /// public virtual int GetNativeOffset() { return _nativeOffset; } /// /// Returns the offset from the start of the IL code for the /// method being executed. This offset may be approximate depending /// on whether the jitter is generating debuggable code or not. /// public virtual int GetILOffset() { return _ilOffset; } /// /// Returns the file name containing the code being executed. This /// information is normally extracted from the debugging symbols /// for the executable. /// public virtual string? GetFileName() { return _fileName; } /// /// Returns the line number in the file containing the code being executed. /// This information is normally extracted from the debugging symbols /// for the executable. /// public virtual int GetFileLineNumber() { return _lineNumber; } /// /// Returns the column number in the line containing the code being executed. /// This information is normally extracted from the debugging symbols /// for the executable. /// public virtual int GetFileColumnNumber() { return _columnNumber; } /// /// Builds a readable representation of the stack frame /// public override string ToString() { StringBuilder sb = new StringBuilder(255); bool includeFileInfoIfAvailable; if (_method != null) { sb.Append(_method.Name); // deal with the generic portion of the method if (_method is MethodInfo methodInfo && methodInfo.IsGenericMethod) { Type[] typars = methodInfo.GetGenericArguments(); sb.Append('<'); int k = 0; bool fFirstTyParam = true; while (k < typars.Length) { if (fFirstTyParam == false) sb.Append(','); else fFirstTyParam = false; sb.Append(typars[k].Name); k++; } sb.Append('>'); } includeFileInfoIfAvailable = true; } else { includeFileInfoIfAvailable = AppendStackFrameWithoutMethodBase(sb); } if (includeFileInfoIfAvailable) { sb.Append(" at offset "); if (_nativeOffset == OFFSET_UNKNOWN) sb.Append(""); else sb.Append(_nativeOffset); sb.Append(" in file:line:column "); sb.Append(_fileName ?? ""); sb.Append(':'); sb.Append(_lineNumber); sb.Append(':'); sb.Append(_columnNumber); } else { sb.Append(""); } sb.Append(Environment.NewLine); return sb.ToString(); } } }