-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Conversation
830b95d
to
b5137f5
Compare
src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs
Outdated
Show resolved
Hide resolved
ad44de3
to
0d3ce1b
Compare
Should we do such changes always via feature switches to make them universally available? Why |
In many embedded system case, the PDBs cannot be deployed together due to insufficient storage issue. The environment variable name is set as such because it is a feature that adds il offset to the stacktrace when there is no line information. If there are better suggestions, it can be changed. |
Tagging subscribers to this area: @tommcdon Issue DetailsAdd IL Offset and Method Token info to the stacktrace if PDBs are not deplyed. It is often deployed without a PDB file to reduce deploying size or for obfuscation. For easier debugging, if the PDB is not deployed, I want to add the Method Token and IL Offset at the stacktrace. The functionality can be turned on and off with the environment variable COMPlus_ILOffsetToStackTrace. (Example)
|
@tommcdon this is one of our older PR's - I wonder whether we could assign someone to shepherd it? that will show up on https://aka.ms/dotnetruntimepulls and you can track there if you find that helpful.. |
src/libraries/System.Private.CoreLib/src/System/Diagnostics/StackTrace.cs
Outdated
Show resolved
Hide resolved
Can you rebase this on the latest "main" and push> |
if (s_ilOffsetToStackTrace) | ||
{ | ||
sb.Append(' '); | ||
string methodToken = mb.MetadataToken.ToString("x"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MetadataToken
is not always available and it can throw exceptions.
For example, it is not available for DynamicMethods. This property throws System.InvalidOperationException
on DynamicMethods.
Metadata tokens are also not available in some flavors of AOT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for review. I'll check those case and fix it
sb.Append(' '); | ||
string methodToken = mb.MetadataToken.ToString("x"); | ||
string ilOffset = sf.GetILOffset().ToString("x"); | ||
sb.AppendFormat(CultureInfo.InvariantCulture, inFileILOffset, mb.Module.Name, methodToken, ilOffset); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module.Name
will just return <Unknown>
in single file mode, and in other cases where the module is not loaded from physical file on disk. Would it better to use Assembly simple name instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you said, assembly simple name looks better.
I agree that enabling this via environment variable won't work for number of cases. The meta point behind this and other feedback: Is this just a quick undocumented hack that does not work half of the time, or should this be a proper feature, with tests, documentation, etc.? |
Considering a case where there is a limitation in ROM size like a CE device or an IoT device, I think that this should be a proper feature. In addition, if this feature is merged, we want to add a functionality to getting line information from methodToken and ILOffset via diagnostics. What do you think about this? |
{ | ||
sb.Append(' '); | ||
string methodToken = mb.MetadataToken.ToString("x"); | ||
string ilOffset = sf.GetILOffset().ToString("x"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ToString use current culture which is huge dependency that can fail, you should pass CultureInfo.InvariantCulture in.
Nonetheless, these are complex calls and can fail so it'd be better to handle exceptions too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix it
@@ -18,6 +18,7 @@ public partial class StackTrace | |||
{ | |||
public const int METHODS_TO_SKIP = 0; | |||
|
|||
private static readonly bool s_ilOffsetToStackTrace = Environment.GetEnvironmentVariable("COMPlus_ILOffsetToStackTrace") == "1"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be better to avoid static constructor in StackTrace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A static constructor was used to avoid unnecessarily checking the environment variable multiple times. Could this be a problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this could cause problems when for example Environment .cctor crashes. Using env variables will also require them to be set every time or won't work on deployments where setting env variables are not supported/allowed. I think using https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/LocalAppContextSwitches.cs would be better.
/cc @jkotas
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code that gets the Environment value from the static constructor is already used in various runtimes.
see :
runtime/src/libraries/System.Private.CoreLib/src/System/Diagnostics/DebugProvider.Unix.cs
Line 8 in 1d12854
private static readonly bool s_shouldWriteToStdErr = Environment.GetEnvironmentVariable("COMPlus_DebugWriteToStdErr") == "1"; |
Also, if there is a crash in Environment .cctor, it is thought that there is a problem in the system already. Should this be considered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marek-safar All review contents have been applied except for the use of static constructor. If it is necessary to change the static constructor to LocalAppContextSwitches, I will apply that as well. Please give me your opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StackTrace is used (and usable) for crashes in the Environment as well so creating circular type initialization dependency between these types is not desirable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll change it to use LocalAppContextCaches.
@@ -3349,6 +3349,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}: methodToken(0x{1}), ILOffset(0x{2})</value> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we use the parentheses syntax anywhere in .NET. Also not sure why ILOffset and not IL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I used the parentheses syntax because it seems more recognizable. Is there a better way to recommend it?
For example,
at ILOffsetTest.App.callD() in ILOffsetTest.dll: methodToken(0x6000003), ILOffset(0xe)
-->
at ILOffsetTest.App.callD() in ILOffsetTest.dll: methodToken `0x6000003`, ILOffset : `0xe`
or
at ILOffsetTest.App.callD() in ILOffsetTest.dll: methodToken 0x6000003, ILOffset 0xe
ILOffset is a property name of StackFrame. I think that ILOffset is more clear than IL because it is a value representing the offset from the method starting point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The debuggers and other diagnostic tools typically use +
to denote the code offset. I do ever remember seeing the code offset in (...)
. Is there any existing tool that prints it like that?
Also, I would omit the space between "token" and the token value to make the two more connected.
There two suggestions combined together would make it:
at ILOffsetTest.App.callA() in test.dll:token6000003+0xE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last suggestion looks good. I will fix it like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I test the last suggestion, it is difficult to recognize if there is no space.
at ILOffsetTest.App.callA() in ILOffsetTest.dll: token0x6000006+0xc
How about add a space after token?
at ILOffsetTest.App.callA() in ILOffsetTest.dll: token 0x6000006+0xc
Line information also has a space between line and number as shown below.
at ILOffsetTest.App.callA() in C:\ILOffsetTest\ILOffsetTest\ILOffsetTest_App.cs:line 64
else | ||
{ | ||
if (s_ilOffsetToStackTrace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fold this into else if
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix it.
0d3ce1b
to
5b6de18
Compare
string methodToken = mb.MetadataToken.ToString("x", CultureInfo.InvariantCulture); | ||
string ilOffset = sf.GetILOffset().ToString("x", CultureInfo.InvariantCulture); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this could be better done with HexConverter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. It seems better to use Convert like below.
string methodToken = Convert.ToString(mb.MetadataToken, 16);
string ilOffset = Convert.ToString(sf.GetILOffset(), 16);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid Convert too, it's unnecessary complex. The code could be as simple as
Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(bytes, mb.MetadataToken);
string methodToken = HexConverter.ToString(bytes.Slice(0, 4), HexConverter.Casing.Lower);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll modify it to use HexConverter like your suggestion.
The example I linked is backwards. Here is a good example
It would contain a link to the new issue you would open. |
I cannot find "PlatformDetection.IsNotMonoInterpreter" in the latest runtime code.
|
Related issue : dotnet#51096
Oh, I can skip the test with [ActiveIssue("xxxx", typeof(PlatformDetection), nameof(PlatformDetection.IsMonoInterpreter))]. |
I create issue and updated TC to skip in mono interpreter mode. |
BTW due to Github bug, to open this PR I have to adjust the URL like this: #44013 |
Thank you @wscho77 for the contribution. We would welcome further contributions if you are interested. |
It will be interesting to see whether we get feedback when people see this in Preview 4. I would imagine the scenario would be callstacks displayed to end users, or received by developers by some telemetry mechanism, since that is the case that is likely missing PDB's. |
To avoid page loading time out (Github bug), I added "?timeline_per_page=5" after link. :)
|
In order to avoid failure due to modification of dotnet#44013, the TC has been modified so that the content after':' is not compared.
What has the feedback around this been like, looks like its still enabled by default? Doesn't feel adding the token info has any value as part of the exception stack for most scenarios? |
I haven't seen any feedback either for it or against it. That could mean nobody cared, or nobody is using the relevant scenario, or I am not seeing the right set of feedback. If anyone else has seen feedback I'd love to hear of it. |
We had assumed the number of customers that would benefit from it is quite small (making sense of IL offsets) but those that do really need it - so perhaps we shouldn't expect much positive feedback. But I haven't seen negative feedback either. My suggestion is to leave it as-is, given we have the appcontext flag, and then based on feedback we can next cycle either leave as is, reverse the default, or just remove the flag. |
My 2 cents: the extra info makes it more likely a stack trace line will span two lines instead of a single line in the console output, making the stack harder to read. Here's a stack trace I was looking at the other day and still had the console window open: 5 lines out of 7 are wrapped; |
Correction: 4 lines out of 5 wrapped are caused by this. I just realized this change added the module name as well. So I would only see a single wrapped line out of 7 without this change. That's why it felt so jarring first time I saw it, I guess. |
I have a similar feeling around the extra info. Stack traces are frequently included in application logs, so this would increase log sizes. Ideally this should be opt-in (esp. the token+offset) |
Some thoughts.. ultimately just guesses. Whenever there's been discussion about what should appear in stack traces, I have seen community feedback has been that the more info we include the better as often they're all the info available to them to trace some elusive issue. Line numbers or equivalent are important, viz the perennial request to know which object in a single line of code caused a NRE. Changing the setting would in many cases mean redeploying an app to one's customers, since the scenario where there's no symbols is likely in customer deployment. I would expect most logs have relatively few stack traces in, or you have bigger problems than the log size? |
We've also seen the community complain about stack trace text being overly noisy/confusing and then being very happy when work was done to simplify it (for example Ben Adams added the StackTraceHidden attribute to remove frames). I'm not convinced this feature will be easy for most devs to use even if we leave it on by default. Converting the IL offsets back into source line offsets requires the developer gives a path to the correct build's PDBs. Unlike dump debugging we have no way to warn them if they specify the wrong ones (because there is no module versioning info in the output text). I also lean towards saying this behavior should be off by default for this release as we continue to get feedback. We could always blog about this or put it in our docs as alternate ways to spread awareness. |
Fair enough! |
Add IL Offset and Method Token info to the stacktrace if PDBs are not deplyed.
It is often deployed without a PDB file to reduce deploying size or for obfuscation.
If deployed without PDB like this, debugging becomes difficult because the line number are not displayed in the exception stacktrace.
For easier debugging, if the PDB is not deployed, I want to add the Method Token and IL Offset at the stacktrace.
When the Method Token and IL Offset are given, the line number can be obtained using PDBs in external development environment.
Applicaiton developer can also add Method Token and IL Offset in the application by directly handle exceptions.
However, to apply this functionality to the entire application of the platform, it is necessary to add this at the runtime side.
The functionality can be turned on and off with the below switch name, and it is truned on by default.
You can set the value of switch with AppContext.SetSwitch() method.
(Example)