How to debug (step into) BinaryFormatter.Deserialize()?
Asked Answered
P

4

6

My app tries to deserialize data sent by client and it fails with the following error:

Exception thrown: 'System.Runtime.Serialization.SerializationException' in mscorlib.dll

Additional information: Cannot get the member '<.ctor>b__0'.

googling gives no results. Okay, I decided I would step into deserialization logic and try to figure out what exactly is causing this. Well, a day has passed and I'm nowhere close.

I used instructions from Microsoft Reference Source website to configure Visual Studio. It does download something

MicrosoftPublicSymbols\mscorlib.pdb\
   DCF1E4D31F6944AC87E7A634262BEE881\mscorlib.pdb (780kb)
   E47257B512BA49BC9FC367C532FC5F1E2\mscorlib.pdb (953kb)

but debugger does not step in.

I googled more and found another way to do it - installed dotTrace app and used it as source server. And that does not help either. I still see the following:

enter image description here

Symbol Load Information popup for mscorlib.pdb says

C:\Users\me\AppData\Local\Temp\SymbolCache\MicrosoftPublicSymbols\mscorlib.pdb\e47257b512ba49bc9fc367c532fc5f1e2\mscorlib.pdb: Symbols loaded.

I can step in into System.Windows.Forms, System.Linq, etc - so generally speaking, it works - it just this particular call to BinaryFormatter.Deserialize() does not work. What could be the reasons for that and how can I get it to step into?

Could it be because of SecuritySafeCritical attribute?

[System.Security.SecuritySafeCritical] 
public Object Deserialize(Stream serializationStream)

I'm using VS 2015 .Net 4.5.2 (though I tried 4.5 with the same results).

Paxwax answered 29/10, 2015 at 15:57 Comment(7)
Do you want to debug microsoft code or to solve deserialization problem?Brie
i do not know how to solve my deserialization problem without debugging MS code so I can see exactly why it fails. Do you have better suggestions?Paxwax
How you got serialzed data? Can you create simpliest example that reproduces behavior?Brie
doing serialization/deserialization from scratch works fine. It's just that piece of data sent by client which fails for unknown reason. I'm afraid a minimal app to reproduce it will be not simple, and not easy to create.Paxwax
I have the same behavior. Was able to to debug other MS dlls, but not BinaryFormatterBrie
Debugging code is easy if you have the right tools. Red gate .NET Reflector allows you to step (debug) into the code. Perhaps you're missing a default constructor here?Lactase
BinaryFormatter should never be used for persistent storage or cross machine communication for exactly the reason you are running in to problems. It is very change intolerant and if the two sides are not running the exact same versions of all the assemblies involved in the object graph you run in to problems like this.Klystron
L
9

Without any details i can assume this is compatibility issue with versions of objects you trying to serialize & deserialize. Looks like client sends you some old object bits(without lambda in constructor). And your server running newer version of software searching for some lambda method.

<.ctor>b__0 - is method name for first lambda method in .ctor (object constructor).

So for example if you had on client's machine object A:

class A {
  public A() {
   int a = 5;
   int b = 7;
   // Plain code, no lambdas
  }
}

Then you updated your class on server introducing lambda in constructor:

class A {
  public A() {
   int a = 5;
   int b = 7;
   Func<int,int> some = x => x * 2 + a; 
  }
}

After that their binary representation is not the same, server version of A has private invisible method <.ctor>b__0 in it.

Generated IL for lamda method in class A1

Leasehold answered 29/10, 2015 at 19:38 Comment(4)
well - good work done but unfortunately that's not the case. The source files have not changed in years, and there is no lambdas in them.Paxwax
@Paxwax did you find out then what it was? This is a really good candidate for a similar problem I am looking into. Sources didn't change, but while doing an assembly level comparison I am suspecting the build output changed due to a different build process being used.Giuseppinagiustina
@Giuseppinagiustina nope, looked promising but was not my case :( if you can get to the bottom of this, please please share :)Paxwax
@Paxwax you didn't change the sources, but were those different builds of the assemblies? This was our case, posted an answer with the details.Giuseppinagiustina
M
2

