Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 11개 있습니다.)
.NET Framework: 475. ETW(Event Tracing for Windows)를 C#에서 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1804

.NET Framework: 483. 코드로 살펴 보는 ETW의 활성화 시점
; https://www.sysnet.pe.kr/2/0/1815

.NET Framework: 915. ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용
; https://www.sysnet.pe.kr/2/0/12244

.NET Framework: 923. C# - ETW(Event Tracing for Windows)를 이용한 Finalizer 실행 감시
; https://www.sysnet.pe.kr/2/0/12255

.NET Framework: 932. C# - ETW 관련 Win32 API 사용 예제 코드 (1)
; https://www.sysnet.pe.kr/2/0/12292

.NET Framework: 933. C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger
; https://www.sysnet.pe.kr/2/0/12296

.NET Framework: 934. C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현
; https://www.sysnet.pe.kr/2/0/12299

.NET Framework: 935. C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer
; https://www.sysnet.pe.kr/2/0/12300

.NET Framework: 936. C# - ETW 관련 Win32 API 사용 예제 코드 (5) - Private Logger
; https://www.sysnet.pe.kr/2/0/12302

개발 환경 구성: 504. ETW - 닷넷 프레임워크 기반의 응용 프로그램을 위한 명령행 도구 etrace 소개
; https://www.sysnet.pe.kr/2/0/12303

.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신
; https://www.sysnet.pe.kr/2/0/12474




C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer

지난 글에서,

C# - ETW 관련 Win32 API 사용 예제 코드 (1)
; https://www.sysnet.pe.kr/2/0/12292

C# - ETW 관련 Win32 API 사용 예제 코드 (2) NT Kernel Logger
; https://www.sysnet.pe.kr/2/0/12296

C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현
; https://www.sysnet.pe.kr/2/0/12299

실습한 내용을 바탕으로 이제 CLR 관련 ETW 이벤트를,

ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용
; https://www.sysnet.pe.kr/2/0/12244

Win32 API를 이용한 방법으로 재구성해 보겠습니다.




우선, CLR을 위한 ETW Provider를 찾아야겠지요. ETW Provider 목록에 보면 당연히 "ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용" 글에서 다룬 "Microsoft-Windows-DotNETRuntime"과 그것의 GUID 값인 "{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}"도 나옵니다.

CLR ETW Providers
; https://learn.microsoft.com/en-us/dotnet/framework/performance/clr-etw-providers

따라서, 지난 글의 소스 코드에서 단순히 ETW Provider GUID 값만 바꿔 다음과 같이 구현할 수 있습니다.

using Microsoft.Samples.Eventing.Interop;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static bool _disposed = false;
        static int _processId = 0;

        static void Main(string[] _)
        {
            _processId = Process.GetCurrentProcess().Id;

            string sessionName = "clrETWSession";
            Guid clrProvider = new Guid("{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}");
            int result = 0;

            ulong traceHandle = 0;
            ulong sessionHandle = 0;

            try
            {
                if (EtwInterop.IsSessionActive(sessionName, out var _) == true)
                {
                }
                else
                {
                    EventTraceProperties prop = new EventTraceProperties(true, sessionName);
                    result = NativeMethods.StartTrace(out sessionHandle, sessionName, ref prop);
                    Console.WriteLine(result);

                    if (result == 0)
                    {
                        ENABLE_TRACE_PARAMETERS enableParameters = new ENABLE_TRACE_PARAMETERS();
                        enableParameters.Version = 1;
                        enableParameters.EnableProperty = (uint)EventEnableProperty.Sid;

                        result = NativeMethods.EnableTraceEx2(sessionHandle, ref clrProvider,
                            NativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER, (byte)TraceEventLevel.Informational,
                            0, 0, 0, ref enableParameters);

                        Console.WriteLine(result);
                    }
                }

                if (result == 0)
                {
                    EventTraceLogfile logFile = new EventTraceLogfile();
                    logFile.LoggerName = sessionName;
                    logFile.EventRecordCallback = EventRecordCallback;

                    logFile.ProcessTraceMode = NativeMethods.PROCESS_TRACE_MODE_EVENT_RECORD | NativeMethods.PROCESS_TRACE_MODE_REAL_TIME
                        | NativeMethods.PROCESS_TRACE_MODE_RAW_TIMESTAMP;
                    traceHandle = NativeMethods.OpenTrace(ref logFile);
                }

                if (traceHandle != 0)
                {
                    Thread t = new Thread((ThreadStart)(
                    () =>
                    {
                        Console.WriteLine("PrcoessTrace: " + EtwInterop.ProcessTrace(traceHandle));
                    }));

                    t.Start();

                    Console.WriteLine("Press ENTER key to exit...");
                    Console.ReadLine();

                    // Console.WriteLine("Disabled: " + EtwInterop.DisableProvider(sessionHandle, ref auditApiCalls));
                    Console.WriteLine("Closed: " + (NativeMethods.CloseTrace(traceHandle) == 0));

                    t.Join();
                }
            }
            finally
            {
                _disposed = true;
                EtwInterop.CloseActiveSession(sessionName);
            }
        }

        static private void EventRecordCallback([In] ref EventRecord eventRecord)
        {
            if (_processId != eventRecord.EventHeader.ProcessId)
            {
                return;
            }
            
            Console.WriteLine(DateTime.Now + ": " + eventRecord.EventHeader.ProcessId);
        }
    }
}




