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

(시리즈 글이 12개 있습니다.)
.NET Framework: 811. (번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes
; https://www.sysnet.pe.kr/2/0/11838

.NET Framework: 816. (번역글) .NET Internals Cookbook Part 2 - GC-related things
; https://www.sysnet.pe.kr/2/0/11869

.NET Framework: 818. (번역글) .NET Internals Cookbook Part 3 - Initialization tricks
; https://www.sysnet.pe.kr/2/0/11871

.NET Framework: 819. (번역글) .NET Internals Cookbook Part 4 - Type members
; https://www.sysnet.pe.kr/2/0/11872

.NET Framework: 820. (번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers
; https://www.sysnet.pe.kr/2/0/11873

.NET Framework: 821. (번역글) .NET Internals Cookbook Part 6 - Object internals
; https://www.sysnet.pe.kr/2/0/11874

.NET Framework: 822. (번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others
; https://www.sysnet.pe.kr/2/0/11876

.NET Framework: 823. (번역글) .NET Internals Cookbook Part 8 - C# gotchas
; https://www.sysnet.pe.kr/2/0/11877

.NET Framework: 824. (번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff
; https://www.sysnet.pe.kr/2/0/11878

.NET Framework: 825. (번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others
; https://www.sysnet.pe.kr/2/0/11879

.NET Framework: 826. (번역글) .NET Internals Cookbook Part 11 - Various C# riddles
; https://www.sysnet.pe.kr/2/0/11882

.NET Framework: 831. (번역글) .NET Internals Cookbook Part 12 - Memory structure, attributes, handles
; https://www.sysnet.pe.kr/2/0/11891




(번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes

이번 글은 다음의 시리즈에 대한 번역 글입니다. ^^ (참고로, 1:1 식으로 번역하지 않았습니다.)

.NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes
; https://blog.adamfurmanek.pl/2019/02/16/net-internals-cookbook-part-1/

글의 내용이 너무 좋아서 ^^ 많은 분들에게 알리고 싶어 원 저자의 허락을 받아 게시하는 것인데, 혹시 내용이 틀린 부분이 있다면 덧글 부탁드립니다.

(첨부 파일은 이 글의 예제 프로젝트를 포함합니다.)





1. System.Exception으로부터 상속받지 않은 객체를 throw로 던지면 어떻게 될까요?

C#의 경우에는 가능하지 않지만 C++/CLI와 같은 언어에서는 System.Exception 객체로부터 상속받지 않은 객체도 throw하는 것이 가능합니다. 예를 들어, integer, string, byte 등의 인스턴스를 직접 throw할 수 있는데 CLR은 이것을 일반 예외와 구분하기 위해 System.Runtime.CompilerServices.RuntimeWrappedException이라는 예외를 따로 제공합니다.

하지만 초기 .NET 1.x 버전에서는 그와 같은 (System.Exception 상속 이외의) 객체들이 RuntimeWrappedException 예외로 처리되지 않고 조건이 없는 catch 문으로만 다룰 수 있었습니다. 그래서 그 당시에는 다음과 같은 식의 예외 처리가 의미가 있었습니다.

// .NET 1.x
try
{
}
catch (Exception e) // System.Exception 및 그의 상속 객체들
{
}
catch // C++/CLI 등의 언어에서 throw할 수 있는 System.Exception을 상속하지 않은 객체들
{
}

위의 코드는 C#의 경우 .NET 2.0부터 컴파일 경고가 발생하는데,

warning CS1058: A previous catch clause already catches all exceptions. All non-exceptions thrown will be wrapped in a System.Runtime.CompilerServices.RuntimeWrappedException.

마지막 catch 문이 절대 실행되지 않기 때문입니다. 즉, .NET 2.0부터는 다음과 같이 마이그레이션을 해야 합니다.

// .NET 2.0 ~ (now)
try
{
}
catch (RuntimeWrappedException e) // C++/CLI 등의 언어에서 throw할 수 있는 System.Exception을 상속하지 않은 객체들
{
}
catch (Exception) // System.Exception 및 그의 상속 객체들
{
}

만약 호환을 위해 .NET 1.x과 같이 처리하고 싶다면 RuntimeCompatibilityAttribute 특성을 설정해야 합니다.

[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)]

실제로 테스트를 해볼까요? ^^

다음의 C/C++ CLI 코드가 있을 때,

public ref class MyTest
{
public:
    void DoMethod()
    {
        throw 5;
    }

    void DoMethod2()
    {
        throw gcnew System::Int32(5);
    }
};

C#에서 이 코드를 다음과 같이 처리해 보면,

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = true)]

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyTest my = new MyTest();

            Test(() => my.DoMethod());
            Test(() => my.DoMethod2());
        }

        private static void Test(Action action)
        {
            try
            {
                action();
            }
            catch (RuntimeWrappedException e1) // C++/CLI 등의 언어에서 throw할 수 있는 System.Exception을 상속하지 않은 관리 객체들
            {
                Console.WriteLine(e1.ToString());
            }
            catch (SEHException e2) // C++/CLI 등의 언어에서 throw할 수 있는 System.Exception을 상속하지 않은 비-관리 객체들
            {
                Console.WriteLine(e2.ToString());
            }
            catch (Exception e3) // System.Exception 및 그의 상속 객체들
            {
                Console.WriteLine(e3.ToString());
            }
            catch
            {
                Console.WriteLine("untyped catch");
            }
        }
    }
}

