Microsoft MVP성태의 닷넷 이야기
.NET Framework: 495. CorElementType의 요소 값 설명 [링크 복사], [링크+제목 복사],
조회: 28173
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 9개 있습니다.)
.NET Framework: 491. 닷넷 Generic 타입의 메타 데이터 토큰 값 알아내는 방법
; https://www.sysnet.pe.kr/2/0/1848

.NET Framework: 494. 값(struct) 형식의 제네릭(Generic) 타입이 박싱되는 경우의 메타데이터 토큰 값
; https://www.sysnet.pe.kr/2/0/1857

.NET Framework: 495. CorElementType의 요소 값 설명
; https://www.sysnet.pe.kr/2/0/1860

.NET Framework: 509. ELEMENT_TYPE_MODIFIER의 조합
; https://www.sysnet.pe.kr/2/0/2894

.NET Framework: 510. 제네릭(Generic) 인자에 대한 메타데이터 등록 확인
; https://www.sysnet.pe.kr/2/0/2907

.NET Framework: 844. C# - 박싱과 언박싱
; https://www.sysnet.pe.kr/2/0/11943

.NET Framework: 955.  .NET 메서드의 Signature 바이트 코드 분석
; https://www.sysnet.pe.kr/2/0/12379

.NET Framework: 1130. C# - ELEMENT_TYPE_INTERNAL 유형의 사용 예
; https://www.sysnet.pe.kr/2/0/12903

.NET Framework: 1174. C# - ELEMENT_TYPE_FNPTR 유형의 사용 예
; https://www.sysnet.pe.kr/2/0/12998




CorElementType의 요소 값 설명

CorElementType은 enum 형식으로 다음의 문서에 기본적인 설명이 있습니다.

CorElementType Enumeration
; https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/metadata/corelementtype-enumeration

기본적인 타입들은 이렇게 매핑됩니다.

ELEMENT_TYPE_VOID:      "System.Void"
ELEMENT_TYPE_BOOLEAN:   "System.Boolean"
ELEMENT_TYPE_I1:        "System.SByte"
ELEMENT_TYPE_U1:        "System.Byte"
ELEMENT_TYPE_I2:        "System.Int16"
ELEMENT_TYPE_U2:        "System.UInt16"
ELEMENT_TYPE_CHAR:      "System.Char"
ELEMENT_TYPE_I4:        "System.Int32"
ELEMENT_TYPE_U4:        "System.UInt32"
ELEMENT_TYPE_I8:        "System.Int64"
ELEMENT_TYPE_U8:        "System.UInt64"
ELEMENT_TYPE_R4:        "System.Single"
ELEMENT_TYPE_R8:        "System.Double"
ELEMENT_TYPE_OBJECT:    "System.Object"
ELEMENT_TYPE_STRING:    "System.String"
ELEMENT_TYPE_I:         "System.IntPtr"
ELEMENT_TYPE_U:         "System.UIntPtr"
ELEMENT_TYPE_SZARRAY:   1차원 배열 Type []
ELEMENT_TYPE_ARRAY:     다차원 배열 Type [,]

이제 그 외의 것을 좀 더 자세하게 알아보겠습니다. 우선 ELEMENT_TYPE_VAR, ELEMENT_TYPE_MVAR인데요. 전자는 타입(class,struct)에 사용된 generic을 의미하고, 후자는 메서드에 사용된 generic입니다.

ELEMENT_TYPE_VAR:   class<T>           (generic class)
ELEMENT_TYPE_MVAR:  void Method<T>(T obj);  (generic method)

관련해서 ELEMENT_TYPE_GENERICINST도 있는데요. 이것은 제네릭 인자를 포함하는 타입인 경우에 해당합니다.

ELEMENT_TYPE_GENERICINST:  void Method(List<int>);

닷넷의 IL 코드를 심도 있게 공부하다 보면, 왜 자바에서 generic을 단순히 object로 처리했는지를 조금이나마 이해하게 됩니다. (닷넷도 generic을 object로 구현했다면 IL 코드가 더 간단해졌을 거라는 의미입니다.)

