Runtime: Exception.ToString() throws an exception when it encounters a frame that requires opening a regular PDB

Created on 27 Apr 2018  路  21Comments  路  Source: dotnet/runtime

I seem to recall this (or something close to this) was reported for .NET 2.0. I did a quick search on the repo, but didn't find a duplicate.

The callstack below is for code that called ToString() on an Exception. It fails on a frame where the method debug information is in a regular PDB.

I'm also interested in just fixing the whole conundrum of not being able to open a regular PDB on CoreCLR (any platform) to give line number information.

coreclr!WinMDInternalImportRO::Release coreclr!ExceptionTracker::ProcessManagedCallFrame coreclr!ExceptionTracker::ProcessOSExceptionNotification coreclr!ProcessCLRException ntdll!RtlpExecuteHandlerForException ntdll!RtlDispatchException ntdll!RtlRaiseException KERNELBASE!RaiseException coreclr!RaiseTheExceptionInternalOnly coreclr!IL_Throw System.Reflection.Metadata!System.Reflection.Throw.OutOfBounds()$##60000A8 System.Reflection.Metadata!System.Reflection.Metadata.Ecma335.MethodDebugInformationTableReader.GetSequencePoints(System.Reflection.Metadata.MethodDebugInformationHandle)$##6000C23 System.Reflection.Metadata!System.Reflection.Metadata.MethodDebugInformation.get_SequencePointsBlob()$##600083E System.Diagnostics.StackTrace!System.Diagnostics.StackTraceSymbols.GetSourceLineInfo(System.String, IntPtr, Int32, IntPtr, Int32, Int32, Int32, System.String ByRef, Int32 ByRef, Int32 ByRef)$##6000003 System.Private.CoreLib!System.Diagnostics.StackFrameHelper.InitializeSourceInfo(Int32, Boolean, System.Exception)$##60018CA System.Private.CoreLib!System.Diagnostics.StackTrace.CaptureStackTrace(Int32, Boolean, System.Threading.Thread, System.Exception)$##60018E3 System.Private.CoreLib!System.Diagnostics.StackTrace..ctor(System.Exception, Boolean)$##60018DD System.Private.CoreLib!System.Environment.GetStackTrace(System.Exception, Boolean)$##6000711 System.Private.CoreLib!System.Exception.GetStackTrace(Boolean)$##6000399 System.Private.CoreLib!System.Exception.ToString(Boolean, Boolean)$##60003A0 System.Private.CoreLib!System.Text.StringBuilder.AppendFormatHelper(System.IFormatProvider, System.String, System.ParamsArray)$##60034BA System.Private.CoreLib!System.String.FormatHelper(System.IFormatProvider, System.String, System.ParamsArray)$##600032D

area-System.Diagnostics enhancement untriaged

Most helpful comment

The original complaint was avoiding an error (exception) when doing ToString() on an exception (which walks its stack. I am assuming there is no pushback on at least no failing. There is an interesting question on exactly what it does do to give the user a hint that their PDB is 'wrong'. Just giving up on line numbers is not as good as giving a parenthetical diagnostic that the PDB is not a portable PDB).

Of course @mjsabby wants it to 'just work'. I think we should at least understand the cost-benefit of that. Presumably the idea would be to make it work on Windows only. Note that this is what we do with perfCounters and I am sure other things that are hard to port to Linux. I think the ability to 'just work' on windows with Microsoft PDBs is useful (I think @danmosemsft scenario of using windows libraries without recompiling seems interesting, and we should understand how common it is likely to be).

My main concern is that line numbers in the exception trace really does 'drag' in a bunch of PDB reading stuff that otherwise the runtime is agnostic to. Ideally the runtime only has the hook, and it is up to other things (like an SDK installer), to wire into the hook (that is install the necessary DLLs).

So, I think it is reasonably to fix the error, and we should gather some data on who is likely to benefit from this (can we describe the scenario where people naturally generated Windows PDBs but want to run on .NET Core?). It seems like our desire to 'converge' .NET Core and desktop scenarios suggests that this will be important, but I don't really have a feel for what we are expecting people to do at build time (if we force them to use new tools (which is sad), then I suppose forcing them to use portable PDBs it not much worse.

But my main suggestion is to fix the uncontroversial part first.

All 21 comments

Seems like we should at least have a more helpful exception than "Read out of bounds" if we read a regular PDB.

We should probably not throw an exception at all. All we have to do is check if the PDB file is at least 4 bytes, and that the first 4 bytes are BSJB.

I think this is an easy fix for 2.1, would we take it?

I'll open a separate issue for fixing the problem of regular PDBs working cross platform for the basic case of show casing line number information when formatting an Exception.

regular PDBs working cross platform

There have been number of issues opened on that. It is not something we have plans to do https://github.com/dotnet/coreclr/issues/8175#issuecomment-261748193

Why not? It's just a change in the StackTrace class to read the lines stream? It will improve so much existing built code that runs on CoreCLR cross platform but is missing this information