WrapNonExceptionThrows = true인 실행 결과와,

throw 5
    System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception.
       at _CxxThrowException(Void* , _s__ThrowInfo* )
       at MyTest.DoMethod() in F:\...\ConsoleApp1\Dll1\pch.h:line 19
       at ConsoleApp1.Program.<>c__DisplayClass0_0.<Main>b__0() in F:\...\ConsoleApp1\ConsoleApp1\Program.cs:line 15
       at ConsoleApp1.Program.Test(Action action) in F:\...\ConsoleApp1\ConsoleApp1\Program.cs:line 23

throw gcnew System::Int32(5)
    System.Runtime.CompilerServices.RuntimeWrappedException: An object that does not derive from System.Exception has been wrapped in a RuntimeWrappedException.
       at MyTest.DoMethod2() in F:\...\ConsoleApp1\Dll1\pch.h:line 23
       at ConsoleApp1.Program.<>c__DisplayClass0_0.<Main>b__1() in F:\...\ConsoleApp1\ConsoleApp1\Program.cs:line 16
       at ConsoleApp1.Program.Test(Action action) in F:\...\ConsoleApp1\ConsoleApp1\Program.cs:line 23

WrapNonExceptionThrows = false인 결과가 다릅니다.

throw 5
    System.Runtime.InteropServices.SEHException (0x80004005): External component has thrown an exception.
       at _CxxThrowException(Void* , _s__ThrowInfo* )
       at MyTest.DoMethod() in F:\...\ConsoleApp1\Dll1\pch.h:line 19
       at ConsoleApp1.Program.<>c__DisplayClass0_0.<Main>b__0() in F:\...\ConsoleApp1\ConsoleApp1\Program.cs:line 15
       at ConsoleApp1.Program.Test(Action action) in F:\...\ConsoleApp1\ConsoleApp1\Program.cs:line 23

throw gcnew System::Int32(5)
    untyped catch





2. ThreadAbortException을 무시할 수 있을까요?

ThreadAbortException 예외는 잡을 수는 있지만 기본적으로 무시하진 않습니다. 만약 무시하고 싶다면 명시적으로 Thread.ResetAbort() 메서드를 호출해야 합니다. 다시 말하면, Thread.Abort를 호출했다고 해서 언제나 해당 스레드가 종료된다고 보장할 수는 없습니다.

또 한가지 재미있는 점은, Thread.Abort를 호출하면 스레드의 나머지 부분은 실행되지 않지만 finally 구문에 작성한 코드는 여전히 실행된다는 점입니다. 결국 대상 스레드가 finally 절의 코드를 수행 중이라면 Thread.Abort는 그것조차도 막을 수 없다는 것입니다. 실제로 CER (Constrained Execution Regions) 처리 시 반드시 수행되어야 하는 보호 코드를 finally 블록에 넣어야 하는 것에는 이런 이유도 있습니다.

참고로, Thread.Abort의 내부 구현은 다음과 같은 식입니다.

  1. OS 스레드를 중지(Suspend)시키고,
  2. 스레드 중지가 요청되었음을 알리는 메타데이터 비트를 설정 후,
  3. 해당 스레드의 APC 큐에 작업을 추가하고,
  4. OS 스레드를 다시 시작(Resume)

따라서 Thread.Abort가 호출되면 스레드는 잠시 멈췄다가 다시 실행이 되는데, APC 큐의 작업은 이후 스레드가 alertable 상태로 진입할 때 실행됩니다. 그때 위의 2번 작업에서 수행한 비트 설정 유무를 체크해서 실질적으로 스레드 종료 작업을 밟게 되는데, 만약 이 과정에서 스레드가 자발적인 alertable 상태로 진입하지 않으면 CLR은 스레드의 IP 레지스터를 강제로 설정하는 방식으로 결국 APC 작업이 수행되도록 합니다.





