La mia applicazione va in crash una volta al mese: come catturare un dump di memoria?
Talvolta la nostra applicazione va in crash, e fin qui è ordinaria amministrazione. Purtroppo ci sono dei casi più sfortunati, in cui il programma va in crash raramente, soltanto in produzione e con particolari condizioni di carico. Il tracing applicativo che magari abbiamo abilitato non ci fornisce alcun indizio utile, il nostro cliente continua a protestare perché i crash gli causano disservizio e minaccia di passare alla concorrenza: in poche parole, abbiamo un grosso problema e non sappiamo come risolverlo. Alcuni di voi sicuramente conosceranno già i Windows Debugging Tools, e magari hanno già avuto esperienza di debugging con WinDbg, ma stavolta la situazione è più problematica, perché il problema si riproduce solo sull’ambiente del cliente e in modo del tutto casuale. In alcuni casi è stato possibile istruire il cliente in modo da fargli lanciare adplus opportunamente configurato, ma si trattava di un fallimento che occorreva una o due volte al giorno, di conseguenza è stato possibile catturare un dump in brevissimo tempo, senza recare troppo disturbo al cliente.
Il nostro scenario è il seguente: applicazione in produzione presso un nostro cliente, il quale non ha le conoscenze necessarie o non vuole lanciare adplus ogni volta che avvia l’applicazione, crash che si verificano sporadicamente ed in modo casuale.
Come procedere per prendere un dump del crash? Facciamo solo un’assunzione: la nostra è un’applicazione .NET 2.0 o 4.0, il che è anche una complicazione in più, perché dobbiamo configurare adplus opportunamente in modo da distinguere le eccezioni managed. Ecco un esempio di file di conifgurazione adplus.exe che fa proprio questo lavoro:
<ADPlus Version="2">
<KeyWords>
<KeyWord Name="LoadSOS"> .loadby sos mscorwks </KeyWord>
<KeyWord Name="CheckException"> !StopOnException System.InvalidOperationException 1;.if (@$t1) { .dump /ma /u ${AdpDumpDir}\FULLDUMP_FirstChance_clr_NET_CLR_${AdpProcName}_.dmp} .else { !PrintException;!ClrStack } </KeyWord>
</KeyWords>
<Settings>
<RunMode>CRASH</RunMode>
<Option>Quiet</Option>
<Option>NoDumpOnFirst</Option>
</Settings>
<PreCommands>
<DebugActions> LoadSOS </DebugActions>
</PreCommands>
<Exceptions>
<Exception Code="CLR">
<Actions1> Log;CheckException </Actions1>
<ReturnAction1>GN</ReturnAction1>
<Actions2>FullDump</Actions2>
<ReturnAction2>GN</ReturnAction2>
</Exception>
<Exception Code='BPE'>
<Actions1> Log </Actions1>
<ReturnAction1> QD </ReturnAction1>
</Exception>
</Exceptions>
</ADPlus>
Facciamo un po’ di osservazioni:
- Questa è la versione 2 di adplus, ossia adplus.exe. Questo file di cofigurazione non funziona se lo date in pasto ad adplus_old.vbs (o adplus.vbs).
- Per entrare nel merito delle eccezioni .NET, adplus deve avere la debugger extension sos.dll corrispondente al runtime .NET utilizzato dalla nostra applicazione. Il caricamento viene effettuato con il comando “.loadby sos mscorwks” per .NET 2.0 e con “.loadby sos clr” per .NET 4.0. Modificate quindi il file di configurazione in base alla versione del runtime utilizzata, altrimenti non funzionerà.
- Questo file di configurazione permette di produrre dei full memory dump sull’eccezione managed System.InvalidOperationException. Potete editarlo impostando l’eccezione causa delle vostre notti insonni.
- Con questa configurazione, premendo CTRL+C ottenete il detach del debugger, senza causare la terminazione della vostra applicazione.
Potete provare molto facilmente la configurazione, lanciando il seguente comando:
Nota: la mia applicazione si chiama System.InvalidOperationException.exe; è obbligatorio selezionare una cartella per l’output e io ho scelto c:\dumps.
Nota2: è possibile lanciare adplus.exe con l’opzione –pmn anziché –pn , facendo sì che il tool parta e rimanga attivo anche quando l’applicazione da monitorare non sta girando. In questo modo adplus rileverà la partenza del nostro eseguibile automaticamente e si aggancerà. Mettete in conto che questa comodità ha un costo in termini di cpu; inoltre se, per esempio, riavviate la macchina, adplus non si risveglierà.
Ora passiamo alla seconda fase: fare in modo che adplus si agganci automaticamente alla nostra applicazione ogni volta che viene avviata.
Si tratta di un’operazione abbastanza delicata: bisogna modificare il Windows registry a mano, creando una chiave che ha lo stesso nome del nostro eseguibile:
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\System.InvalidOperationException.exe]
"debugger"="adplus.exe -c C:\\Debuggers(x86)\\new_adplus.config -o c:\\dumps -sc"
Come avrete sicuramente notato, la riga di comando è cambiata: utilizziamo ora l’opzione –sc invece della –pn; la differenza sta nel fatto che -sc lancia l’applicazione, mentre -pn si aspetta di trovarla già avviata. La chiave di registry “Image File Execution Options” non fa altro che postporre il nome dell’applicazione alla riga di comando specificata nella stringa “debugger” (la riga di esempio che vedete funziona solo se la cartella del debugger è nel path di sistema).
C’è un’altra differenza, questa volta nel file di configurazione: non possiamo più usare “.loadby sos clr” perché quando viene lanciato adplus mscorwks potrebbe non essere in memoria. Quello che dobbiamo fare è sostituire la loadby con: “.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll”, caricando quindi la dll della debugger extension.
<ADPlus Version="2">
<KeyWords>
<KeyWord Name="LoadSOS"> .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll </KeyWord>
[...]
A questo punto vi invito a provare voi stessi con un’applicazione .NET.
Importante: questa tecnica funziona bene con le Windows application, ma non è adatta ai Windows service e alle web application, per cui bisogna adottare tecniche un po’ differenti che ho comunque intenzione di spiegare con i prossimi post.
Buon debugging a tutti!
Senior Support Engineer
Developer Support Core