그리고, 값 형식과 참조 형식을 나누는 요소가 있습니다.

ELEMENT_TYPE_VALUETYPE:     값 형식
ELEMENT_TYPE_CLASS:         참조 형식

더 설명할 필요가 없겠죠? ^^

ELEMENT_TYPE_BYREF 값은 좀 재미있습니다. 이것은 out, ref C# 키워드가 사용되었을 때 지정됩니다. 이것을 달리 말하면? C#은 그 자체 내의 문법에서 out, ref를 구분하는 것일 뿐 IL 코드 입장에서 보면 동일하게 ELEMENT_TYPE_BYREF으로 취급된다는 점입니다.

또한, ELEMENT_TYPE_PTR 값은 unsafe 구문에서 포인터를 다루는 경우 사용됩니다.

일단, 여기까지는 그런대로 쉽게 이해할 수 있는 요소들인데!




이 부분부터는 주로 C++/CLI로 인해 도입된 것인데 C# 등의 언어로 프로그램할 때는 볼 수 없습니다. 우선, 다음의 값을 볼까요?

ELEMENT_TYPE_CMOD_REQD:     C language required modifier.
ELEMENT_TYPE_CMOD_OPT:      C language optional modifier.

이 2개의 값이 잘 설명된 문서는, 14년 동안 "C++ At Work" 컬럼을 담당했던 Paul DiLascia의 마지막 글입니다.

Rationales, Highlights, and a Farewell (C++/CLI의 설계 원리와 발전 과정)
; https://learn.microsoft.com/en-us/archive/msdn-magazine/2007/february/c-at-work-rationales-highlights-and-a-farewell (영문)
; https://learn.microsoft.com/ko-kr/archive/msdn-magazine/2007/february/c-at-work-rationales-highlights-and-a-farewell

우선, ELEMENT_TYPE_CMOD_OPT 옵션은 .NET Reflector 등의 툴로 보면 modopt 선언자로 표현되는데, 이 옵션이 사용된 구문을 호출 측에서 알지 못해도 전체적인 실행 방식에는 영향이 없을 때 사용하는 것입니다. (여기서 '호출 측'이라는 것은 엄밀히 닷넷 기반의 '언어 개발자'가 해당할 것입니다.)

Modopt (optional modifier) is an MSIL declarator that says to CLI consumers: if you understand what this thing is, great; if not, you can safely ignore it.


"Rationales, Highlights, and a Farewell" 글에서는 modopt의 사례로 C++/CLI의 const 예약어가 사용된 구문을 보여줍니다. 즉, 아래의 g_private 변수는,

// C++/CLI
ref class A {
   static const int g_private = 1;
};

컴파일 후 C++의 const 예약어가 사용되었음을 호출 측에 힌트를 줄 수 있도록 다음과 같은 IL 코드로 변환됩니다.

field public static int32 
    modopt([mscorlib]System.Runtime.CompilerServices.IsConst)
        g_private = int32(0x00000001)

반면, ELEMENT_TYPE_CMOD_REQD는 modreq로 표현되고,

modreq (required modifier) says: if you don't understand this thing, you can't use this function. Volatile is an example of a modreq.


반드시 호출 측에서 이에 대한 의미를 알아야만 사용할 수 있음을 의미합니다. 일례로 volatile 예약어가 지정된 사례를 들고 있습니다.

아울러, "Rationales, Highlights, and a Farewell" 글에서 왜 C++/CLI에서 const 함수가 문법적으로 허용되지 않는지를 설명하고 있는데요. 왜냐하면 CLI는 이 2개의 modifier가 함수에 적용되는 것은 허용하지 않기 때문입니다. (즉, 필드나 함수의 인자에만 적용할 수 있습니다.)

modopt가 사용되는 또 다른 예제를 wikipedia에 포함된 코드에서 볼 수 있는데, 다음과 같이 C++/CLI로 정의된 전역 함수의 경우 기본 C++ calling convention에 따라 Cdecl을 따르고,

class A {
   // public: virtual void __stdcall meth() { }
};

