Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

windbg 분석 사례 - WPF 응용 프로그램의 UI가 반응하지 않는 문제

예전에 닷넷 엑스퍼트 시절에 ^^ 끝마쳤던 프로젝트였는데... 거의 5년 정도가 지나서 실제 구축된 이후 발생하는 문제에 대해서 문의가 왔습니다. 대략 문제는 이랬습니다. 응용 프로그램의 특성상 한번 부팅되면 1년이고 2년이고 계속 쓰는 시스템이었는데 30일 정도만 지나면 시스템에 마비가 온다는 것이었습니다. 고객사 개발팀은 나름 로그도 남겨보고 했지만 원인을 알 수 없어 고심을 하다가 결국 제게 도움을 요청한 것입니다. .NET WPF로 구축된 사례였는데 그당시 제가 개발 팀장을 맡고 있었기에 사실 저도 이 문제가 그리 좋은 소식은 아니었습니다. 자신이 기여한 프로젝트가 문제를 겪고 있다는 사실을 반길 개발자는 없겠지요. ^^

그렇다고 해서 제가 그 정도의 증상만 듣고 해결할 수는 없기 때문에, 문제가 발생한 시점의 덤프를 남겨서 보내달라고 했습니다. (다행히, 시스템 마비로 보이지만 프로그램은 돌고 있어서 덤프를 생성할 수 있었습니다.)

자, 그럼 기지개 한번 쭉 켜고 덤프 분석을 시작해 볼까요? ^^ 우선 SOS를 올리고,

0:000> .load "C:\\Windows\\Microsoft.NET\\Framework\\v2.0.50727\\SOS.dll"

아울러 sosex도 함께 올리는 것이 좋습니다.

SOSEX v4.0 Now Available 
; http://www.stevestechspot.com/SOSEXV40NowAvailable.aspx

0:000> .load "C:\\temp\\sosex.dll"

그런데, 첫 번째 확장 명령어부터 실행이 안되는군요. ^^

0:000> !clrstack
Failed to load data access DLL, 0x80004005
Verify that 1) you have a recent build of the debugger (6.2.14 or newer)
            2) the file mscordacwks.dll that matches your version of mscorwks.dll is 
                in the version directory
            3) or, if you are debugging a dump file, verify that the file 
                mscordacwks_<arch>_<arch>_<version>.dll is on your symbol path.
            4) you are debugging on the same architecture as the dump file.
                For example, an IA64 dump file must be debugged on an IA64
                machine.

You can also run the debugger command .cordll to control the debugger's
load of mscordacwks.dll.  .cordll -ve -u -l will do a verbose reload.
If that succeeds, the SOS command should work on retry.

If you are debugging a minidump, you need to make sure that your executable
path is pointing to mscorwks.dll as well.

예의 그 오류인데,

windbg의 mscordacwks DLL 로드 문제 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/1375

windbg의 mscordacwks DLL 로드 문제
; https://www.sysnet.pe.kr/2/0/1181

Windbg - ERROR: Unable to load DLL mscordacwks_x86_x86_2.0.50727.4200.dll, Win32 error 0n2
; https://www.sysnet.pe.kr/2/0/994

고객사에 다시 요청해서 문제가 발생한 PC에 있는 "c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll" 파일을 보내달라고 했습니다. 그런 다음 .cordll 명령어로 파일명을 얻어내고,

0:000> .cordll -ve -u -l
CLRDLL: c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscordacwks.dll:2.0.50727.7905 f:0
doesn't match desired version 2.0.50727.3625 f:0
CLRDLL: Unable to find mscordacwks_x86_x86_2.0.50727.3625.dll by mscorwks search
CLRDLL: Unable to find 'mscordacwks_x86_x86_2.0.50727.3625.dll' on the path
CLRDLL: Unable to get version info for 'e:\symbols\mscorwks.dll\4E154C985a9000\mscordacwks_x86_x86_2.0.50727.3625.dll', Win32 error 0n87
CLRDLL: ERROR: Unable to load DLL mscordacwks_x86_x86_2.0.50727.3625.dll, Win32 error 0n87
CLR DLL status: ERROR: Unable to load DLL mscordacwks_x86_x86_2.0.50727.3625.dll, Win32 error 0n87