위와 같이 실행하면 현재 프로세스에서 발생하는 CLR 이벤트를 종류에 상관없이 모두 콜백을 받는데요, "ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용" 글에서는 이벤트 종류를 선택할 수 있는 코드가,

userSession.EnableProvider(
    ClrTraceEventParser.ProviderGuid,
    TraceEventLevel.Verbose,
    (ulong)(
    ClrTraceEventParser.Keywords.Contention |  // thread contention timing
    ClrTraceEventParser.Keywords.Threading |   // threadpool events
    ClrTraceEventParser.Keywords.Exception |   // get the first chance exceptions
    ClrTraceEventParser.Keywords.GCHeapAndTypeNames | 
    ClrTraceEventParser.Keywords.Type | // for finalizer and exceptions type names
    ClrTraceEventParser.Keywords.GC     // garbage collector details
    )
);

있었으므로 이 부분에 대해서도 좀 더 봐야 할 필요가 있습니다. 우선, 위의 코드에서 "Keywords"에 담긴 값들을 ETW 이벤트에 대한 필터링 조건으로 주고 있는데 역어셈블을 해 보면 다음과 같이 값을 확인할 수 있습니다.

[Flags]
public enum Keywords : long
{
    None = 0L,
    All = -65L,
    GC = 1L,
    GCHandle = 2L,
    Binder = 4L,
    Loader = 8L,
    Jit = 0x10L,
    NGen = 0x20L,
    StartEnumeration = 0x40L,
    StopEnumeration = 0x80L,
    Security = 0x400L,
    AppDomainResourceManagement = 0x800L,
    JitTracing = 0x1000L,
    Interop = 0x2000L,
    Contention = 0x4000L,
    Exception = 0x8000L,
    Threading = 0x10000L,
    JittedMethodILToNativeMap = 0x20000L,
    OverrideAndSuppressNGenEvents = 0x40000L,
    Type = 0x80000L,
    GCHeapDump = 0x100000L,
    GCSampledObjectAllocationHigh = 0x200000L,
    GCHeapSurvivalAndMovement = 0x400000L,
    GCHeapCollect = 0x800000L,
    GCHeapAndTypeNames = 0x1000000L,
    GCSampledObjectAllocationLow = 0x2000000L,
    GCAllObjectAllocation = 0x2200000L,
    SupressNGen = 0x40000L,
    PerfTrack = 0x20000000L,
    Stack = 0x40000000L,
    ThreadTransfer = 0x80000000L,
    Debugger = 0x100000000L,
    Monitoring = 0x200000000L,
    Codesymbols = 0x400000000L,
    Compilation = 0x1000000000L,
    CompilationDiagnostic = 0x2000000000L,
    Default = 0x14c14fccbdL,
    JITSymbols = 0x60098L,
    GCHeapSnapshot = 0x1980001L
}

그런데, 이 값들은 Microsoft.Diagnostics.Tracing.TraceEvent 라이브러리 제작자가 임의로 결정한 것이 아니고 ETW manifest 파일, 즉 공급자가 정해 놓은 것입니다.

C:\Windows\Microsoft.NET\Framework\v4.0.30319\CLR-ETW.man
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\CLR-ETW.man

실제로 CLR-ETW.man 파일을 보면 다음과 같은 내용들을 볼 수 있습니다.

...[생략]...