void test_pointer_operations(int param) { }

이를 IL 코드로 컴파일된 후에 살펴보면 Cdecl 호출 관례 정보가 modopt를 이용해 기록된 것을 볼 수 있습니다.

.method assembly static void modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl)
         test_pointer_operations(int32 param) cil managed

그 외에, 우리가 너무나 많이 사용하고 있는 System.Data.dll 어셈블리에도 SNINativeMethodWrapper 타입에 MarshalConsumerInfo 메서드의 signature에 modopt가 있는 것을 볼 수 있습니다.

// Declaring Type: SNINativeMethodWrapper 
// Assembly: System.Data, Version=4.0.0.0 

[ResourceExposure(ResourceScope.None)]
private static unsafe void MarshalConsumerInfo(ConsumerInfo consumerInfo, Sni_Consumer_Info* modopt(IsImplicitlyDereferenced) native_consumerInfo)
{
    // ...[생략]...

    // 심지어 형변환 코드에도 사용됩니다.
    native_consumerInfo[0] = (Sni_Consumer_Info* modopt(IsImplicitlyDereferenced)) consumerInfo.defaultBufferSize;
    // ...[생략]...
}

그런데, 여기서 제가 한 가지 풀지 못한 것이 있습니다. 보면 "IsImplicitlyDereferenced"라는 타입이 modopt에 부가적인 값으로 전달되고 있는데 이에 대한 정보를 메서드의 SIGNATURE 값에서는 찾을 수 없었습니다. (MarshalConsumerInfo 메서드의 signature 분석은 아래에서 나옵니다.)



그다음, ELEMENT_TYPE_MODIFIER에 대해 알아보겠습니다. 이것은 단독으로 정의되는 경우는 없고 ELEMENT_TYPE_PINNED, ELEMENT_TYPE_SENTINEL과 OR(|)연산자로 함께 사용됩니다.
이것이 사용된 사례를 위에서 이미 소개한 SNINativeMethodWrapper.MarshalConsumerInfo 메서드의 signature에서 볼 수 있습니다.


ildasm.exe를 이용해 signature를 보면 "SIG: 00 02 01 12 20 20 45 0f 11 30" 값이 나오는데, 이를 분석하면 다음과 같습니다.

00: static
02: 2개의 인자
01: (return type) void
12: ELEMENT_TYPE_CLASS 
20: ConsumerInfo type (class type token - compressed)
20: ELEMENT_TYPE_CMOD_OPT
45: IsImplicitlyDereferenced type (class type token - compressed)
0f: ELEMENT_TYPE_PTR
11: ELEMENT_TYPE_VALUETYPE
30: Sni_Consumer_Info (value type token - compressed)

타입 ConsumerInfo: 0x08 --> compressed로 바꾸면 0x20
타입 Sni_ConsumerInfo: 0x0C --> compressed로 바꾸면 0x30

위에서 ConsumerInfo와 같은 타입 토큰의 경우 compressed 유형으로 0x20인데, 이를 unpack 시키면 0x02000008(mdTypeDef == 0x02000000)값이 나옵니다. 실제로 CFF Explorer 같은 도구를 이용해 메타데이터 테이블의 TypeDef 영역을 조사해 보면 이렇게 8번 인덱스 위치에 ConsumerInfo 타입값이 정의된 것을 볼 수 있습니다.

element_type_1.png

본론으로 돌아와서 ELEMENT_TYPE_MODIFIER | ELEMENT_TYPE_PINNED 값이 사용되고 있는데 관련 설명을 찾아보면,

Processing the XML File
; https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/xmldoc/processing-the-xml-file

ELEMENT_TYPE_PINNED is represented as a '^' following the modified type. The C# compiler never generates this.


역시 C# 컴파일러로 인해 생성되는 일은 없고 C++/CLI의 "^" 연산자가 사용되는,

Hashtable^ hash = gcnew Hashtable();

이런 유형의 인자 타입을 받는 경우에 ELEMENT_TYPE_PINNED 값이 지정되는 것입니다. 참고로 다음과 같은 부가적인 자료도 나오는 군요.

