Microsoft MVP성태의 닷넷 이야기
디버깅 기술: 40. 상황별 GetFunctionPointer 반환값 정리 - x86 [링크 복사], [링크+제목 복사]
조회: 20480
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 16개 있습니다.)
VC++: 36. Detours 라이브러리를 이용한 Win32 API - Sleep 호출 가로채기
; https://www.sysnet.pe.kr/2/0/631

.NET Framework: 187. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선
; https://www.sysnet.pe.kr/2/0/942

디버깅 기술: 40. 상황별 GetFunctionPointer 반환값 정리 - x86
; https://www.sysnet.pe.kr/2/0/1027

VC++: 56. Win32 API 후킹 - Trampoline API Hooking
; https://www.sysnet.pe.kr/2/0/1231

VC++: 57. 웹 브라우저에서 Flash만 빼고 다른 ActiveX를 차단할 수 있을까?
; https://www.sysnet.pe.kr/2/0/1232

VC++: 58. API Hooking - 64비트를 고려해야 한다면? EasyHook!
; https://www.sysnet.pe.kr/2/0/1242

개발 환경 구성: 419. MIT 라이선스로 무료 공개된 Detours API 후킹 라이브러리
; https://www.sysnet.pe.kr/2/0/11764

.NET Framework: 883. C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기)
; https://www.sysnet.pe.kr/2/0/12132

.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64
; https://www.sysnet.pe.kr/2/0/12143

.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12144

디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법
; https://www.sysnet.pe.kr/2/0/12148

.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법
; https://www.sysnet.pe.kr/2/0/12150

.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)
; https://www.sysnet.pe.kr/2/0/12151

.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)
; https://www.sysnet.pe.kr/2/0/12152

.NET Framework: 898. Trampoline을 이용한 후킹의 한계
; https://www.sysnet.pe.kr/2/0/12153

.NET Framework: 968. C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법
; https://www.sysnet.pe.kr/2/0/12409




상황별 GetFunctionPointer 반환값 정리 - x86

우선, 이 글을 읽기 전에 다음의 글을 읽어보시면 도움이 됩니다.

실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선
; https://www.sysnet.pe.kr/2/0/942

Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후
; https://www.sysnet.pe.kr/2/0/1023

간단히 설명하자면, RuntimeMethodHandle 타입에서 제공되는 GetFunctionPointer 메서드를 이용하면 해당 함수의 주소를 알 수가 있습니다. 예를 들어, 아래와 같은 식입니다.

=== 현재 호출 중인 함수의 주소를 알고 싶다면? ====
void Test()
{
    StackFrame st = new StackFrame(0);
    IntPtr ptr = st.GetMethod().MethodHandle.GetFunctionPointer();
}

=== 특정 메서드의 함수 주소를 알고 싶다면? ====
MethodBase func = typeof([...Type...]).GetMethod("[...MethodName...]", ...{예: BindingFlags.Static | BindingFlags.NonPublic }...);
IntPtr pMain = func.MethodHandle.GetFunctionPointer();

하지만, 이 값은 다양한 상황에서 약간의 차이를 보여주는데 개별 경우의 수에 따라 어떻게 다른지 알아보는 것이 이번 글의 목적입니다.

이후 설명하는 내용은 별다른 이야기가 없는 경우 기본적으로 다음과 같은 환경임을 가정합니다.

  • NET 4.0 Console Application
  • Debug Build
  • 체크 해제 - Enable the Visual Studio hosting process
  • 체크 - Enable unmanaged code debugging
  • 심벌 서버 지정 - 마이크로소프트 공용 PDB 심벌 파일 배포 서버




EXE 어셈블리의 Main 메서드


Main 메서드는 진입 함수이기 때문에 GetFunctionPointer를 이용하여 JIT 컴파일 이전 단계의 값을 알아낼 수는 없습니다. 따라서 JIT 이후의 값만 테스트 해볼 수 있는데요. Main 메서드에 대한 GetFunctionPointer 출력 결과는 실제 JIT 컴파일 된 기계어 코드의 주소가 출력되는 아주 표준적인 동작을 보여줍니다. 확인 과정은 다음과 같습니다.

static void Main(string[] args)
{
    ShowMainFunc();
}