<keywords>
    <keyword name="GCKeyword" mask="0x1" message="$(string.RuntimePublisher.GCKeywordMessage)" symbol="CLR_GC_KEYWORD"/>
    <keyword name="GCHandleKeyword" mask="0x2" message="$(string.RuntimePublisher.GCHandleKeywordMessage)" symbol="CLR_GCHANDLE_KEYWORD"/>
    <keyword name="FusionKeyword" mask="0x4" message="$(string.RuntimePublisher.FusionKeywordMessage)" symbol="CLR_FUSION_KEYWORD"/>
    <keyword name="LoaderKeyword" mask="0x8" message="$(string.RuntimePublisher.LoaderKeywordMessage)" symbol="CLR_LOADER_KEYWORD"/>
    <keyword name="JitKeyword" mask="0x10" message="$(string.RuntimePublisher.JitKeywordMessage)" symbol="CLR_JIT_KEYWORD"/>
    <keyword name="NGenKeyword" mask="0x20" message="$(string.RuntimePublisher.NGenKeywordMessage)" symbol="CLR_NGEN_KEYWORD"/>
    <keyword name="StartEnumerationKeyword" mask="0x40" message="$(string.RuntimePublisher.StartEnumerationKeywordMessage)" symbol="CLR_STARTENUMERATION_KEYWORD"/>
    <keyword name="EndEnumerationKeyword" mask="0x80" message="$(string.RuntimePublisher.EndEnumerationKeywordMessage)" symbol="CLR_ENDENUMERATION_KEYWORD"/>
    <!-- Keyword mask 0x100 is now defunct -->
    <!-- Keyword mask 0x200 is now defunct -->
    <keyword name="SecurityKeyword" mask="0x400" message="$(string.RuntimePublisher.SecurityKeywordMessage)" symbol="CLR_SECURITY_KEYWORD"/>
    <keyword name="AppDomainResourceManagementKeyword" mask="0x800" message="$(string.RuntimePublisher.AppDomainResourceManagementKeywordMessage)" symbol="CLR_APPDOMAINRESOURCEMANAGEMENT_KEYWORD"/>
    <keyword name="JitTracingKeyword" mask="0x1000" message="$(string.RuntimePublisher.JitTracingKeywordMessage)" symbol="CLR_JITTRACING_KEYWORD"/>
    <keyword name="InteropKeyword" mask="0x2000" message="$(string.RuntimePublisher.InteropKeywordMessage)" symbol="CLR_INTEROP_KEYWORD"/>
    <keyword name="ContentionKeyword" mask="0x4000" message="$(string.RuntimePublisher.ContentionKeywordMessage)" symbol="CLR_CONTENTION_KEYWORD"/>
    <keyword name="ExceptionKeyword" mask="0x8000" message="$(string.RuntimePublisher.ExceptionKeywordMessage)" symbol="CLR_EXCEPTION_KEYWORD"/>
    <keyword name="ThreadingKeyword" mask="0x10000" message="$(string.RuntimePublisher.ThreadingKeywordMessage)" symbol="CLR_THREADING_KEYWORD"/>
    <keyword name="JittedMethodILToNativeMapKeyword" mask="0x20000" message="$(string.RuntimePublisher.JittedMethodILToNativeMapKeywordMessage)" symbol="CLR_JITTEDMETHODILTONATIVEMAP_KEYWORD"/>
    <keyword name="OverrideAndSuppressNGenEventsKeyword" mask="0x40000" message="$(string.RuntimePublisher.OverrideAndSuppressNGenEventsKeywordMessage)" symbol="CLR_OVERRIDEANDSUPPRESSNGENEVENTS_KEYWORD"/>
    <keyword name="TypeKeyword" mask="0x80000" message="$(string.RuntimePublisher.TypeKeywordMessage)" symbol="CLR_TYPE_KEYWORD"/>
    <keyword name="GCHeapDumpKeyword" mask="0x100000" message="$(string.RuntimePublisher.GCHeapDumpKeywordMessage)" symbol="CLR_GCHEAPDUMP_KEYWORD"/>
    <keyword name="GCSampledObjectAllocationHighKeyword" mask="0x200000" message="$(string.RuntimePublisher.GCSampledObjectAllocationHighKeywordMessage)" symbol="CLR_GCHEAPALLOCHIGH_KEYWORD"/>
    <keyword name="GCHeapSurvivalAndMovementKeyword" mask="0x400000" message="$(string.RuntimePublisher.GCHeapSurvivalAndMovementKeywordMessage)" symbol="CLR_GCHEAPSURVIVALANDMOVEMENT_KEYWORD"/>
    <keyword name="GCHeapCollectKeyword" mask="0x800000" message="$(string.RuntimePublisher.GCHeapCollectKeyword)" symbol="CLR_GCHEAPCOLLECT_KEYWORD"/>
    <keyword name="GCHeapAndTypeNamesKeyword" mask="0x1000000" message="$(string.RuntimePublisher.GCHeapAndTypeNamesKeyword)" symbol="CLR_GCHEAPANDTYPENAMES_KEYWORD"/>
    <keyword name="GCSampledObjectAllocationLowKeyword" mask="0x2000000" message="$(string.RuntimePublisher.GCSampledObjectAllocationLowKeywordMessage)" symbol="CLR_GCHEAPALLOCLOW_KEYWORD"/>
    <keyword name="PerfTrackKeyword" mask="0x20000000" message="$(string.RuntimePublisher.PerfTrackKeywordMessage)" symbol="CLR_PERFTRACK_KEYWORD"/>
    <keyword name="StackKeyword" mask="0x40000000" message="$(string.RuntimePublisher.StackKeywordMessage)" symbol="CLR_STACK_KEYWORD"/>
    <keyword name="ThreadTransferKeyword" mask="0x80000000" message="$(string.RuntimePublisher.ThreadTransferKeywordMessage)" symbol="CLR_THREADTRANSFER_KEYWORD"/>
    <keyword name="DebuggerKeyword" mask="0x100000000" message="$(string.RuntimePublisher.DebuggerKeywordMessage)" symbol="CLR_DEBUGGER_KEYWORD"/>
    <keyword name="MonitoringKeyword" mask="0x200000000" message="$(string.RuntimePublisher.MonitoringKeywordMessage)" symbol="CLR_MONITORING_KEYWORD"/>
</keywords>

...[생략]...

이런 식으로 ETW Provider 측에서 제공하는 "Keywords"에 기반을 둔 필터링은 EnableProvider2 메서드의 5번째 인자에 전달하면 되고,

ulong matchAnyKeywords = (ulong)ClrProviderKeywords.Exception;

result = NativeMethods.EnableTraceEx2(sessionHandle, ref clrProvider,
    NativeMethods.EVENT_CONTROL_CODE_ENABLE_PROVIDER, (byte)TraceEventLevel.Informational,
    matchAnyKeywords, 0, 0, ref enableParameters);

따라서 위의 코드는 Exception에 대한 조건만을 전달했으므로 이후 콜백 함수는 Exception이 발생했을 때에만 호출되는 것을 확인할 수 있습니다.




EnableTraceEx2의 6번째 인자를 보면 또 다른 필터링 조건이 있는데, 다음의 글에서 자세한 설명을 볼 수 있습니다.

Tampering with Windows Event Tracing: Background, Offense, and Defense
; https://medium.com/palantir/tampering-with-windows-event-tracing-background-offense-and-defense-4be7ac62ac63

Microsoft.Diagnostics.Tracing.TraceEvent 라이브러리에는 다음과 같이 정의되어 있고,

// Used int the EVENT_FILTER_DESCRIPTOR.Type field 
internal const int EVENT_FILTER_TYPE_NONE = (0x00000000);
internal const int EVENT_FILTER_TYPE_SCHEMATIZED = unchecked((int)(0x80000000));
internal const int EVENT_FILTER_TYPE_SYSTEM_FLAGS = unchecked((int)(0x80000001));
internal const int EVENT_FILTER_TYPE_TRACEHANDLE = unchecked((int)(0x80000002));      // Used with CAPTURE_STATE to get a rundown delivered only to your session
internal const int EVENT_FILTER_TYPE_PID = unchecked((int)(0x80000004));              // Ptr points at array of ints.   (Size determined by byteSize/sizeof(int)
internal const int EVENT_FILTER_TYPE_EXECUTABLE_NAME = unchecked((int)(0x80000008));  // Ptr points at string, can have ';' to separate names. 
internal const int EVENT_FILTER_TYPE_PACKAGE_ID = unchecked((int)(0x80000010));       // Ptr points at string, can have ';' to separate names.
internal const int EVENT_FILTER_TYPE_PACKAGE_APP_ID = unchecked((int)(0x80000020));   // Package Relative App Id = (PRAID);
internal const int EVENT_FILTER_TYPE_PAYLOAD = unchecked((int)(0x80000100));          // Can filter on 
internal const int EVENT_FILTER_TYPE_EVENT_ID = unchecked((int)(0x80000200));         // Ptr points at EVENT_FILTER_EVENT_ID
internal const int EVENT_FILTER_TYPE_STACKWALK = unchecked((int)(0x80001000));        // Ptr points at EVENT_FILTER_EVENT_ID

가장 관심 있는 항목이 PID와 같은 조건이 될 수 있습니다. 사실 시스템 전체에 걸쳐서 이벤트가 오기 때문에 잦은 콜백 함수는 시스템 부하를 늘리므로, 가령 Process ID를 필터링 조건으로 지정해 특정 프로세스에 해당하는 이벤트만 받게 해도 부담이 줄기 때문입니다.