I'm asking why not, because we have this code in CCI (https://github.com/Microsoft/cci/tree/master/PDBReaderAndWriter/PdbReader) that I think will just work. Is isn't it just a PR away? Or is there something more fundamental I'm missing.

If we wanted to support it, it needs to work everywhere and not just for Exception stack trace. It is hard to explain otherwise.

But certain scenarios are much more likely to be useful vs others. It is about compromise.

Being able to get stack traces with line numbers for code that you depend on is pretty useful when an exception occurs in production where the debug information was encoded in a regular pdb.

It is likely you will never need to debug that code which has the regular pdb, but will immensely benefit from that line number information on any platform.

I would categorize this improvement in the strictly better category - no scenario regresses but at least one scenario improves.

EDIT: I couldn't find any place in the runtime (i.e. not the DAC) that reads PDBs, if there is such a location I'm willing to fix that too.

EDIT 2: In fact, if there is a need to debug, you can convert the regular PDB to Portable PDB on Windows. But asking that for just line numbers is again a tax not needed to paid by everyone, i.e. it is not pay-for-play.

Right. It is about compromise. The compromise that we have settled on is to have Windows PDB to portable PDB converter: https://github.com/dotnet/symreader-converter . You can use that if you want to have line numbers on Unix, but you have Windows PDB for your binary for some reason.

And I'm saying that the compromise can be strictly better for this scenario which is much more common than debugging a PDB.

I couldn't find any place in the runtime (i.e. not the DAC) that reads PDBs

Visual Studio/Code debugger and other diagnostic tooling is not open source...

I disagree that it is strictly better. It is strictly more confusing to explain what will work vs. what will not work with it.

We want the cross-plat diagnostic tools (debuggers, profilers) to worry about portable PDB only.

Also, Windows PDBs are many times larger than portable PDBs. Your binary footprint will be much smaller if you deploy portable PDBs with your app to get line numbers.

I'm confused about your reticence because we have many places in the CoreFX/CoreCLR where compromises are made to benefit the user not make it harder for them.

Your counter proposal requires every person who discovers this to generate portable pdbs from their windows pdbs, and either make it a build process or repackage it themselves. Maybe this component is used in multiple places so now the technical debt is acquired by the consumer multiple places rather than the publisher.

And in this issue I'm proposing that the experience be the same on .NET Core cross plat which can be easily achieved with a library change, it's not even part of the runtime.

Maybe I'm isolated in my thinking that this is a good thing that we're able to reduce the pain for developers who are still stuck in the dual world of .NET/.NET Core.

What do others in the team and community think?

Your counter proposal requires every person who discovers this to generate portable pdbs from their windows pdbs

If you use the regular .NET Core tooling, you should never run into this. .NET Core tooling will always generate the portable .PDBs for .NET Core apps. If there are corner cases where it is not the case, it is something to fix in the .NET Core tooling.

You will only run into this when you manually stitch together things using something completely custom. Very few people do that and there many thing they have to discover to figure this out. I do not feel bad about converting Windows pdbs to portable pdbs as one more thing in the list.

@jkotas would this not potentially occur when consuming libraries? Many were built using desktop tooling, or if you build locally, expect to be built by desktop tooling. We generally expect them to work on .NET Core.

@vancem since he is usually interested in PDB discussions.

@jkotas would this not potentially occur when consuming libraries?

Do we have build option today to publish .PDBs for libraries from NuGet packages into your app? If we do, it can take care of conversion at build time.

@jkotas I was thinking more of the case where a Core app is consuming libraries built for NETFX, and wish to debug: generally that would mean cloning their repo, and building binary/PDB for yourself, which would generally make Windows PDB unless you re-work their build process. Perhaps that's not a concern?

The original complaint was avoiding an error (exception) when doing ToString() on an exception (which walks its stack. I am assuming there is no pushback on at least no failing. There is an interesting question on exactly what it does do to give the user a hint that their PDB is 'wrong'. Just giving up on line numbers is not as good as giving a parenthetical diagnostic that the PDB is not a portable PDB).

Of course @mjsabby wants it to 'just work'. I think we should at least understand the cost-benefit of that. Presumably the idea would be to make it work on Windows only. Note that this is what we do with perfCounters and I am sure other things that are hard to port to Linux. I think the ability to 'just work' on windows with Microsoft PDBs is useful (I think @danmosemsft scenario of using windows libraries without recompiling seems interesting, and we should understand how common it is likely to be).

My main concern is that line numbers in the exception trace really does 'drag' in a bunch of PDB reading stuff that otherwise the runtime is agnostic to. Ideally the runtime only has the hook, and it is up to other things (like an SDK installer), to wire into the hook (that is install the necessary DLLs).

So, I think it is reasonably to fix the error, and we should gather some data on who is likely to benefit from this (can we describe the scenario where people naturally generated Windows PDBs but want to run on .NET Core?). It seems like our desire to 'converge' .NET Core and desktop scenarios suggests that this will be important, but I don't really have a feel for what we are expecting people to do at build time (if we force them to use new tools (which is sad), then I suppose forcing them to use portable PDBs it not much worse.

But my main suggestion is to fix the uncontroversial part first.

I am confused by this issue. Do I understand it correctly that:

  1. If I run a .Net Core application with non-portable PDB on Unix, Exception.ToString() internally throws an exception, which is then caught here. As a consequence, the result of Exception.ToString() doesn't have line numbers.
  2. If I run a .Net Core application with non-portable PDB on Windows, line numbers are included in Exception.ToString().

To me, this sounds like a reasonable state to be in, especially if adding support for reading non-portable PDBs on Unix would take a lot of effort.

Adding some indication to the output that an exception was thrown seems reasonable to me, assuming it wouldn't be too noisy.


What I did to see that my understanding is right:

  1. Start on Windows.
  2. dotnet new console
  3. Specify <DebugType>Full</DebugType> in csproj to force non-portable PDB and add the following code to Program.cs:

    c# try { throw new Exception(); } catch (Exception ex) { Console.WriteLine(ex); }

  4. dotnet publish

  5. Verify that the PDB does not start with BSJB (it starts with Microsoft C/C++ MSF 7.00).
  6. dotnet .\bin\Debug\netcoreapp2.0\publish\hwapp.dll prints the line number:

    PS C:\code\tmp\hwapp> dotnet .\bin\Debug\netcoreapp2.0\publish\hwapp.dll
    System.Exception: Exception of type 'System.Exception' was thrown.
       at hwapp.Program.Main(String[] args) in C:\code\tmp\hwapp\Program.cs:line 9
    
  7. Switch to Linux (in my case, WSL Ubuntu), copy the publish directory over or use some shared file system.
  8. dotnet bin/Debug/netcoreapp2.0/publish/hwapp.dll doesn't print the line number:

    svick@Svick:/mnt/c/code/tmp/hwapp$ dotnet bin/Debug/netcoreapp2.0/publish/hwapp.dll
    System.Exception: Exception of type 'System.Exception' was thrown.
       at hwapp.Program.Main(String[] args)
    

This issue belongs to System.Diagnostics.StackTrace not to SRM.

Hi @jkotas , @danmosemsft . I just ran into this bug tonight. My scenario is debuging a VS test unit test in Visual Studio on Windows. I've got some .NET Standard assemblies being tested, but the unit tests are targeting .NET Framework. I don't think VS's test harness lets me target .NET Core, at least not with my old projects.

I'm guessing this is probably 80% likely to be the same issue, as opposed to a novel symbol generation bug. My code is in some strange state - an ArgumentNullException was thrown, it was caught, then I'm calling Exception.ToString() to pass as a parameter to Contract.Assert. But Exception.ToString() throws, presumably due to this same issue. I've also got a breakpoint set in this method that I'm debugging. And I'm sure there's absolutely 0 chance that something weird happened on the stack.

Key part of the stack trace is probably this set of calls. Both EnergyNet and EnergyNetServerLibrary's target framework is a .NET Standard 2.0. EnergyNetTest is .NET Framework 4.8. And I imagine the VSTestIntegration assembly is targeting the desktop.

mscorlib.dll!System.Exception.ToString() Line 432   C#
EnergyNetServerLibrary.dll!FlexCharging.EnergyNet.EventLogManager<FlexCharging.EnergyNet.UserInteractionEvent>.WriteEntries(System.DateTime date, System.Collections.Generic.List<FlexCharging.EnergyNet.UserInteractionEvent> entries, bool compressed) Line 204   C#

EnergyNetServerLibrary.dll!FlexCharging.EnergyNet.EventLogManager.SaveLogs() Line 90 C#
EnergyNetTest.dll!EnergyNetTest.EnergyNetUnitTest.WriteUserInteractionEvents() Line 585 C#
[Native to Managed Transition]
[Managed to Native Transition]
Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration.dll!Microsoft.VisualStudio.TestPlatform.MSTestFramework.TestMethodRunner.DefaultTestMethodInvoke(object[] args) Unknown

In the off chance that this is unrelated to the existing bug, I'll save a copy of all the binaries & symbols as they are right now. Let me know if you want me to swing by campus to show someone.
-- Brian (no longer on the BCL dev team)
Exception from MethodDebugInformationTableReader.txt

Exception.ToString() throws

This exception is thrown&handled inside Exception.ToString(). I know that it may be confusing if you have "stop at all exceptions" enabled.

We track .NET Core issues only in this repo. If you believe that it is something that needs to be fixed in .NET Framework, it is best to report it via VS feedback tool - https://github.com/dotnet/core/#getting-help .

Closing this issue:

  • We have no plans to enable support for Windows PDBs on Unix
  • There may be situations where an exception is thrown & handled inside Exception.ToString that is by design.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

bencz picture bencz  路  3Comments

iCodeWebApps picture iCodeWebApps  路  3Comments

nalywa picture nalywa  路  3Comments

omariom picture omariom  路  3Comments

yahorsi picture yahorsi  路  3Comments