고객사로부터 전달받은 mscordacwks.dll 파일을 mscordacwks_x86_x86_2.0.50727.3625.dll 파일로 이름을 바꾸고 심벌 폴더에 저장해 두었습니다.

처음 덤프를 받았을 때는 해당 덤프가 crash 덤프인 줄 알았습니다. 그래서 일단 "!analyze -v"를 실행해 보았는데요.

0:000> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************

*** WARNING: Unable to verify checksum for WindowsBase.ni.dll
...[생략]...
*** WARNING: Unable to verify checksum for System.Data.ni.dll

FAULTING_IP: 
+0
00000000 ??              ???

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 00000000
   ExceptionCode: 80000003 (Break instruction exception)
  ExceptionFlags: 00000000
NumberParameters: 0

FAULTING_THREAD:  0000052c

DEFAULT_BUCKET_ID:  WRONG_SYMBOLS

PROCESS_NAME:  TestShell.exe

ERROR_CODE: (NTSTATUS) 0x80000003 - {EXCEPTION}  Breakpoint  A breakpoint has been reached.

EXCEPTION_CODE: (HRESULT) 0x80000003 (2147483651) - One or more arguments are invalid

NTGLOBALFLAG:  0

APPLICATION_VERIFIER_FLAGS:  0

APP:  TestShell.exe

MANAGED_STACK: 
(TransitionMU)
0012CA70 1F68A89F Test_Support!Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine)+0x2067
0012D6A0 1F680A3A Test_Support!Test.Support.CPosition.ModifyPos(Double, Double)+0x6aa
0012D8D8 1F68034F Test_Support!Test.Support.CDailyMapper.ModifyPos(Double, Double)+0x2df
...[생략]...
0012F400 578440CC WindowsBase_ni!System.Windows.Threading.Dispatcher.Run()+0x4c
...[생략]...0012F458 03B600BC TestShell!Test.Shell.App.Main()+0x4c
(TransitionUM)

MANAGED_STACK_COMMAND:  _EFN_StackTrace

PRIMARY_PROBLEM_CLASS:  WRONG_SYMBOLS

BUGCHECK_STR:  APPLICATION_FAULT_WRONG_SYMBOLS

LAST_CONTROL_TRANSFER:  from 16ea93b4 to 16ea399d

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0012b328 16ea93b4 00000001 fffffde7 0012bba4 AxObj+0x399d
0012bbf8 16ea9e3e 0012be38 0012bdc4 16e93d5c AxObj+0x93b4
0012bc1c 16ea9d30 16e93d5c 0012bc4c 0012be10 AxObj+0x9e3e
0012bc98 16ea9f07 16e93d5c 0012be38 0012be10 AxObj+0x9d30
...[생략]...
0012ffb8 79004de3 00360034 7c7e7077 00370035 mscoree!ShellShim__CorExeMain+0x29
0012ffc0 7c7e7077 00370035 00360034 7ffd3000 mscoree!_CorExeMain_Exported+0x8
0012fff0 00000000 79004ddb 00000000 78746341 kernel32!BaseProcessStart+0x23


STACK_COMMAND:  ~0s; .ecxr ; kb

FOLLOWUP_IP: 
AxObj+399d
16ea399d 8955fc          mov     dword ptr [ebp-4],edx

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  axobj+399d

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: AxObj

IMAGE_NAME:  AxObj.dll

DEBUG_FLR_IMAGE_TIMESTAMP:  521259cc

FAILURE_BUCKET_ID:  WRONG_SYMBOLS_80000003_AxObj.dll!Unknown

BUCKET_ID:  APPLICATION_FAULT_WRONG_SYMBOLS_axobj+399d

WATSON_STAGEONE_URL:  ...

Followup: MachineOwner
---------

보시는 바와 같이 딱히 ^^; 문제가 없습니다. ERROR_CODE가 0x80000003은 도대체 뭘까요? 이에 대해서는 다음의 글에 나와 있습니다.

