Principi di analisi dei bugcheck di Windows

Quante volte ci si è trovati in una normale situazione di lavoro o di gioco e Windows si è improvvisamente fermato visualizzando una “inquietante” schermata blu con scritte bianche apparentemente incomprensibili?
In questo articolo vedremo di capire un po’ cosa succede nel momento in cui si verifica una condizione tale per la quale Windows non è più in grado di proseguire il suo lavoro e si ferma senza dare all’utente alcuna alternativa se non quella di riavviare il PC e ripartire da zero (o quasi).

Perchè Windows si blocca?

I fattori che possono portare il sistema operativo alla condizione di bug check sono differenti: un tentivo di referenziare un’area di memoria che provoca una violazione di accesso, un’eccezione imprevista, un comportamento errato di un driver in modalità kernel o un serio problema hardware sono aclune delle cause più frequenti del vericarsi di un errore di stop. Il punto fondamentale da tenere presente è che Windows opta per una gestione “prudente” della situazione di errore: il sistema operativo può continuare ad operare dopo aver isolato il problema ed aver cercato di attuare qualche strategia di recupero, ma è una strategia che comporta il rischio non trascurabile di propagazione dell’errore con la possibilità di una situazione ancora più critica che può portare alla corruzione dei dati in memoria o sul disco. Per evitare quersto rischio, Windows opta quella che viene definita una “fail, fast and safe policy”, che consiste nell’eseguire queste operazioni

  1. arresto dell’esecuzione
  2. passaggio ad una modalità grafica VGA a bassa risoluzione (disponibile in qualsiasi scheda video supportata da Windows) e visualizzazione di uno sfondo blu
  3. scrittura dello stato della memoria e delle informazioni relative all’errore in un file con estensione .DMP (chiamato memory dump file)
  4. visualizzazione di un codice di errore (stop code) contenente un messaggio ed alcune indicazioni all’utente

Nelle versioni di Windows fino alla 7, la schermata di errore aveva questo aspetto

Figura 1 – Esempio di schermata di bugcheck (fino a Windows 7)

Nelle successive versioni di Windows, la schermata ha assunto un aspetto “meno inquietante”, ma meno esplicativo.

Figura 2 – Esempio di schermata di bugcheck (versioni di Windows successive a Windows 7)

A titolo di analisi statistica, può essere interessante osservare la distribuzione dei bug checks in base alla loro causa: un interessante riassunto grafico delle cause degli errori di stop è quello fornito dal libro “Windows Internals, 6th Edition” di Mark Russinovich.

Figura 3 – Ripartizione delle cause più frequenti dei bugcheck

Un po’ di terminologia

Blue screen: la schermata visualizzata nel momento in cui si verifica una condizione di errore critico che porta all’arresto di Windows. Una visualizzazione a bassa risoluzione VGA contenente informazioni quali il codice di stop, il nome del driver coinvolto nell’errore e le operazioni eseguite dal sistema operativo prima del blocco totale o del riavvio del computer
Crash dump file o memory dump file: file con estensione .DMP contenente informazioni per l’analisi dell’errore con un debugger; può avere diverse dimensioni (fino a quelle della memoria fisica installata nel sistema) in base alle impostazioni di configurazione del sistema. Per impostazioni predefinita, viene scritta nella cartella %SYSTEMROOT%\Minidump.
Debugger: un programma utilizzato per individuare e correggere errori in un altro programma. Consente di eseguire un processo ed i suoi threads un passo alla volta e di ispezionare la memoria, le variabili ed altri elementi del processo e dei suoi threads.
Kernel mode: modalità della CPU dove vengono eseguiti i servizi di sistema ed alcuni device drivers. In questa modalità, tutta la memoria è accessibile e sono disponibili tutte le interfacce e le istruzioni della CPU.
Minidump file: una versione ridotta di un file di dump della memoria completo (chiamato anche kernel memory dump). Di solito, è sufficiente per individuare la causa del problema senza dover generare un file di dump completo.
STOP code: il codice che identifica l’errore che ha provocato l’arresto del funzionamento del kernel e dell’esecuzione del sistema operativo; è il primo valore esadecimale (ad esempio, 0x00000001) visualizzato nella schermata blu, seguito da quattro parametri che descrivono ulteriormente il problema.
Symbol files: insieme di files che contengono le informazioni di debug relative ad una specifica applicazione, driver o DLL. Il debugger viene configurato per poter accedere ad un apposito sito pubblico Microsoft (denominato symbols server) da cui è possibile prelevare questi simboli all’occorrenza.

