Penso proprio di avere la soluzione in tasca, ma come sempre è meglio confrontarsi con gli altri e quale posto migliore del blog? Anticipando le conclusioni non sono così convinto che si tratti di bug... perlomeno è discutibile.
Il tutto è originato da un post di Paolo suggeritomi da Cristian su cui ho fatto un po' di ricerche un paio di giorni fa a seguito del quale anche Marco (nei commenti del mio post) appoggiava la mia tesi.
Mentre questo pomeriggio rimuginavo sulla frase riportata su MSDN: "StackTrace might not report as many method calls as expected, due to code transformations that occur during optimization".... subito mi son detto: "ma si!!! È un problema di inlining!".
Il mio sospetto era quindi rispetto al fatto che certe chiamate vengono messe inline in modo automatico dal jitter. Visto che lo stacktrace va a prendere le informazioni sullo stack, se una call viene eliminata, allora automaticamente vengono prese le informazioni della call ancora più a monte rispetto a quella desiderata.
Questo quindi giustifica il fatto che venga riportato "System.Web" (vedi post di Paolo) in quanto è proprio System.Web.Dll a fare l'hosting degli appdomain che caricano le applicazioni web.
Adesso dovevo ancora solo dimostrarlo.
Una cosa che non mi andava giù era il fatto di non riuscire a fare debugging e sempre questo pomeriggio ho avuto un'altra idea: "faccio ngen della dll e vediamo se riesco a iniettare codice e fare l'attach del debugger".... ha funzionato!
Il passo successivo, dovendo debuggare una versione release e quindi priva di simboli, era quello di identificare nell'assembler un punto da cui partire. Questo è stato molto semplice grazie al codice iniettato, una semplice dll C++ managed/unmanaged. Poi, grazie ai pdb riuscivo comunque a vedere i nomi delle call.
Ecco quindi la versione Release della ExecuteAssemblyName:
Prima di entrare del merito della riga blu e dei parametri indicati dalle frecce rosse, vediamo ora la versione Debug (che ricordo non presenta il problema così come già evidenziato da Paolo).
Ecco quindi la versione Debug della ExecuteAssemblyName:
Ovviamente in questo caso è possibile vedere il sorgente visto che è la versione Debug.
Questo metodo non fa altro che chiamare la GetExecutingAssembly (che presenta il problema) e la GetExecutingAssemblyOk (che invece funziona sempre).
Infine vediamo la chiamata Debug della GetExecutingAssembly:
Ora, per chi legge che non conosce l'assembler, il compilatore C++, quando crea l'assembler, chiama i vari metodo con un offset rispetto all'inizio della classe. Perciò la call dword ptr [eax+offset] è una chiamata al metodo, cosa che appare chiaro nella versione debug.
La dimostrazione che la versione Release ha effettuato inlining sta nella comparazione delle frecce rosse. Infatti le chiamate ai metodi GetFrame, GetMethod etc. vengono effettuate nella versione Debug dove ci saremmo aspettati che avvenga (GetExecutingAssembly) mentre nella versione Release già dentro la ExecuteAssemblyName.
Ulteriore dimostrazione lo è la riga blu della prima figura che è subito dopo una "ret" (return). Questo indica il termine del metodo GetExecutingAssembly e l'inizio della GetExecutingAssemblyOk. Quello che appare subito è che la GetExecutingAssemblyOk non è soggetta ad inlining e infatti restituisce il risultato aspettato.
L'operazione di inlining effettuata da ngen (e che viene fatta naturalmente anche da jitter) ha fatto sì che una call venisse soprressa per aumetare la performance dell'operazione. Questo però ha giocato un brutto scherzo a Paolo e Cristian in quanto lo stack si è 'accorciato' inaspettatamente.
Alcune considerazioni
- Come è ovvio che sia la versione release ha una serie di ottimizzazioni che la rendono molto più performante. Purtroppo in giro vedo spesso applicazioni date in versione debug e questo è ovviamente sbagliato per anche molti altri aspetti che adesso evito per brevità.
- Come più volte ho avuto modo di dire è sempre necessario fare i test sulla versione release e non fidarsi di quelli fatti nelle debug perchè ci sono diversi cambiamenti. Fortunatamente questo è quanto Paolo e Cristian stavano facendo e si sono accorti del problema per tempo.
- Non so se considerare questo un bug, non solo perchè un avvisto sibillino era già presente in msdn ma perchè sarebbe impossibile a priori capire se l'inling verrà fatto dal jitter o meno. Visto che Paolo ha già aperto una chiamata a Microsoft, sapremo presto se verrà considerato bug o no.
Mi spiace solo per la lunghezza del post :-)