【问题标题】:How to debug (step into) BinaryFormatter.Deserialize()?如何调试(步入)BinaryFormatter.Deserialize()?
【发布时间】:2015-10-29 15:57:46
【问题描述】:

我的应用尝试反序列化客户端发送的数据,但失败并出现以下错误:

抛出异常: mscorlib.dll 中的“System.Runtime.Serialization.SerializationException”

附加信息:无法获取成员“<.ctor>b__0”。

谷歌搜索没有结果。好的,我决定进入反序列化逻辑并尝试找出究竟是什么导致了这种情况。好吧,一天过去了,我还差得远呢。

我使用来自Microsoft Reference Source 网站的说明来配置 Visual Studio。它确实下载了一些东西

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

但调试器不介入。

我搜索了更多内容,并找到了另一种方法 - 安装了 dotTrace 应用程序和 used it as source server。这也无济于事。我仍然看到以下内容:

Symbol Load Information mscorlib.pdb 的弹出窗口说

C:\Users\me\AppData\Local\Temp\SymbolCache\MicrosoftPublicSymbols\mscorlib.pdb\e47257b512ba49bc9fc367c532fc5f1e2\mscorlib.pdb: 已加载符号。

我可以进入 System.Windows.Forms、System.Linq 等 - 所以一般来说,它可以工作 - 只是这个对 BinaryFormatter.Deserialize() 的特殊调用不起作用。这可能是什么原因,我怎样才能让它介入?

会不会是SecuritySafeCritical属性的原因?

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

我正在使用 VS 2015 .Net 4.5.2(尽管我尝试了 4.5 得到了相同的结果)。

【问题讨论】:

  • 你想调试微软代码还是解决反序列化问题?
  • 我不知道如何在不调试 MS 代码的情况下解决我的反序列化问题,所以我可以确切地看到它失败的原因。你有更好的建议吗?
  • 您是如何获得序列化数据的?你能创建一个重现行为的最简单的例子吗?
  • 从头开始进行序列化/反序列化工作正常。只是客户端发送的那条数据由于未知原因而失败。恐怕要重现它的最小应用程序并不简单,也不容易创建。
  • BinaryFormatter 不应该用于持久存储或跨机器通信,这正是您遇到问题的原因。这是非常不容忍变化的,如果双方没有运行对象图中涉及的所有程序集的完全相同版本,则会遇到这样的问题。

标签: .net visual-studio debugging debug-symbols


【解决方案1】:

如果没有任何细节,我可以假设这是与您尝试序列化和反序列化的对象版本的兼容性问题。看起来客户端向您发送了一些旧对象位(构造函数中没有 lambda)。并且您的服务器正在运行更新版本的软件来搜索一些 lambda 方法。

<.ctor>b__0 - 是 .ctor(对象构造函数)中第一个 lambda 方法的方法名称。

例如,如果您在客户端的机器上拥有对象 A:

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

然后你在服务器上更新了你的类,在构造函数中引入了 lambda:

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

在它们的二进制表示不一样之后,服务器版本的 A 有私有的不可见方法 <.ctor>b__0 在里面。

【讨论】:

  • 很好 - 做得很好,但不幸的是事实并非如此。源文件多年未变,其中也没有 lambda。
  • @avs099 你知道那是什么吗?对于我正在研究的类似问题,这是一个非常好的候选者。来源没有改变,但在进行装配级别比较时,我怀疑构建输出由于使用了不同的构建过程而发生了变化。
  • @eglasius nope,看起来很有希望,但不是我的情况:(如果你能深入了解,请分享:)
  • @avs099 您没有更改源代码,但是这些不同的程序集版本吗?这是我们的情况,发布了详细的答案。
【解决方案2】:

Microsoft 不会上传每个 mscorlib.dll 更新的源代码,这就是为什么您只能获取没有任何源数据的公共 PDB。但是有一个来自 Redgate's Reflector 的 Visual Studio 插件,您可以在其中 decompile 3rd party DLLs and step through them in the VS debugger

Jetbrains 的 DotPeek 也可以通过 supports PDB generation and hosting of a Symbol server 进行调试。

也许这可以帮助您调试问题。

【讨论】:

  • 不清楚 - 你是说有一个带有二进制格式化程序源的公共 PDB,但它适用于旧版本的 .net?那我在哪里可以得到它?回复:dotPeek - 正如我在问题中提到的,我尝试过但没有帮助。
  • 没有公共 PDB 没有链接的源代码。卸载任何 .net 更新以返回 4.5.2 RTM。现在您可以从参考服务器获取私有 PDB + 源:referencesource.microsoft.com
【解决方案3】:

tl;dr:不同版本的编译器(或不同的设置?)可能会为对应于匿名函数的生成方法生成不同的名称。如果此类方法由序列化类的私有字段指向,则会出现异常,即使源在 2 个构建之间没有更改。


我只是跟踪了确切的情况类型,但是在 asp.net 应用程序的会话中触发了反序列化。像本例一样,使用了 BinaryFormatter。

<.ctor>b__0 对应一个匿名函数对应的生成方法。

现在,这里的问题是在序列化过程中对这种方法的依赖,因为不能保证名称在不同的构建中是相同的(即使源代码不变)。这几乎肯定会追踪到序列化类的私有实例字段中的某种委托。请注意,声明匿名函数的类不一定是在私有字段中保存对该函数的引用的类。

不幸的是,我没有时间了解为什么同一个源会为匿名函数生成不同的名称,但考虑到所涉及项目的历史,它要么是传递给它的选项的不同编译器版本。我确信它是更早的。

如果您可以访问双方的程序集,则可以确认更改。起初,我尝试在 DotPeek 中导出两个程序集的反汇编源,然后对文件夹进行比较。这并没有证明是一个好的过程,但这可能是由于需要设置一些 DotPeek 设置或其他原因。

效果更好的是结合使用 ndepend 和反射器。您可以对前面的进行组装比较。我这样做的方式是更改其中一个内置查询,以获取具有任何类型更改的序列化类的所有构造函数。这将其缩小到几个类/构造函数(如果匿名函数是在不可序列化的类中创建的,则存在无法以这种方式捕获它的风险)。

一旦我把它归结为几个构造函数,从 ndepend 我打开了一个使用反射器的旧与新比较。这不仅以与异常相同的格式显示方法名称,而且已经显示让我在代码库中找到正确的方法。

新建类后,我发现最好在单独的 resharper 窗口中打开每个程序集并查看该类的方法。那里很明显。

还要注意,在更改代码的情况下,即使是相同的编译器版本/选项也可能会给您不同的名称,因此在可序列化的类中拥有指向函数的私有字段是非常脆弱的。以下答案对此进行了扩展:https://stackoverflow.com/a/1133465/66372

【讨论】:

    猜你喜欢
    • 2012-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 2012-05-13
    • 2013-02-16
    • 2015-12-04
    相关资源
    最近更新 更多