La schermata di errore

Indipendentemente dalla causa (o dalla catena di cause) di un crash di sistema, la funzione responsabile dell’arresto critico si chiama KeBugCheckEx, documentata nel Windows Driver Kit (WDK) e definita nel file header wdm.h delle API del kernel di Windows.
La funzione prende in input uno stop code (noto anche come bug check code) e quattro parametri specifici dello stop stesso. Dopo aver eseguito il masking di tutti gli interrupts su tutte le CPU (per ignorare qualsiasi altra richiesta), la funzione commuta la visualizzazione dello schermo in una modalità grafica VGA a bassa risoluzione, visualizza uno sfondo blu (o di altro colore, in base alla versione di Windows) e lo stop code (con i suoi quattro parametri specifici), seguito da un testo descrittivo per l’utente. Il passo finale prevede l’invocazione delle callbacks (registrate chiamando la funzione KeRegisterBugCheckCallback) di tutti i device drivers registrati per consentire agli stessi di arrestare in maniera sicura i devices da essi gestiti. Successivamente, la funziona invoca le registered reason callbacks (registrate chiamando la funzione KeRegisterBugCheckReasonCallback), che permette ai driver di arricchire il crash dump file con ulteriori informazioni o di scrivere queste informazioni su devices alternativi. Infine, KeBugCheckEx visualizza la rappresentazione testuale dello stop code, il suo codice numerico ed i suoi quattro parametri descrittivi, anche se, in alcune situazioni, è possibile che le strutture dati del sistema siano state compromesse ad un livello tale da impedire la visualizzazione della schermata di stop e da causare il riavvio automatico del sistema.

Identificare l’errore di stop

Gli errori di stop sono molteplici: ognuno ha le sue possibili cause e può richiedere una specifica procedura di troubleshooting.
Il primo passo da compiere è quello di identificare correttamente l’errore, operazione che richiede di disporre delle seguenti informazioni

  • stop error number: questo numero esadecimale identifica univocamente l’errore di stop;
  • stop error parameters: quattro parametri che forniscono informazioni aggiuntive e specifiche sull’errore di stop;
  • driver information: quando disponibile, la driver information indica quella che sembra essere la causa più probabile dell’errore.

Queste informazioni sono solitamente visualizzato come parte del messaggio di stop, per cui è utile prenderne note per averle come riferimento durante il troubleshooting.
Se il Sistema operativo si riavvia immediatamente dopo aver completato le operazioni che fanno parte dalla procedura di gestione dell’errore di stop, è (solitamente) possibile ricavare queste informazioni dal log Sistema nel Visualizzatore Eventi di Windows. Se l’informazione non è disponibile in nessuno dei due modi precedentemente elencati, la terza (e più importante, per le fasi successive) fonte da cui attingere è il file di dump della memoria creato da Windows.

Comprendere il messaggio di stop

