Compartir a través de


Depuración de un interbloqueo

Cuando un subproceso necesita acceso exclusivo al código o a algún otro recurso, solicita un bloqueo. Si es posible, Windows responde dando este bloqueo al subproceso. En este momento, nada más del sistema puede acceder al código bloqueado. Esto sucede todo el tiempo y es una parte normal de cualquier aplicación multiproceso bien escrita. Aunque un segmento de código determinado solo puede tener un bloqueo cada vez, varios segmentos de código pueden tener su propio bloqueo.

Un interbloqueo surge cuando dos o más subprocesos han solicitado bloqueos en dos o más recursos, en una secuencia incompatible. Por ejemplo, supongamos que thread One ha adquirido un bloqueo en el recurso A y, a continuación, solicita acceso al recurso B. Mientras tanto, Thread Two ha adquirido un bloqueo en el recurso B y, a continuación, solicita acceso al recurso A. Ninguno de los subprocesos puede continuar hasta que se reinigue el bloqueo del otro subproceso y, por lo tanto, ninguno de los subprocesos puede continuar.

Los interbloqueos en modo de usuario surgen cuando varios subprocesos, normalmente de una sola aplicación, han bloqueado el acceso entre sí al mismo recurso. Sin embargo, varios subprocesos de varias aplicaciones también pueden bloquear el acceso entre sí a un recurso global o compartido, como un evento global o semáforo.

Los interbloqueos en modo kernel surgen cuando varios subprocesos (del mismo proceso o de procesos distintos) han bloqueado el acceso de los demás al mismo recurso de kernel.

El procedimiento utilizado para depurar un interbloqueo depende de si el interbloqueo se produce en modo de usuario o en modo kernel.

Depuración de un interbloqueo de User-Mode

Cuando se produce un interbloqueo en modo de usuario, use el procedimiento siguiente para depurarlo:

  1. Emita la extensión !ntsdexts.locks . En el modo de usuario, solo puede escribir !locks en el símbolo del depurador; Se asume el prefijo ntsdexts .

  2. Esta extensión muestra todas las secciones críticas asociadas al proceso actual, junto con el identificador del subproceso propietario y el recuento de bloqueos para cada sección crítica. Si una sección crítica tiene un recuento de bloqueos de cero, no está bloqueado. Use el comando ~ (Estado de subproceso) para ver información sobre los subprocesos que poseen las otras secciones críticas.

  3. Use el comando kb (Display Stack Backtrace) para cada uno de estos subprocesos para determinar si están esperando en otras secciones críticas.

  4. Con la salida de estos comandos kb , puede encontrar el interbloqueo: dos subprocesos que están esperando en un bloqueo mantenido por el otro subproceso. En raras ocasiones, un interbloqueo podría deberse a más de dos subprocesos que contienen bloqueos en un patrón circular, pero la mayoría de los interbloqueos solo implican dos subprocesos.

A continuación se muestra una ilustración de este procedimiento. Comience con la extensión !ntdexts.locks :

0:006>  !locks 
CritSec ftpsvc2!g_csServiceEntryLock+0 at 6833dd68
LockCount          0
RecursionCount     1
OwningThread       a7
EntryCount         0
ContentionCount    0
*** Locked

CritSec isatq!AtqActiveContextList+a8 at 68629100
LockCount          2
RecursionCount     1
OwningThread       a3
EntryCount         2
ContentionCount    2
*** Locked

CritSec +24e750 at 24e750
LockCount          6
RecursionCount     1
OwningThread       a9
EntryCount         6
ContentionCount    6
*** Locked

La primera sección crítica que se muestra no tiene bloqueos y, por lo tanto, se puede omitir.

La segunda sección crítica mostrada tiene un recuento de bloqueos de 2 y es, por lo tanto, una posible causa de un interbloqueo. El subproceso propietario tiene un identificador de subproceso de 0xA3.