static void ShowMainFunc()
{
    MethodBase func = typeof(Program).GetMethod("Main", BindingFlags.Static | BindingFlags.NonPublic);
    IntPtr pMain = func.MethodHandle.GetFunctionPointer();
    ClassLibrary1.Class1.OutputFunctionAddress("Main", pMain);
}

=== 출력 결과 ===

Main Function == 68d850

"Immediate Window"에서 sos.dll을 이용하여 확인해 보면 정확히 일치하는 것을 확인할 수 있습니다.

!load "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\SOS.dll"  
extension c:\windows\microsoft.net\framework\v4.0.30319\sos.dll loaded

!name2ee ConsoleApplication1.exe!ConsoleApplication1.Program.Main  
PDB symbol for clr.dll not loaded
Module:      00152e9c
Assembly:    ConsoleApplication1.exe
Token:       1eb2701706000001
MethodDesc:  001533f0
Name:        ConsoleApplication1.Program.Main(System.String[])
JITTED Code Address: 0068d850




같은 어셈블리에 정의된 메서드의 JIT 이전


아래와 같이 테스트 코드를 작성하고, GetFunctionPointer 값을 확인해 볼 수 있습니다.

static void Main(string[] args)
{
    Program pg = new Program();

    ShowTestFunc("Test1 - Before JITting", "Test1");
    pg.Test1();
}

private void Test1()
{
    Console.WriteLine("Test1 called!");
}

static void ShowTestFunc(string text, string methodName)
{
    MethodBase func = typeof(Program).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
    IntPtr pFunc = func.MethodHandle.GetFunctionPointer();
    ClassLibrary1.Class1.OutputFunctionAddress(text, pFunc);
}

=== 출력 결과 ===

Test1 - Before JITting Function == 2ac070

반면에, .NET Disassembly 창에서 값을 확인해 보면,

    22:             pg.Test1();
00000053 8B 4D F8             mov         ecx,dword ptr [ebp-8] 
00000056 39 09                cmp         dword ptr [ecx],ecx 
00000058 FF 15 04 34 2A 00    call        dword ptr ds:[002A3404h] 
0000005e 90                   nop 

getfunctionpointer_output_1.png

0x2ac015 값인데 0x2ac070과는 5B 값만큼의 차이가 납니다. 즉, JIT 되기 이전의 메서드에 대해서는 GetFunctionPointer가 가리키는 값이 Stub 주솟값이 아니라는 것을 알 수 있습니다.






같은 어셈블리에 정의된 메서드의 JIT 이후


이 경우에는 Main 메서드에서 확인한 것처럼 GetFunctionPointer 반환값과 JIT 컴파일된 메서드의 주솟값이 일치합니다.




다른 어셈블리에 정의된 메서드의 JIT 이전


이 경우에도 "같은 어셈블리에 정의된 메서드의 JIT 이전"에서 살펴본 대로 결과가 다르게 나옵니다. 단지 그때는 0x5B만큼의 차이가 났지만 이번에는 0x33만큼의 차이가 납니다. 사실 0x5B, 0x33이라는 절대적인 값의 차이는 의미가 없습니다. 왜냐하면 해당 클래스에 정의된 몇 번째의 메서드를 비교했느냐에도 값이 달라질 수 있기 때문에, 단지 의미가 있다면 어쨌든 'offset 값이 다르다' 는 정도가 될 것입니다.




다른 어셈블리에 정의된 메서드의 JIT 이후


이 경우에도 Main 메서드에서 확인한 것처럼 GetFunctionPointer 반환값과 JIT 컴파일 된 메서드의 주소값이 일치합니다.




NGen 된 BCL(Base Class Library) 메서드



다음은, 예제 코드와 결과입니다.

public static void ShowCreateCommandAddress()
{
    MethodBase func = typeof(SqlConnection).GetMethod("CreateCommand", BindingFlags.Instance | BindingFlags.Public);

    IntPtr pOld = func.MethodHandle.GetFunctionPointer();
    OutputFunctionAddress("CreateCommand", pOld);
}

=== 출력 결과 ===

CreateCommand Function == 5114fd34

