Поделиться через


Отладка взаимоблокировки

Если потоку требуется монопольный доступ к коду или другому ресурсу, он запрашивает блокировку. Если это возможно, Windows отвечает, предоставляя эту блокировку потоку. На этом этапе ни у кого другого в системе нет доступа к заблокированным кодам. Это происходит постоянно и является нормальной частью любого хорошо написанного многопоточного приложения. Хотя для определенного сегмента кода одновременно может быть только одна блокировка, каждый из нескольких сегментов кода может иметь собственную блокировку.

Взаимоблокировка возникает, когда два или более потоков запрашивают блокировки для двух или более ресурсов в несовместимой последовательности. Например, предположим, что поток 1 получил блокировку ресурса A, а затем запрашивает доступ к ресурсу B. Тем временем второй поток получил блокировку ресурса B, а затем запрашивает доступ к ресурсу A. Ни один поток не может продолжать работу до тех пор, пока не будет откачана блокировка другого потока, и, следовательно, ни один поток не может продолжить работу.

Взаимоблокировки в пользовательском режиме возникают, когда несколько потоков, обычно одного приложения, блокируют доступ друг друга к одному ресурсу. Однако несколько потоков нескольких приложений также могут блокировать доступ друг друга к глобальному или общему ресурсу, например к глобальному событию или семафору.

Взаимоблокировки в режиме ядра возникают, когда несколько потоков (из одного процесса или отдельных процессов) блокируют доступ друг друга к одному и тому же ресурсу ядра.

Процедура, используемая для отладки взаимоблокировки, зависит от того, происходит ли взаимоблокировка в пользовательском режиме или в режиме ядра.

Отладка взаимоблокировки User-Mode

Если взаимоблокировка возникает в пользовательском режиме, выполните следующую процедуру для ее отладки:

  1. Выпустите расширение !ntsdexts.locks . В пользовательском режиме можно просто ввести !locks в командной строке отладчика; Предполагается префикс ntsdexts .

  2. Это расширение отображает все критические разделы, связанные с текущим процессом, а также идентификатор для потока-пользователя и число блокировок для каждого критического раздела. Если критически важный раздел имеет нулевое число блокировок, он не блокируется. Используйте команду ~ (состояние потока), чтобы просмотреть сведения о потоках, которым принадлежат другие критические разделы.

  3. Используйте команду kb (Display Stack Backtrace) для каждого из этих потоков, чтобы определить, ожидают ли они другие критические разделы.

  4. Используя выходные данные этих команд базы знаний , можно найти взаимоблокировку: два потока, каждый из которых ожидает блокировки, удерживаемой другим потоком. В редких случаях взаимоблокировка может быть вызвана более чем двумя потоками, удерживающими блокировки в циклической схеме, но большинство взаимоблокировок включают только два потока.

Ниже приведена иллюстрация этой процедуры. Вы начинаете с расширения !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

Первый отображаемый критический раздел не имеет блокировок, поэтому его можно игнорировать.

Второй отображаемый критический раздел имеет число блокировок 2 и, следовательно, является возможной причиной взаимоблокировки. У владеющего потока есть идентификатор 0xA3.

Этот поток можно найти, перечислив все потоки с помощью команды ~ (состояние потока) и найдя поток с этим идентификатором:

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

На этом экране первым элементом является номер внутреннего потока отладчика. Второй элемент ( Id поле) содержит два шестнадцатеричных числа, разделенных десятичной запятой. Число перед десятичной запятой является идентификатором процесса; число после десятичной запятой — это идентификатор потока. В этом примере вы увидите, что идентификатор потока 0xA3 соответствует потоку no 4.

Затем используйте команду kb (Display Stack Backtrace), чтобы отобразить стек, соответствующий потоку номер 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

Обратите внимание, что этот поток вызывает функцию WaitForCriticalSection , что означает, что он не только имеет блокировку, но и ожидает код, заблокированный чем-то другим. Мы можем узнать, какой критический раздел мы ждем, просмотрев первый параметр вызова WaitForCriticalSection. Это первый адрес в разделе Args to Child: "24e750". Поэтому этот поток ожидает критического раздела по адресу 0x24E750. Это был третий критический раздел, указанный расширением !locks , которое вы использовали ранее.

Иными словами, поток 4, которому принадлежит второй критический раздел, ожидает третьего критического раздела. Теперь обратите внимание на третий критический раздел, который также заблокирован. У владеющего потока есть идентификатор 0xA9. Возвращаясь к выходным ~ данным команды, которые вы видели ранее, обратите внимание, что поток с этим идентификатором имеет номер 6. Отображение обратной трассировки стека для этого потока:

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

Этот поток также ожидает освобождения критического раздела. В этом случае он ожидает критического раздела в 0x68629100. Это был второй критический раздел в списке, созданном ранее расширением !locks .

Это взаимоблокировка. Поток 4, которому принадлежит второй критический раздел, ожидает третьего критического раздела. Поток 6, которому принадлежит третий критический раздел, ожидает второго критического раздела.

Убедившись в характере этой взаимоблокировки, можно использовать обычные методы отладки для анализа потоков 4 и 6.

Отладка взаимоблокировки Kernel-Mode

Существует несколько расширений отладчика, которые полезны для отладки взаимоблокировок в режиме ядра:

  • Расширение !kdexts.locks отображает сведения обо всех блокировках ресурсов ядра и потоках, содержащих эти блокировки. (В режиме ядра можно просто ввести !locks в командной строке отладчика; предполагается префикс kdexts .)

  • Расширение !qlocks отображает состояние всех блокировок спина в очереди.

  • Расширение !wdfkd.wdfspinlock отображает сведения об объекте spin-lock Kernel-Mode Driver Framework (KMDF).

  • Расширение !взаимоблокировки используется в сочетании с driver Verifier для обнаружения несогласованного использования блокировок в коде, которые могут привести к взаимоблокировкам.

При возникновении взаимоблокировки в режиме ядра используйте расширение !kdexts.locks , чтобы получить список всех блокировок, которые в настоящее время получили потоки.

Обычно взаимоблокировку можно определить, найдя один неисполняющийся поток, который удерживает монопольную блокировку ресурса, необходимого для выполняющегося потока. Большинство блокировок являются общими.