Depuración de interbloqueos: DRIVER_VERIFIER_DETECTED_VIOLATION (C4): 0x1001

Cuando el Comprobador de controladores detecta una infracción de jerarquía de bloqueo por subproceso, genera la comprobación de errores 0xC4: DRIVER_VERIFIER_DETECTED_VIOLATION con un valor de parámetro 1 de 0x1001.

Cuando la opción Detección de interbloqueos está activada (la detección de interbloqueos forma parte de las opciones estándar del comprobador de controladores), el comprobador de controladores realiza un seguimiento de cada bloqueo por subproceso asignado y el orden en el que se adquirió y liberó. Una violación de la jerarquía de bloqueo por subproceso significa que el comprobador de controladores ha detectado una situación en la que, en al menos un caso, LockA se adquiere y se mantiene antes de que se adquiera LockB, y en otro, LockB se adquiere y se mantiene antes de que se requiera LockA.

Importante Esta comprobación de errores se produce siempre que el comprobador de controladores detecta que se ha producido la infracción de la jerarquía, no cuando se produce una situación de interbloqueo real. Esta infracción exige firmemente que los bloqueos compartidos entre los distintos componentes de un controlador siempre deben adquirirse y liberarse en un orden que haga imposible el interbloqueo de dos subprocesos.

Novedad de Windows 8.1 Cuando el comprobador de controladores encuentra esta infracción, si el depurador está conectado, le pedirá una entrada sobre el error. En Windows 8 y versiones anteriores de Windows, esta infracción produce una comprobación de errores inmediata.

************ 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?

Para depurar esta infracción en un equipo que ejecuta Windows 8.1, elija B (Interrumpir) y escriba el comando del depurador sugerido (!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'.

El comando !deadlock 3 también se puede usar para mostrar más información, incluida la pila en el momento de la última adquisición:

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

El depurador sugiere usar el comando kb (Mostrar seguimiento de pila) para mostrar el seguimiento de la pila actual.

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

La salida del depurador muestra que el controlador en cuestión infringía esta regla mediante la adquisición y el mantenimiento del bloqueo A antes de adquirir el bloqueo B en un subproceso, y ahora ha adquirido el bloqueo B y está intentando adquirir el bloqueo A en otro subproceso. Tenga en cuenta que el primer subproceso (subproceso 0) finaliza, por lo que la adquisición y la posterior liberación de esos dos bloqueos se produjeron en algún momento desde que se cargó la imagen del controlador.

Cuando se cargan los símbolos adecuados para el controlador de prueba, el depurador mostrará la función en la que se adquirió el bloqueo en ese momento. En este ejemplo, sucede que tanto el Bloqueo A como el Bloqueo B se adquieren en la misma función. En el subproceso 0, ambos se adquieren en SystemControlIrpWorker. En el subproceso 1, ambos se adquieren en DeviceControlIrpWorker (se muestran en la salida del bloqueo B de !deadlock 3 y la salida de la pila actual (kb).

En este momento, una revisión del código fuente de cada función debe revelar que existe una ruta de acceso de código donde se pueden adquirir los bloqueos en este orden.

Both MyTestDriver!AlphaLock y MyTestDriver!BravoLock son objetos disponibles globalmente en el controlador:

include "MyTestDriverHeader.h"

// Locks used to control access to various objects
extern KSPIN_LOCK AlphaLock;
extern KSPIN_LOCK BravoLock;

Dentro de la función SystemControlIrpWorker, existe una ruta de acceso donde se adquiere AlphaLock (Bloqueo A en la salida !deadlock) y se mantiene cuando se adquiere BravoLock (Bloqueo B). También vale la pena señalar que los bloqueos se liberan correctamente en el orden inverso en el que se adquieren. (El código siguiente se edita en gran medida para mostrar solo los elementos necesarios para generar este escenario).

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>>
}

Si revisa la siguiente función de ejemplo DeviceControlIrpWorker, puede ver que es posible adquirir los bloqueos en orden inverso. Es decir, BravoLock se puede adquirir y mantener al intentar adquirir AlphaLock. El ejemplo siguiente se ha simplificado, pero muestra que hay una posible ruta de acceso en la que se podría producir una infracción.

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>>
}

Para corregir esta posible infracción, lo correcto sería asegurarse de que cada vez que el controlador intenta adquirir AlphaLock, comprueba y se asegura de que BravoLock no se mantiene. La corrección más sencilla podría ser simplemente liberar BravoLock y volver a adquirirlo en cuanto se adquiera AlphaLock. Sin embargo, es posible que se necesiten cambios de código más significativos si es fundamental que los datos que proteja BravoLock no cambien mientras esperan AlphaLock y la readquisición de BravoLock.

Para obtener más información sobre los bloqueos por subproceso y otras técnicas de sincronización, consulte Bloqueos por subproceso.

Bloqueos por subproceso

Comprobación de errores 0xC4: DRIVER_VERIFIER_DETECTED_VIOLATION

!deadlock