위의 코드에 사용된 SqlConnection 타입을 담은 System.Data 어셈블리가 NGen 된 이미지 버전으로 사용되었음을 어떻게 알 수 있을까요? Visual Studio 의 디버그 상태에서 "Ctrl + D, M"으로 Modules 창을 띄우면 해당 어셈블리에 대해서 System.Data.ni.dll이 목록에 포함되어 있다는 사실로 알 수 있습니다.

getfunctionpointer_output_2.png

재미있는 점은, 특별히 DLL 로딩 주소의 충돌이 발생하지 않는 한 이미 기계어 코드로 출력된 상태이기 때문에, EXE 프로그램을 재실행하는 경우에도 JIT 컴파일 된 주솟값이 바뀌지 않습니다.

참고로, 이번 경우에는 지난 이야기에서도 한번 다룬 적이 있는데요.

Visual Studio의 .NET Disassembly 창의 call 호출에 사용되는 주소의 의미는?
; https://www.sysnet.pe.kr/2/0/1019

그때도 언급했지만, GetFunctionPointer 반환값(위에서는 0x5114fd34)은 메서드의 JIT 컴파일된 주소값이 아닙니다. 이에 대해서 다시 한번 sos.dll을 이용해서 알아보겠습니다.

!load "C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\SOS.dll"  
extension c:\windows\microsoft.net\framework\v4.0.30319\sos.dll loaded

!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand
PDB symbol for clr.dll not loaded
Module:      510b1000
Assembly:    System.Data.dll
Token:       4ce43204060024c9
MethodDesc:  510bbb38
Name:        System.Data.SqlClient.SqlConnection.CreateCommand()
JITTED Code Address: 51459370

위의 결과에서처럼 실제 "System.Data.SqlClient.SqlConnection.CreateCommand" 메서드의 body가 JIT 컴파일 되어 기계어로 출력된 주소는 "0x51459370"이고, 역어셈블을 해보면 본래의 메서드임을 알 수 있습니다.

!u 51459370
preJIT generated code
System.Data.SqlClient.SqlConnection.CreateCommand()
Begin 51459370, size 1f
>>> 51459370 57               push        edi
51459371 56               push        esi
51459372 8BF9             mov         edi,ecx
51459374 B9B0FC1851       mov         ecx,5118FCB0h (MT: System.Data.SqlClient.SqlCommand)
51459379 E81AF2CEFF       call        51148598 (?gpViaConns@@3PAPAVVia@@A)
5145937E 8BF0             mov         esi,eax
51459380 57               push        edi
51459381 8BCE             mov         ecx,esi
51459383 33D2             xor         edx,edx
51459385 E86205CFFF       call        511498EC (System.Data.SqlClient.SqlCommand..ctor(System.String, System.Data.SqlClient.SqlConnection), mdToken: 060023e2)
5145938A 8BC6             mov         eax,esi
5145938C 5E               pop         esi
5145938D 5F               pop         edi
5145938E C3               ret

그렇다고 GetFunctionPointer 반환값(위에서는 0x5114fd34)이 전혀 이상한 값이 나온 것은 아닙니다. 왜냐하면 역어셈블을 해보면 다음과 같이 jmp 구문이 나오기 때문입니다.

!u 5114fd34
Unmanaged code
5114FD34 B838BB0B51       mov         eax,510BBB38h
5114FD39 90               nop
5114FD3A E8418AFFFF       call        51148780
5114FD3F E92C963000       jmp         51459370
5114FD44 B854BB0B51       mov         eax,510BBB54h
5114FD49 90               nop
5114FD4A E8318AFFFF       call        51148780
5114FD4F E9248AFFFF       jmp         51148778
5114FD54 B8F0BB0B51       mov         eax,510BBBF0h
5114FD59 90               nop

실제로 코드에서 SqlConnection.CreateCommand를 사용한 경우, 그 호출을 포함한 메서드가 JIT 컴파일 될 때 기록되는 call에 사용되는 주소는 GetFunctionPointer 반환값과 일치합니다. 즉, JIT 컴파일 된 메서드의 기계어 코드에 대한 절대 주솟값을 직접 코드에서 다루지 않고 경유하는 지점을 별도로 두어 사용하는 것입니다.




NGen 되지 않은 BCL메서드의 JIT 이전 (또는, NGen 없이 GAC에 등록만 된 어셈블리)