Dump Analysis: Debugging a Multi-Process Hang
; http://www.codemachine.com/article_rpcchain.html

If the exception code is either 0x80000007 or 0x80000003, it is an indication of a manually generated application dump as opposed to an application crash dump.


즉, 시스템에 의해 남겨진 crash 덤프는 아니고 사용자가 일부러 남긴 덤프였던 것입니다. (나중에 고객사에 알아봤는데, 문제가 발생한 시점에 외부에서 해당 프로세스의 덤프를 남긴 것이라고 합니다.)

또한, !pe를 실행해 봐도 역시 아무런 예외 기록이 없습니다.

0:000> !printexception
There is no current managed exception on this thread

순간 땀이 나기 시작합니다. ^^ 예외도 없이 시스템이 얼어버리다니... 왠지 내 실력으로 분석할만한 수준을 넘어선 것 같았습니다. 혹시, 디바이스 간의 문제가 아닐까... 하는 상상까지 하게 되었는데요.

다행히 그다음 명령에서 다시 안정을 찾을 수 있었습니다. ^^

0:000> !threads
ThreadCount: 529
UnstartedThread: 0
BackgroundThread: 507
PendingThread: 0
DeadThread: 21
Hosted Runtime: no
                                      PreEmptive   GC Alloc           Lock
       ID OSID ThreadOBJ    State     GC       Context       Domain   Count APT Exception
   0    1  52c 001601b8      6020 Enabled  00000000:00000000 00164fc0     0 STA
   2    2  db8 0015c138      b220 Enabled  00000000:00000000 00164fc0     0 MTA (Finalizer)
XXXX    3    0 001bcef8      5820 Enabled  00000000:00000000 00164fc0     0 MTA
   4    4  cdc 05e2e0b8       220 Enabled  00000000:00000000 00164fc0     0 Ukn
   5    5  8a0 001fa7c8    80a220 Enabled  00000000:00000000 00164fc0     0 MTA (Threadpool Completion Port)
   8    6   ec 05e5b598   380b220 Enabled  00000000:00000000 00164fc0     0 MTA (Threadpool Worker)
... [생략] ...
 529  210 134c 214648a8   380b220 Enabled  0c6f2c18:0c6f46c8 00164fc0     0 MTA (Threadpool Worker)
 530  211 136c 21464c90   380b220 Enabled  0c6f490c:0c6f6340 00164fc0     0 MTA (Threadpool Worker)

오호~~~ 스레드 수가 500개가 넘는다니, 참으로 반가운 상황이었습니다. ^^ 그래요, 이렇게 문제 상황이 드러나야 분석을 할 수 있으니까요.

그런데, 도대체 스레드들에 어떤 문제가 발생한 것일까요? 500개의 콜스택을 남기는 것은 너무 많으므로 다음과 같이 일단 전체 콜스택을 로그 파일로 남기고,

.logopen d:\eestack_log.txt
~*e!clrstack
.logclose

해당 로그 파일을 읽어 동일한 콜스택의 종류를 세는 프로그램을 만들어 돌려보았습니다.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace CallStackCounter
{
    class Program
    {
        static void Main(string[] args)
        {
            string filePath = @"d:\eestack_log.txt";

            int threadCount = 0;

            StringBuilder sb = new StringBuilder();
            Dictionary<string, int> perThread = new Dictionary<string, int>();

            foreach (var item in File.ReadAllLines(filePath))
            {
                string txt = item;
                if (txt.IndexOf("OS Thread Id:") != -1)
                {
                    string callStack = sb.ToString();
                    sb = new StringBuilder();
                    if (perThread.ContainsKey(callStack) == true)
                    {
                        perThread[callStack]++;
                    }
                    else
                    {
                        perThread.Add(callStack, 1);
                    }

                    threadCount++;
                    continue;
                }

                if (txt != null)
                {
                    txt = txt.Trim();
                }

                if (string.IsNullOrEmpty(txt) == true)
                {
                    continue;
                }

                string stackFrame = TrimCallStackFrame(txt);
                if (string.IsNullOrEmpty(stackFrame) == false)
                {
                    sb.AppendLine(stackFrame);
                }
            }

            Console.WriteLine("Total # of threds: " + threadCount);
            Console.WriteLine();

            foreach (var thread in perThread)
            {
                Console.WriteLine("# of threads: " + thread.Value);
                Console.WriteLine(thread.Key);

                Console.WriteLine();
            }
        }

        private static string TrimCallStackFrame(string txt)
        {
            string[] infos = txt.Split(' ');

            if (infos.Length < 3)
            {
                return string.Empty;
            }

            int value;
            if (Int32.TryParse(infos[0], System.Globalization.NumberStyles.AllowHexSpecifier, null, out value) == false)
            {
                return string.Empty;
            }

            if (Int32.TryParse(infos[1], System.Globalization.NumberStyles.AllowHexSpecifier, null, out value) == false)
            {
                return string.Empty;
            }

            int pos = txt.IndexOf(' ');
            pos = txt.IndexOf(' ', pos + 1);

            txt = txt.Substring(pos + 1);

            if (txt.IndexOf("[HelperMethodFrame") != -1)
            {
                pos = txt.IndexOf("] ");
                txt = txt.Substring(pos + 2);
            }

            if (txt.IndexOf("GCFrame: ") != -1)
            {
                pos = txt.IndexOf("]");
                txt = txt.Substring(pos + 1);
            }

            if (txt.IndexOf("[ComPlusMethodFrameGeneric: ") != -1)
            {
                pos = txt.IndexOf("]");
                txt = txt.Substring(pos + 1);
            }

            return txt;
        }
    }
}

그랬더니, 452개의 스레드가 Wait 상태에 있고, Main UI를 실행중인 스레드는 SetPosition이라는 메서드를 실행 중에 있었습니다.

...[생략]...

# of threads: 1
[ComPlusMethodFrameGeneric: 0012ca10] TestCOMLib.TestClass.SetPosition(Int32, Double, Double, Int32, Double, Double, Int32, Double, Double, System.String, System.Object, Int32, Int32, Int32, Int32)
Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine)
Test.Support.CPosition.ModifyPos(Double, Double)
Test.Support.CDailyMapper.ModifyPos(Double, Double)
...[생략]...
System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
System.Windows.Threading.Dispatcher.Run()
System.Windows.Application.RunDispatcher(System.Object)
System.Windows.Application.RunInternal(System.Windows.Window)
System.Windows.Application.Run(System.Windows.Window)
System.Windows.Application.Run()
Test.Shell.App.Main()

...[생략]...

# of threads: 452
System.Threading.WaitHandle.WaitOneNative(Microsoft.Win32.SafeHandles.SafeWaitHandle, UInt32, Boolean, Boolean)
System.Threading.WaitHandle.WaitOne(Int64, Boolean)
System.Threading.WaitHandle.WaitOne(System.TimeSpan, Boolean)
System.Windows.Threading.DispatcherOperation+DispatcherOperationEvent.WaitOne()
System.Windows.Threading.DispatcherOperation.Wait(System.TimeSpan)
System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Boolean)
System.Windows.Threading.Dispatcher.Invoke(System.Delegate, System.Object[])
Test.Shell.App.tickTimer_Elapsed(System.Object, System.Timers.ElapsedEventArgs)
System.Timers.Timer.MyTimerCallback(System.Object)
System.Threading._TimerCallback.TimerCallback_Context(System.Object)
System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
System.Threading._TimerCallback.PerformTimerCallback(System.Object)

...[생략]...


452개의 스레드 콜스택을 보면 중간에 tickTimer_Elapsed가 있고, 그것을 호출한 메서드가 System.Timers.Timer 객체로 나옵니다. 그러니까, 상황 정리를 해 보면 대충 이런 식입니다.

  1. Main UI 스레드가 현재 SetPosition을 실행하면서 block 된 상태임.
  2. System.Timers.Timer 객체는 1초마다 스레드 풀로부터 스레드 하나를 가져와서 Elapsed 이벤트를 발생
  3. Elapsed 이벤트를 수신하는 사용자 코드에서 Main UI 스레드를 통해 UI 요소를 접근해야 하므로 Dispatcher.Invoke를 호출
  4. 하지만, Main UI 스레드가 block 된 상태이므로 다른 스레드로부터 들어오는 Dispatcher.Invoke의 델리게이트를 실행할 수 없음. 따라서, Elapsed 이벤트의 Invoke 호출은 대기하게 되고 해당 스레드는 종료하지 않음.
  5. Elapsed 이벤트가 1초마다 발생하므로 지속적으로 스레드가 누적됨.