3. AccessViolationException 유의 예외를 잡을 수 있을까요?

그와 같은 예외를 잡으려면 해당 메서드에 대해 HandleProcessCorruptedStateExceptionsAttribute 특성을 지정해야 합니다. 예를 들어 볼까요? ^^

using System;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            MyMethod();
        }

        public static int MyMethod()
        {
            try
            {
                Marshal.Copy(new byte[] { 42 }, 0, (IntPtr)1000, 1);
            }
            catch (Exception e)
            {
                System.Console.WriteLine("Catched: " + e.ToString());
                return 1;
            }

            return 0;
        }
    }
}

위의 예제는, System.AccessViolationException 예외가 발생하지만 catch 문의 코드가 실행되지 않습니다. 반면 HandleProcessCorruptedStateExceptions 특성을 MyMethod에 다음과 같이 지정하면,

[HandleProcessCorruptedStateExceptions]
public static int MyMethod()
{
    // ...[생략]...
}

이번에는 MyMethod 안에 있는 catch 문의 코드가 실행됩니다. 위의 상황은 .NET 2.0부터 바뀐 것이고 .NET 1.x 시절에는 무조건 catch 문으로 AV 예외를 잡을 수 있었습니다. 만약 .NET 2.0에서도 HandleProcessCorruptedStateExceptions 특성 없이 AV 예외를 잡고 싶다면 app.config에 다음과 같은 설정을 하면 됩니다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <runtime>
        <legacyCorruptedStateExceptionsPolicy enabled="true" />
    </runtime>
</configuration>

이렇게 바꾸고 HandleProcessCorruptedStateExceptions 특성을 주석 처리해도 catch 문의 코드가 실행되는 걸 확인할 수 있습니다. 그런데 사실 .NET 1.x에서는 System.AccessViolationException 타입도 없었습니다. 원래는 .NET 1.x의 경우 AV 예외를 System.NullReferenceException으로 처리했는데요, 이것도 .NET 2.0 이상에서 하위 호환을 위해 1.x처럼 AV 예외를 null 참조 예외로 바꿀 수 있도록 옵션이 제공됩니다.

<legacyNullReferenceExceptionPolicy enabled="true" />

따라서, legacyCorruptedStateExceptionsPolicy, legacyNullReferenceExceptionPolicy 옵션이 모두 true라면 HandleProcessCorruptedStateExceptions 특성이 없어도 AV 예외가 .NET 1.x와 동일하게 System.NullReferenceException으로 잡히게 됩니다.





4. finally 블록의 코드가 실행되지 않거나, finally가 중첩된 경우 특정 finally만 실행되는 경우도 있나요?

두 가지 상황 모두 발생할 수 있습니다.

우선 첫 번째 상황의 경우, 예를 들어 응용 프로그램을 작업 관리자 등을 이용해 강제 종료를 하면 finally 코드가 수행되지 않는 것을 확인할 수 있습니다. 또는, 코드로 테스트해 보려면 Environment.FailFast 메서드로 재현할 수 있습니다.

using System;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Thread.Sleep(1000 * 5);
            Environment.FailFast("FailFast called!");
        }
        finally
        {
            Console.WriteLine("Finally");
        }
    }
}

이 프로그램은 5초 후에 Environment.FailFast가 호출되지만 "Finally" 문자열 출력을 볼 수 없습니다.

두 번째 상황의 경우는 이전에 설명한 HandleProcessCorruptedStateExceptions의 도움을 받아 재현할 수 있습니다. 가령 다음과 같이 try/finally 블록이 있는 경우,

private static void Method1()
{
    try
    {
        Method2();
    }
    finally
    {
        Console.WriteLine("Method 1");
    }
}

private static void Method2()
{
    try
    {
        Method3();
    }
    finally
    {
        Console.WriteLine("Method 2");
    }
}

private static void Method3()
{
    throw new ApplicationException("TEST");
}

Method3에서의 예외로 인해 Method2의 finally와 Method1의 finally가 차례대로 실행됩니다. 반면, Method3에서 System.AccessViolationException이 발생하는 상황에서 Method1에만 HandleProcessCorruptedStateExceptions 특성을 지정했다면,

[HandleProcessCorruptedStateExceptions]
private static void Method1()
{
    try
    {
        Method2();
    }
    finally
    {
        Console.WriteLine("Method 1");
    }
}

private static void Method2()
{
    try
    {
        Method3();
    }
    finally
    {
        Console.WriteLine("Method 2");
    }
}