NGen 되지 않은 BCL 함수라? 그게 어떻게 가능할까요? 바로 .NET Profiler를 이용하면 가능합니다. .NET Profiler의 옵션 중에서 COR_PRF_USE_PROFILE_IMAGES를 지정하게 되면 NGen 모듈을 사용하지 않는다고 이전에 설명을 했었지요. ^^

닷넷 프로파일러 - IL 코드 재작성
; https://www.sysnet.pe.kr/2/0/767

그렇게 해서 동일하게 SqlConnection.CreateCommand 메서드의 JIT 단계 이전에 GetFunctionPointer 값을 출력해 보니 0x30ce70이 나왔습니다. 물론, sos.dll로 확인해 보면,

!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand  

PDB symbol for clr.dll not loaded
Module:      00303bc0
Assembly:    System.Data.dll
Token:       2a469854060024c9
MethodDesc:  00b499a4
Name:        System.Data.SqlClient.SqlConnection.CreateCommand()
Not JITTED yet. Use !bpmd -md 00b499a4 to break on run.

정말로 JIT된 상태가 아니라고 나옵니다. 그런데, GetFunctionPointer 반환값(0x30ce70)이 재미있습니다. 역어셈블을 해보면 다음과 같이 정확한 stub 코드의 주솟값임을 알 수 있습니다.

!u 0x30ce70
Unmanaged code
0030CE70 B8A499B400       mov         eax,0B499A4h
0030CE75 90               nop
0030CE76 E865620970       call        703A30E0
0030CE7B E9D039FAFF       jmp         002B0850
0030CE80 0000             add         byte ptr [eax],al
0030CE82 0000             add         byte ptr [eax],al
0030CE84 0000             add         byte ptr [eax],al
0030CE86 0000             add         byte ptr [eax],al
0030CE88 0000             add         byte ptr [eax],al
0030CE8A 0000             add         byte ptr [eax],al

JIT 전 상태이니, 실제로 SqlConnection.CreateCommand를 호출하는 코드를 .NET Disassembly로 확인해 보면 다음과 같습니다.