스레드가 계속 누적되는 것은 사실 현상에 불과한 것입니다. 결정적인 원인은 Main UI 스레드가 블록되었기 때문에 WPF의 전체 UI 렌더링이 멈춰버린 것입니다.

이런 경우, 어쩌면 System.Timers.Timer의 스레드 풀을 사용하는 동작이 도움이 되었다고도 볼 수 있습니다. 만약, 1개의 스레드로 Elapsed를 발생시키는 것이었다면 그 스레드 하나만 멈췄을 것이고, 프로세스 덤프를 떴을 때 일반적인 콜 스택만 나와서 원인 규명이 더 힘들었을 수 있습니다. 즉, 단순히 SetPosition 메서드가 호출되는 시점에 프로세스 덤프를 뜬 것과 별반 다르지 않은 상황이기 때문입니다. (그렇다고 System.Timers.Timer 사용을 독려하는 것이 아닙니다. 단지 긍정적인 우연이 겹쳤다는 것을 이야기하고 싶은 것입니다.)

그 외에 혹시나 싶어 자원을 조사해 봤습니다. 힙 사용량을 봤지만,

0:008>  !EEHeap
... [생략]...
------------------------------
GC Heap Size  0x2079394(34,050,964)

24일 동안 켜놓은 WPF 프로그램인데도 GC Heap의 총 크기가 34MB면 상당히 양호한 수준입니다. 힙에 누적되어 있는 객체는 볼 것도 없지만, 그래도 확인해 보는 습관을 들이는 것이 좋겠지요. ^^

0:008> !dumpheap -stat
total 729019 objects
Statistics:
      MT    Count    TotalSize Class Name
7b22eaec        1           12 System.Windows.Forms.FlowLayoutSettings
7b22ea88        1           12 System.Windows.Forms.Layout.FlowLayout
...[생략]...
79333594     3820      2248156 System.Byte[]
793042f4    76166      2513388 System.Object[]
79330b24    63074      3664460 System.String
Total 729019 objects

역시, 딱히 문제되는 수준은 아닙니다. 콜스택으로 다시 돌아가서, 그럼 구체적으로 어떤 코드를 실행하다 멈췄는지 알 수 있을까요? Main UI 스레드의 가장 상단에 있는 메서드는 COM 개체 호출로 보이므로 바로 아래의 Test.Support.CTestManager.SetMethod를 살펴봐야 합니다.

0:000> !clrstack -p
OS Thread Id: 0x52c (0)
ESP       EIP     
0012ca10 16ea399d [ComPlusMethodFrameGeneric: 0012ca10] TestCOMLib.TestClass.SetPosition(Int32, Double, Double, Int32, Double, Double, Int32, Double, Double, System.String, System.Object, Int32, Int32, Int32, Int32)
0012ca70 1f68a89f Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine)
    PARAMETERS:
        this = 0x0ba5b180
        vessel = 0x022c8544
...[생략]...

우선, 이 메서드를 가진 DLL 파일을 알아내야 합니다. (만약 해당 응용 프로그램의 PDB 파일과 DLL 파일이 있다면 이 작업은 생략할 수 있습니다.) 다음과 같이 명령을 내리면 Test.Support.CTestManager.SetMethod 메서드가 정의된 모듈을 찾아낼 수 있습니다.

0:000> !name2ee *!Test.Support.CTestManager.SetMethod
Module: 790c1000 (mscorlib.dll)
--------------------------------------
...[생략]...
--------------------------------------
Module: 03c0bbc0 (Test.Support.dll)
Token: 0x0600062e
MethodDesc: 1715fd28
Name: Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine)
JITTED Code Address: 1f688838
--------------------------------------
...[생략]...

