이 문서는 http://blogs.msdn.com/ntdebugging blog 의 번역이며 원래의 자료가 통보 없이 변경될 수 있습니다. 이 자료는 법률적 보증이 없으며 의견을 주시기 위해 원래의 blog 를 방문하실 수 있습니다. (http://blogs.msdn.com/ntdebugging/archive/2007/06/15/hung-window-no-source-no-problem-part-2.aspx)"

 

Hung Window?, No Source?, No problem!! Part 2
Jeff Dailey 작성


안녕하세요 저의 이름은 Jeff 입니다. 저는 Microsoft CPR (Critical Problem Resolution) platform team 의 Escalation Engineer 입니다. 이 블로그는 저의 이전 블로그의 2편 입니다. Hung Window?, No source?, No problem!! Part 1 blog.  이번 실습에서는 Multi thread application 과 동기화 객체 그리고 여러가지 문제점들 그리고 어떻게 분석하는지를 알려 드릴 것 입니다. 이 실습을 하기 위해 저의 이전 블로그에서 dumphugnwindow 와 badwindow.exe 를 다운 받고 Debugging tool 을 다운 받으시기 바랍니다.
Debugging tools:
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
이전 blog http://blogs.msdn.com/ntdebugging/archive/2007/05/29/detecting-and-automatically-dumping-hung-gui-based-windows-applications.aspx

 

위의 두가지를 설치해야 이후 내용을 시작할 수 있습니다. 앞으로 우리는 왜 Window 화면출력이 멈추었고 응답 없음 상태에 빠졌는지 Debug 해 보도록 하겠습니다.

1 단계 : Badwindow.exe 실행
2 단계 : Dumphungwindow.exe 실행
3 단계 : Badwindow.exe 메뉴에서 Hang – Hang type 2 선택
Window 가 메시지를 처리하지 못하고 응답 없음 상태에 빠지고 badwindow.exe 의 덤프가 생성되는 것을 확인해야 합니다.

 

************ OUTPUT *************
C:\source\dumphungwindow\release>dumphungwindow.exe
Dumps will be saved in C:\Users\jeffda\AppData\Local\Temp\
scanning for hung windows
**
Hung Window found dumping process (12912) badwindow.exe
Dumping unresponsive process
C:\Users\jeffda\AppData\Local\Temp\HWNDDump_Day6_14_2007_Time7_34_5_Pid12912_badwindow.exe.dmp
Dump complete

Hung Window found dumping process (12912) badwindow.exe
Dumping unresponsive process
C:\Users\jeffda\AppData\Local\Temp\HWNDDump_Day6_14_2007_Time7_34_24_Pid12912_badwindow.exe.dmp\jeffda\AppData\Local\Temp\HWNDDump_Day6_12_2007_Time9_53_56_Pid7924_badwindow.exe.dmp
Dump complete*
************ OUTPUT *************

 

4단계 : c:\websymbols 와 같은 로컬 심볼 폴더를 만듭니다.
5 단계 : WinDbg 에서 File – Symbols 에서 다음과 같이 Symbol 을 설정 합니다.
SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
http://www.microsoft.com/whdc/devtools/debugging/debugstart.mspx 에 보다 자세한 정보가 있습니다.
6 단계 : WinDbg 에서 File – Open crash dump 를 선택한 후 dump 파일을 선택 합니다. WinDbg 에서 보여지는 초기 화면은 다음과 같을 것 입니다.

 

Microsoft (R) Windows Debugger  Version 6.7.0001.0
Copyright (c) Microsoft Corporation. All rights reserved.

***** WARNING: Your debugger is probably out-of-date.
*****          Check http://dbg for updates.