private static void CallFunc()
{
            SqlConnection con = new SqlConnection();
    38:             con.CreateCommand();
00000035 8B 4D FC             mov         ecx,dword ptr [ebp-4] 
00000038 39 09                cmp         dword ptr [ecx],ecx 
0000003a E8 41 13 76 FD       call        FD761380 
0000003f 90                   nop 

오호~~~ call FF가 아니라 call E8 기계어 코드에 직접적인 변위값을 받고 있습니다. FD761380 주소가 어디인지 계산하기 위해서는 예전에 살펴봤던 그 방법을 사용해야 합니다.

Visual Studio의 .NET Disassembly 창의 call 호출에 사용되는 주소의 의미는?
; https://www.sysnet.pe.kr/2/0/1019

!name2ee ClassLibrary1.dll!ClassLibrary1.Class1.CallFunc
Module:      003034f4
Assembly:    ClassLibrary1.dll
Token:       2a449f8406000003
MethodDesc:  003039d8
Name:        ClassLibrary1.Class1.CallFunc()
JITTED Code Address: 02babaf0

!u 02babaf0
Normal JIT generated code
ClassLibrary1.Class1.CallFunc()
Begin 02babaf0, size 45
>>> 02BABAF0 55               push        ebp
02BABAF1 8BEC             mov         ebp,esp
02BABAF3 83EC08           sub         esp,8
02BABAF6 833D9437300000   cmp         dword ptr ds:[00303794h],0
02BABAFD 7405             je          02BABB04
02BABAFF E817A9AA6D       call        7065641B (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
02BABB04 33D2             xor         edx,edx
02BABB06 8955FC           mov         dword ptr [ebp-4],edx
02BABB09 90               nop
02BABB0A B9E09CB400       mov         ecx,0B49CE0h (MT: System.Data.SqlClient.SqlConnection)
02BABB0F E83E72816D       call        703C2D52 (JitHelp: CORINFO_HELP_NEW_CROSSCONTEXT)
02BABB14 8945F8           mov         dword ptr [ebp-8],eax
02BABB17 8B4DF8           mov         ecx,dword ptr [ebp-8]
02BABB1A E8795EE5FF       call        02A01998 (System.Data.SqlClient.SqlConnection..ctor(), mdToken: 060024ec)
02BABB1F 8B45F8           mov         eax,dword ptr [ebp-8]
02BABB22 8945FC           mov         dword ptr [ebp-4],eax
02BABB25 8B4DFC           mov         ecx,dword ptr [ebp-4]
02BABB28 3909             cmp         dword ptr [ecx],ecx
02BABB2A E8411376FD       call        0030CE70 (System.Data.SqlClient.SqlConnection.CreateCommand(), mdToken: 060024c9)
02BABB2F 90               nop
02BABB30 90               nop
02BABB31 8BE5             mov         esp,ebp
02BABB33 5D               pop         ebp
02BABB34 C3               ret

정말 맞는지 그때처럼 계산해 보면,

02BABB2F + FD761380 ==> 4byte값만 취하면 ==> 0x0030ceaf - (.NET Disassembly 창 변위) 0x3f == 0030CE70

정확하군요. ^^ 게다가 0x30ce70 값은 GetFunctionPointer로 반환받은 값과 동일합니다.

정리해 보면, NGen되지 않은 BCL 메서드의 경우 GetFunctionPointer 값은 Stub 코드 위치를 정확하게 가리키고 있다는 차이가 있습니다.




NGen 되지 않은 BCL 메서드의 JIT 이후


이것 또한 재미있습니다. NGen되기 이전에 GetFunctionPointer 값이 0030CE70이었는데, NGen 후에도 그 값은 그대로 변하지 않았습니다. 물론, JIT 컴파일은 되었습니다.

!name2ee System.Data.dll!System.Data.SqlClient.SqlConnection.CreateCommand  

Module:      00303bc0
Assembly:    System.Data.dll
Token:       2d289f9c060024c9
MethodDesc:  00b499a4
Name:        System.Data.SqlClient.SqlConnection.CreateCommand()
JITTED Code Address: 05f1c280

더욱 재미있는 점이 하나 있다면, JIT 이전/이후의 값에 대한 패치를 "데이터"영역이 아닌, 코드 영역에서 직접 수정한다는 것입니다.

이 때문에 JIT 이전의 GetFunctionPointer 값으로 반환된 stub 코드의 역어셈블 값에서 jmp 코드의 오퍼랜드 값이 "JITTED Code Address"로 바뀌었습니다.

!u 0x30ce70

Unmanaged code
0030CE70 B8A499B400       mov         eax,0B499A4h
0030CE75 90               nop
0030CE76 E865620970       call        703A30E0
0030CE7B E900F4C005       jmp         05F1C280  <== JIT 이전값: jmp 002B0850
0030CE80 00B000EB7CB0     add         byte ptr [eax+B07CEB00h],dh
0030CE86 03EB             add         ebp,ebx
0030CE88 78B0             js          0030CE3A
0030CE8A 06               push        es
0030CE8B EB74             jmp         0030CF01
0030CE8D B009             mov         al,9




이 외에 더 알아봐야 할 경우의 수가 있을까요? (있다면, 다시 추가하면 될 것이고.)

이러한 일련의 검사를 통해 알 수 있는 중요한 사실은, 적어도 JIT 컴파일 이후의 GetFunctionPointer 반환값은 안전하게 해당 메서드를 call 할 수 있는 주소를 포함하고 있다는 점입니다. (너무 당연한 결론이죠? ^^;)

혹시 아래의 글 기억나세요?

실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선
; https://www.sysnet.pe.kr/2/0/942

원래의 "Runtime Method Replacer" 소스 코드에서는 JIT 컴파일 된 함수의 주소를 (.NET 버전별로 바뀔 가능성이 있는) offset 연산을 통해 계산되었지만, 제가 개선한 소스 코드에서는 그 부분을 GetFunctionPointer 반환값으로 대체했기 때문에 .NET 버전에 상관없이 안정적으로 동작하는 소스 코드가 얻어진 것입니다.

자, 여기서 '메서드 치환'에 대해서 잠깐 생각해 보면, 이를 위해서는 '치환되어질 원본 메서드의 주소'값과 '치환될 새로운 메서드의 주소'값을 알아야만 가능합니다.

여전히 "Runtime Method Replacer"는 '치환되어질 원본 메서드의 주소'값에 대해서는 불안정한 offset 값 계산을 하고 있으며 그 때문에 다양한 메서드의 변종(? - 예를 들어 Ngen된 BCL 함수의 경우)에 대해서는 치환하지 못하는 단점이 있습니다.

사실, 제가 이번 글에서 시도했던 작업의 목표는 GetFunctionPointer 반환값의 정합성을 확인하기 위한 것은 아니었고, 어떤 경우에서든지 안정적으로 '치환되어질 원본 메서드의 주소' 값을 찾는 것이 목표였는데 아쉽게도 달성하지 못한 것 같습니다. 대신에 ^^ 정리해 놓은 것이 아까워서 이렇게 다른 제목으로 글을 쓰게 된 것입니다.

혹시나 닷넷 메서드 가로채기에 관심있으신 분들은 위의 글을 읽어보시고 저와 같은 시행착오를 겪지 않기를 바랍니다. ^^



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

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/17/2023]

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

비밀번호

댓글 작성자
 




[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13601정성태4/19/2024232닷넷: 2243. C# - PCM 사운드 재생(NAudio)파일 다운로드1
13600정성태4/18/2024295닷넷: 2242. C# - 관리 스레드와 비관리 스레드
13599정성태4/17/2024353닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)파일 다운로드1
13598정성태4/16/2024394닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더파일 다운로드2
13597정성태4/15/2024454닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력파일 다운로드1
13596정성태4/14/2024819닷넷: 2238. C# - WAV 기본 파일 포맷파일 다운로드1
13595정성태4/13/2024945닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)파일 다운로드1
13594정성태4/12/20241022닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)파일 다운로드1
13593정성태4/8/20241051닷넷: 2235. MSBuild - AccelerateBuildsInVisualStudio 옵션
13592정성태4/2/20241207C/C++: 165. CLion으로 만든 Rust Win32 DLL을 C#과 연동
13591정성태4/2/20241169닷넷: 2234. C# - WPF 응용 프로그램에 Blazor App 통합파일 다운로드1
13590정성태3/31/20241073Linux: 70. Python - uwsgi 응용 프로그램이 k8s 환경에서 OOM 발생하는 문제
13589정성태3/29/20241143닷넷: 2233. C# - 프로세스 CPU 사용량을 나타내는 성능 카운터와 Win32 API파일 다운로드1
13588정성태3/28/20241197닷넷: 2232. C# - Unity + 닷넷 App(WinForms/WPF) 간의 Named Pipe 통신파일 다운로드1
13587정성태3/27/20241156오류 유형: 900. Windows Update 오류 - 8024402C, 80070643
13586정성태3/27/20241300Windows: 263. Windows - 복구 파티션(Recovery Partition) 용량을 늘리는 방법
13585정성태3/26/20241096Windows: 262. PerformanceCounter의 InstanceName에 pid를 추가한 "Process V2"
13584정성태3/26/20241050개발 환경 구성: 708. Unity3D - C# Windows Forms / WPF Application에 통합하는 방법파일 다운로드1
13583정성태3/25/20241158Windows: 261. CPU Utilization이 100% 넘는 경우를 성능 카운터로 확인하는 방법
13582정성태3/19/20241420Windows: 260. CPU 사용률을 나타내는 2가지 수치 - 사용량(Usage)과 활용률(Utilization)파일 다운로드1
13581정성태3/18/20241588개발 환경 구성: 707. 빌드한 Unity3D 프로그램을 C++ Windows Application에 통합하는 방법
13580정성태3/15/20241138닷넷: 2231. C# - ReceiveTimeout, SendTimeout이 적용되지 않는 Socket await 비동기 호출파일 다운로드1
13579정성태3/13/20241494오류 유형: 899. HTTP Error 500.32 - ANCM Failed to Load dll
13578정성태3/11/20241630닷넷: 2230. C# - 덮어쓰기 가능한 환형 큐 (Circular queue)파일 다운로드1
13577정성태3/9/20241876닷넷: 2229. C# - 닷넷을 위한 난독화 도구 소개 (예: ConfuserEx)
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...