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

.NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법

.NET 스레드 콜 스택 덤프 (1) - 다른 스레드의 스택 덤프 구하는 방법
.NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법
.NET 스레드 콜 스택 덤프 (3) - MSE 소스 코드 개선
.NET 스레드 콜 스택 덤프 (4) - .NET 4.0을 지원하지 않는 MSE 응용 프로그램 원인 분석
.NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법
.NET 스레드 콜 스택 덤프 (6) - MDbg를 이용한 방법
.NET 스레드 콜 스택 덤프 (7) - ClrMD(Microsoft.Diagnostics.Runtime)를 이용한 방법

닷넷에서 스레드 호출 스택을 얻으려고 하는 시도를 하다가 어느 새 여기까지 와버렸군요. ^^; 지난 마지막 이야기에서 "mscordbi.dll"을 직접 사용하는 방법을 다뤄보겠다고 하면서 맺었는데... 진작에 테스트를 했었으나, 시간이 없어 미뤄오다가 이제서야 정리를 해서 공개를 합니다.

자... 그럼 이번에도 그다지 재미없는 글이 되겠지만 ^^ 본론으로 들어가 보도록 하겠습니다.




문제를 다시 정리해 보면, 현재 MSE 의 소스 코드로는 .NET 2.0 응용 프로그램의 Call Stack 만을 얻을 수 있을 뿐 .NET 4.0 의 콜 스택을 얻지 못한다는 단점이 있었습니다. 다행히 .NET 4.0 의 콜 스택을 얻는 방법으로 그 힌트를 CLR Stack Explorer 가 .NET 4.0 응용 프로그램에서도 동작한다는 것에서 얻었는데, 그래서 이번에는 CLR Stack Explorer 처럼 직접 mscordbi.dll 에서 제공되는 기능만으로 문제 해결을 해보려는 것입니다.

비록 MSE 응용 프로그램에서 예외는 ICorPublish에서 발생했지만, 사실 ICorPublish 의 기능 자체는 콜 스택 덤프를 뜨는 것과는 무관합니다. 정작 중요한 것은 ICorDebug 인터페이스인데요.

이에 대한 간략한 사용법은 다음의 예제에 공개되어 있습니다.

How to get a V2.0 ICorDebug object
; http://blogs.msdn.com/b/jmstall/archive/2005/01/15/353717.aspx

ICorDebug re-architecture in CLR 4.0
; http://blogs.msdn.com/b/rmbyers/archive/2008/10/27/icordebug-re-architecture-in-clr-4-0.aspx

처음 시도에서는 위의글에 설명된 CoCreateInstance로 직접 ICorDebug 를 생성해 보았습니다.

#include <cor.h>
#include <corhdr.h>
#include <cordebug.h>

#pragma comment(lib, "corguids.lib")

    CoInitialize(NULL);

    {
        ICorDebug *pCorDebug;

        HRESULT hr = ::CoCreateInstance(CLSID_CorDebug, NULL, CLSCTX_INPROC_SERVER, IID_ICorDebug,
            (void **)&pCorDebug);
        if (hr != S_OK)
        {
            return 0;
        }
    }

    CoUninitialize();

문제가 대번에 나오더군요. Visual Studio의 출력창에서 .NET 2.0 용의 mscordbi.dll 파일이 로드가 되는 것을 확인할 수 있었습니다.

'ConsoleApp.exe': Loaded 'C:\Windows\SysWOW64\clbcatq.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\SysWOW64\mscoree.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscordbi.dll', Cannot find or open the PDB file

이전에 설명했던 것처럼 ICorDebug 등에 대한 Class Factory는,

EXTERN_C const IID LIBID_CORDBLib;

EXTERN_C const CLSID CLSID_CorDebug;

#ifdef __cplusplus

class DECLSPEC_UUID("6fef44d0-39e7-4c77-be8e-c9f8cf988630")
CorDebug;

mscoree.dll에 정의되어 있습니다. 결국 mscoree.dll 측에서 적절하지 않은 버전의 mscordbi.dll을 로드한다는 것인데요. 따라서, mscoree.dll을 경유하지 말고 직접 mscordbi.dll을 로드하는 방법을 찾아야 했습니다. 일단 mscoree.dll에서 CoClass를 제공하는 이상 mscordbi.dll 자체에서는 숨겨진 다른 방식으로 인터페이스를 노출시켜 준다는 의미가 되는데요. Depends 도구를 이용하여 mscordbi.dll을 살펴보니, 친절하게도 CreateCordbObject 라는 이름으로 된 함수가 export 되어 있었습니다.

