2016-04-17 51 views
7

Sto interpretando un rapporto di eccezione da un'app di Windows Phone C#. Un metodo genera un NullReferenceException. Il metodo va:NullReferenceException vs. MSIL

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

E 'coerente con m_Field essere nulla - non c'è semplicemente niente altro lì che può eventualmente essere nullo. Ma ecco la parte misteriosa.

Il GetILOffset() da StackFrame da StackTrace per l'oggetto di eccezione restituisce 0x13. Il MSIL per il metodo, come mostrato da ILDASM, va:

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 

Ecco cosa non capisco. Se l'offset è effettivamente 0x13, significa che la riga ldarg causa l'eccezione. Ma il comando è documentato come non fare eccezioni. È callvirt che dovrebbe buttare, non è vero? O l'offset è relativo a qualcosa di diverso dal metodo che inizia? ldfld può anche lanciare, ma solo se l'oggetto this è nullo; ciò non è possibile in C# AFAIK.

I documenti menzionano che le informazioni di debug potrebbero ostacolare l'offset, ma è una versione di rilascio.

La DLL che sto esaminando con ILDASM è esattamente quella che ho spedito al Windows Phone Store come parte del mio XAP.

risposta

3

Quando JIT genera il codice macchina, genera anche MSIL < -> mapping dei codici macchina. Quando si ottiene un'eccezione nel codice generato, il runtime utilizzerà i mapping per identificare l'offset IL.

Il JIT può riordinare le istruzioni della macchina come parte delle sue ottimizzazioni (quando sono abilitate), questo può rendere le mappature sempre più approssimative e granulari. Se l'accesso al campo è stato portato in avanti (l'accesso alla memoria è relativamente lento, a volte è bene iniziare a caricarlo bene prima di averne bisogno) quindi l'eccezione può sembrare essere stata lanciata da una precedente istruzione IL.


ho massacrati uno dei miei programmi di utilità di debug per fare le seguenti:

  • avviare un processo di destinazione ed eseguire fino a quando v'è un'eccezione
  • cattura l'IL byte & IL-to-native mappature
  • (rozzamente) disassemblare l'IL con indicatori che mostrano quali istruzioni IL sono raggruppate insieme con la stessa mappatura.

Allora ho fatto funzionare lo strumento su un processo fittizio che fa più o meno ciò che si mostra in questione, ed ha ottenuto la seguente (build di rilascio):

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 

Come si può vedere, la ldarg.0, ldfld e le istruzioni callvirt sono tutte trattate dalla stessa mappatura, quindi se una di queste genera un'eccezione, tutte le mappe torneranno allo stesso offset IL (0x13).

+0

Ma non è l'accesso al campo ('ldfld') che dovrebbe essere lanciato (poiché' questo' non è 'null'), è il' callvirt'. E guardando allo smontaggio, non penso che il riordino sia la causa qui. – svick

+0

Punto giusto sull'accesso/riordino del campo, ma continuo a pensare che possa essere spiegato da una ridotta granularità nelle mappature. Verificherò le mappature attuali e tornerò con i risultati. –

+0

@svick, ho riprodotto, controllato i mapping e aggiornato con i risultati. –