Skip to content

Native AOT StackFrame method info #92869

Closed as not planned
Closed as not planned

Description

@vaind

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

Activity

  1. ghost added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Oct 2, 2023
  2. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Oct 2, 2023
  3. vitek-karas commented on Oct 2, 2023

    @vitek-karas
    Member

    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?

  4. removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Oct 2, 2023
  5. vaind commented on Oct 2, 2023

    @vaind
    Author

    Thanks for the quick response @vitek-karas

    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)?

  6. MichalStrehovsky commented on Oct 2, 2023

    @MichalStrehovsky
    Member

    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.

  7. vaind commented on Oct 3, 2023

    @vaind
    Author

    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)?

  8. MichalStrehovsky commented on Oct 3, 2023

    @MichalStrehovsky
    Member

    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:

    1. 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.
    2. 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?).
    3. 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 some Type 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).

  9. vaind commented on Oct 3, 2023

    @vaind
    Author

    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.

  10. vaind commented on Oct 4, 2023

    @vaind
    Author

    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:

    public virtual string CreateStackTraceString(IntPtr ip, bool includeFileInfo, out bool isStackTraceHidden)
    {
    string methodName = GetMethodName(ip, out IntPtr methodStart, out isStackTraceHidden);
    if (methodName != null)
    {
    if (ip != methodStart)
    {
    methodName = $"{methodName} + 0x{(ip - methodStart):x}";
    }
    return methodName;
    }
    // If we don't have precise information, try to map it at least back to the right module.
    string moduleFullFileName = RuntimeAugments.TryGetFullPathToApplicationModule(ip, out IntPtr moduleBase);
    // Without any callbacks or the ability to map ip correctly we better admit that we don't know
    if (string.IsNullOrEmpty(moduleFullFileName))
    {
    return "<unknown>";
    }
    ReadOnlySpan<char> fileNameWithoutExtension = Path.GetFileNameWithoutExtension(moduleFullFileName.AsSpan());
    int rva = (int)(ip - moduleBase);
    return $"{fileNameWithoutExtension}!<BaseAddress>+0x{rva:x}";
    }

    It would be useful to have the package name (not just the namespace & class) whenever available. What's the chance of this changing?

  11. MichalStrehovsky commented on Oct 5, 2023

    @MichalStrehovsky
    Member

    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:

    AppContext.TryGetSwitch("Diagnostics.DisableMetadataStackTraceResolution", out bool disableMetadata);

    (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).

  12. vaind commented on Oct 5, 2023

    @vaind
    Author

    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.

  13. MichalStrehovsky commented on Oct 5, 2023

    @MichalStrehovsky
    Member

    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).

  14. vaind commented on Oct 5, 2023

    @vaind
    Author

    Ah, that clears things up then, thanks. I'll ignore it then.

  15. MichalStrehovsky commented on Jan 5, 2024

    @MichalStrehovsky
    Member

    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.

  16. ghost removed
    untriagedNew issue has not been triaged by the area owner
    on Jan 5, 2024
  17. locked and limited conversation to collaborators on Feb 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions