NullReferenceException vs MSIL

I interpret the C # Windows Phone Application Exclusion Report. The method throws a NullReferenceException . The method goes:

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

This is consistent with the fact that m_Field is null - there simply is nothing there that can be null. But here is the mysterious part.

GetILOffset() from the StackFrame from the StackTrace for the exception object returns 0x13. The MSIL for the method, as shown by ILDASM, goes:

 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 

That’s what I don’t understand. If the offset is really 0x13, this means that the ldarg line throws an exception. But the command is documented as an exception to any exceptions. This is the callvirt that should quit, right? Or is it an offset relative to something other than the beginning of the method? ldfld can also throw, but only if the this object is null; which is not possible in C # AFAIK.

The docs mention that debugging information may interfere with the offset, but this is a release build.

The DLL that I am viewing with ILDASM is the one that I sent to the Windows Phone Store as part of my XAP.

+7
c # .net-assembly windows-phone cil
source share
1 answer

When JIT generates machine code, it also generates MSIL machine code mappings. When you get an exception in the generated code, the runtime will use mappings to identify the IL offset.

JIT is allowed to reorder machine instructions as part of its optimizations (when they are included), this can lead to comparisons becoming more approximate and detailed. If access to the field has been ported (access to memory is relatively slow, sometimes it’s good to start loading it long before you need it), then the exception may be thrown by an earlier IL statement.


I removed one of my debugging utilities to do the following:

  • run the target process and run it until an exception is thrown.
  • captures bytes of IL and IL-to-native mappings
  • (roughly) parse the IL with indicators showing which IL commands are grouped together with the same display.

Then I ran the tool in a bogus process, which does something like what you show in the question, and got the following (release build):

 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 

As you can see, the instructions ldarg.0 , ldfld and callvirt covered by the same mapping, so if any of these triggers throw an exception, they will all be mapped back to the same IL offset (0x13).

+3
source share

All Articles