File: src\System\Diagnostics\StackFrameHelper.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\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.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
 
namespace System.Diagnostics
{
    // READ ME:
    // Modifying the order or fields of this object may require other changes
    // to the unmanaged definition of the StackFrameHelper class, in
    // VM\DebugDebugger.h. The binder will catch some of these layout problems.
    internal sealed class StackFrameHelper
    {
        private int[]? rgiOffset;
        private int[]? rgiILOffset;
 
#pragma warning disable 414
        // dynamicMethods is an array of System.Resolver objects, used to keep
        // DynamicMethodDescs AND collectible LoaderAllocators alive for the lifetime of StackFrameHelper.
        private object? dynamicMethods; // Field is not used from managed.
 
        private IntPtr[]? rgMethodHandle;
        private string[]? rgAssemblyPath;
        private Assembly?[]? rgAssembly;
        private IntPtr[]? rgLoadedPeAddress;
        private int[]? rgiLoadedPeSize;
        private bool[]? rgiIsFileLayout;
        private IntPtr[]? rgInMemoryPdbAddress;
        private int[]? rgiInMemoryPdbSize;
        // if rgiMethodToken[i] == 0, then don't attempt to get the portable PDB source/info
        private int[]? rgiMethodToken;
        private string?[]? rgFilename;
        private int[]? rgiLineNumber;
        private int[]? rgiColumnNumber;
        private bool[]? rgiLastFrameFromForeignExceptionStackTrace;
        private int iFrameCount;
#pragma warning restore 414
 
        [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
        [return: UnsafeAccessorType("System.Diagnostics.StackTraceSymbols, System.Diagnostics.StackTrace, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
        private static extern object CreateStackTraceSymbols();
 
        [UnsafeAccessor(UnsafeAccessorKind.Method)]
        private static extern void GetSourceLineInfo(
            [UnsafeAccessorType("System.Diagnostics.StackTraceSymbols, System.Diagnostics.StackTrace, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")] object target,
            Assembly? assembly, string assemblyPath, IntPtr loadedPeAddress,
            int loadedPeSize, bool isFileLayout, IntPtr inMemoryPdbAddress, int inMemoryPdbSize, int methodToken, int ilOffset,
            out string? sourceFile, out int sourceLine, out int sourceColumn);
 
        private static object? s_stackTraceSymbolsCache;
 
        [ThreadStatic]
        private static int t_reentrancy;
 
        public StackFrameHelper()
        {
            rgMethodHandle = null;
            rgiMethodToken = null;
            rgiOffset = null;
            rgiILOffset = null;
            rgAssemblyPath = null;
            rgAssembly = null;
            rgLoadedPeAddress = null;
            rgiLoadedPeSize = null;
            rgiIsFileLayout = null;
            rgInMemoryPdbAddress = null;
            rgiInMemoryPdbSize = null;
            dynamicMethods = null;
            rgFilename = null;
            rgiLineNumber = null;
            rgiColumnNumber = null;
 
            rgiLastFrameFromForeignExceptionStackTrace = null;
 
            // 0 means capture all frames.  For StackTraces from an Exception, the EE always
            // captures all frames.  For other uses of StackTraces, we can abort stack walking after
            // some limit if we want to by setting this to a non-zero value.  In Whidbey this was
            // hard-coded to 512, but some customers complained.  There shouldn't be any need to limit
            // this as memory/CPU is no longer allocated up front.  If there is some reason to provide a
            // limit in the future, then we should expose it in the managed API so applications can
            // override it.
            iFrameCount = 0;
        }
 
        //
        // Initializes the stack trace helper. If fNeedFileInfo is true, initializes rgFilename,
        // rgiLineNumber and rgiColumnNumber fields using the portable PDB reader if not already
        // done by GetStackFramesInternal (on Windows for old PDB format).
        //
 
        internal void InitializeSourceInfo(bool fNeedFileInfo, Exception? exception)
        {
            StackTrace.GetStackFramesInternal(this, fNeedFileInfo, exception);
 
            if (!fNeedFileInfo)
                return;
 
            // Check if this function is being reentered because of an exception in the code below
            if (t_reentrancy > 0)
                return;
 
            t_reentrancy++;
            try
            {
                if (s_stackTraceSymbolsCache == null)
                {
                    // We could race with another thread. It doesn't matter if we win or lose, the losing instance will be GC'ed and all threads including this one will
                    // use the winning instance
                    Interlocked.CompareExchange(ref s_stackTraceSymbolsCache, CreateStackTraceSymbols(), null);
                }
 
                for (int index = 0; index < iFrameCount; index++)
                {
                    // If there was some reason not to try get the symbols from the portable PDB reader like the module was
                    // ENC or the source/line info was already retrieved, the method token is 0.
                    if (rgiMethodToken![index] != 0)
                    {
                        GetSourceLineInfo(s_stackTraceSymbolsCache!, rgAssembly![index], rgAssemblyPath![index]!, rgLoadedPeAddress![index], rgiLoadedPeSize![index], rgiIsFileLayout![index],
                            rgInMemoryPdbAddress![index], rgiInMemoryPdbSize![index], rgiMethodToken![index],
                            rgiILOffset![index], out rgFilename![index], out rgiLineNumber![index], out rgiColumnNumber![index]);
                    }
                }
            }
            catch
            {
            }
            finally
            {
                t_reentrancy--;
            }
        }
 
        public MethodBase? GetMethodBase(int i)
        {
            // There may be a better way to do this.
            // we got RuntimeMethodHandles here and we need to go to MethodBase
            // but we don't know whether the reflection info has been initialized
            // or not. So we call GetMethods and GetConstructors on the type
            // and then we fetch the proper MethodBase!!
            IntPtr mh = rgMethodHandle![i];
 
            if (mh == IntPtr.Zero)
                return null;
 
            IRuntimeMethodInfo? mhReal = RuntimeMethodHandle.GetTypicalMethodDefinition(new RuntimeMethodInfoStub(new RuntimeMethodHandleInternal(mh), this));
 
            return RuntimeType.GetMethodBase(mhReal);
        }
 
        public int GetOffset(int i) { return rgiOffset![i]; }
        public int GetILOffset(int i) { return rgiILOffset![i]; }
        public string? GetFilename(int i) { return rgFilename?[i]; }
        public int GetLineNumber(int i) { return rgiLineNumber == null ? 0 : rgiLineNumber[i]; }
        public int GetColumnNumber(int i) { return rgiColumnNumber == null ? 0 : rgiColumnNumber[i]; }
 
        public bool IsLastFrameFromForeignExceptionStackTrace(int i)
        {
            return rgiLastFrameFromForeignExceptionStackTrace != null && rgiLastFrameFromForeignExceptionStackTrace[i];
        }
 
        public int GetNumberOfFrames() { return iFrameCount; }
    }
}