Il messaggio di stop fornisce informazioni relative all’errore che rappresentano un ausilio per il system administrator che si occuperà di isolare e (possibilmente) risolvere la causa del problema che ha portato allo stop.
Nella versione più dettagliata, il messaggio di stop comprende le seguenti sezioni

  • Bugcheck Information: questa sezione fornisce il nome descrittivo dell’errore di stop. Il nome è direttamente correlato al numero dell’errore di stop presente nella sezione Technical Information.
  • Recommended User Action: questa sezione informa l’utente dell’errore e dell’arresto di Windows. Indica anche il nome simbolico dell’errore di stop (nell’esempio in Figura 1, il nome simbolico è DRIVER_IRQL_NOT_LESS_OR_EQUAL) e prova a fornire una descrizione del problema e dei suggerimenti di recovery.
  • Technical Information: questa sezione contiene il numero dell’errore (noto anche come bugcheck code) seguito da quattro codici specifici dell’errore (visualizzati come numeri esadecimali con prefisso “0x” e racchiusi tra parentesi). Nell’esempio in Figura 1, il numero dell’errore di stop è 0x000000D1 (spesso scritto nella forma abbreviata 0xD1).
  • Driver Information: questa sezione identifica (o cerca di identificare) il driver associato con l’errore di stop.
  • Debug Port e Dump Status Information: questa sezione indica i parametri di una porta Component Object Model (COM) utilizzata da un debugger in modalità kernel (se attivo). Se il sistema operativo è stato configurator per la creazione di un file di dump della memoria, questa sezione informa anche sull’esito di questa operazione.

Raccogliere le informazioni su un crash dump in kernel mode

Le moderne versioni di Windows sono configurate per raccogliere le informazioni di dump della memoria in caso di crash del sistema e scriverle all’interno di un apposito file.
Le impostazioni di generazione di questo file sono accessibili nella scheda “Advanced” della finestra delle proprietà del sistema, come in figura.

Figura 4 – Configurazione delle opzioni per la creazione del file di dump

Abbiamo la possibilità di scegliere il tipo (complete, kernel, small ed automatic) di file di dump da generare, la posizione sul file system in cui crearlo, la possibilità (fortemente consigliata) di scrivere un evento nel log Sistema del Visualizzatore Eventi di Windows.
Consiglio fortemente di eseguire un test per verificare la corretta creazione di un file di dump: la procedura è molto semplice ed è descritta nell’articolo “Forcing a system crash from the keyboard” (vedi l’elenco dei riferimenti alla fine).

Preparazione dell’ambiente di debugging

Dopo aver impostato la configurazione del sistema operativo per la generazione del file di dump della memoria, dobbiamo procurarci gli strumenti adatti per esaminare il file stesso, ovvero un debugger. Oltre al “classico” Visual Studio, abbiamo due opzioni tra cui scegliere

  • installare l’intero set dei Debugging Tools for Windows, che comprendono debuggers ed altri utili strumenti (un elenco completo è disponibile qui); questi tools possono essere installati come un pacchetto standalone o come parte dell’installazione del Windows SDK o del Windows Driver Kit, in base alle nostre esigenze;
  • installare WinDbg, ovvero il solo debugger, che mette a disposizione anche della capacità di scripting oltre ad un’interfaccia più allineata alla moderna user experience di Windows

Una volta completata l’installazione degli strumenti, è necessario configurarli specificando la posizione della cartella dove verranno scaricati i simboli di debugging, un passo fondamentale per permettere al debugger di disporre di tutte le informazioni necessarie per l’esecuzione delle analisi dei file di dump.
I simboli di debugging possono essere scaricati su un disco locale oppure prelevati on demand da un apposito server accessibile via Internet: questa opzione è preferibile dal momento che le informazioni contenute nei simboli sono costantemente aggiornate con il rilascio di nuove versioni dei programmi, oltre che con la disponibilità di hotifxes e service packs di Windows.
Per configurare la posizione della cartella contenente i simboli di debugging, è sufficiente creare una cartella nel filesystem (ad esempio, C:\SymbolServer) ed impostare una nuova variabile di ambiente (il nome può essere scelto liberamente) nella finestra delle impostazioni avanzate di sistema

Figura 5 – La creazione della variable di ambiente per la cartella dei simboli di debugging

Infine, apriamo WinDbg, facciamo click sulla tab File e poi su Settings: inseriamo l’espressione SRV*C:\SymbolServer*https://msdl.microsoft.com/download/symbols (attenzione alla sintassi!) nella casella Default Symbol path: e premiamo OK per confermare.

Figura 6 – Impostazione della sorgente per il download dei simboli di debugging in WinDbg

Per curiosità, diamo uno sguardo veloce al contenuto della cartella che abbiamo scelto come posizione per il download dei simboli di debugging: ecco un piccolo estratto di quello che potremo trovare al suo interno, un insieme di folder e subfolders per ognuno dei moduli o drivers che sono stati coinvolti nell’analisi di un file di dump ed un equivalente insieme di folders e subfolders per le corrispondenti informazioni di debugging (ad esempio, si possono notare le cartelle relative al driver NTFS.SYS ed alle corrispondenti informazioni di debugging nella cartella NTFS.PDB)

Figura 7 – La cartella locale con i simboli di debugging scaricati

Le configurazioni sono terminate: è il momento di esaminare qualche caso pratico.
Procuriamoci qualche file di dump e vediamo cosa possiamo ricavarne.

Analizzare un file di dump – Scenario 1

Apriamo WinDbg, facciamo click su File e su Open Dump File: scegliamo il file e premiamo Open.

Figura 8 – Apertura di un file di dump

Il debugger apre il file ed inizia ad esaminarlo, poi acquisisce i necessari simboli del kernel e visualizza alcune informazioni di base.

Figura 9 – Esame preliminare del file di dump

A questo punto si parte con l’analisi, che richiede l’esecuzione del comando !analyze -v (flag che indica l’utilizzo della modalità verbose di visualizzazione delle informazioni): possiamo digitarlo nella casella con il prompt kd> oppure fare click sul link visualizzato

Figura 10 – Avvio dell’analisi

Il warning visualizzato può far già suonare un primo campanello d’allarme e metterci sulla giusta strada verso l’individuazione della causa del problema. Scorrendo l’output visualizzato verso il basso abbiamo la conferma dei nostri iniziali sospetti.

Figura 11 – Prima individuazione del responsabile

La voce FAILURE_BUCKET_ID punta proprio al responsabile del problema, il driver atikmpag.sys. Possiamo ottenere qualche ulteriore informazione facendo click sul link con il nome del modulo in questione o utilizzando il comando lmvm (List Loaded Modules) seguito dal nome del driver (il click sul link esegue direttamente la stessa operazione)

Figura 12 – Visualizzazione delle informazioni su un modulo

In questo caso, siamo stati abbastanza fortunati, trattandosi di un problema che appare abbastanza frequentemente: si tratta di un malfunzionamento del driver della scheda video (una veloce ricerca conferma il fatto che atikmpag.sys è il nome del driver kernel delle schede video ATI Radeon), il più delle volte risolvibile con un aggiornamento del driver video in questione o con la disinstallazione dello stesso e l’installazione di una versione immediatamente precedente (strategia da adottare nel caso in cui il problema si sia manifestato in seguito all’installazione di una nuova versione del driver video).

Analizzare un file di dump – Scenario 2

Come da procedura, apriamo il file di dump ed iniziamo l’analisi

Figura 13 – Avvio dell’analisi

Qui siamo in presenza di un bug check abbastanza comune: si tratta di un’eccezione non intercettata e non gestita (il che, come è noto, porta alla terminazione improvvisa del thread in queseione) da parte di un thread di sistema.
Il valore del primo parametro descrittivo del bug check ci fornisce già un possibile indizio sulla natura del problema: è sufficiente scorrere verso il basso l’output per averne conferma

Figura 14 – Individuazione della causa e del responsabile