단지, 6번째 인자에 해당하는 필터링 조건은 운영체제 제한이 있는 듯한데, Microsoft.Diagnostics.Tracing.TraceEvent의 소스 코드에 윈도우 8.1부터 가능하다고 나옵니다.

/// <summary>
/// This return true on OS version beyond 8.1 (windows Version 6.3).   It means most of the
/// per-event filtering is supported.  
/// </summary>
public static bool FilteringSupported
{
    get
    {
        if (!s_IsEtwFilteringSupported.HasValue)
        {
            var ret = false;

            // For Windows Versions above windows 8, OSVersion lies and returns 6.2 (window 8) even though
            // the windows version is higher.  We have to try harder to figure out whether we are windows 8 or something
            // later.   Currently we look at the file version number of an OS DLL.  
            // There is probably a better way.   
            var winDir = Environment.GetEnvironmentVariable("WinDir");
            var kernel32 = Path.Combine(winDir, @"system32\Kernel32.dll");
            if (File.Exists(kernel32))
            {
                using (var kernel32PE = new PEFile.PEFile(kernel32))
                {
                    var versionInfo = kernel32PE.GetFileVersionInfo();
                    if (versionInfo != null)
                    {
                        // versionInfo.FileVersion is now the real version number we want but it is a string, not a 
                        // number.   Our tests is if version number bigger than 6.3 (as a string) or a two or more digit 
                        // major version.   
                        if (string.Compare("6.3", versionInfo.FileVersion) <= 0 || 2 <= versionInfo.FileVersion.IndexOf('.'))
                        {
                            ret = true;
                        }
                    }
                }
            }
            s_IsEtwFilteringSupported = ret;
        }
        return s_IsEtwFilteringSupported.Value;
    }
}

이에 대한 구현 코드는, 사실상 Microsoft.Diagnostics.Tracing.TraceEvent 소스 코드를 그대로 베끼는 것이 더 나을 정도이므로 여기서는 생략하겠습니다. ^^

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




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

[연관 글]






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

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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  81  82  83  84  [85]  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11812정성태2/11/201915723오류 유형: 511. Windows Server 2003 VM 부팅 후 로그인 시점에 0xC0000005 BSOD 발생
11811정성태2/11/201921079오류 유형: 510. 서버 운영체제에 NVIDIA GeForce Experience 실행 시 wlanapi.dll 누락 문제
11810정성태2/11/201918628.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
11809정성태2/11/201920790.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
11808정성태2/8/201922125디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
11807정성태1/29/201920026Windows: 156. 가상 디스크의 용량을 복구 파티션으로 인해 늘리지 못하는 경우 [4]
11806정성태1/29/201919661디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/201921838.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [4]파일 다운로드1
11804정성태1/24/201919671Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/201918602디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/201920245.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/201921565개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/201820635오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/201822429.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/201821258개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/201817249개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
11796정성태12/19/201817587개발 환경 구성: 424. vcpkg - "File does not have expected hash" 오류를 무시하는 방법
11795정성태12/19/201820899Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회 [1]
11794정성태12/16/201816950오류 유형: 508. Get-AzureWebsite : Request to a downlevel service failed.
11793정성태12/16/201819528개발 환경 구성: 423. NuGet 패키지 제작 - Native와 Managed DLL을 분리하는 방법 [1]
11792정성태12/11/201819191Graphics: 34. .NET으로 구현하는 OpenGL (11) - Per-Pixel Lighting파일 다운로드1
11791정성태12/11/201819241VS.NET IDE: 130. C/C++ 프로젝트의 시작 프로그램으로 .NET Core EXE를 지정하는 경우 닷넷 디버깅이 안 되는 문제 [1]
11790정성태12/11/201817776오류 유형: 507. Could not save daemon configuration to C:\ProgramData\Docker\config\daemon.json: Access to the path 'C:\ProgramData\Docker\config' is denied.
11789정성태12/10/201831458Windows: 153. C# - USB 장치의 연결 및 해제 알림을 위한 WM_DEVICECHANGE 메시지 처리 [2]파일 다운로드2
11788정성태12/4/201817663오류 유형: 506. SqlClient - Value was either too large or too small for an Int32.Couldn't store <2151292191> in ... Column
11787정성태11/29/201821842Graphics: 33. .NET으로 구현하는 OpenGL (9), (10) - OBJ File Format, Loading 3D Models파일 다운로드1
... 76  77  78  79  80  81  82  83  84  [85]  86  87  88  89  90  ...