private static void Method3()
{
    Marshal.Copy(new byte[] { 42 }, 0, (IntPtr)1000, 1);
}

이번에는 화면에 Method1의 finally 블록만 실행되는 것을 확인할 수 있습니다.





5. fault란?

C#에서 지원하는 예외 처리 블록은 try, catch, finally로 3가지가 있습니다. 반면 IL 수준으로 내려가면 fault 블록을 하나 더 정의할 수 있는데 이는 예외가 발생하는 경우에만 실행됩니다. (finally의 경우 예외 유무에 상관없이 실행되는 것과 비교됩니다.)

따라서 IL 코드로 다음과 같이 작성해 ilasm.exe로 컴파일하면 fault 영역을 테스트할 수 있습니다.

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly 'bacccde7-828a-4616-94e8-da4332f65171'
{
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module 'bacccde7-828a-4616-94e8-da4332f65171.dll'
// MVID: {63E2991D-2F64-4FE2-8CF3-0A1E1F6A1FD1}
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x01280000

.class public auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main() cil managed
  {
    // 
    .entrypoint
    .maxstack  1
    .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
    .line 6,6 : 2,3 ''
    IL_0000:  nop
    .line 7,7 : 6,7 ''
    .try
    {
      IL_0001:  nop
      .line 9,9 : 3,4 ''
      IL_0002:  nop
      IL_0003:  leave.s    IL_0013

      .line 9,9 : 11,12 ''
    }  // end .try
    fault
    {
      IL_0005:  nop
      .line 10,10 : 4,52 ''
      IL_0006:  ldstr      "This will not be executed."
      IL_000b:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_0010:  nop
      .line 11,11 : 3,4 ''
      IL_0011:  nop
      IL_0012:  endfinally
      .line 16707566,16707566 : 0,0 ''
    }  // end handler
    IL_0013:  nop
    .line 13,13 : 6,7 ''
    .try
    {
      IL_0014:  nop
      .line 14,14 : 4,42 ''
      IL_0015:  ldstr      "Some exception"
      IL_001a:  newobj     instance void [mscorlib]System.Exception::.ctor(string)
      IL_001f:  throw

      .line 15,15 : 11,12 ''
    }  // end .try
    fault
    {
      IL_0020:  nop
      .line 16,16 : 4,44 ''
      IL_0021:  ldstr      "You will see this."
      IL_0026:  call       void [mscorlib]System.Console::WriteLine(string)
      IL_002b:  nop
      .line 17,17 : 3,4 ''
      IL_002c:  nop
      IL_002d:  endfinally
    }  // end handler
  } // end of method Program::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  } // end of method Program::.ctor

} // end of class Program





6. 예외 필터(exception filter)란?

