【问题标题】:NullReferenceException vs. MSILNullReferenceException 与 MSIL
【发布时间】:2016-04-17 21:03:50
【问题描述】:

我正在解释来自 C# Windows Phone 应用程序的异常报告。一个方法抛出一个NullReferenceException。方法是:

public void OnDelete(object o, EventArgs a)
{
    if (MessageBox.Show(Res.IDS_AREYOUSURE, Res.IDS_APPTITLE, MessageBoxButton.OKCancel) == MessageBoxResult.OK)
        m_Field.RequestDelete();
}

这与m_Field 为空是一致的——那里没有其他任何东西可以为空。但这是神秘的部分。

来自StackFrameGetILOffset() 来自StackTrace 的异常对象返回0x13。如 ILDASM 所示,该方法的 MSIL 为:

IL_0000:  call       string App.Res::get_IDS_AREYOUSURE()
IL_0005:  call       string App.Res::get_IDS_APPTITLE()
IL_000a:  ldc.i4.1
IL_000b:  call       valuetype (...) System.Windows.MessageBox::Show(...)
IL_0010:  ldc.i4.1
IL_0011:  bne.un.s   IL_001e
IL_0013:  ldarg.0
IL_0014:  ldfld      class App.Class2 App.Class1::m_Field
IL_0019:  callvirt   instance void App.Class2::RequestDelete()
IL_001e:  ret

这是我不明白的。如果偏移量确实是 0x13,则意味着 ldarg 行会导致异常。但是该命令被记录为没有引发任何异常。应该抛出的是callvirt,不是吗?或者是相对于方法开始以外的东西的偏移量? ldfld 也可以抛出,但前提是 this 对象为空;这在 C# AFAIK 中是不可能的。

文档提到调试信息可能会妨碍偏移,但它是发布版本。

我正在使用 ILDASM 检查的 DLL 正是我作为 XAP 的一部分发送到 Windows Phone 商店的那个。

【问题讨论】:

    标签: c# windows-phone .net-assembly cil


    【解决方案1】:

    当 JIT 生成机器码时,它也会生成 MSIL 机器码映射。当您在生成的代码中遇到异常时,运行时将使用映射来识别 IL 偏移量。

    允许 JIT 重新排序机器指令作为其优化的一部分(启用它们时),这可能导致映射变得更加近似和细化。如果字段访问被提前(内存访问相对较慢,有时最好在需要之前就开始加载它),那么异常可能看起来是由更早的 IL 指令引发的。


    我砍掉了我的一个调试实用程序来执行以下操作:

    • 启动一个目标进程并运行直到出现异常
    • 捕获 IL 字节和 IL 到本机的映射
    • (粗略地)反汇编 IL,并使用指示符显示哪些 IL 指令通过相同的映射组合在一起。

    然后我在一个虚拟进程上运行该工具,该进程大致完成您在问题中显示的内容,并得到以下结果(发布版本):

    IL_0000: call 0600000B
    IL_0005: call 0600000A
    IL_000A: ldc.i4.1
    IL_000B: call 0A000014
    IL_0010: ldc.i4.1
    IL_0011: bne.un.s 30
    ----
    IL_0013: ldarg.0
    IL_0014: ldfld 04000001
    IL_0019: callvirt 06000004
    ----
    IL_001E: ret
    

    如您所见,ldarg.0ldfldcallvirt 指令都被相同的映射覆盖,因此如果其中任何一个触发异常,它们都会映射回相同的 IL 偏移量(0x13 )。

    【讨论】:

    • 但是应该抛出的不是字段访问(ldfld)(因为this不是null),而是callvirt。看看拆卸,我不认为重新排序是这里的原因。
    • 关于字段访问/重新排序的公平点,但我仍然认为可以通过减少映射中的粒度来解释。我会仔细检查实际的映射并返回结果。
    • @svick,我复制、检查了映射并更新了结果。
    • 很好的发现。那好吧。顺便说一句,如何查看 MSIL 到本机的映射(在运行时或离线使用 DLL 的副本)?如果我们能得到的最佳崩溃位置指针是 MSIL 范围,我想看看那些。
    • 在生成本机代码之前,映射不会存在,这通常意味着您需要从正在运行的进程或内存转储中提取它(您可能可以直接从 ngen'ed 图像中提取它但我从未调查过)。为了获得这些映射,我特别使用了托管调试器 api 和 GetILToNativeMapping。同样的事情也可以通过profiler api 获得。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-18
    • 1970-01-01
    • 2023-02-04
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 2011-05-01
    相关资源
    最近更新 更多