how_to_use_icordebug_1.png

다행히, API 설명까지 공개되어 있군요. ^^

CreateCordbObject Function
; http://msdn.microsoft.com/en-us/library/cc994509.aspx

종합해서, 다음과 같이 특정 버전의 mscordbi.dll 로드가 가능했습니다.

===== C/C++ 버전 =====

typedef HRESULT (__stdcall *CordbCreateObjectFunc)(int iDebuggerVersion, IUnknown **ppCordb);

CComQIPtr<ICorDebug> pCorDebug;
HMODULE hModule = ::LoadLibrary(L"C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordbi.dll");
if (hModule == NULL)
{
    break;
}

CordbCreateObjectFunc pCordbCreateObjectFunc = (CordbCreateObjectFunc)::GetProcAddress(hModule,"CreateCordbObject");

IUnknown *pUnk;
hr = pCordbCreateObjectFunc(CorDebugVersion_4_0, (IUnknown **)&pUnk);
if (hr != S_OK)
{
    break;
}

pCorDebug = pUnk;
if (pCorDebug == NULL)
{
    break;
}

===== C# 버전 =====
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr LoadLibrary(string lpFileName);

[DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int CordbCreateObjectFunc(int iDebuggerVersion, [Out, MarshalAs(UnmanagedType.Interface)] out object pUnknown);

IntPtr pModule = LoadLibrary("C:\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordbi.dll");
if (pModule == null)
{
    return;
}

IntPtr pFunc = GetProcAddress(pModule, "CreateCordbObject");
if (pFunc == IntPtr.Zero)
{
    return;
}

CordbCreateObjectFunc pCordbCreateObjectFunc = Marshal.GetDelegateForFunctionPointer(pFunc, typeof(CordbCreateObjectFunc))
    as CordbCreateObjectFunc;
if (pCordbCreateObjectFunc == null)
{
    return;
}

// object pUnknown;
pCordbCreateObjectFunc(4, out pUnknown);

ICorDebug corDebug = pUnknown as ICorDebug;

참고로, 현재 CorDebugVersion_4_0 상수값에 대해서는 어떠한 웹 문서에도 찾아볼 수가 없었는데, 최신 버전의 Windows SDK 에 포함된 cordebug.idl 파일을 직접 검색해 보면 다음과 같이 정의되어 있는 것을 볼 수 있습니다.

E:\Program Files\Microsoft SDKs\Windows\v7.1\Include\cordebug.idl

    CorDebugInvalidVersion = 0,
    CorDebugVersion_1_0 = CorDebugInvalidVersion + 1,  // 1
    CorDebugVersion_1_1 = CorDebugVersion_1_0 + 1,     // 2
    CorDebugVersion_2_0 = CorDebugVersion_1_1 + 1,     // 3
    // CLR v4 - next major CLR version after CLR v2
    // Includes Silverlight 4
    CorDebugVersion_4_0 = CorDebugVersion_2_0 + 1,     // 4

실제로 이렇게 해서 로드를 해보면, 정확하게 4.0 버전의 mscordbi.dll 만 Visual Studio 출력창에 보입니다.

'ConsoleApp.exe': Loaded 'C:\Windows\System32\version.dll', Cannot find or open the PDB file
IMGSF01-DLL_PROCESS_ATTACH'ConsoleApp.exe': Loaded 'C:\Windows\System32\ntmarta.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\System32\Wldap32.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\System32\msimg32.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscordbi.dll', Cannot find or open the PDB file
'ConsoleApp.exe': Loaded 'C:\Windows\System32\wtsapi32.dll', Cannot find or open the PDB file

성공입니다. ^^




mscordbi.dll 파일 경로를 직접 쓴다는 것은 아무래도 부담스러운 데요. 이 때문에 How to get a V2.0 ICorDebug object 글에서 설명한 CreateDebuggingInterfaceFromVersion 함수를 사용해 보았습니다.

CreateDebuggingInterfaceFromVersion Function
; http://msdn.microsoft.com/en-us/library/ms232068.aspx
(.NET 4.0 에서는 "obsolete" 로 명시되었지만, 여전히 동작합니다.)

처음에는 다음과 같이 사용해 보았는데요.

CComPtr<ICorDebug> pCorDebug;
hr = ::CreateDebuggingInterfaceFromVersion(CorDebugVersion_4_0, L"", (IUnknown **)&pCorDebug);

아쉽게도 hr == E_INVALIDARG 오류가 발생해서, 문서를 자세히 보니 두번째 szDebuggeeVersion 인자를 주어야 한다는 것을 알았습니다. 역시 문서에 보면 여기에 넘겨줄 문자열은 GetVersionFromProcessGetRequestedRuntimeVersion API 에서 구할 수 있다고 되어 있는데, 제 상황에서는 GetVersionFromProcess를 실행해 보았으나 "" 문자열만 반환되었습니다. (참고로, 문서에 보면 .NET 4.0 의 경우 GetVersionFromProcess API역시 "deprecated" 되었습니다.)

// 아래의 코드는 .NET 4.0 용에서 동작하지 않음.
wchar_t versionText[1024] = { 0 };
DWORD dwVersionLength = 0;
hr = GetVersionFromProcess((HANDLE)processHandle, versionText, 1024, &dwVersionLength);

// 그래서, .NET 4.0에 해당하는 문자열을 넣어주는 것으로 해결
hr = ::CreateDebuggingInterfaceFromVersion(CorDebugVersion_4_0, L"v4.0.30319", (IUnknown **)&pCorDebug);

==== C# 버전 ====
[DllImport("MSCorEE.dll", CharSet = CharSet.Unicode, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
static extern object CreateDebuggingInterfaceFromVersion(int iDebuggerVersion, string szDebuggerVersion);

ICorDebug corDebug = CreateDebuggingInterfaceFromVersion(4, "v4.0.30319") as ICorDebug;

어쨌든, Visual Studio 의 출력창에 로드된 DLL 을 확인해 보면, 정확하게 .NET 4.0 용의 mscordbi.dll 이 로드된 것을 확인할 수 있습니다.




ICorDebug 를 얻었으니, 이로부터 다음과 같이 ICorDebugProcess 인터페이스를 얻어올 수 있습니다.

CComPtr<ICorDebugProcess> pCorDebugProcess;
hr = pCorDebug->DebugActiveProcess(dwPid, true, &pCorDebugProcess);
if (hr != S_OK)
{
    break;
}

하지만, 세상일이 그렇게 간단하지 않더군요. ^^ 실제로 위와 같이 호출해 보면 다음과 같은 예외가 발생합니다.

hr == E_FAIL

First-chance exception at 0x000007fefe18cacd in ConsoleApp.exe: Microsoft C++ exception: HRException * __ptr64 at memory location 0x002ff558..
First-chance exception at 0x000007fefe18cacd in ConsoleApp.exe: Microsoft C++ exception: [rethrow] at memory location 0x00000000..
First-chance exception at 0x000007fefe18cacd in ConsoleApp.exe: Microsoft C++ exception: HRException * __ptr64 at memory location 0x002ff558..

ICorDebug를 처음 사용해 보는 저 같은 초보 프로그래머가 흔히 겪는 실수일 텐데요. ICorDebugProcess를 얻기 전에, 반드시 콜백 핸들러를 지정해 주어야만 저런 예외가 발생하지 않습니다.

그래서, 다른 소스 코드를 참조한 결과 일반적으로 다음과 같은 수순으로 연결되는 것을 볼 수 있었습니다.

pCorDebug->SetManagedHandler(this); // ICorDebugManagedCallback 를 구현한 인스턴스를 설정

hr = pCorDebug->CanLaunchOrAttach(processId, false);
if (hr != S_OK)
{
    break;
}

CComPtr<ICorDebugProcess> pCorDebugProcess;
hr = pCorDebug->DebugActiveProcess(processId, false, &pCorDebugProcess);
if (hr != S_OK)
{
    break;
}

일단, 위와 같이 ICorDebugProcess로 대상 프로세스에 Attach 를 정상적으로 시키면, 이제 현재 생성되어 있는 스레드 목록을 얻어야 하는데요. 이 작업은 ICorDebugController 를 얻음으로써 가능합니다.

CComPtr<ICorDebugProcess> pCorDebugProcess;
hr = pCorDebug->DebugActiveProcess(dwPid, false, &pCorDebugProcess);
if (hr != S_OK)
{
    break;
}

pCorDebugProcess->Continue(FALSE);

bool exit = false;
CComQIPtr<ICorDebugController> pCorDebugController = pCorDebugProcess;

ICorDebugController::EnumerateThreads 메서드를 이용하여 스레드를 열람할 수 있는데, 주의할 것은 곧바로 이 상태로 진입해서는 안된다는 것입니다. ICorDebugManagedCallback::CreateThread 이벤트 단계와 협업을 해야 하는데, 이 부분은 MSE 소스 코드를 보고서야 알게 된 사항이어서 이로 인한 문제를 해결하기까지 처음에 시간이 많이 걸렸습니다.

즉, 다음과 같이 WaitForSingleObject 등의 수단을 이용해서 기다렸다가 스레드를 열람해야 하는데,

::WaitForSingleObject(_threadStartEventHandle, INFINITE);

CComPtr<ICorDebugThreadEnum> pThreadsEnum;
hr = pCorDebugController->EnumerateThreads(&pThreadsEnum);
if (hr != S_OK)
{
    exit = true;
    break;
}

이에 대한 동기화 이벤트는 다음과 같이 ICorDebugManagedCallback::CreateThread 단계를 구성해서 처리를 해줘야 합니다.

virtual HRESULT STDMETHODCALLTYPE CreateThread(ICorDebugAppDomain *pAppDomain, ICorDebugThread *thread) 
{
    HRESULT hr;

    do
    {
        CComPtr<ICorDebugProcess> pProcess;
        hr = thread->GetProcess(&pProcess);
        if (hr != S_OK || pProcess == NULL)
        {
            break;
        }

        BOOL bQueued = FALSE;
        hr = pProcess->HasQueuedCallbacks(NULL, &bQueued);
        if (hr != S_OK)
        {
            break;
        }

        if (bQueued == FALSE)
        {
            ((CManagedStackTrace *)this)->SignalAttachedProcess(); // ::SetEvent(_threadStartEventHandle);
            return S_OK;
        }
        else
        {
            pAppDomain->Continue(FALSE);
            return S_OK;
        }

    } while (false);

    ((CManagedStackTrace *)this)->SignalAttachedProcess();  // ::SetEvent(_threadStartEventHandle);

    return S_OK;
}

솔직히, 위와 같은 처리 절차는 그냥은 알기 힘들고 MSE 소스 코드가 있었기 때문에 가능한 것 같습니다. 참고로, HasQueuedCallbacks 에 대한 처리는 다음의 문서를 참조하십시오.

Using ICorDebugProcess::HasQueuedCallbacks
; http://blogs.msdn.com/b/jmstall/archive/2005/07/27/hasqueuedcallbacks.aspx

그 다음, ICorDebugManagedCallback 의 중요 이벤트마다 적절하게 Continue 메서드를 호출해 주어야 하는 문제가 있습니다. 이를 몰랐을 때는, 처리하는 중에 다음과 같이 ICorDebugManagedCallback::DebuggerError 에서 오류를 만나서 당황했었는데요.

virtual HRESULT STDMETHODCALLTYPE DebuggerError(ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode)  
{
    // CORDBG_E_INTEROP_NOT_SUPPORTED 0x8013134D (-2146233523) 오류 발생
    return E_NOTIMPL;
}

다음과 같은 콜백 메서드에 대해서 S_OK 반환과 Continue 메서드를 호출해 주어야만 했습니다. (역시 MSE 소스 코드를 보고 알았습니다.)

virtual HRESULT STDMETHODCALLTYPE CreateProcess(ICorDebugProcess *pProcess) 
{
    pProcess->Continue(FALSE);
    return S_OK;
}

virtual HRESULT STDMETHODCALLTYPE LoadModule(ICorDebugAppDomain *pAppDomain, ICorDebugModule *pModule)  
{
    pAppDomain->Continue(FALSE);
    return S_OK;
}

virtual HRESULT STDMETHODCALLTYPE CreateAppDomain(ICorDebugProcess *pProcess, ICorDebugAppDomain *pAppDomain) 
{
    pAppDomain->Attach();
    pProcess->Continue(FALSE);
    return S_OK;
}

virtual HRESULT STDMETHODCALLTYPE DebuggerError(ICorDebugProcess *pProcess, HRESULT errorHR, DWORD errorCode)  
{
    pProcess->Continue(FALSE);
    return S_OK;
}

또 한가지 주의할 점은 위에서 pProcess->Continue 등의 함수에서 TRUE/FALSE 값은 fIsOutOfBand 값으로 나오는데요. 이에 대해서는 다음의 MSDN 문서를 봐야 합니다.

ICorDebugController::Continue Method
; http://msdn.microsoft.com/en-us/library/ms231588.aspx

Continue continues the process after a call to the ICorDebugController::Stop method.

When doing mixed-mode debugging, do not call Continue on the Win32 event thread unless you are continuing from an out-of-band event.

An in-band event is either a managed event or a normal unmanaged event during which the debugger supports interaction with the managed state of the process. In this case, the debugger receives the ICorDebugUnmanagedCallback::DebugEvent callback with its fOutOfBand parameter set to false.
An out-of-band event is an unmanaged event during which interaction with the managed state of the process is impossible while the process is stopped due to the event. In this case, the debugger receives the ICorDebugUnmanagedCallback::DebugEvent callback with its fOutOfBand parameter set to true.


결국 Continue 인자에 정상적인 TRUE/FALSE 값을 넘겨주려면 ICorDebugUnmanagedCallback 도 구현해 주어야 하고, 위에서 ICorDebug::SetManagedHandler 로 ICorDebugManagedCallback를 등록해 주었던 것처럼 ICorDebug::SetUnmanagedHandler 로 ICorDebugUnmanagedCallback도 등록해서 처리해 주어야 합니다.

일단, 제 경우에는 테스트 수준이다 보니 무조건 FALSE로 넘겼으나... 나중에 그거 마저 구현을 한 소스를 기회가 되면 올리겠습니다.

이 정도만 했으면 일단 .NET 4.0 응용 프로그램에 대해서도 정상적으로 Call Stack 을 얻을 수 있습니다. 나머지 소스 코드는 MSE C# 버전의 작업들을 지리하게 C/C++ 코드로 다시 작성한 코드들의 나열이기 때문에 이 글에서는 생략했지만, 첨부된 소스 코드에는 넣어두었으므로 참고하시기 바랍니다.

마지막으로, 제가 이번 테스트를 진행하면서 어려움을 겪은 것이 하나 더 있었는데 이를 말씀드려야 할 것 같습니다. ^^

위의 소스 코드로 작업을 하는데, 때로는 정상적으로 동작하다가도 어쩌다가 ICorDebug::DebugActiveProcess 를 호출했을 때 반환값이 hr == 0x8013132e 으로 나오는 이상한 문제가 발생했습니다.

검색도 해보았지만, 건수가 하나만 나올 뿐 답이 없더군요. ^^;

Error: attaching for debug to external process 
; http://community.sharpdevelop.net/forums/p/8929/24848.aspx

훗날, 저처럼 ICorDebug 가지고 놀다가 이런 시행 착오를 거치지 않으시길 바라면서 정리하자면... ^^;

바로, 대상 응용 프로그램이 이미 디버거에 붙어(Attach)있는 상태인 경우에 발생합니다. 제 경우에는, ICorDebug::DebugActiveProcess 를 호출하는 응용 프로그램을 디버깅하는 목적으로 그 자신의 콜 스택을 남기도록 스스로 프로세스를 재생성해서 테스트 하는 경우였습니다.

2개의 디버거가 하나의 EXE 프로세스를 제어할 수 없다는 것을 생각하면 응당 이해는 되지만... 습관대로 디버거를 시작했다가 저런 결과가 나오면 오류가 무엇인지 감이 안 잡히더군요. ^^ 어쨌든, 테스트 하실 때 이미 Visual Studio 로 디버깅 중인 프로세스를 다시 DebugActiveProcess로 테스트 하는 실수는 하지 마시기 바랍니다.

그나저나... 꽤나 쉽지 않은 분야인 것 같습니다. 디버깅이란 분야는!




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

[연관 글]





[최초 등록일: ]
[최종 수정일: 8/12/2020 ]

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

비밀번호

댓글 쓴 사람
 



2012-02-26 06시16분
소스 코드를 아래의 글에 따라 업그레이드를 했습니다.

Implementing ICorDebugManagedCallback2
; http://blogs.msdn.com/b/andrew_richards/archive/2012/01/30/implementing-icordebugmanagedcallback2.aspx

위의 코드를 보니, Continue 메서드에 무조건 false 값을 주는 것으로 봐서 ICorDebugUnmanagedCallback 과의 연동이 꼭 필요한 것은 아닌 것 같습니다. (물론, 추측입니다.)

일단 테스트 상으로는, .NET 2.0 x86/x64, .NET 4.0 x86/x64에 대해서 잘 동작합니다.
정성태
2020-08-12 11시17분
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12350정성태9/25/202095Windows: 175. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수파일 다운로드1
12349정성태9/25/202046Linux: 32. Ubuntu 20.04 - docker를 위한 tcp 바인딩 추가
12348정성태9/25/202032오류 유형: 658. 리눅스 docker - Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock
12347정성태9/25/202079Windows: 174. WSL 2의 네트워크 통신 방법
12346정성태9/25/202033오류 유형: 657. IIS - http://localhost 방문 시 Service Unavailable 503 오류 발생
12345정성태9/25/202029오류 유형: 656. iisreset 실행 시 "Restart attempt failed." 오류가 발생하지만 웹 서비스는 정상적인 경우
12344정성태9/25/202038Windows: 173. 서비스 관리자에 "IIS Admin Service"가 등록되어 있지 않다면?
12343정성태9/24/2020141.NET Framework: 945. C# - 닷넷 응용 프로그램에서 메모리 누수가 발생할 수 있는 패턴
12342정성태9/25/2020113디버깅 기술: 171. windbg - 인스턴스가 살아 있어 메모리 누수가 발생하고 있는지 확인하는 방법
12341정성태9/23/2020161.NET Framework: 944. C# - 인스턴스가 살아 있어 메모리 누수가 발생하고 있는지 확인하는 방법파일 다운로드1
12340정성태9/23/202088.NET Framework: 943. WPF - WindowsFormsHost를 담은 윈도우 생성 시 메모리 누수
12339정성태9/21/202069오류 유형: 655. 코어 모드의 윈도우는 GUI 모드의 윈도우로 교체가 안 됩니다.
12338정성태9/21/202042오류 유형: 654. 우분투 설치 시 "CHS: Error 2001 reading sector ..." 오류 발생
12337정성태9/21/202042오류 유형: 653. Windows - Time zone 설정을 바꿔도 반영이 안 되는 경우
12336정성태9/21/2020148.NET Framework: 942. C# - WOL(Wake On Lan) 구현
12335정성태9/21/202053Linux: 31. 우분투 20.04 초기 설정 - 고정 IP 및 SSH 설치
12334정성태9/21/202033오류 유형: 652. windbg - !py 확장 명령어 실행 시 "failed to find python interpreter"
12333정성태9/20/202098.NET Framework: 941. C# - 전위/후위 증감 연산자에 대한 오버로딩 구현 (2)
12332정성태9/18/202088.NET Framework: 940. C# - Windows Forms ListView와 DataGridView의 예제 코드파일 다운로드1
12331정성태9/24/202081오류 유형: 651. repadmin /syncall - 0x80090322 The target principal name is incorrect.
12330정성태9/20/2020227.NET Framework: 939. C# - 전위/후위 증감 연산자에 대한 오버로딩 구현 [2]파일 다운로드1
12329정성태9/16/2020115오류 유형: 650. ASUS 메인보드 관련 소프트웨어 설치 후 ArmouryCrate.UserSessionHelper.exe 프로세스 무한 종료 현상
12328정성태9/16/202088VS.NET IDE: 150. TFS의 이력에서 "Get This Version"과 같은 기능을 Git으로 처리한다면?
12327정성태9/12/2020147.NET Framework: 938. C# - ICS(Internet Connection Sharing) 제어파일 다운로드1
12326정성태9/12/2020138개발 환경 구성: 516. Azure VM의 Network Adapter를 실수로 비활성화한 경우
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...