예외를 바로 처리하는 대신에, 해당 예외의 조건을 미리 검사한 후 처리할 수 있는 구문이 바로 "예외 필터"입니다. 다른 시리즈의 글(Part 2 - Handling and rethrowing exceptions in C#)에서 예외 처리 구조에 대해 언급했지만 필터의 경우 두 단계로 이뤄진 예외 처리 구조에서 첫 번째 시점에 실행된다는 차이점이 있습니다.

그렇다면 이것이 기존 구문의 조합으로 가능한 간편 표기법(syntax sugar)일까요? 이를 위해 다음의 코드를 빌드하고,

using System;

public class Program
{
    public static void Main()
    {
        try
        {
        }
        catch (Exception e) when (e.Message == "Message") // C# 6.0부터 예외 필터 지원
        {
        }
    }
}

IL 코드로 보면,

.try
{
  IL_0001:  nop
  .line 9,9 : 3,4 ''
  IL_0002:  nop
  IL_0003:  leave.s    IL_002e

  .line 16707566,16707566 : 0,0 ''
}  // end .try
filter
{
  IL_0005:  isinst     [mscorlib]System.Exception
  IL_000a:  dup
  IL_000b:  brtrue.s   IL_0011

  IL_000d:  pop
  IL_000e:  ldc.i4.0
  IL_000f:  br.s       IL_0027

  IL_0011:  stloc.0
  .line 10,10 : 22,51 ''
  IL_0012:  ldloc.0
  IL_0013:  callvirt   instance string [mscorlib]System.Exception::get_Message()
  IL_0018:  ldstr      "Message"
  IL_001d:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_0022:  stloc.1
  .line 16707566,16707566 : 0,0 ''
  IL_0023:  ldloc.1
  IL_0024:  ldc.i4.0
  IL_0025:  cgt.un
  IL_0027:  endfilter
  .line 16707566,16707566 : 0,0 ''
}  // end filter
{  // handler
  IL_0029:  pop
  .line 10,10 : 51,52 ''
  IL_002a:  nop
  .line 11,11 : 3,4 ''
  IL_002b:  nop
  IL_002c:  leave.s    IL_002e

  .line 12,12 : 2,3 ''
}  // end handler

별도의 filter 영역이 지정된 것을 볼 수 있습니다. 즉, 간단하게 또 다른 C# 코드로 엮어낼 수 있는 syntax sugar 구현이 아닙니다.




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







[최초 등록일: ]
[최종 수정일: 4/19/2024]

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

비밀번호

댓글 작성자
 



2019-03-13 12시20분
[kernel] filter 는 stack unwinding 이 안되어서 덤프만들때 잘 쓰고 있습니다! (https://www.thomaslevesque.com/2015/06/21/exception-filters-in-c-6/ ) 번역해주셔서 고맙습니다!
[guest]

... 121  122  123  124  125  126  127  [128]  129  130  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1855정성태2/10/201520795개발 환경 구성: 256. WebDAV Redirector - Sysinternals 폴더 연결 시 "The network path was not found" 오류 해결 방법
1854정성태2/10/201521762Windows: 104. 폴더는 삭제할 수 없지만, 그 하위 폴더/파일은 생성/삭제/변경하는 보안 설정
1853정성태2/6/201552055웹: 29. 여신금융협회 웹 사이트의 "Netscape 6.0은 지원하지 않습니다." 오류 메시지 [5]
1852정성태2/5/201522463.NET Framework: 492. .NET CLR Memory 성능 카운터의 의미파일 다운로드1
1851정성태2/5/201523395VC++: 88. 하룻밤의 꿈 - 인텔 하스웰의 TSX Instruction 지원 [2]
1850정성태2/4/201544187Windows: 103. 작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미 [4]
1849정성태2/4/201524177기타: 51. DropBox의 CPU 100% 현상 [1]파일 다운로드1
1848정성태2/4/201519410.NET Framework: 491. 닷넷 Generic 타입의 메타 데이터 토큰 값 알아내는 방법 [2]
1847정성태2/3/201522684기타: 50. C# - 윈도우에서 dropbox 동기화 폴더 경로 및 종료하는 방법
1846정성태2/2/201532006Windows: 102. 제어판의 프로그램 추가/삭제 항목을 수동으로 실행하고 싶다면? [1]
1845정성태1/26/201532886Windows: 101. 제어판의 "Windows 자격 증명 관리(Manage your credentials)"를 금지시키는 방법
1844정성태1/26/201530844오류 유형: 269. USB 메모리의 용량이 비정상적으로 보여진다면? [7]
1843정성태1/24/201521899VC++: 87. 무시할 수 없는 Visual C++ 런타임 함수 성능
1842정성태1/23/201544393개발 환경 구성: 255. 노트북 키보드에 없는 BREAK 키를 다른 키로 대체하는 방법
1841정성태1/21/201519371오류 유형: 268. Win32 핸들 관련 CLR4 보안 오류 사례
1840정성태1/8/201527602오류 유형: 267. Visual Studio - CodeLens 사용 시 CPU 100% 현상
1839정성태1/5/201520515디버깅 기술: 69. windbg 분석 사례 - cpu 100% 현상 (2)
1838정성태1/4/201540231기타: 49. 윈도우 내레이터(Narrator) 기능 끄는 방법(윈도우에 파란색의 굵은 테두리 선이 나타난다면?) [4]
1837정성태1/4/201526327디버깅 기술: 68. windbg 분석 사례 - 메모리 부족 [1]
1836정성태1/4/201526341디버깅 기술: 67. windbg - 덤프 파일과 handle 정보
1835정성태1/3/201526814개발 환경 구성: 254. SQL 서버 역시 SSL 3.0/TLS 1.0만을 지원하는 듯!
1834정성태1/3/201551441개발 환경 구성: 253. TLS 1.2를 적용한 IIS 웹 사이트 구성
1833정성태1/3/201527508.NET Framework: 490. System.Data.SqlClient는 SSL 3.0/TLS 1.0만 지원하는 듯! [3]
1832정성태1/2/201520620오류 유형: 266. Azure에 응용 프로그램 게시 중 로그인 오류
1831정성태1/1/201528514디버깅 기술: 66. windbg 분석 사례 - cpu 100% 현상 (1) [1]
1830정성태1/1/201527556오류 유형: 265. svchost.exe 프로세스(IP Helper: IPHLPSVC)의 CPU 100% 현상
... 121  122  123  124  125  126  127  [128]  129  130  131  132  133  134  135  ...