Para encontrar este subproceso, enumere todos los subprocesos con el comando ~ (Estado del subproceso) y busque el subproceso con este identificador:

0:006>  ~
   0  Id: 1364.1330 Suspend: 1 Teb: 7ffdf000 Unfrozen
   1  Id: 1364.17e0 Suspend: 1 Teb: 7ffde000 Unfrozen
   2  Id: 1364.135c Suspend: 1 Teb: 7ffdd000 Unfrozen
   3  Id: 1364.1790 Suspend: 1 Teb: 7ffdc000 Unfrozen
   4  Id: 1364.a3 Suspend: 1 Teb: 7ffdb000 Unfrozen
   5  Id: 1364.1278 Suspend: 1 Teb: 7ffda000 Unfrozen
.  6  Id: 1364.a9 Suspend: 1 Teb: 7ffd9000 Unfrozen
   7  Id: 1364.111c Suspend: 1 Teb: 7ffd8000 Unfrozen
   8  Id: 1364.1588 Suspend: 1 Teb: 7ffd7000 Unfrozen

En esta pantalla, el primer elemento es el número de subproceso interno del depurador. El segundo elemento (el Id campo) contiene dos números hexadecimales separados por un separador decimal. El número antes del separador decimal es el identificador de proceso; el número después del separador decimal es el identificador del subproceso. En este ejemplo, verá que el identificador de subproceso 0xA3 corresponde al número de subproceso 4.

A continuación, use el comando kb (Display Stack Backtrace) para mostrar la pila que corresponde al número de subproceso 4:

0:006>  ~4 kb
  4  id: 97.a3   Suspend: 0 Teb 7ffd9000 Unfrozen
ChildEBP RetAddr  Args to Child
014cfe64 77f6cc7b 00000460 00000000 00000000 ntdll!NtWaitForSingleObject+0xb
014cfed8 77f67456 0024e750 6833adb8 0024e750 ntdll!RtlpWaitForCriticalSection+0xaa 
014cfee0 6833adb8 0024e750 80000000 01f21cb8 ntdll!RtlEnterCriticalSection+0x46
014cfef4 6833ad8f 01f21cb8 000a41f0 014cff20 ftpsvc2!DereferenceUserDataAndKill+0x24
014cff04 6833324a 01f21cb8 00000000 00000079 ftpsvc2!ProcessUserAsyncIoCompletion+0x2a
014cff20 68627260 01f21e0c 00000000 00000079 ftpsvc2!ProcessAtqCompletion+0x32
014cff40 686249a5 000a41f0 00000001 686290e8 isatq!I_TimeOutContext+0x87
014cff5c 68621ea7 00000000 00000001 0000001e isatq!AtqProcessTimeoutOfRequests_33+0x4f
014cff70 68621e66 68629148 000ad1b8 686230c0 isatq!I_AtqTimeOutWorker+0x30
014cff7c 686230c0 00000000 00000001 000c000a isatq!I_AtqTimeoutCompletion+0x38
014cffb8 77f04f2c 00000000 00000001 000c000a isatq!SchedulerThread_297+0x2f
00000001 000003e6 00000000 00000001 000c000a kernel32!BaseThreadStart+0x51

Observe que este subproceso tiene una llamada a la función WaitForCriticalSection , lo que significa que no solo tiene un bloqueo, está esperando código bloqueado por otra cosa. Podemos averiguar en qué sección crítica estamos esperando examinando el primer parámetro de la llamada a WaitForCriticalSection. Esta es la primera dirección de Args to Child: "24e750". Por lo tanto, este subproceso está esperando la sección crítica en la dirección 0x24E750. Esta fue la tercera sección crítica enumerada por la extensión !locks que usó anteriormente.

En otras palabras, el subproceso 4, que posee la segunda sección crítica, está esperando la tercera sección crítica. Ahora, preste atención a la tercera sección crítica, que también está bloqueada. El subproceso propietario tiene 0xA9 de identificador de subproceso. Volviendo a la salida del ~ comando que vio anteriormente, tenga en cuenta que el subproceso con este identificador es el número de subproceso 6. Muestra el retroceso de la pila para este subproceso:

0:006>  ~6 kb 
ChildEBP RetAddr  Args to Child
0155fe38 77f6cc7b 00000414 00000000 00000000 ntdll!NtWaitForSingleObject+0xb
0155feac 77f67456 68629100 6862142e 68629100 ntdll!RtlpWaitForCriticalSection+0xaa 
0155feb4 6862142e 68629100 0009f238 686222e1 ntdll!RtlEnterCriticalSection+0x46
0155fec0 686222e1 0009f25c 00000001 0009f238 isatq!ATQ_CONTEXT_LISTHEAD__RemoveFromList
0155fed0 68621412 0009f238 686213d1 0009f238 isatq!ATQ_CONTEXT__CleanupAndRelease+0x30
0155fed8 686213d1 0009f238 00000001 01f26bcc isatq!AtqpReuseOrFreeContext+0x3f
0155fee8 683331f7 0009f238 00000001 01f26bf0 isatq!AtqFreeContext+0x36
0155fefc 6833984b ffffffff 00000000 00000000 ftpsvc2!ASYNC_IO_CONNECTION__SetNewSocket
0155ff18 6833adcd 77f05154 01f26a58 00000000 ftpsvc2!USER_DATA__Cleanup+0x47
0155ff28 6833ad8f 01f26a58 000a3410 0155ff54 ftpsvc2!DereferenceUserDataAndKill+0x39
0155ff38 6833324a 01f26a58 00000000 00000040 ftpsvc2!ProcessUserAsyncIoCompletion+0x2a
0155ff54 686211eb 01f26bac 00000000 00000040 ftpsvc2!ProcessAtqCompletion+0x32
0155ff88 68622676 000a3464 00000000 000a3414 isatq!AtqpProcessContext+0xa7
0155ffb8 77f04f2c abcdef01 ffffffff 000ad1b0 isatq!AtqPoolThread+0x32
0155ffec 00000000 68622644 abcdef01 00000000 kernel32!BaseThreadStart+0x51

Este subproceso también está esperando que se libere una sección crítica. En este caso, está esperando la sección crítica en 0x68629100. Esta fue la segunda sección crítica de la lista generada anteriormente por la extensión !locks .

Este es el interbloqueo. El subproceso 4, que posee la segunda sección crítica, está esperando la tercera sección crítica. El subproceso 6, que posee la tercera sección crítica, está esperando la segunda sección crítica.

Después de confirmar la naturaleza de este interbloqueo, puede usar las técnicas de depuración habituales para analizar subprocesos 4 y 6.

Depuración de un interbloqueo de Kernel-Mode

Hay varias extensiones del depurador que son útiles para depurar interbloqueos en modo kernel:

  • La extensión !kdexts.locks muestra información sobre todos los bloqueos contenidos en los recursos del kernel y los subprocesos que contienen estos bloqueos. (En el modo kernel, solo puede escribir !locks en el símbolo del depurador; se asume el prefijo kdexts ).

  • La extensión !qlocks muestra el estado de todos los bloqueos de número en cola.

  • La extensión !wdfkd.wdfspinlock muestra información sobre un objeto de bloqueo de número de Kernel-Mode Driver Framework (KMDF).

  • La extensión !deadlock se usa junto con el Comprobador de controladores para detectar el uso incoherente de bloqueos en el código que tienen la posibilidad de provocar interbloqueos.

Cuando se produce un interbloqueo en modo kernel, use la extensión !kdexts.locks para enumerar todos los bloqueos adquiridos actualmente por los subprocesos.

Normalmente, puede identificar el interbloqueo si encuentra un subproceso que no se ejecuta que contiene un bloqueo exclusivo en un recurso que requiere un subproceso en ejecución. La mayoría de los bloqueos se comparten.