Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 7개 있습니다.)
.NET Framework: 167. 다른 스레드의 호출 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/802

.NET Framework: 260. .NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법
; https://www.sysnet.pe.kr/2/0/1162

.NET Framework: 261. .NET 스레드 콜 스택 덤프 (3) - MSE 소스 코드 개선
; https://www.sysnet.pe.kr/2/0/1163

.NET Framework: 262. .NET 스레드 콜 스택 덤프 (4) - .NET 4.0을 지원하지 않는 MSE 응용 프로그램 원인 분석
; https://www.sysnet.pe.kr/2/0/1164

.NET Framework: 311. .NET 스레드 콜 스택 덤프 (5) - ICorDebug 인터페이스 사용법
; https://www.sysnet.pe.kr/2/0/1249

.NET Framework: 392. .NET 스레드 콜 스택 덤프 (6) - MDbg를 이용한 방법
; https://www.sysnet.pe.kr/2/0/1534

.NET Framework: 606. .NET 스레드 콜 스택 덤프 (7) - ClrMD(Microsoft.Diagnostics.Runtime)를 이용한 방법
; https://www.sysnet.pe.kr/2/0/11043




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

닷넷에서 스레드 호출 스택을 얻으려고 하는 시도를 하다가 어느새 여기까지 와버렸군요. ^^; 지난 마지막 이야기에서 "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
; https://learn.microsoft.com/en-us/archive/blogs/rmbyers/icordebug-re-architecture-in-clr-4-0

처음 시도에서는 위의글에 설명된 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
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/createcordbobject-function

종합해서, 다음과 같이 특정 버전의 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
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/createdebugginginterfacefromversion-function
(.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
; https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.cordebuginterop.icordebugprocess.hasqueuedcallbacks

그다음, 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
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/debugging/icordebugcontroller-continue-method

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/21/2023]

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

비밀번호

댓글 작성자
 



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

Implementing ICorDebugManagedCallback2
; (broken) 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)
13399정성태8/9/20233392닷넷: 2135. C# - 지역 변수로 이해하는 메서드 매개변수의 값/참조 전달
13398정성태8/3/20234159스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
13397정성태7/23/20233663닷넷: 2134. C# - 문자열 연결 시 string.Create를 이용한 GC 할당 최소화
13396정성태7/22/20233350스크립트: 54. 파이썬 pystack 소개 - 메모리 덤프로부터 콜 스택 열거
13395정성태7/20/20233313개발 환경 구성: 685. 로컬에서 개발 중인 ASP.NET Core/5+ 웹 사이트에 대해 localhost 이외의 호스트 이름으로 접근하는 방법
13394정성태7/16/20233263오류 유형: 873. Oracle.ManagedDataAccess.Client - 쿼리 수행 시 System.InvalidOperationException
13393정성태7/16/20233429닷넷: 2133. C# - Oracle 데이터베이스의 Sleep 쿼리 실행하는 방법
13392정성태7/16/20233301오류 유형: 872. Oracle - ORA-01031: insufficient privileges
13391정성태7/14/20233374닷넷: 2132. C# - sealed 클래스의 메서드를 callback 호출했을 때 인라인 처리가 될까요?
13390정성태7/12/20233345스크립트: 53. 파이썬 - localhost 호출 시의 hang 현상
13389정성태7/5/20233337개발 환경 구성: 684. IIS Express로 호스팅하는 웹을 WSL 환경에서 접근하는 방법
13388정성태7/3/20233519오류 유형: 871. 윈도우 탐색기에서 열리지 않는 zip 파일 - The Compressed (zipped) Folder '[...].zip' is invalid. [1]파일 다운로드1
13387정성태6/28/20233541오류 유형: 870. _mysql - Commands out of sync; you can't run this command now
13386정성태6/27/20233608Linux: 61. docker - 원격 제어를 위한 TCP 바인딩 추가
13385정성태6/27/20233828Linux: 60. Linux - 외부에서의 접속을 허용하기 위한 TCP 포트 여는 방법
13384정성태6/26/20233570.NET Framework: 2131. C# - Source Generator로 해결하는 enum 박싱 문제파일 다운로드1
13383정성태6/26/20233318개발 환경 구성: 683. GPU 런타임을 사용하는 Colab 노트북 설정
13382정성태6/25/20233358.NET Framework: 2130. C# - Win32 API를 이용한 윈도우 계정 정보 (예: 마지막 로그온 시간)파일 다운로드1
13381정성태6/25/20233746오류 유형: 869. Fatal Python error: init_fs_encoding: failed to get the Python codec of the filesystem encoding
13380정성태6/24/20233196스크립트: 52. 파이썬 3.x에서의 동적 함수 추가
13379정성태6/23/20233210스크립트: 51. 파이썬 2.x에서의 동적 함수 추가
13378정성태6/22/20233101오류 유형: 868. docker - build 시 "CANCELED ..." 뜨는 문제
13377정성태6/22/20236904오류 유형: 867. 파이썬 mysqlclient 2.2.x 설치 시 "Specify MYSQLCLIENT_CFLAGS and MYSQLCLIENT_LDFLAGS env vars manually" 오류
13376정성태6/21/20233287.NET Framework: 2129. C# - Polly를 이용한 클라이언트 측의 요청 재시도파일 다운로드1
13375정성태6/20/20232987스크립트: 50. Transformers (신경망 언어모델 라이브러리) 강좌 - 2장 코드 실행 결과
13374정성태6/20/20233111오류 유형: 866. 파이썬 - <class 'AttributeError'> module 'flask.json' has no attribute 'JSONEncoder'
1  2  3  4  5  6  7  8  [9]  10  11  12  13  14  15  ...