NullReferenceException 与 MSIL
NullReferenceException vs. MSIL
我正在解释来自 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
为 null 一致 - 没有其他可能为 null 的东西。但这是神秘的部分。
来自StackFrame
的GetILOffset()
来自StackTrace
的异常对象returns0x13。如 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 商店的那个。
当 JIT 生成机器代码时,它还会生成 MSIL <--> 机器代码映射。当您在生成的代码中遇到异常时,运行-time 将使用映射来识别 IL 偏移量。
作为优化的一部分,允许 JIT 对机器指令重新排序(当它们被启用时),这可能导致映射变得更近似和 g运行ular。如果字段访问被提前(内存访问相对较慢,有时在你需要它之前就开始加载它是好的)那么异常可能看起来是由更早的 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.0
、ldfld
和callvirt
指令都被同一个映射覆盖,所以如果其中任何一个触发异常,它们都会映射回到相同的 IL 偏移量 (0x13)。
我正在解释来自 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
为 null 一致 - 没有其他可能为 null 的东西。但这是神秘的部分。
来自StackFrame
的GetILOffset()
来自StackTrace
的异常对象returns0x13。如 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 商店的那个。
当 JIT 生成机器代码时,它还会生成 MSIL <--> 机器代码映射。当您在生成的代码中遇到异常时,运行-time 将使用映射来识别 IL 偏移量。
作为优化的一部分,允许 JIT 对机器指令重新排序(当它们被启用时),这可能导致映射变得更近似和 g运行ular。如果字段访问被提前(内存访问相对较慢,有时在你需要它之前就开始加载它是好的)那么异常可能看起来是由更早的 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.0
、ldfld
和callvirt
指令都被同一个映射覆盖,所以如果其中任何一个触发异常,它们都会映射回到相同的 IL 偏移量 (0x13)。