posts - 644, comments - 2003, trackbacks - 137

My Links

News

Raffaele Rialdi website

Su questo sito si trovano i miei articoli, esempi, snippet, tools, etc.

Archives

Post Categories

Image Galleries

Blogs

Links

La soluzione al problema di "StackTrace, Assembly e .NET"

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 :-)

Print | posted on domenica 12 dicembre 2004 23:58 | Filed Under [ .NET [Italiano] ]

Feedback

Gravatar

# re: La soluzione al problema di "StackTrace, Assembly e .NET"

Certo Luca,
daccordissimo e sono infatti le cose che ci siamo sentiti dire in persona da Peter Drayton e Jim Hogg al workshop UGIdotNET di Aprile 2003.
Resta il fatto (come da mio post successivo) che è necessario fare attenzione a come e quando si usa StackTrace perchè non tiene conto (e non vedo come potrebbe) dell'inlining.
L'unica soluzione è quella di usare NoInlining con l'attributo MethodImpl.
Il problema in questo caso era avere la prova che fosse veramente dovuto all'inlining e credo che ormai non ci siano più dubbi.
13/12/2004 01:58 | Raffaele Rialdi
Gravatar

# re: Feedback interessante al mio post sul presunto bug di .NET

13/12/2004 03:32 | Il Blog di Paolo Pialorsi
Gravatar

# re: La soluzione al problema di "StackTrace, Assembly e .NET"

Ottimo lavoro Raffaele! Ricordami di offrirti una birra alla prima occasione. Come ho lasciato detto nel mio blog, trovo che questa situazione sia sul filo di lana. L'inlining ovviamente non è un bug e le ottimizzazioni del Jitter sono ben accette, ci mancherebbe :-), però il fatto che la classe StackFrame cambi comportamento a causa del Jitter ... mi piace poco!
13/12/2004 03:34 | Paolo Pialorsi
Gravatar

# re: La soluzione al problema di "StackTrace, Assembly e .NET"

Grazie Paolo :)
13/12/2004 11:10 | Raffaele Rialdi
Gravatar

# re: La soluzione al problema di "StackTrace, Assembly e .NET"

Prima di tutto bravo Raffaele!
Leggendo i commenti ho visto che hai già scritto che manda il NoInlining ed è questo il punto: siccome la ExecutingAssemblyName di Paolo chiama la GetExecutingAssembly che costruisce l'istanza di StackTrace... per sapere il metodo giusto passa un parametro che è il numero di livelli per cui risalire lo stack. Con la NoInlining di GetExecutingAssembly garantirebbe che il numero passato sia corretto. In generale, se si fa un metodo che analizza lo stack bisogna garantire che questo non sia modificato da ottimizzazioni del compilatore.
In questo senso quindi dovremmo dire che è la GetExecutingAssembly che deve provvedere a garantire un servizio corretto. Se lo chiediamo a chi sviluppato la classe StackTrace, probabilmente ci sentiremmo rispondere che quella classe funziona benissimo e fa quello che è scritto sulla documentazione.
Un'altra strategia potrebbe essere quella di analizzare i frame dello StackTrace per risalire al primo metodo "utile", scartando quelli "interni", ma mi sembra una soluzione peggiore del male (prestazioni bye-bye, insomma).
13/12/2004 13:10 | Marco Russo
Gravatar

# re: La soluzione al problema di "StackTrace, Assembly e .NET"

Grazie Marco,
convengo e stavo appunto discutendo qui in ufficio che una strategia potesse essere quella di risalire lo stack uno step alla volta in modo identificare la call sulla base di un attributo custom.... ma le prestazioni come dici anche tu, ne risentirebbero.
Inoltre bisognerebbe prevedere quale call è 'candidabile' ad essere risalita, cosa che non sarebbe accettabile se si vuole usare questo sistema per realizzare un meccanismo di logging.
13/12/2004 13:32 | Raffaele Rialdi
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET