Debug di deadlock - DRIVER_VERIFIER_DETECTED_VIOLATION (C4): 0x1001
Quando Driver Verifier rileva una violazione della gerarchia di blocchi di selezione, Driver Verifiergenera il controllo bug 0xC4: DRIVER_VERIFIER_DETECTED_VIOLATION con un valore di parametro 1 di 0x1001.
Quando l'opzione Rilevamento deadlock è attiva (il rilevamento dei deadlock fa parte delle opzioni standard del driver), Driver Verifier tiene traccia di ogni blocco spin allocato e dell'ordine in cui è stato acquisito e liberato. Una violazione della gerarchia di blocchi indica che Driver Verifier ha rilevato una situazione in cui, in almeno un caso, LockA viene acquisito e mantenuto prima dell'acquisizione di LockB e in un altro, LockB viene acquisito e mantenuto prima che Venga richiesto LockA .
Importante Questo controllo di bug si verifica ogni volta che si verifica la violazione della gerarchia, non quando si verifica una situazione di deadlock effettiva. Questa violazione impone fortemente che tutti i blocchi condivisi tra i vari componenti di un driver debbano essere sempre acquisiti e rilasciati in un ordine che rende impossibile il deadlock di due thread.
Novità di Windows 8.1 Quando driver Verifier rileva questa violazione, se il debugger è collegato, il debugger richiederà l'input sull'errore. In Windows 8 e versioni precedenti di Windows questa violazione comporta un controllo immediato dei bug.
************ Verifier Detected a Potential Deadlock *************
**
** Type !deadlock in the debugger for more information.
**
*****************************************************************
*** Verifier assertion failed ***
(B)reak, (I)gnore, (W)arn only, (R)emove assert?
Per eseguire il debug di questa violazione in un computer che esegue Windows 8.1, scegliere B (interruzione) e immettere il comando del debugger suggerito (!deadlock):
kd> !deadlock
issue: 00001001 97dd800c 86858ce0 00000000
Deadlock detected (2 locks in 2 threads):
Thread 0: A B.
Thread 1: B A.
Where:
Thread 0 = TERMINATED.
Thread 1 = c4ae2040.
Lock A = 97dd800c (MyTestDriver!AlphaLock+0x00000000) - Type 'Spinlock'.
Lock B = 97dd8008 (MyTestDriver!BravoLock+0x00000000) - Type 'Spinlock'.
Il comando !deadlock 3 può essere usato anche per visualizzare altre informazioni, incluso lo stack al momento dell'ultimo acquisizione:
kd> !deadlock 3
issue: 00001001 97dd800c 86858ce0 00000000
Deadlock detected (2 locks in 2 threads):
#
Thread 0: TERMINATED took locks in the following order:
Lock A = 97dd800c (MyTestDriver!AlphaLock+0x00000000) - Type 'Spinlock'.
Node: 8685acd8
Stack: 97dd65b7 MyTestDriver!SystemControlIrpWorker+0x00000027
97dd605a MyTestDriver!Dispatch_SystemControl+0x0000001a
820c4b4d nt!IovCallDriver+0x000002cc
81ca3772 nt!IofCallDriver+0x00000062
81eb165e nt!IopSynchronousServiceTail+0x0000016e
81eb5518 nt!IopXxxControlFile+0x000003e8
81eb4516 nt!NtDeviceIoControlFile+0x0000002a
81d27e17 nt!KiSystemServicePostCall+0x00000000
Lock B = 97dd8008 (MyTestDriver!BravoLock+0x00000000) - Type 'Spinlock'.
Node: 86833578
Stack: 97dd65c5 MyTestDriver!SystemControlIrpWorker+0x00000a4a
97dd605a MyTestDriver!Dispatch_SystemControl+0x0000001a
820c4b4d nt!IovCallDriver+0x000002cc
81ca3772 nt!IofCallDriver+0x00000062
81eb165e nt!IopSynchronousServiceTail+0x0000016e
81eb5518 nt!IopXxxControlFile+0x000003e8
81eb4516 nt!NtDeviceIoControlFile+0x0000002a
81d27e17 nt!KiSystemServicePostCall+0x00000000
#
Thread 1: c4ae2040 (ThreadEntry = 86833a08) took locks in the following order:
Lock B = 97dd8008 (MyTestDriver!BravoLock+0x00000000) - Type 'Spinlock'.
Node: 86858ce0
Stack: 97dd65ef MyTestDriver!DeviceControlIrpWorker+0x0000005f
97dd605a MyTestDriver!Dispatch_DeviceControl+0x0000001a
820c4b4d nt!IovCallDriver+0x000002cc
81ca3772 nt!IofCallDriver+0x00000062
81eb165e nt!IopSynchronousServiceTail+0x0000016e
81eb5518 nt!IopXxxControlFile+0x000003e8
81eb4516 nt!NtDeviceIoControlFile+0x0000002a
81d27e17 nt!KiSystemServicePostCall+0x00000000
Lock A = 97dd800c (MyTestDriver!AlphaLock+0x00000000) - Type 'Spinlock'.
Stack: << Current stack trace - use kb to display it >>
Il debugger suggerisce di usare il comando kb (Display Stack Backtrace) per visualizzare l'analisi dello stack corrente.
kd> kb
ChildEBP RetAddr Args to Child
89b2cac4 820da328 97dd800c 86858ce0 00000000 nt!VfReportIssueWithOptions+0x86
89b2caf4 820d92a2 00000001 00000000 97dd65fd nt!ViDeadlockAnalyze+0x1d1
89b2cb7c 820d424e 86858ce0 00000000 97dd65fd nt!VfDeadlockAcquireResource+0x2fd
89b2cb9c 97dd65fd 00007780 89b2cbbc 97dd605a nt!VerifierKfAcquireSpinLock+0x8c
89b2cba8 97dd605a 9a9e7780 88d08f48 00000000 MyTestDriver!DeviceControlIrpWorker+0x54a
89b2cbbc 820c4b4d 9a9e7780 88d08f48 820c4881 MyTestDriver!Dispatch_DeviceControl+0x1a
(Inline) -------- -------- -------- -------- nt!IopfCallDriver+0x47
89b2cbe0 81ca3772 81eb165e b3c9ff80 88d08f48 nt!IovCallDriver+0x2cc
89b2cbf4 81eb165e 88d08fdc 88d08f48 00000000 nt!IofCallDriver+0x62
L'output del debugger mostra che il driver in questione ha violato questa regola acquisendo e tenendo premuto Lock A prima di acquisire Lock B su un thread e ora ha acquisito Lock B e sta tentando di acquisire Lock A su un altro thread. Si noti che il primo thread (Thread 0) viene terminato, quindi l'acquisizione e il rilascio successivo di questi due blocchi si sono verificati a un certo punto dopo il caricamento dell'immagine del driver.
Quando vengono caricati simboli appropriati per il driver di test, il debugger mostrerà la funzione in cui è stato acquisito il blocco al momento. In questo esempio si verifica che sia Lock A che Lock B vengano acquisiti nella stessa funzione. In Thread 0 vengono entrambi acquisiti in SystemControlIrpWorker. Nel thread 1 vengono entrambi acquisiti in DeviceControlIrpWorker (mostrato nell'output di Blocco B da !deadlock 3 e l'output dello stack corrente (kb).
A questo punto, una revisione del codice sorgente di ogni funzione dovrebbe rivelare che esiste un percorso di codice in cui è possibile acquisire i blocchi in tale ordine.
Entrambi MyTestDriver! AlphaLock e MyTestDriver! BravoLock sono oggetti disponibili a livello globale nel driver:
include "MyTestDriverHeader.h"
// Locks used to control access to various objects
extern KSPIN_LOCK AlphaLock;
extern KSPIN_LOCK BravoLock;
All'interno della funzione SystemControlIrpWorker esiste un percorso in cui viene acquisito AlphaLock (Lock A nell'output !deadlock ) e mantenuto quando viene acquisito BravoLock (Lock B). Vale anche la pena notare che i blocchi vengono rilasciati correttamente nell'ordine inverso in cui vengono acquisiti. Il codice seguente viene modificato pesantemente per mostrare solo gli elementi necessari per generare questo scenario.
NTSTATUS SystemControlIrpWorker(_In_ PIRP Irp)
{
KIRQL IrqlAlpha;
KIRQL IrqlBravo;
// <<Other local variable declarations removed>>
// <<Various lines of code not shown>>
KeAcquireSpinLock (&AlphaLock, &IrqlAlpha);
// <<Various lines of code not shown>>
KeAcquireSpinLock (&BravoLock, &IrqlBravo);
// <<Various lines of code not shown>>
KeReleaseSpinLock (&BravoLock, IrqlBravo);
KeReleaseSpinLock (&AlphaLock, IrqlAlpha);
// <<Various lines of code not shown>>
}
Se si esamina la seguente funzione di esempio DeviceControlIrpWorker , è possibile osservare che è possibile acquisire i blocchi in ordine inverso. Vale a dire, BravoLock può essere acquisito e mantenuto quando si tenta di acquisire AlphaLock. L'esempio seguente è semplificato, ma mostra che esiste un possibile percorso in cui potrebbe verificarsi una violazione.
NTSTATUS DeviceControlIrpWorker(_In_ PIRP Irp,
_In_ BOOLEAN bSomeCondition)
{
KIRQL IrqlAlpha;
KIRQL IrqlBravo;
// <<Other local variable declarations removed>>
if (bSomeCondition == FALSE)
{
//
// Note that if bSomeCondition is FALSE, then AlphaLock is acquired here
// If bSomeCondition is TRUE, it is not needed to be acquired right now
//
KeAcquireSpinLock (&AlphaLock, &IrqlAlpha);
}
// <<Various lines of code not shown>>
KeAcquireSpinLock (&BravoLock, &IrqlBravo);
// <<Various lines of code not shown>>
if (bSomeCondition == TRUE)
{
//
// Need to acquire AlphaLock here for upcoming code logic
//
KeAcquireSpinLock (&AlphaLock, &IrqlAlpha);
// <<Various lines of code not shown>>
KeReleaseSpinLock (&AlphaLock, IrqlAlpha);
}
// <<Various lines of code not shown>>
KeReleaseSpinLock (&BravoLock, IrqlBravo);
if (bSomeCondition == FALSE)
{
//
// Release the AlphaLock, which was acquired much earlier
//
KeReleaseSpinLock (&AlphaLock, IrqlAlpha);
}
// <<Various lines of code not shown>>
}
Per correggere questa potenziale violazione, la cosa corretta da fare sarebbe garantire che ogni volta che il driver tenta di acquisire AlphaLock, controlla e assicura che BravoLock non sia mantenuto. La correzione più semplice potrebbe essere quella di rilasciare semplicemente BravoLock e acquisirla nuovamente non appena AlphaLock viene acquisito. Tuttavia, potrebbero essere necessarie modifiche al codice più significative se è fondamentale che qualsiasi data BravoLock stia proteggendo non cambia durante l'attesa di AlphaLock e la ri-acquisizione di BravoLock.
Per altre informazioni sui blocchi di selezione e altre tecniche di sincronizzazione, vedere Spin Locks.