Loading Dump File [C:\Users\jeffda\AppData\Local\Temp\HWNDDump_Day6_12_2007_Time9_53_34_Pid7924_badwindow.exe.dmp]
User Mini Dump File with Full Memory: Only application data is available

Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols;srv
Executable search path is:
Windows Vista Version 6000 MP (2 procs) Free x86 compatible
Product: WinNt, suite: SingleUserTS
Debug session time: Tue Jun 12 09:53:35.000 2007 (GMT-4)
System Uptime: 11 days 18:41:43.089
Process Uptime: 0 days 0:00:32.000
....................................
Loading unloaded module list
.
eax=00000000 ebx=00000002 ecx=00000000 edx=00000000 esi=00000000 edi=00000000
eip=777faec5 esp=0017faf4 ebp=0017fb8c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!ZwWaitForMultipleObjects+0x15:
777faec5 c21400          ret     14h
0:000> !reload


7단계 : 디버거 명령창에서 다음 명령을 입력합니다.
~*k

다음과 같은 출력을 볼 수 있을 것 입니다.

 

.  0  Id: 3270.2b10 Suspend: 0 Teb: 7efdd000 Unfrozen
ChildEBP RetAddr 
0017faf0 76e4edb5 ntdll!ZwWaitForMultipleObjects+0x15
0017fb8c 76e430c3 kernel32!WaitForMultipleObjectsEx+0x11d
0017fba8 00401502 kernel32!WaitForMultipleObjects+0x18
0017fbc8 0040139b badwindow!hangtype2+0x42 [c:\source\badwindow\badwindow\badwindow.cpp @ 340]
0017fc24 772a87af badwindow!WndProc+0x17b [c:\source\badwindow\badwindow\badwindow.cpp @ 274]
0017fc50 772a8936 user32!InternalCallWinProc+0x23
0017fcc8 772a8a7d user32!UserCallWinProcCheckWow+0x109
0017fd2c 772a8ad0 user32!DispatchMessageWorker+0x380
0017fd3c 004010fb user32!DispatchMessageW+0xf
0017ff0c 00401817 badwindow!wWinMain+0xfb [c:\source\badwindow\badwindow\badwindow.cpp @ 124]
0017ffa0 76eb19f1 badwindow!__tmainCRTStartup+0x150 [f:\sp\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 589]
0017ffac 7782d109 kernel32!BaseThreadInitThunk+0xe
0017ffec 00000000 ntdll!_RtlUserThreadStart+0x23

   1  Id: 3270.2cd0 Suspend: 0 Teb: 7efda000 Unfrozen
ChildEBP RetAddr 
026ffebc 777ecfad ntdll!ZwWaitForSingleObject+0x15
026fff20 777ecf78 ntdll!RtlpWaitOnCriticalSection+0x154
026fff48 0040153c ntdll!RtlEnterCriticalSection+0x152
026fff64 757c2848 badwindow!hangtype2threada+0x2c [c:\source\badwindow\badwindow\badwindow.cpp @ 358]
026fff9c 757c28c8 msvcr80!_endthread+0x4b
026fffa0 76eb19f1 msvcr80!_endthread+0xcb
026fffac 7782d109 kernel32!BaseThreadInitThunk+0xe
026fffec 00000000 ntdll!_RtlUserThreadStart+0x23


ID 의 오른쪽에 있는 숫자들은 Process id 와 Thread id 를 나타냅니다. 3270.2b10 은 3270이 process id 이고 2b10 은 thread id 입니다. 그리고 Suspend: 0 Teb: 7efdd000 Unfrozen 은 thread 상태 정보 입니다.

각각의 항목은 thread 의 콜 스택 입니다. 가장 최근에 호출된 함수가 스택의 가장 위에 위치 합니다.각각의 호출은 스택을 증가 시킵니다. Thread 0 번을 확인해 보면 winproc 함수가 hangtype2 를 호출하고 멈추어 있는 것을 확인할 수 있을 것 입니다. Hangtype 2 는 WaitForMultipleObjects 를 호출하였고 WaitForMuiltipleObjects 를 좀더 살펴 보겠습니다.

WaitForMultipleObjects 의 문서는 아래에 있습니다.
http://msdn2.microsoft.com/en-us/library/ms687025.aspx

