You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When published as a native AOT binary, System.Diagnostics.StackFrame doesn't have method information (as returned by GetMethod). Interestingly, some info is available at runtime because it is printed by ToString(). It would be preferrable to expose the module & method name via appropriate methods so that we don't have to rely on parsing ToString() output. See an example output below:
_options.LogDebug("### ToString = {0}",stackFrame.ToString());_options.LogDebug("### Has Native Image = {0}",stackFrame.HasNativeImage());_options.LogDebug("### Native Image Base = 0x{0:x}",stackFrame.GetNativeImageBase());_options.LogDebug("### Native Interface pointer = 0x{0:x}",stackFrame.GetNativeIP());_options.LogDebug("### Offset = {0}",stackFrame.GetNativeOffset());_options.LogDebug("### FileName = {0}",stackFrame.GetFileName());_options.LogDebug("### Line no = {0}",stackFrame.GetFileLineNumber());_options.LogDebug("### HasMethod = {0}",stackFrame.HasMethod());_options.LogDebug("### Method = {0}",stackFrame.GetMethod());_options.LogDebug("### ILOffset = {0}",stackFrame.GetILOffset());
ToString = System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task, ConfigureAwaitOptions) + 0x4b at offset 75 in file:line:column <filename unknown>:0:0
Has Native Image = True
Native Image Base = 0x7ff7ee540000
Native Interface pointer = 0x7ff7ee6cfb7b
Offset = 75
FileName =
Line no = 0
HasMethod = False
Method =
ILOffset = -1
As a side note, how does runtime resolve the method name in ToString() to such a nicely-looking string? If I try to symbolicate via the native debug info in the generated .PDB, the symbol name I get is S_P_CoreLib_System_Runtime_CompilerServices_TaskAwaiter__HandleNonSuccessAndDebuggerNotification
By default NativeAOT apps carry enough information about each method to be able to print out the ToString you see. This can be disabled to improve application size, since it's only used for this purpose, in which case the ToString uses method address instead.
The real reason this is not implemented currently is that the GetMethod would have to return a made up MethodInfo, with only a subset of the normal functionality. That is pretty complex to implement correctly such that it provides good experience across all of reflection. You could use that MethodInfo in other reflection calls and that should fail in a reasonable way.
What is the scenario where you want the detailed breakdown of the method information from a stack trace?
What is the scenario where you want the detailed breakdown of the method information from a stack trace?
To show stack trace for error-reporting SDKs, namely Sentry. It's possible to do ("offline") symbolication on the server using the generated PDB but, as I've mentioned in the original post, the symbol name doesn't look as good as the C# method info returned by ToString().
AFAIK the only way to get the "nice" method name at this time is to try and parse ToString() output on the client. Or is there another way to get access to the "information about each method to be able to print out the ToString you see"?
I understand this can be disabled by setting StackTraceSupport=false in which case it's understandable to just use what's in the PDB, but if the info is available, it would be preferrable to use it.
Alternatively, is it possible to get the correct c# module, class and method names from PDB(or DWARF for linux and macOS)?
To show stack trace for error-reporting SDKs, namely Sentry. It's possible to do ("offline") symbolication on the server using the generated PDB but, as I've mentioned in the original post, the symbol name doesn't look as good as the C# method info returned by ToString().
Is offline symbolication the approach that Sentry uses for IL2CPP? If so, similar approach could be used to generate stacks for Native AOT. I assume the problem right now is that the debug information generated by native AOT still doesn't look enough like C++. The name mangling scheme native AOT uses is not reversible.
Is offline symbolication the approach that Sentry uses for IL2CPP? If so, similar approach could be used to generate stacks for Native AOT.
For IL2CPP (in Unity) we go through a couple more hoops that are not necessary with .NET Native AOT - I've already successfully symbolicated stack traces based on the list of loaded native images (from native APIs) and the image & symbol addresses as provided by the .net stacktrace runtime API. So that all works as expected.
I assume the problem right now is that the debug information generated by native AOT still doesn't look enough like C++. The name mangling scheme native AOT uses is not reversible.
Exactly, that's why I'm looking at improving the information from PDB with whatever is available at runtime. This info (before mangling) is available at runtime but as I've mentioned in the original post, it's only part of the ToString() result.
Since you're saying " The name mangling scheme native AOT uses is not reversible." - how would you say we can get the demangled info at runtime (short of parsing ToString() output)?
Exactly, that's why I'm looking at improving the information from PDB with whatever is available at runtime. This info (before mangling) is available at runtime but as I've mentioned in the original post, it's only part of the ToString() result.
Since you're saying " The name mangling scheme native AOT uses is not reversible." - how would you say we can get the demangled info at runtime (short of parsing ToString() output)?
Parsing ToString is the only thing that will work for .NET 7/8.
For .NET 9, we could consider these, in order of preference:
Fix debug info emission to work better. We already generate some information about types and methods that supplements the mangled method name. The compiler already generates something (the code is around here) but I've never seen it surfaced anywhere in a debugger. The people who originally wrote all of this code are long gone and all of this is now owned by "everyone" so it's not owned by anyone and nobody is up-to-speed on it. If we want to work on it, we'd want to wait for Discussion: ObjWriter in C# #77178.
Introduce a new API on StackFrame that would let one obtain the information that is needed, but is not a MethodBase. (e.g. do we just need a stringified type name and stringified method name? Do we want stringified parameter types?).
Start generating fake MethodBase for these that only contain the information to the extent that compiler keeps track of it. E.g. asking for method flags would throw, asking for declaring type would return someType that will also be severely messed up (not knowing its assembly, or if its nested, etc.), etc.
3 is my least preferred option because we'd need to very carefully document the small subset of things that work reliably. one can absolutely get a 100% usable MethodBase if the method on the stack frame was considered reflection-visible by the compiler. But anything else will get the hobbled MethodBase. That makes it somewhat more annoying to test. We do not want to add more stuff to the stack trace metadata to make the MethodBase more useful because stack trace metadata already accounts for almost 10% of the executable size (try publishing an app with native AOT and then again with <PropertyGroup><StackTraceSupport>false</StackTraceSupport></PropertyGroup> to see the overhead even of this minimal metadata).
I completely agree with your reasoning and would be looking forward to get this info in the future. In the meantime, I'll resort to parsing ToString() output.
When looking at ToString() for various frames, I've found that it won't give us the package information unless the method info is missing. See the following code:
When you say "package information", do you mean the native module name and native address?
I meant the module name.
As for the switch - that would disable the class+namespace+method name resolution right? That's not what I'm looking for. Instead, I'd like to be able to get the class+namespace+method name and the module name at the same time.
As you can see in the CreateStackTraceString() above, if a method name is available, we don't get any info about the module. And with all these calls being internal, the only way I can get any info is ToString(), as we've already established.
As for the switch - that would disable the class+namespace+method name resolution right? That's not what I'm looking for. Instead, I'd like to be able to get the class+namespace+method name and the module name at the same time.
The module name we print is not the module name you think it is. It is the name of the native module, not the original source assembly.
The original source assembly name gets dropped during native compilation because we don't have any use for it (it's not part of the "official" stack trace format string). We could potentially include it if we go with option 2 in the above comment. (It might be a good exercise if you could propose a shape for this API that you would need).
Activity
vitek-karas commentedon Oct 2, 2023
By default NativeAOT apps carry enough information about each method to be able to print out the
ToStringyou see. This can be disabled to improve application size, since it's only used for this purpose, in which case theToStringuses method address instead.The real reason this is not implemented currently is that the
GetMethodwould have to return a made upMethodInfo, with only a subset of the normal functionality. That is pretty complex to implement correctly such that it provides good experience across all of reflection. You could use thatMethodInfoin other reflection calls and that should fail in a reasonable way.What is the scenario where you want the detailed breakdown of the method information from a stack trace?
vaind commentedon Oct 2, 2023
Thanks for the quick response @vitek-karas
To show stack trace for error-reporting SDKs, namely Sentry. It's possible to do ("offline") symbolication on the server using the generated PDB but, as I've mentioned in the original post, the symbol name doesn't look as good as the C# method info returned by
ToString().AFAIK the only way to get the "nice" method name at this time is to try and parse ToString() output on the client. Or is there another way to get access to the "information about each method to be able to print out the ToString you see"?
I understand this can be disabled by setting
StackTraceSupport=falsein which case it's understandable to just use what's in the PDB, but if the info is available, it would be preferrable to use it.Alternatively, is it possible to get the correct c# module, class and method names from PDB(or DWARF for linux and macOS)?
MichalStrehovsky commentedon Oct 2, 2023
Is offline symbolication the approach that Sentry uses for IL2CPP? If so, similar approach could be used to generate stacks for Native AOT. I assume the problem right now is that the debug information generated by native AOT still doesn't look enough like C++. The name mangling scheme native AOT uses is not reversible.
vaind commentedon Oct 3, 2023
For IL2CPP (in Unity) we go through a couple more hoops that are not necessary with .NET Native AOT - I've already successfully symbolicated stack traces based on the list of loaded native images (from native APIs) and the image & symbol addresses as provided by the .net stacktrace runtime API. So that all works as expected.
Exactly, that's why I'm looking at improving the information from PDB with whatever is available at runtime. This info (before mangling) is available at runtime but as I've mentioned in the original post, it's only part of the ToString() result.
Since you're saying " The name mangling scheme native AOT uses is not reversible." - how would you say we can get the demangled info at runtime (short of parsing ToString() output)?
MichalStrehovsky commentedon Oct 3, 2023
Parsing
ToStringis the only thing that will work for .NET 7/8.For .NET 9, we could consider these, in order of preference:
MethodBase. (e.g. do we just need a stringified type name and stringified method name? Do we want stringified parameter types?).MethodBasefor these that only contain the information to the extent that compiler keeps track of it. E.g. asking for method flags would throw, asking for declaring type would return someTypethat will also be severely messed up (not knowing its assembly, or if its nested, etc.), etc.3 is my least preferred option because we'd need to very carefully document the small subset of things that work reliably. one can absolutely get a 100% usable
MethodBaseif the method on the stack frame was considered reflection-visible by the compiler. But anything else will get the hobbledMethodBase. That makes it somewhat more annoying to test. We do not want to add more stuff to the stack trace metadata to make theMethodBasemore useful because stack trace metadata already accounts for almost 10% of the executable size (try publishing an app with native AOT and then again with<PropertyGroup><StackTraceSupport>false</StackTraceSupport></PropertyGroup>to see the overhead even of this minimal metadata).vaind commentedon Oct 3, 2023
Thanks @MichalStrehovsky
I completely agree with your reasoning and would be looking forward to get this info in the future. In the meantime, I'll resort to parsing
ToString()output.vaind commentedon Oct 4, 2023
When looking at
ToString()for various frames, I've found that it won't give us the package information unless the method info is missing. See the following code:runtime/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs
Lines 35 to 59 in 2542302
It would be useful to have the package name (not just the namespace & class) whenever available. What's the chance of this changing?
MichalStrehovsky commentedon Oct 5, 2023
When you say "package information", do you mean the native module name and native address?
If so, you can force stack traces to only report things in that format by setting this AppContext switch:
runtime/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/DeveloperExperience/DeveloperExperience.cs
Line 31 in 2542302
(Either call AppContext.SetSwitch programmatically at startup or places this value as a RuntimeHostConfigurationOption into the MSBuild project file)
I just realized we don't have any testing for this switch. If you do take a dependency on it, please submit a test to https://github.com/dotnet/runtime/tree/main/src/tests/nativeaot/SmokeTests/StackTraceMetadata to test this so that we don't regress it (the test can simply set the appcontext switch and inspect a stack trace afterwards).
vaind commentedon Oct 5, 2023
I meant the module name.
As for the switch - that would disable the class+namespace+method name resolution right? That's not what I'm looking for. Instead, I'd like to be able to get the class+namespace+method name and the module name at the same time.
As you can see in the
CreateStackTraceString()above, if a method name is available, we don't get any info about the module. And with all these calls being internal, the only way I can get any info isToString(), as we've already established.MichalStrehovsky commentedon Oct 5, 2023
The module name we print is not the module name you think it is. It is the name of the native module, not the original source assembly.
The original source assembly name gets dropped during native compilation because we don't have any use for it (it's not part of the "official" stack trace format string). We could potentially include it if we go with option 2 in the above comment. (It might be a good exercise if you could propose a shape for this API that you would need).
vaind commentedon Oct 5, 2023
Ah, that clears things up then, thanks. I'll ignore it then.
MichalStrehovsky commentedon Jan 5, 2024
I wrote up a proposal for trim-safe/aot-safe version of this API: #96528.
I'm going to close this since it basically supersedes this.