diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/ExceptionTestAssembly/ExceptionTestAssembly.cs b/src/libraries/System.Diagnostics.StackTrace/tests/ExceptionTestAssembly/ExceptionTestAssembly.cs
new file mode 100644
index 0000000000000..49902b1000c02
--- /dev/null
+++ b/src/libraries/System.Diagnostics.StackTrace/tests/ExceptionTestAssembly/ExceptionTestAssembly.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+class Program
+{
+ public static void Foo()
+ {
+ throw new NullReferenceException();
+ }
+}
diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/ExceptionTestAssembly/ExceptionTestAssembly.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/ExceptionTestAssembly/ExceptionTestAssembly.csproj
new file mode 100644
index 0000000000000..d7b60a977d7ce
--- /dev/null
+++ b/src/libraries/System.Diagnostics.StackTrace/tests/ExceptionTestAssembly/ExceptionTestAssembly.csproj
@@ -0,0 +1,10 @@
+
+
+ true
+ netstandard2.0
+ None
+
+
+
+
+
diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs
index 894374ba29b4d..0464a22a44014 100644
--- a/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs
+++ b/src/libraries/System.Diagnostics.StackTrace/tests/StackTraceTests.cs
@@ -3,9 +3,13 @@
using System.Collections.Generic;
using System.Globalization;
+using System.IO;
using System.Linq;
using System.Reflection;
+using System.Reflection.Emit;
using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+using Microsoft.DotNet.RemoteExecutor;
using Xunit;
namespace System.Diagnostics
@@ -300,6 +304,72 @@ public void ToString_NullFrame_ThrowsNullReferenceException()
Assert.Equal(Environment.NewLine, stackTrace.ToString());
}
+ [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/51096", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))]
+ public void ToString_ShowILOffset()
+ {
+ string AssemblyName = "ExceptionTestAssembly.dll";
+ string SourceTestAssemblyPath = Path.Combine(Environment.CurrentDirectory, AssemblyName);
+ string regPattern = @":token 0x([a-f0-9]*)\+0x([a-f0-9]*)";
+
+ // Normal loading case
+ RemoteExecutor.Invoke((asmPath, asmName, p) =>
+ {
+ AppContext.SetSwitch("Switch.System.Diagnostics.StackTrace.ShowILOffsets", true);
+ var asm = Assembly.LoadFrom(asmPath);
+ try
+ {
+ asm.GetType("Program").GetMethod("Foo").Invoke(null, null);
+ }
+ catch (Exception e)
+ {
+ Assert.Contains(asmName, e.InnerException.StackTrace);
+ Assert.True(Regex.Match(e.InnerException.StackTrace, p).Success);
+ }
+ }, SourceTestAssemblyPath, AssemblyName, regPattern).Dispose();
+
+ // Assembly.Load(Byte[]) case
+ RemoteExecutor.Invoke((asmPath, asmName, p) =>
+ {
+ AppContext.SetSwitch("Switch.System.Diagnostics.StackTrace.ShowILOffsets", true);
+ var inMemBlob = File.ReadAllBytes(asmPath);
+ var asm2 = Assembly.Load(inMemBlob);
+ try
+ {
+ asm2.GetType("Program").GetMethod("Foo").Invoke(null, null);
+ }
+ catch (Exception e)
+ {
+ Assert.Contains(asmName, e.InnerException.StackTrace);
+ Assert.True(Regex.Match(e.InnerException.StackTrace, p).Success);
+ }
+ }, SourceTestAssemblyPath, AssemblyName, regPattern).Dispose();
+
+ // AssmblyBuilder.DefineDynamicAssembly() case
+ RemoteExecutor.Invoke((p) =>
+ {
+ AppContext.SetSwitch("Switch.System.Diagnostics.StackTrace.ShowILOffsets", true);
+ AssemblyName asmName = new AssemblyName("ExceptionTestAssembly");
+ AssemblyBuilder asmBldr = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
+ ModuleBuilder modBldr = asmBldr.DefineDynamicModule(asmName.Name);
+ TypeBuilder tBldr = modBldr.DefineType("Program");
+ MethodBuilder mBldr = tBldr.DefineMethod("Foo", MethodAttributes.Public | MethodAttributes.Static, null, null);
+ ILGenerator ilGen = mBldr.GetILGenerator();
+ ilGen.ThrowException(typeof(NullReferenceException));
+ ilGen.Emit(OpCodes.Ret);
+ Type t = tBldr.CreateType();
+ try
+ {
+ t.InvokeMember("Foo", BindingFlags.InvokeMethod, null, null, null);
+ }
+ catch (Exception e)
+ {
+ Assert.Contains("RefEmit_InMemoryManifestModule", e.InnerException.StackTrace);
+ Assert.True(Regex.Match(e.InnerException.StackTrace, p).Success);
+ }
+ }, regPattern).Dispose();
+ }
+
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
private static StackTrace NoParameters() => new StackTrace();
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
diff --git a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj
index b112648c18363..93673781e705f 100644
--- a/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj
+++ b/src/libraries/System.Diagnostics.StackTrace/tests/System.Diagnostics.StackTrace.Tests.csproj
@@ -2,6 +2,7 @@
$(NetCoreAppCurrent)
true
+ true
@@ -17,4 +18,7 @@
-
\ No newline at end of file
+
+
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 807cca91a37ae..2b214c82eaf72 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -3253,6 +3253,9 @@
The timeout must represent a value between -1 and Int32.MaxValue, inclusive.
+
+ in {0}:token 0x{1:x}+0x{2:x}
+
in {0}:line {1}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs
index 3c6ba961c79d3..10865d4432158 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs
@@ -206,6 +206,7 @@ internal void ToString(TraceFormat traceFormat, StringBuilder sb)
string word_At = SR.GetResourceString(nameof(SR.Word_At), defaultString: "at");
// We also want to pass in a default for inFileLineNumber.
string inFileLineNum = SR.GetResourceString(nameof(SR.StackTrace_InFileLineNumber), defaultString: "in {0}:line {1}");
+ string inFileILOffset = SR.GetResourceString(nameof(SR.StackTrace_InFileILOffset), defaultString: "in {0}:token 0x{1:x}+0x{2:x}");
bool fFirstFrame = true;
for (int iFrameIndex = 0; iFrameIndex < _numOfFrames; iFrameIndex++)
{
@@ -323,6 +324,17 @@ internal void ToString(TraceFormat traceFormat, StringBuilder sb)
sb.Append(' ');
sb.AppendFormat(CultureInfo.InvariantCulture, inFileLineNum, fileName, sf.GetFileLineNumber());
}
+ else if (LocalAppContextSwitches.ShowILOffsets && mb.ReflectedType != null)
+ {
+ string assemblyName = mb.ReflectedType.Module.ScopeName;
+ try
+ {
+ int token = mb.MetadataToken;
+ sb.Append(' ');
+ sb.AppendFormat(CultureInfo.InvariantCulture, inFileILOffset, assemblyName, token, sf.GetILOffset());
+ }
+ catch (System.InvalidOperationException) {}
+ }
}
// Skip EDI boundary for async
diff --git a/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs b/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs
index d669bd4cccc4e..ed450d7494369 100644
--- a/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs
@@ -47,5 +47,23 @@ public static bool SerializationGuard
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => GetCachedSwitchValue("Switch.System.Runtime.Serialization.SerializationGuard", ref s_serializationGuard);
}
+
+ private static int s_showILOffset;
+ private static bool GetDefaultShowILOffsetSetting()
+ {
+ if (s_showILOffset < 0) return false;
+ if (s_showILOffset > 0) return true;
+
+ bool isSwitchEnabled = AppContextConfigHelper.GetBooleanConfig("Switch.System.Diagnostics.StackTrace.ShowILOffsets", true);
+ s_showILOffset = isSwitchEnabled ? 1 : -1;
+
+ return isSwitchEnabled;
+ }
+
+ public static bool ShowILOffsets
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => GetDefaultShowILOffsetSetting();
+ }
}
}