정의된 모듈명이 Test.Support.dll이라고 나왔으니 lm 명령을 통해 해당 모듈의 시작 주소를 알아냅니다.

0:000> lm
start    end        module name
...[생략]...
06510000 068cc000   Test_Support   (deferred)             
...[생략]...
1b570000 1b5c4000   msjetoledb40   (deferred)             
1b5d0000 1b665000   mswstr10   (deferred)             
1f370000 1f375000   msadcer    (deferred)             
7d5a0000 7dd9d000   shell32    (deferred)             
7e5a0000 7e65a000   unidrvui   (deferred)             
7e8c0000 7e971000   userenv    (deferred)             

Unloaded modules:
60340000 60348000   culture.dll
1b670000 1b73b000   MSWDAT10.DLL
0d940000 0d974000   mxdwdui.DLL
69780000 69797000   FontSub.dll
6eb40000 6eb7b000   compstui.dll

그럼, savemodule 명령어를 이용해 이 어셈블리를 파일로 저장할 수 있습니다.

!savemodule 06510000 C:\temp\Test_Support.dll

거의 다 왔습니다. ^^ 이제 실제로 콜 스택이 실행된 옵셋값을 구해야 하는데요. 아쉽게도 sos.dll은 이 값을 출력해 주지 않습니다. 대신 sosex.dll의 mk 명령어는 이를 출력해 주기 때문에 다음과 같이 구할 수 있습니다.

0:000> !mk
Thread 0:
        SP       IP
00:U 0012b30c 16ea399d AxObj+0x399d
01:U 0012b330 16ea93b4 AxObj+0x93b4
...[생략]...
0e:U 0012c78c 79f18cb1 mscorwks!CLRToCOMWorker+0x14f
0f:U 0012c9c4 0435141a CLRStub[StubLinkStub]@435141a
10:M 0012ca00 1f68a89f Test.Support.CTestManager.SetMethod(Test.DTO.ITestMachine)(+0x978 IL,+0x2067 Native)
11:M 0012d6a0 1f680a3a Test.Support.CPosition.ModifyPos(Double, Double)(+0x231 IL,+0x6aa Native)
12:U 0012d718 7c94005d ntdll!RtlFreeHeap+0x647
13:M 0012d8c8 1f68034f Test.Support.CDailyMapper.ModifyPos(Double, Double)(+0x123 IL,+0x2df Native)
...[생략]...
5e:U 0012fa3c 79f20cf1 mscorwks!SystemDomain::ExecuteMainMethod+0x456
5f:U 0012ff0c 79f20edb mscorwks!ExecuteEXE+0x59
60:U 0012ff5c 79f20e0b mscorwks!_CorExeMain+0x15c
61:U 0012ffa4 7900b77b mscoree!_CorExeMain+0x2e
62:U 0012ffb4 7900b73d mscoree!ShellShim__CorExeMain+0x29
63:U 0012ffc0 79004de3 mscoree!_CorExeMain_Exported+0x8
64:U 0012ffc8 7c7e7077 kernel32!BaseProcessStart+0x23

보시는 바와 같이 IL 옵셋값이 0x978로 나옵니다. 그럼, 저장된 Test.Support.dll 파일을 .NET Reflector로 불러온 다음 IL 보기 모드에서 0x978의 위치에 어떤 코드가 있는지 짐작할 수 있습니다.

...[생략]...
    L_0962: ldc.i4.0 
    L_0963: box int32
    L_0968: ldc.i4.0 
    L_0969: ldc.i4.0 
    L_096a: ldc.i4.1 
    L_096b: ldloc.s flag2
    L_096d: brtrue.s L_0972
    L_096f: ldc.i4.0 
    L_0970: br.s L_0973
    L_0972: ldc.i4.1 
    L_0973: callvirt instance void [Interop.TestCOMLib]TestCOMLib.TestClass::SetPosition(int32, float64, float64, int32, float64, float64, int32, float64, float64, string, object, int32, int32, int32, int32)
    L_0978: nop 
    L_0979: ldarg.0 
    L_097a: ldfld class [Interop.TestCOMLib]TestCOMLib.TestClass Test.Support.CTargetMgr::m_pTargets
    L_097f: ldc.i4.s 10
    L_0981: ldloc.s classf
