Felsöka ett dödläge

När en tråd behöver exklusiv åtkomst till kod eller någon annan resurs begär den ett lås. Om det kan svarar Windows genom att ge det här låset till tråden. I det här läget kan inget annat i systemet komma åt den låsta koden. Detta händer hela tiden och är en normal del av alla välskrivna flertrådade program. Även om ett visst kodsegment bara kan ha ett lås i taget, kan flera kodsegment ha ett eget lås.

Ett dödläge uppstår när två eller flera trådar har begärt lås på två eller flera resurser i en inkompatibel sekvens. Anta till exempel att Thread One har skaffat ett lås på resurs A och sedan begär åtkomst till resurs B. Under tiden har Thread Two skaffat ett lås på Resurs B och begär sedan åtkomst till Resurs A. Ingen av trådarna kan fortsätta tills den andra trådens lås har avsänts och därför kan ingen av trådarna fortsätta.

Dödlägen i användarläge uppstår när flera trådar, vanligtvis av ett enda program, har blockerat varandras åtkomst till samma resurs. Flera trådar i flera program kan dock också blockera varandras åtkomst till en global/delad resurs, till exempel en global händelse eller semafor.

Dödlägen i kernelläge uppstår när flera trådar (från samma process eller från olika processer) har blockerat varandras åtkomst till samma kernelresurs.

Proceduren som används för att felsöka ett dödläge beror på om dödläget inträffar i användarläge eller i kernelläge.

Felsöka ett User-Mode dödläge

När ett dödläge inträffar i användarläge använder du följande procedur för att felsöka det:

  1. Utfärda !ntsdexts.locks-tillägg. I användarläge kan du bara skriva !locks i felsökningsprompten; det antas att prefixet ntsdexts används.

  2. Det här tillägget visar alla kritiska avsnitt som är associerade med den aktuella processen, tillsammans med ID:t för den ägande tråden och antalet lås för varje kritiskt avsnitt. Om ett kritiskt avsnitt har ett låsantal på noll är det inte låst. Använd kommandot ~ (trådstatus) för att se information om de trådar som äger de andra kritiska avsnitten.

  3. Använd kommandot kb (Display Stack Backtrace) för var och en av dessa trådar för att avgöra om de väntar på andra kritiska avsnitt.

  4. Med hjälp av utdata från dessa kb kommandon kan du hitta dödläget: två trådar som var och en väntar på ett lås som innehas av den andra tråden. I sällsynta fall kan ett dödläge orsakas av mer än två trådar som håller lås i ett cirkulärt mönster, men de flesta dödlägen omfattar bara två trådar.

Här är en illustration av den här proceduren. Du börjar med tillägget !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

Det första kritiska avsnittet som visas har inga lås och kan därför ignoreras.

Det andra kritiska avsnittet som visas har ett låsantal på 2 och är således en möjlig orsak till ett dödläge. Den ägande tråden har ett tråd-ID för 0xA3.

Du hittar den här tråden genom att visa alla trådar med kommandot ~ (trådstatus) och leta efter tråden med det här ID:t:

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

I den här visningen är det första objektet felsökarens interna trådnummer. Det andra objektet (fältet Id) innehåller två hexadecimala tal avgränsade med en decimal. Talet före decimaltecknet är process-ID. talet efter decimaltecknet är tråd-ID:t. I det här exemplet ser du att tråd-ID 0xA3 motsvarar trådnummer 4.

Du använder sedan kommandot kb (Display Stack Backtrace) för att visa stacken som motsvarar trådnummer 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

Observera att den här tråden har ett anrop till funktionen WaitForCriticalSection, vilket innebär att den inte bara har ett lås, utan väntar på kod som är låst av något annat. Vi kan ta reda på vilket kritiskt avsnitt vi väntar på genom att titta på den första parametern för anropet till WaitForCriticalSection. Det här är den första adressen under Args to Child: "24e750". Den här tråden väntar alltså på det kritiska avsnittet på adressen 0x24E750. Det här var det tredje kritiska avsnittet som listades av !locks tillägg som du använde tidigare.

Med andra ord väntar tråd 4, som äger det andra kritiska avsnittet, på det tredje kritiska avsnittet. Rikta nu uppmärksamheten mot det tredje kritiska avsnittet, som också är låst. Den ägande tråden har tråd-ID 0xA9. När du återgår till utdata från kommandot ~ som du såg tidigare bör du notera att tråden med det här ID:t är trådnummer 6. Visa stackspåret för den här tråden.

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

Även den här tråden väntar på att ett kritiskt avsnitt ska frigöras. I det här fallet väntar den på det kritiska avsnittet på 0x68629100. Det här var det andra kritiska avsnittet i listan som genererades tidigare av !locks-tillägget.

Det här är dödläget. Tråd 4, som äger det andra kritiska avsnittet, väntar på det tredje kritiska avsnittet. Tråd 6, som äger det tredje kritiska avsnittet, väntar på det andra kritiska avsnittet.

När du har bekräftat det här dödläget kan du använda vanliga felsökningstekniker för att analysera trådarna 4 och 6.

Felsöka ett Kernel-Mode dödläge

Det finns flera tillägg för felsökningsprogram som är användbara för felsökning av dödlägen i kernelläge:

  • !kdexts.locks-tillägget visar information om alla lås som finns på kernelresurser och trådarna som innehåller dessa lås. (I kernelläge kan du bara skriva !locks vid felsökningsprompten, då kdexts-prefixet antas.)

  • Tillägget !qlocks visar tillståndet för alla köade spinnlås.

  • Tillägget !wdfkd.wdfspinlock visar information om ett Kernel-Mode KMDF-spin-lock-objekt (Driver Framework).

  • Tillägget !deadlock används tillsammans med drivrutinsverifieraren för att identifiera inkonsekvent användning av lås i koden som kan orsaka dödlägen.

När ett dödläge inträffar i kernelläge använder du !kdexts.locks-tillägget för att lista alla lås som för närvarande innehas av trådar.

Du kan vanligtvis hitta dödläget genom att hitta en tråd som inte körs och som innehåller ett exklusivt lås på en resurs som krävs av en körande tråd. De flesta låsen delas.