Critical Section のデバッグ情報


本日は、Critical Section についてご紹介をさせていただきます。Critical Section を用いることにより、アプリケーションが使用する各種リソース(ヒープ メモリ、変数等)をスレッド間で排他制御を行うことができるようになります。以下の図では、黒い実線がそのスレッドでの動作が可能な状態を示し、赤実線が排他を行いたい処理になります。また、Thread 2 の赤点線は、排他待ちとなります。この図からもわかるとおり、Thread 1 の LeaveCriticalSection により、Thread 2 の処理が行うことが可能になります。

image

しかしながら、この便利な機能も、実装を誤るとデッドロックを起こしてしまうことがあります。その結果、アプリケーションがハング (停止) 状態となってしまう要因の一つとなります。そのため、Windows は、デバッグを行う際に有効となる情報を CRITICAL_SECTION 構造体に格納します。

具体的には、Windows SDK に同梱されている “WinNT.h” ファイル には、CRITICAL_SECTION 構造体が定義が行われており、DebugInfo メンバー変数に情報が格納されることとなります。

typedef struct _RTL_CRITICAL_SECTION {
    PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

    //
    //  The following three fields control entering and exiting the critical
    //  section for the resource
    //

    LONG LockCount;
    LONG RecursionCount;
    HANDLE OwningThread;        // from the thread’s ClientId->UniqueThread
    HANDLE LockSemaphore; .
    ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

Windbg には、解析を用意にするための !cs コマンドが用意されています。!cs コマンドです。
*詳細は以下の技術資料をご参照ください。

!cs
https://msdn.microsoft.com/en-us/library/windows/hardware/ff562294.aspx

MSDN からの引用となりますが、!cs コマンドを実行すると以下のような情報を確認することができます。

0:001> !cs
—————————————–
DebugInfo          = 0x6A261D60
Critical section   = 0x6A262820 (ntdll!RtlCriticalSectionLock+0x0)
LOCKED
LockCount          = 0x0
OwningThread       = 0x460
RecursionCount     = 0x1
LockSemaphore      = 0x0
SpinCount          = 0x0
—————————————–
DebugInfo          = 0x6A261D80
Critical section   = 0x6A262580 (ntdll!DeferedCriticalSection+0x0)
NOT LOCKED
LockSemaphore      = 0x7FC
SpinCount          = 0x0
—————————————–
DebugInfo          = 0x6A262600
Critical section   = 0x6A26074C (ntdll!LoaderLock+0x0)
NOT LOCKED
LockSemaphore      = 0x0
SpinCount          = 0x0
—————————————–
DebugInfo          = 0x77fbde20
Critical section   = 0x77c8ba60 (GDI32!semColorSpaceCache+0x0)
LOCKED
LockCount          = 0x0
OwningThread       = 0x00000dd8
RecursionCount     = 0x1
LockSemaphore      = 0x0
SpinCount          = 0x00000000
—————————————–

また、このデバッグ情報の詳細を表示するためには、!cs –d オプションを利用します。

0:001> !cs -d 0x6A262080
DebugInfo          = 0x6A262080
Critical section   = 0x7803B0F8 (MSVCRT!__app_type+0x4)
NOT LOCKED
LockSemaphore      = 0x0
SpinCount          = 0x0

“WinNT.h” ファイル内の CRITICAL_SECTION 構造体のコメントにある通り、Critical Section のリソースがある状態、つまり、排他制御中となる場合にデバッグ情報が生成されます。なお、アプリケーションのメモリ使用量を減らすため、本領域を生成させないようにすることも可能です。この場合には、Windows Vista 以降の OS で追加された、InitializeCriticalSectionEx API 関数にて、CRITICAL_SECTION_NO_DEBUG_INFO フラグを設定することで実現します。

InitializeCriticalSectionEx function
https://msdn.microsoft.com/library/windows/desktop/ms683477.aspx

このデバッグ情報は、DeleteCriticalSection API 関数を利用することにより、解放が行われます。このため、InitializeCriticalSection API 関数の対となるDeleteCriticalSection API 関数の呼び出しが行われるまでは、本デバッグ情報分のメモリが増加しますが、想定された動作となります。

今回は、Critical Section / デバッグ方法・情報についてご紹介をいたしました。排他制御は、マルチスレッドにて並列処理をさせる際にメモリ破壊を防ぐ重要な機能となります。アプリケーションがクラッシュした場合に、メモリ破壊となりそうな処理を見直す機会になりますと幸いです。