...[생략]...

IL 코드를 조금 볼 줄 안다면 C# 코드의 어느 위치에 해당하는 지 금방 알 수 있습니다. 따라서 문제가 발생한 소스코드의 라인정보까지 얻은 것이나 다름없습니다. 일단, 이걸로 분석은 끝!




저야 SetPosition에서 왜 block 되었는지 알 수 있는 방법이 없습니다. 그래서 고객사에 이 사실을 알렸고 분석 끝에 SetPosition의 인자로 double 값이 전달되는데 이 중에 NaN이 입력되면 COM 객체 내부에서 block 상태에 빠지는 확률이 높아진다는 것을 알게 되었습니다.

그 부분은 별도로 아래의 글로 정리해 두었으니 참고하세요. ^^

C# - double 값에 대한 windbg 확인
; https://www.sysnet.pe.kr/2/0/1526




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 6/28/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2013-11-06 12시17분
[Lyn] 와... 디버거 정말 잘 다루시네요... 부럽습니다.
[guest]
2013-11-06 12시39분
제가 '점진적'으로 공부하는 스타일입니다. ^^ windbg도 그냥 가끔씩 포스트를 하나 읽거나, 명령어 하나를 익혀나가는 식으로 하다 보니, 그게 쌓여서 저렇게 조합이 되는 것 같습니다.
정성태
2013-11-06 04시44분
[[armdri]] Windows form 프로그램을 하다가 화면이 멈추는 현상이 존재했는데...
NaN 도 참고해봐야 할 것 같습니다.

감사합니다.
[guest]
2013-11-06 01시02분
사실 위의 NaN 문제는 지도와 관련된 컨트롤에 입력이 되어서 문제였던 것입니다. 윈도우 폼에서 화면이 멈춘다면, 마찬가지로 어떤 이유에서든 Main UI 스레드가 특정 작업을 하고 있거나 block 상태이기 때문에 그럴 것입니다. 아마도 그것도 멈췄을 때 덤프를 떠 보면 거의 원인이 나올 것입니다.
정성태
2018-11-12 10시03분
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13598정성태4/16/2024210닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드1
13597정성태4/15/2024293닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/2024511닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/2024527닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/2024755닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/2024948닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241186C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241155닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241069Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241132닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241185닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신파일 다운로드1
13587정성태3/27/20241135오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241265Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241089Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241044개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
13583정성태3/25/20241145Windows: 261. CPU Utilization이 100% 넘는 경우를 성능 카운터로 확인하는 방법
13582정성태3/19/20241218Windows: 260. CPU 사용률을 나타내는 2가지 수치 - 사용량(Usage)과 활용률(Utilization)파일 다운로드1
13581정성태3/18/20241366개발 환경 구성: 707. 빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법
13580정성태3/15/20241131닷넷: 2231. C# - ReceiveTimeout, SendTimeout이 적용되지 않는 Socket await 비동기 호출파일 다운로드1
13579정성태3/13/20241493오류 유형: 899. HTTP Error 500.32 - ANCM Failed to Load dll
13578정성태3/11/20241620닷넷: 2230. C# - 덮어쓰기 가능한 환형 큐 (Circular queue)파일 다운로드1
13577정성태3/9/20241850닷넷: 2229. C# - 닷넷을 위한 난독화 도구 소개 (예: ConfuserEx)
13576정성태3/8/20241539닷넷: 2228. .NET Profiler - IMetaDataEmit2::DefineMethodSpec 사용법
13575정성태3/7/20241662닷넷: 2227. 최신 C# 문법을 .NET Framework 프로젝트에 쓸 수 있을까요?
13574정성태3/6/20241552닷넷: 2226. C# - "Docker Desktop for Windows" Container 환경에서의 IPv6 DualMode 소켓
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...