PRB: A pinning pointer passed to variable-argument function causes System.BadImageFormatException 
; http://support.microsoft.com/kb/959389

마지막으로 남은 2가지 요소는 짧은 설명으로 대신합니다.

ELEMENT_TYPE_TYPEDBYREF: A typed reference. (System.TypedReference)
ELEMENT_TYPE_SENTINEL: A type modifier that is a sentinel for a list of a variable number of parameters.

(첨부한 파일은 compressed token값을 알아내는 소스코드를 포함합니다.)




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 10/24/2023]

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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  69  70  71  72  73  74  [75]  ...
NoWriterDateCnt.TitleFile(s)
12153정성태2/23/202024406.NET Framework: 898. Trampoline을 이용한 후킹의 한계파일 다운로드1
12152정성태2/23/202021417.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)파일 다운로드1
12151정성태2/22/202024054.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)파일 다운로드1
12150정성태2/21/202024137.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 [1]파일 다운로드1
12149정성태2/20/202021046.NET Framework: 894. eBEST C# XingAPI 래퍼 - 연속 조회 처리 방법 [1]
12148정성태2/19/202025728디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법 [1]
12147정성태2/19/202021035디버깅 기술: 162. x86/x64의 기계어 코드 최대 길이
12146정성태2/18/202022225.NET Framework: 893. eBEST C# XingAPI 래퍼 - 로그인 처리파일 다운로드1
12145정성태2/18/202023847.NET Framework: 892. eBEST C# XingAPI 래퍼 - Sqlite 지원 추가파일 다운로드1
12144정성태2/13/202024015.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기파일 다운로드1
12143정성태2/13/202018427.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64파일 다운로드1
12142정성태2/12/202022350.NET Framework: 889. C# 코드로 접근하는 MethodDesc, MethodTable파일 다운로드1
12141정성태2/10/202021346.NET Framework: 888. C# - ASP.NET Core 웹 응용 프로그램의 출력 가로채기 [2]파일 다운로드1
12140정성태2/10/202022706.NET Framework: 887. C# - ASP.NET 웹 응용 프로그램의 출력 가로채기파일 다운로드1
12139정성태2/9/202022401.NET Framework: 886. C# - Console 응용 프로그램에서 UI 스레드 구현 방법
12138정성태2/9/202028609.NET Framework: 885. C# - 닷넷 응용 프로그램에서 SQLite 사용 [6]파일 다운로드1
12137정성태2/9/202020253오류 유형: 592. [AhnLab] 경고 - 디버거 실행을 탐지했습니다.
12136정성태2/6/202021900Windows: 168. Windows + S(또는 Q)로 뜨는 작업 표시줄의 검색 바가 동작하지 않는 경우
12135정성태2/6/202027695개발 환경 구성: 468. Nuget 패키지의 로컬 보관 폴더를 옮기는 방법 [2]
12134정성태2/5/202024969.NET Framework: 884. eBEST XingAPI의 C# 래퍼 버전 - XingAPINet Nuget 패키지 [5]파일 다운로드1
12133정성태2/5/202022721디버깅 기술: 161. Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기
12132정성태1/28/202025736.NET Framework: 883. C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기) [1]파일 다운로드1
12131정성태1/27/202024465개발 환경 구성: 467. LocaleEmulator를 이용해 유니코드를 지원하지 않는(한글이 깨지는) 프로그램을 실행하는 방법 [1]
12130정성태1/26/202022031VS.NET IDE: 142. Visual Studio에서 windbg의 "Open Executable..."처럼 EXE를 직접 열어 디버깅을 시작하는 방법
12129정성태1/26/202029059.NET Framework: 882. C# - 키움 Open API+ 사용 시 Registry 등록 없이 KHOpenAPI.ocx 사용하는 방법 [3]
12128정성태1/26/202023171오류 유형: 591. The code execution cannot proceed because mfc100.dll was not found. Reinstalling the program may fix this problem.
... 61  62  63  64  65  66  67  68  69  70  71  72  73  74  [75]  ...