La prima riga in figura spiega chiaramente la causa dell’errore: il codice “0xc0000005” (STATUS_ACCESS_VIOLATION) e la successiva frase “The instruction at 0x%p referenced memory at 0x%p. The memory could not be %s” fanno capire che il bug check è stato scatenato da una violazione di accesso alla memoria: il thread ha provato ad accedere ad una locazione di memoria per eseguire un’operazione non consentita e questo tentativo ha generato l’eccezione che ha portato alla sua terminazione ed al bugcheck finale. Il codice di eccezione è un valore assegnato ad un membro della struttura NTSTATUS, definita nel file header ntstatus.h del Windows Driver Kit.
L’ultima operazione da eseguire è cercare di individuare la causa del problema: anche in questo caso, la voce FAILURE_BUCKET_ID indica il possibile responsabile, il driver iusb3xhc.sys appartenente all’Intel USB 3.0 eXtensible Host Controller Driver.
Un ultimo passo è quello di visualizzare il thread in esecuzione al momento del crash usando il comando !thread

Figura 15 – Esame del thread

Il cui output ci fornisce un’informazione importante, ovvero gli “estremi” del campo di indagine, rappresentati dai valori posti dopo le diciture Base e Limit: eseguiamo un dump dello stack (per mezzo del comando dps a cui passeremo in ordine il valore di “Limit” e quello di “Base”) per cercare altri elementi o moduli che possano essere coinvolti nel problema; scorrendo verso il basso il lungo output visualizzato, balza all’occhio un’altra voce potenzialmente sospetta

Figura 16 – Individuazione di una potenziale concausa

Al momento del bug check, anche un filter driver appartenente ad sistema di protezione antimalware non Microsoft era presente nello stack del thread, il che ci porta a pensare ad un possibile conflitto con questo programma.
In questo scenario, disabilitare la protezione antimalware e verificare la disponibilità di aggiornamenti del controller del bus/hub USB può essere di aiuto nella risoluzione del problema.

Analizzare un file di dump – Scenario 3

Iniziamo l’analisi nel solito modo, aprendo il file di dump ed usando il comando !analyze -v

Figura 17 – Avvio dell’analisi

In questo caso, siamo in presenza di un serio errore hardware le cui informazioni sono fornite dalla Windows Hardware Error Architecture (WHEA) e memorizzate all’interno di un’istanza della struttura WHEA_ERROR_RECORD definita nel file header ntddk.h del Windows Driver Kit; questa struttura descrive un error record contenente i dettagli di una condizione di errore hardware verificatasi nel sistema.
Il valore del primo parametro descrittivo del bug check indica che la causa è una machine check exception (solitamente imputabile alla CPU): scorrendo verso il basso l’output abbiamo la prima conferma dalla voce FAILURE_BUCKET_ID

Figura 18 – Individuazione della causa

Il valore del primo parametro descrittivo del bug check fronisce l’indirizzo della struttura WHEA_ERROR_RECORD ed è quello che ci interessa particolarmente: il comando !errrec riceve questo indirizzo come parametro e visualizza le informazio contenute all’interno di questa struttura

Figura 19 – Analisi del record di errore

Le prime righe dell’output confermano le prime deduzioni ricavate all’inizio dell’analisi: il problema è relativo alla CPU.
Questo tipo di errore non lascia molta libertà di azione all’utente: in questo scenario, sarebbe consigiliabile far eseguire dei test approfonditi sulla CPU per verificarne il corretto funzionamento.

Conclusioni

In questo articolo abbiamo voluto esaminare a grandi linee quali sono i primi passi da compiere per avventurarsi nell’analisi di un file di dump della memoria e provare a capire le cause di un bug check.
Ogni specifico errore di stop ha caratteristiche peculiari e richiede una particolare procedura di analisi e troubleshooting (oltre alla conoscenza dei meccanismi di funzionamento del sistema operativo), ragione per cui queste informazioni vogliono essere un punto di partenza per stimolare la curiosità e fornire i primi strumenti di base per comprendere le cause di un bugcheck e risolverlo.

Riferimenti