DWORD WINAPI WaitForMultipleObjects( DWORD nCount, const HANDLE* lpHandles, BOOL bWaitAll, DWORD dwMilliseconds
함수의 형태는 위와 같고 전달된 파라미터를 살펴보도록 하겠습니다.

 

0:000> kv
ChildEBP RetAddr  Args to Child             
0017faf0 76e4edb5 00000002 0017fb40 00000000 ntdll!ZwWaitForMultipleObjects+0x15 (FPO: [5,0,0])
0017fb8c 76e430c3 0017fb40 0017fbc4 00000001 kernel32!WaitForMultipleObjectsEx+0x11d (FPO: [Non-Fpo])
0017fba8 00401502 00000002 0017fbc4 00000001 kernel32!WaitForMultipleObjects+0x18 (FPO: [Non-Fpo])
0017fbc8 0040139b 00401220 0017fbfc 00401220 badwindow!hangtype2+0x42 (FPO: [0,2,0]) (CONV: cdecl) [c:\source\badwindow\badwindow\badwindow.cpp @ 340]
0017fc24 772a87af 00063d36 00000111 00008004 badwindow!WndProc+0x17b (CONV: stdcall) [c:\source\badwindow\badwindow\badwindow.cpp @ 274]
0017fc50 772a8936 00401220 00063d36 00000111 user32!InternalCallWinProc+0x23
0017fcc8 772a8a7d 00000000 00401220 00063d36 user32!UserCallWinProcCheckWow+0x109 (FPO: [Non-Fpo])
0017fd2c 772a8ad0 00401220 00000000 0017ff0c user32!DispatchMessageWorker+0x380 (FPO: [Non-Fpo])
0017fd3c 004010fb 0017fd54 00403938 00000001 user32!DispatchMessageW+0xf (FPO: [Non-Fpo])
0017ff0c 00401817 00400000 00000000 00280f8c badwindow!wWinMain+0xfb (CONV: stdcall) [c:\source\badwindow\badwindow\badwindow.cpp @ 124]
0017ffa0 76eb19f1 7efde000 0017ffec 7782d109 badwindow!__tmainCRTStartup+0x150 (FPO: [Non-Fpo]) (CONV: cdecl) [f:\sp\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 589]
0017ffac 7782d109 7efde000 0017fb9e 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
0017ffec 00000000 00401987 7efde000 00000000 ntdll!_RtlUserThreadStart+0x23 (FPO: [Non-Fpo])

 

첫 번째 파라미터는 00000002 이고 기다릴 Object 의 개수 입니다. 두 번째 파라미터는 Object 배열의 주소 이고 Object 들을 보기 위해 배열을 나열해 보도록 하겠습니다.

 

0:000> dd 0017fbc4
0017fbc4  000000c4 000000c8 0040139b 00401220
0017fbd4  0017fbfc 00401220 00063d36 0017fc48
0017fbe4  772a8989 772a894d 53ca28e7 00000000
0017fbf4  00063d36 00401220 00000000 00000000
0017fc04  00000000 0017fca0 00000001 00000000
0017fc14  ffffffff 772a88e5 53c4f4b4 75c12459
0017fc24  0017fc50 772a87af 00063d36 00000111
0017fc34  00008004 00000000 00401220 dcbaabcd

0:000> !handle 000000c4
Handle 000000c4
  Type            Thread
0:000> !handle 000000c8
Handle 000000c8
  Type            <Error retrieving type>


두 번째 값을 보면 어떤 이유로 인해 handle 타입을 나타내기에 충분한 정보가 dump 되지 않았다는 것을 볼 수 있습니다. Handle 은 kernel 에 있는 handle table 의 인덱스 값입니다. Dump 가 생성될 때 Handle 정보가 모두 포함되지 않았을 가능성이 있습니다. 이것은 큰 문제가 되지 않으며 간단하게 어떤 일이 있었는지 확인해 볼 것 입니다.

이전 블로그에서 UF 명령의 사용법을 설명해 드렸습니다.이제 어셈블리 코드를 보면서 확인해 나가도록 하겠습니다.

0:000> uf 00401502
badwindow!hangtype2 [c:\source\badwindow\badwindow\badwindow.cpp @ 334]:

ESP(스택 포인터, 메모리에서 스택이 커진다는 것을 기억할 것) 를 감소시켜 공간확보
  334 004014c0 83ec08          sub     esp,8

ESP 값을 나중에 사용할 수 있게 저장
  334 004014c3 56              push    esi


_beginthread 의 함수 포인터를 구해 ESI 에 저장. http://msdn2.microsoft.com/en-us/library/kdzttdcb(VS.80).aspx 문서에 따르면 첫번째 파라미터는 start address, 두번째 파라미터는 Stack size, 세 번째 파라미터는 arglist 입니다.
  337 004014c4 8b3580204000    mov     esi,dword ptr [badwindow!_imp___beginthread (00402080)]

_beginthread 의 마지막 파라미터를  스택에 넣습니다. 0 이 사용된 것은 arglist 가 없다는 것 입니다.
  337 004014ca 6a00            push    0

이 공간이 stack spce 입니다. Debugger 에서 ? 2EE0h 명령을 사용해서 16진수를 10진수로 변환해 보면 12000 값을 확인할 수 있습니다.
  337 004014cc 68e02e0000      push    2EE0h

우리가 사용할 Thread 함수의 포인터를 저장 합니다. 여기서는 hangtype2threada 입니다.
  337 004014d1 6810154000      push    offset badwindow!hangtype2threada (00401510)

_beginthread 를 호출하여 thread 를 시작 합니다. Return 값은 thread handle 입니다.
  337 004014d6 ffd6            call    esi

_beginthread 의 마지막 파라미터를  스택에 넣습니다. 0 이 사용된 것은 arglist 가 없다는 것 입니다.
  338 004014d8 6a00            push    0

_beginthread 에서 사용할 Stack space 를 전달 합니다.
  338 004014da 68e02e0000      push    2EE0h

Thread 함수를 위한 함수 포인터를 전달 합니다. 여기서는 hangtype2threadb 입니다.
  338 004014df 6870154000      push    offset badwindow!hangtype2threadb (00401570)

ESP+1 (stack)에 EAX(_beginthrea 호출 결과)를 저장 합니다.
  338 004014e4 8944241c        mov     dword ptr [esp+1Ch],eax

_beginthread 를 호출 하여 thread 를 시작 합니다. Return 값은 thread handle 입니다.
  338 004014e8 ffd6            call    esi

ESP 에 값을 더해 stack 을 정리 합니다.
  338 004014ea 83c418          add     esp,18h

WaitForMultipleObjects 에서 사용할 Wait 시간을 저장 합니다. 0xFFFFFFFF(-1) 로 영원히 대기 합니다.
  340 004014ed 6aff            push    0FFFFFFFFh

마지막 호출한 _beginthread 의 return 값인 thread handle 값을 스택에 저장 합니다.
  340 004014ef 8944240c        mov     dword ptr [esp+0Ch],eax


아래는 우리의 Wait 방식 입니다. 이번 경우에 WaitAll 로 모든 handle 이 signal 될 때 까지 대기 합니다.
  340 004014f3 6a01            push    1

EAX 에 Wait 하게될 handle 정보를 저장 합니다.
  340 004014f5 8d44240c        lea     eax,[esp+0Ch]

Stack 에 handle 포인터 배열 값을 저장 합니다.
  340 004014f9 50              push    eax

Object 의 개수를 저장 합니다. 여기서는 2 입니다.
  340 004014fa 6a02            push    2

Hangtype2threada, hangtype2threadb 가 종료되기를 WaitForMultipleObjects 함수를 호출하여 대기 합니다.
  340 004014fc ff1510204000    call    dword ptr [badwindow!_imp__WaitForMultipleObjects (00402010)]

ESI register 값을 stack 에서 가져 옵니다. 아래 코드들은 WaitForMultipleObjects 에서 return 된 후 호출 됩니다.
  340 00401502 5e              pop     esi 

Stack 포인터 값을 감소 시킵니다.
  342 00401503 83c408          add     esp,8

Return 합니다.
  342 00401506 c3              ret

 

소스 코드는 아래와 같습니다.

void hangtype2(void)
{
      HANDLE handles[2];

      handles[0] = (HANDLE)_beginthread(hangtype2threada, 12000, NULL);
      handles[1] = (HANDLE)_beginthread(hangtype2threadb, 12000, NULL);
      WaitForMultipleObjects(2,handles,1,INFINITE);

}

무엇이 잘못 되었을까요? 다시 thread 를 보도록 하겠습니다.

Message 를 처리하는 Main thread 가 다른 두 개의 thread 를 기다리고 있다는 것을 확인할 수 있습니다. Badwindow!hangtype2thread 는 아직 실행중이고 다른 하나인 hangtype2threadb 는 종료 되었음을 확인할 수 있습니다.

0  Id: 3270.2b10 Suspend: 0 Teb: 7efdd000 Unfrozen
ChildEBP RetAddr 
0017faf0 76e4edb5 ntdll!ZwWaitForMultipleObjects+0x15
0017fb8c 76e430c3 kernel32!WaitForMultipleObjectsEx+0x11d
0017fba8 00401502 kernel32!WaitForMultipleObjects+0x18
0017fbc8 0040139b badwindow!hangtype2+0x42 [c:\source\badwindow\badwindow\badwindow.cpp @ 340]
0017fc24 772a87af badwindow!WndProc+0x17b [c:\source\badwindow\badwindow\badwindow.cpp @ 274]
0017fc50 772a8936 user32!InternalCallWinProc+0x23
0017fcc8 772a8a7d user32!UserCallWinProcCheckWow+0x109
0017fd2c 772a8ad0 user32!DispatchMessageWorker+0x380
0017fd3c 004010fb user32!DispatchMessageW+0xf
0017ff0c 00401817 badwindow!wWinMain+0xfb [c:\source\badwindow\badwindow\badwindow.cpp @ 124]
0017ffa0 76eb19f1 badwindow!__tmainCRTStartup+0x150 [f:\sp\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 589]
0017ffac 7782d109 kernel32!BaseThreadInitThunk+0xe
0017ffec 00000000 ntdll!_RtlUserThreadStart+0x23

Hangtype2threada 를 확인해 보면 RtlEnterCriticalSection 에서 block 되었음을 볼 수 있습니다.

   1  Id: 3270.2cd0 Suspend: 0 Teb: 7efda000 Unfrozen
ChildEBP RetAddr 
026ffebc 777ecfad ntdll!ZwWaitForSingleObject+0x15
026fff20 777ecf78 ntdll!RtlpWaitOnCriticalSection+0x154
026fff48 0040153c ntdll!RtlEnterCriticalSection+0x152
026fff64 757c2848 badwindow!hangtype2threada+0x2c [c:\source\badwindow\badwindow\badwindow.cpp @ 358]
026fff9c 757c28c8 msvcr80!_endthread+0x4b
026fffa0 76eb19f1 msvcr80!_endthread+0xcb
026fffac 7782d109 kernel32!BaseThreadInitThunk+0xe
026fffec 00000000 ntdll!_RtlUserThreadStart+0x23

Critical section 호출에 어떠한 문제가 있는지 확인해 보겠습니다.

Thread 1 번으로 context 를 변경 합니다.

0:000> ~1s
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=00403780 edi=00000000
eip=777fa69d esp=026ffec0 ebp=026fff20 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!ZwWaitForSingleObject+0x15:
777fa69d c20c00          ret     0Ch

Call stack 을 확인한 후 entercriticalsection 의 첫 번째 파라미터를 확인 합니다.

0:001> kv
ChildEBP RetAddr  Args to Child             
026ffebc 777ecfad 000000cc 00000000 00000000 ntdll!ZwWaitForSingleObject+0x15 (FPO: [3,0,0])
026fff20 777ecf78 00000000 00000000 76e61d5a ntdll!RtlpWaitOnCriticalSection+0x154 (FPO: [Non-Fpo])
026fff48 0040153c 00403780 00000000 00000000 ntdll!RtlEnterCriticalSection+0x152 (FPO: [Non-Fpo])
026fff64 757c2848 00000000 51b22bb2 00000000 badwindow!hangtype2threada+0x2c (FPO: [Uses EBP] [1,1,0]) (CONV: cdecl) [c:\source\badwindow\badwindow\badwindow.cpp @ 358]
026fff9c 757c28c8 76eb19f1 02274358 026fffec msvcr80!_endthread+0x4b (FPO: [Non-Fpo])
026fffa0 76eb19f1 02274358 026fffec 7782d109 msvcr80!_endthread+0xcb (FPO: [Non-Fpo])
026fffac 7782d109 02274358 026ffb9e 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
026fffec 00000000 757c286e 02274358 00000000 ntdll!_RtlUserThreadStart+0x23 (FPO: [Non-Fpo])

Cs 명령을 통해서 많은 정보를 확인할 수 있습니다.

0:001> !cs 00403780
-----------------------------------------
Critical section   = 0x00403780 (badwindow!csCritSec1+0x0)
DebugInfo          = 0x0029bd40
LOCKED             < It’s LOCKED.
LockCount          = 0x1
WaiterWoken        = No
OwningThread       = 0x00002048  < This is the owning thread.
RecursionCount     = 0x14
LockSemaphore      = 0xCC
SpinCount          = 0x00000000

!locks 명령을 사용해서 lock 된 다른 critical section 을 확인할 수 있습니다.
0:001> !locks

CritSec badwindow!csCritSec1+0 at 00403780
WaiterWoken        No
LockCount          1
RecursionCount     20
OwningThread       2048
EntryCount         0
ContentionCount    1
*** Locked

Scanned 156 critical sections

어떤 thread 가 동작중이며 2048 은 어떤 thread 일까요?

0:001> ~
#  0  Id: 3270.2b10 Suspend: 0 Teb: 7efdd000 Unfrozen
.  1  Id: 3270.2cd0 Suspend: 0 Teb: 7efda000 Unfrozen

이 부분에서 문제점이 있습니다. Hangtype2threada 와 hangtype2threadb 모두 같은 critical section 을 사용합니다. 그러나 hagntype2threadb 에 문제점이 있습니다. 함수 내부를 살펴보며 어떤 문제점이 있는지 확인해 보도록 하겠습니다.

unassembled 한 부분에서 Badwindow!hangtype2 의 address 를 확인 한 후 symbol 이 있다면 ln 명령을 사용하여 어떤 함수인지 확인할 수 있습니다.

0:001> ln 00401570
c:\source\badwindow\badwindow\badwindow.cpp(370)
(00401570)   badwindow!hangtype2threadb   |  (004015e0)   badwindow!hangtype3thread
Exact matches:
    badwindow!hangtype2threadb (void *)

정확히 함수를 찾은 것을 볼 수 있습니다. 이 함수를 unassembled 해서 어떤 문제점이 있는지 확인해 보도록 하겠습니다.

0:001> uf 00401570
badwindow!hangtype2threadb [c:\source\badwindow\badwindow\badwindow.cpp @ 370]:

ECX 저장
  370 00401570 51              push    ecx

EBX 저장
  370 00401571 53              push    ebx

Sprint 함수의 주소를 EBX 에 전달
  371 00401572 8b1d7c204000    mov     ebx,dword ptr [badwindow!_imp__sprintf (0040207c)]

EBP 저장
  371 00401578 55              push    ebp

Outputdebugstring 의 주소를 ebp 에 전달
  371 00401579 8b2d14204000    mov     ebp,dword ptr [badwindow!_imp__OutputDebugStringA (00402014)]

ESI 저장
  371 0040157f 56              push    esi

EnterCriticalSection 의 주소를 ESI 에 전달
  371 00401580 8b351c204000    mov     esi,dword ptr [badwindow!_imp__EnterCriticalSection (0040201c)]

EDI 저장
  371 00401586 57              push    edi

Sleep 의 주소를 EDI 에 전달
  371 00401587 8b3d0c204000    mov     edi,dword ptr [badwindow!_imp__Sleep (0040200c)]

16진수로 14h 를 ESP+10h (local stack) 에 저장 아마도 카운터 일 것 같습니다.
  371 0040158d c744241014000000 mov     dword ptr [esp+10h],14h

Critical section 의 주소인 csCritSec1 00403780 을 stack 에 저장 합니다.
  374 00401595 6880374000      push    offset badwindow!csCritSec1 (00403780)

Entercriticalsction 을 호출 합니다.
  374 0040159a ffd6            call    esi

250 를 스택에 저장 합니다.
  376 0040159c 68fa000000      push    0FAh
Sleep 을 호출 합니다. (250ms 동안 대기 합니다.)
  376 004015a1 ffd7            call    edi
포인터 값을 스택에 저장 합니다. 어떤 내용인지 확인해 보면 다음과 같습니다.

0:001> db 004021e4
004021e4  57 65 20 61 72 65 20 69-6e 20 68 61 6e 67 74 79  We are in hangty
004021f4  70 65 32 74 68 72 65 61-64 62 00 00 48 00 00 00  pe2threadb..H...
00402204  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
00402214  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  377 004015a3 68e4214000      push    offset badwindow!`string' (004021e4)

 

포인터 값을 스택에 저장 합니다. 어떤 내용일까요?
0:001> db 00403380
00403380  57 65 20 61 72 65 20 69-6e 20 68 61 6e 67 74 79  We are in hangty
00403390  70 65 32 74 68 72 65 61-64 62 00 00 00 00 00 00  pe2threadb......
004033a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
004033b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  377 004015a8 6880334000      push    offset badwindow!szTrace (00403380)

Sprint 를 호출
  377 004015ad ffd3            call    ebx

Stack 정리
  377 004015af 83c408          add     esp,8

Sprint 에 전달하였던 output buffer 를 스택에 저장
  378 004015b2 6880334000      push    offset badwindow!szTrace (00403380)

Outputdebugstring 호출
  378 004015b7 ffd5            call    ebp

csCritSec2 의 주소를 스택에 저장
  380 004015b9 6868374000      push    offset badwindow!csCritSec2 (00403768)

csCritSec2 를 사용해서 LeaveCriticalSection 호출
  380 004015be ff1524204000    call    dword ptr [badwindow!_imp__LeaveCriticalSection (00402024)]

Local 변수인 count fmf 감소 시킵니다.
  382 004015c4 836c241001      sub     dword ptr [esp+10h],1

Count 값이 0 인지 확인 0 이 아니라면 loop 의 가장 위로 다시 jump
  382 004015c9 75ca            jne     badwindow!hangtype2threadb+0x25 (00401595)

저장 하였던 register 를 다시 복원하고  return
  382 004015cb 5f              pop     edi
  382 004015cc 5e              pop     esi
  382 004015cd 5d              pop     ebp
  382 004015ce 5b              pop     ebx
  387 004015cf 59              pop     ecx
  387 004015d0 c3              ret

Bug 를 확인 하셨나요? 자세히 살펴 보시고 소스가 필요 하시면 아래를 참조 하십시오.
void __cdecl hangtype2threadb(void *)
{
      int i=0;
      while(1)
      {
            EnterCriticalSection(&csCritSec1);
            Sleep(250);
            sprintf(szTrace, "We are in hangtype2threadb");
            OutputDebugStringA(szTrace);
            LeaveCriticalSection(&csCritSec2);
            i++;
            if(i==20)
            {
                  break;
            }
      }
}

Critical section 하나를 요청한 후 다른 Critical section 을 해제하고 있습니다. 다음 함수를 보면 count 관련 작업을 진행한 후 csCritSec1 을 소유한 상태로 thread 가 종료 됩니다. 이 문제를 해결하는 방법은 아주 간단하여 csCritSec2 를 해제 하지 않고 CsCritSec1 을 해제 하면 됩니다. 그러나 소스 코드가 없었다면 어떻게 될까요? 역시 간단 합니다. Debugger 를 사용해서 기계어를 어셈블리어로 분석하면 됩니다. 종종 code 를 수정해야 할 때 메모리에 있는 기계어를 수정하기도 합니다.

변경 작업을 하기 위해 command line 에서 windbg.exe C:\source\badwindow\release\badwindow.exe 를 사용하여 실행 해야 합니다. (badwindow 샘플과 동일한 폴더를 사용하는 것으로 가정 하겠습니다.) WinDbg 가 실행되면 symbol path 를 다음 명령을 사용해서 설정해 주십시오. .sympath SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols

우리의 잘못된 함수입니다.
csCritSec2 의 주소를 스택에 넣습니다. << 잘못된 Critical Section
  380 004015b9 6868374000      push    offset badwindow!csCritSec2 (00403768)

LeaveCriticalSection 을 호출하여 csCritSec2 를 해제 합니다.
  380 004015be ff1524204000    call    dword ptr [badwindow!_imp__LeaveCriticalSection (00402024)]

csCritSec1 의 주소인 00403780 을 스택에 저장 합니다. << 올바른 Critical Section
  374 00401595 6880374000      push    offset badwindow!csCritSec1 (00403780)

Entercriticalsection 을 호출 합니다.
  374 0040159a ffd6            call    esi

어떤 Critical Section 을 leavecriticalsection에 사용하도록 변경해야 하는지 기억해 보십시오.
004015b9 6868374000  (BAD)   
00401595 6880374000  (GOOD)
메모리를 수정하여 잘못된 명령을 수정하도록 하겠습니다.
0:001> eb 004015b9
004015b9 68 68  << 입력 68
68
004015ba 68 80  << 68을 원하지 않습니다. 80을 입력 합니다.
80
004015bb 37  << Memory 수정을 종료 합니다.

수정된 코드 입니다.

0:001> uf 00401570
badwindow!hangtype2threadb [c:\source\badwindow\badwindow\badwindow.cpp @ 370]:
  370 00401570 51              push    ecx
  370 00401571 53              push    ebx
  371 00401572 8b1d7c204000    mov     ebx,dword ptr [badwindow!_imp__sprintf (0040207c)]
  371 00401578 55              push    ebp
  371 00401579 8b2d14204000    mov     ebp,dword ptr [badwindow!_imp__OutputDebugStringA (00402014)]
  371 0040157f 56              push    esi
  371 00401580 8b351c204000    mov     esi,dword ptr [badwindow!_imp__EnterCriticalSection (0040201c)]
  371 00401586 57              push    edi
  371 00401587 8b3d0c204000    mov     edi,dword ptr [badwindow!_imp__Sleep (0040200c)]
  371 0040158d c744241014000000 mov     dword ptr [esp+10h],14h
ENTERING CORRET CRITICAL SECTION csCritSec1
  374 00401595 6880374000      push    offset badwindow!csCritSec1 (00403780)
  374 0040159a ffd6            call    esi
  376 0040159c 68fa000000      push    0FAh
  376 004015a1 ffd7            call    edi
  377 004015a3 68e4214000      push    offset badwindow!`string' (004021e4)
  377 004015a8 6880334000      push    offset badwindow!szTrace (00403380)
  377 004015ad ffd3            call    ebx
  377 004015af 83c408          add     esp,8
  378 004015b2 6880334000      push    offset badwindow!szTrace (00403380)
  378 004015b7 ffd5            call    ebp
LEAVING CORRECT CRITICAL SECTION csCritSec1
  380 004015b9 6880374000      push    offset badwindow!csCritSec1 (00403780) 
  380 004015be ff1524204000    call    dword ptr [badwindow!_imp__LeaveCriticalSection
(00402024)]
  382 004015c4 836c241001      sub     dword ptr [esp+10h],1
  382 004015c9 75ca            jne     badwindow!hangtype2threadb+0x25 (00401595)
  382 004015cb 5f              pop     edi
  382 004015cc 5e              pop     esi
  382 004015cd 5d              pop     ebp
  382 004015ce 5b              pop     ebx
  387 004015cf 59              pop     ecx
  387 004015d0 c3              ret

다시 프로그램을 실행(명령창에서 G 명령 입력) 시키면 정상 동작 하는 것을 확인할 수 있습니다.
정상 동작하는 것을 확인한 후 이 프로그램을 만든 개발자에게 코드 수정을 요청할 수 있습니다.

많은 도움이 되셨기를 빕니다.

감사합니다. Jeff