Bagikan melalui


Mendiagnosis Kebuntuan

Ketika utas memerlukan akses eksklusif ke kode atau beberapa sumber daya lainnya, ia meminta kunci. Jika bisa, Windows merespons dengan memberikan kunci ini ke utas. Pada titik ini, tidak ada lagi dalam sistem yang dapat mengakses kode terkunci. Ini terjadi sepanjang waktu dan merupakan bagian normal dari aplikasi multithreaded yang ditulis dengan baik. Meskipun segmen kode tertentu hanya dapat memiliki satu kunci pada satu waktu, beberapa segmen kode masing-masing dapat memiliki kunci mereka sendiri.

Kebuntuan muncul ketika dua utas atau lebih telah meminta kunci pada dua sumber daya atau lebih, dalam urutan yang tidak kompatibel. Misalnya, anggaplah Thread One telah memperoleh kunci pada Sumber Daya A lalu meminta akses ke Sumber Daya B. Sementara itu, Thread Two telah memperoleh kunci pada Resource B dan kemudian meminta akses ke Resource A. Tidak ada utas yang dapat dilanjutkan sampai kunci utas lain dilepaskan, dan, oleh karena itu, tidak ada utas yang dapat dilanjutkan.

Kebuntuan antar proses dalam mode pengguna terjadi ketika beberapa utas, biasanya dari satu aplikasi, saling memblokir akses ke sumber daya yang sama. Namun, beberapa utas beberapa aplikasi juga dapat memblokir akses satu sama lain ke sumber daya global/bersama, seperti peristiwa global, atau semaphore.

Kebuntuan kernel-mode muncul ketika beberapa utas (dari proses yang sama atau dari proses yang berbeda) telah memblokir akses satu sama lain ke sumber daya kernel yang sama.

Prosedur yang digunakan untuk men-debug kebuntuan tergantung pada apakah kebuntuan terjadi dalam mode pengguna atau dalam mode kernel.

Memperbaiki Deadlock User-Mode

Ketika kebuntuan terjadi dalam mode pengguna, gunakan prosedur berikut untuk men-debugnya:

  1. Terbitkan ekstensi !ntsdexts.locks . Dalam mode pengguna, Anda hanya dapat mengetik !locks di prompt debugger; awalan ntsdexts diasumsikan.

  2. Ekstensi ini menampilkan semua bagian kritis yang terkait dengan proses saat ini, bersama dengan ID utas pemilik dan jumlah kunci untuk setiap bagian kritis. Jika bagian penting memiliki jumlah kunci nol, bagian tersebut tidak dikunci. Gunakan perintah ~ (Status Utas) untuk melihat informasi tentang utas yang memiliki bagian kritis lainnya.

  3. Gunakan perintah kb (Display Stack Backtrace) untuk setiap utas ini untuk menentukan apakah mereka menunggu pada bagian penting lainnya.

  4. Dengan menggunakan output perintah kb ini, Anda dapat menemukan kebuntuan: dua utas yang masing-masing menunggu kunci yang dipegang oleh utas lainnya. Dalam kasus yang jarang terjadi, kebuntuan dapat disebabkan oleh lebih dari dua utas yang memegang kunci dalam pola melingkar, tetapi sebagian besar kebuntuan hanya melibatkan dua utas.

Berikut adalah ilustrasi prosedur ini. Anda mulai dengan ekstensi !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

Bagian penting pertama yang ditampilkan tidak memiliki kunci dan, oleh karena itu, dapat diabaikan.

Bagian kritis kedua yang ditampilkan memiliki jumlah kunci sebanyak 2, sehingga kemungkinan menjadi penyebab kebuntuan. Utas pemilik memiliki ID utas 0xA3.

Anda dapat menemukan utas ini dengan mencantumkan semua utas dengan perintah ~ (Status Utas), dan mencari utas dengan ID ini:

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

Dalam tampilan ini, item pertama adalah nomor utas internal debugger. Item kedua ( Id bidang) berisi dua angka heksadesimal yang dipisahkan oleh titik desimal. Angka sebelum titik desimal adalah ID proses; angka setelah titik desimal adalah ID utas. Dalam contoh ini, Anda melihat bahwa ID utas 0xA3 sesuai dengan utas nomor 4.

Anda kemudian menggunakan perintah kb (Display Stack Backtrace) untuk menampilkan tumpukan yang sesuai dengan nomor utas 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

Perhatikan bahwa utas ini memiliki panggilan ke fungsi WaitForCriticalSection , yang berarti bahwa tidak hanya memiliki kunci, ia menunggu kode yang dikunci oleh sesuatu yang lain. Kita dapat mengetahui bagian penting mana yang kita tunggu dengan melihat parameter pertama panggilan ke WaitForCriticalSection. Ini adalah alamat pertama di bawah Args to Child: "24e750". Jadi utas ini menunggu di bagian penting di alamat 0x24E750. Ini adalah bagian penting ketiga yang tercantum oleh ekstensi !locks yang Anda gunakan sebelumnya.

Dengan kata lain, utas 4, yang memiliki bagian penting kedua, menunggu di bagian penting ketiga. Sekarang ubah perhatian Anda ke bagian penting ketiga, yang juga dikunci. Utas pemilik memiliki ID utas 0xA9. Kembali ke output ~ perintah yang Anda lihat sebelumnya, perhatikan bahwa utas dengan ID ini adalah nomor utas 6. Tampilkan jejak balik tumpukan untuk utas ini:

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

Utas ini juga menunggu bagian penting untuk dibebaskan. Dalam hal ini, ia menunggu di bagian kritis di 0x68629100. Ini adalah bagian penting kedua dalam daftar yang dihasilkan sebelumnya oleh ekstensi !locks .

Ini kebuntuannya. Thread 4, yang memiliki bagian kritis kedua, menunggu pada bagian kritis ketiga. Thread 6, yang memiliki bagian penting ketiga, menunggu di bagian kritis kedua.

Setelah mengonfirmasi sifat kebuntuan ini, Anda dapat menggunakan teknik debugging biasa untuk menganalisis utas 4 dan 6.

Men-debug Kebuntuan Kernel-Mode

Ada beberapa ekstensi debugger yang berguna untuk men-debug kebuntuan dalam mode kernel:

  • Ekstensi !kdexts.locks menampilkan informasi tentang semua kunci yang disimpan pada sumber daya kernel dan utas yang memegang kunci ini. (Pada mode kernel, Anda dapat mengetik !locks pada perintah debugger; awalan kdexts diasumsikan.)

  • Ekstensi !qlocks menampilkan status semua kunci putar yang diantrekan.

  • Ekstensi !wdfkd.wdfspinlock menampilkan informasi tentang objek penguncian putar Kernel-Mode Driver Framework (KMDF).

  • Ekstensi !deadlock digunakan bersama dengan Driver Verifier untuk mendeteksi penggunaan kunci yang tidak konsisten dalam kode Anda yang berpotensi menyebabkan kebuntuan.

Ketika kebuntuan terjadi dalam mode kernel, gunakan ekstensi !kdexts.locks untuk mencantumkan semua kunci yang saat ini diperoleh oleh utas.

Anda biasanya dapat menentukan kebuntuan dengan menemukan satu utas yang tidak dijalankan yang memegang kunci eksklusif pada sumber daya yang diperlukan oleh utas yang dieksekusi. Sebagian besar kunci dibagikan.