Microsoft doesn't upload the source of every mscorlib.dll update, this is why you only get public PDBs without any source data. But there is a Visual Studio Addon from Redgate's Reflector where you can decompile 3rd party DLLs and step through them in the VS debugger.

enter image description here

enter image description here

DotPeek from Jetbrains also supports PDB generation and hosting of a Symbol server to allow debugging.

Maybe this helps you to debug your issue.

Medeiros answered 12/11, 2015 at 5:22 Comment(2)
not clear - are you saying there IS a public PDB with binary formatter sources but it's for an older version of .net? Where can I get it then? re: dotPeek - as I mentioned in my question, i tried it but it did not help.Paxwax
no the public PDBs have no linked source code. uninstall any .net updates to go back to the 4.5.2 RTM. Now you can get the private PDB + source from the reference server: referencesource.microsoft.comMedeiros
G
2

tl;dr: different version of a compiler (or different settings?) may generate different names for the generated method corresponding to an anonymous function. If such a method is pointed by a private field of a serialized class you get the exception, even though the source didn't change between the 2 builds.


I just tracked the exact type of situation, but with the deserialization triggered in an asp.net application's session. Like this case, the BinaryFormatter is used.

<.ctor>b__0 corresponds to the generated method corresponding to an anonymous function.

Now, the problem here is the dependency on such a method during serialization, because the name is not guaranteed to be the same over different builds (even with an unchanged source). This almost surely tracks to some kind of delegate in a private instance field of a serialized class. Note that such the class where the anonymous function is declared is not necessarily the class that holds the reference to that function in a private field.

Unfortunately I didn't have time to track why the same source produces different names for the anonymous function, but given the history of the project involved it is either a different compiler version of the options passed to it. I am convinced it is the earlier.

If you have access to the assemblies in both sides, you can confirm the change. At first I tried exporting the dissembled source of both assemblies in DotPeek and then doing a diff of the folders. That didn't prove a good process, but it may be due to some DotPeek settings that need to be set or something.

What worked better was using a combination of ndepend and reflector. You can do an assembly comparison of the earlier. The way I did it was to change one of the build in queries to get all constructors of serialized classes that had any kind of change. This narrowed it down to a few classes/constructors (there is a risk of not catching it this way, if the anonymous function was created in a non serializable class).

Once I had it down to a few constructors, from ndepend I opened an old vs new comparison that uses reflector for it. This not only showed the method name in the same format as the exception, but already showed got me to the right one in the code base.

Once I new the class, I found it better to open each assembly in a separate resharper window and see the methods of the class. It is quite visible there.

Also note that in the case where the code is changed, even the same compiler version/option may give you different names, so it is very brittle to have private fields in serializable classes that point to functions. The following answer expands on it: https://mcmap.net/q/83410/-could-we-save-delegates-in-a-file-c

Giuseppinagiustina answered 24/11, 2016 at 7:39 Comment(0)
P
1

We just spent a few weeks debugging the same problem. In our case, code had changed but we stepped through every commit and couldn't see anything obvious that should have broken things.

We found the one commit that did break things and thanks to @Alexey Korovin's answer, we were able to use dotPeek (didn't know it existed till last week) to track down what had happened.

It was a seemingly miraculous renumbering of unintentionally serialized lambdas, something as innocuous-looking as

public event EventHandler StatUpdated = delegate{};

But why the renumbering? All of a sudden b__6 was b__0, and other numbers in different modules had changed.

The answer is that Visual Studio had changed the order of some modules in the csproj file and since they were compiled first, their anonymous members were enumerated earlier. We re-ordered things to how they had been and it's all better. We now are also making sure that we don't accidentally serialize anonymous delegates are are actively searching for a replacement for the Binary Formatter. Unfortunately we have to stay aware of this because of backwards compatibility, but at least we now have figured out the first likely place to look.

It's possible that something similar happens when people upgrade to newer versions of Visual Studio.

Phinney answered 21/8, 2023 at 4:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.