Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IL Offset and Method Token to stacktrace #44013

Merged
merged 14 commits into from
Apr 12, 2021
Merged
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<DebugType>None</DebugType>
</PropertyGroup>
<ItemGroup>
<Compile Include="ExceptionTestAssembly.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit please sort using statements

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll fix

using Microsoft.DotNet.RemoteExecutor;
using Xunit;

namespace System.Diagnostics
Expand Down Expand Up @@ -300,6 +304,71 @@ public void ToString_NullFrame_ThrowsNullReferenceException()
Assert.Equal(Environment.NewLine, stackTrace.ToString());
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess not needed any more? And same in other test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the default value is true, but it can be changed.
I think it is necessary to setup test environment.

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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
<TestRuntime>true</TestRuntime>
<IncludeRemoteExecutor>true</IncludeRemoteExecutor>
</PropertyGroup>
<ItemGroup>
<Compile Include="StackFrameExtensionsTests.cs" />
Expand All @@ -17,4 +18,7 @@
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).pdb" />
</ItemGroup>
</Project>
<ItemGroup>
<ProjectReference Include="ExceptionTestAssembly\ExceptionTestAssembly.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3253,6 +3253,9 @@
<data name="SpinWait_SpinUntil_TimeoutWrong" xml:space="preserve">
<value>The timeout must represent a value between -1 and Int32.MaxValue, inclusive.</value>
</data>
<data name="StackTrace_InFileILOffset" xml:space="preserve">
<value>in {0}:token 0x{1:x}+0x{2:x}</value>
</data>
<data name="StackTrace_InFileLineNumber" xml:space="preserve">
<value>in {0}:line {1}</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
{
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}