어서 빨리 System.Object로 시작해서 CLR을 배우고 싶지만 .NET Framework 환경의 CLR의 기반에 대해 좀
더 자세히 짚고 넘어가야 할 것 같습니다. 그래서 이번엔 이 부분에 대해서 정리를 하면서 공부를 해보려고 합니다.
MSCorLib.dll
이런 코드를 하나 만들어 봅시다. C# 코드입니다. 우리는 CLR을 공부하려고 하는 것이지 C#을 공부하려는 건 아니지만 앞으로 C#을 이용하여 많은 코드를 작성할 예정입니다. 암튼 빌드 해봐야겠죠. 워워- VS 2010 IDE 아직 띄우지 마세요. 훌륭한 개발툴 깔아놓고서도 쓰지 않는게 억울하긴 하지만 지난번과 같이 Visual Studio Command Prompt를 실행해서 빌드를 해 봅시다.
.exe의 실행파일이 생성되고 실행 해 보면 Hi 라고 문자열을 출력합니다.
csc는 C#컴파일러 입니다. 인자를 살펴보면 /out 은 생성될 실행파일명을 지정하는 것이고 /t는 Target의 약자로 exe로 지정되었다는 것은 Win32 어플리케이션을 생성하겠다는 의미입니다. 그리고 /r은 Reference의 약자인데 코드에서 사용된 외부에서 정의된 타입의 참조를 어떤 모듈을 이용하여 해결하겠다는 의미입니다. 마지막으로 hi.cs는 컴파일할 소스 파일이지요.
여기서 우리가 자세히 살펴봐야 할 부분은 /r의 인자인 MSCorLib.dll 입니다.
우선 소스를 다시 한번 살펴 봅시다. 소스에서는 System.Console.WriteLine이라는 메서드를 사용하고 있습니다. 여기서 System.Console 이라는 타입은 C# 컴파일러에서는 정의되어 있는 타입이 아니기 때문에 위에서 보면 MSCorLib.dll이라는 어셈블리 모듈을 참조하여 컴파일을 하고 있습니다.
MSCorLib.dll은 특별한 파일로서 모든 기본 타입을 제공하고 있습니다. Byte, Char등의 타입부터 그 외에 기본적으로 사용하는 많은 타입들을 포함한 어셈블리입니다. 그야말로 핵심 모듈이지요. 그렇기 때문에 C#등의 컴파일러에서는 /r의 인자로 MSCorLib.dll 만큼은 생략해도 기본적으로 이 모듈은 참조하여 컴파일을 하게 됩니다.
결국
라고 해도 정상적으로 컴파일은 된다는 말이지요. /out 옵션과 /t옵션의 기본값도 원래는 소스파일명(확장자 제외한).exe 이고 /t도 기본이 exe이므로 본래는
라고 해도 컴파일은 문제없이 잘 됩니다. csc 컴파일러의 인자 중에서는 기본 라이브러리 모듈은 MSCorLib.dll을 참조하지 말라고 지시하는 인자가 있는데 바로 /nostdlib 입니다.
만약에 hi.cs 를 /nostdlib 인자를 주어서 컴파일을 하면 어떻게 될까요.
당연히 컴파일이 실패합니다. 'System.Object' 타입을 찾을 수 없다고 하면서 말이지요. System.Object는 모든 타입의 기본입니다. 모든 타입은 System.Object로부터 파생이 됩니다. 기본 라이브러리 모듈을 참조하지 않았으니 모든 타입의 기본이 되는 System.Object 타입을 찾지 못하는 건 당연한 결과지요. 다시 말하지만 MSCorLib.dll 은 .NET Framework의 핵심 모듈입니다.
Metadata
자 우리는 hi.exe 라는 작은 프로그램을 생성하였습니다. 이제 이 생성된 파일을 좀 까봐야겠습니다. 지난번에 실행파일은 IL코드와 메타데이터가 포함되어 있다고 배웠습니다. 좀 더 정확히 말하자면 PE32(또는 PE32+)헤더와 CLR헤더 IL코드 그리고 메타데이터가 포함되어 있습니다. 한번 살펴봐야죠.
.NET에서 생성하는 PE32 포맷의 실행파일은 기존의 PE32 형식의 실행 파일과 거의 동일합니다. 거기에 .NET Metadata 관련 섹션 정보가 추가되어 있는 형식입니다.
생성된 파일의 Metadata 정보를 확인하기 전에 Metadata의 구조는 어떤식으로 되어 있는지 확인해 봐야겠습니다. Metadata의 구조는 Microsoft SDK에 포함되어 있는 CorHdr.h 라는 헤더 파일에 IMAGE_COR20_HEADER 라는 구조체로 정의가 되어 있습니다.
좀 보기 힘들긴 하지만 위에서부터 하나하나 확인해보죠. 우리가 위에서 생성한 hi.exe 파일을 대상으로 확인하려고 합니다. 직접 헤더를 분석하는 툴을 만들어도 좋겠지만(^-^;;) CFF Explorer 라는 툴을 이용하여 헤더를 확인해 보기로 합니다.
CFF Explorer로 확인해 보면 .NET Directory 섹션부터 위의 헤더 정보와 동일한 이름의 정보들이 순차적으로 기록되어 있는 것을 확인할 수 있습니다. 굉장히 많은 정보 중에서 우리가 짚고 넘어가야 할 정보들은 메타데이터 정의 테이블과 메타데이터 참조 테이블입니다.
Corhdr.h 파일에서 메타 데이터 정의 테이블과 참조 테이블 리스트들의 이름들을 확인해 볼 수 있습니다.
// Token definitions
typedef mdToken mdModule; // Module token (roughly, a scope)
typedef mdToken mdTypeRef; // TypeRef reference (this or other scope)
typedef mdToken mdTypeDef; // TypeDef in this scope
typedef mdToken mdFieldDef; // Field in this scope
typedef mdToken mdMethodDef; // Method in this scope
typedef mdToken mdParamDef; // param token
typedef mdToken mdInterfaceImpl; // interface implementation token
typedef mdToken mdMemberRef; // MemberRef (this or other scope)
typedef mdToken mdCustomAttribute; // attribute token
typedef mdToken mdPermission; // DeclSecurity
typedef mdToken mdSignature; // Signature object
typedef mdToken mdEvent; // event token
typedef mdToken mdProperty; // property token
typedef mdToken mdModuleRef; // Module reference (for the imported modules)
여기서 확인해 봐야 할 테이블들은 다음과 같습니다.
메타 데이터 정의 테이블 리스트
메타 데이터 참조 테이블 리스트
위의 두 테이블을 참조하여 파일의 메타 데이터 항목들을 살펴 봅니다.
ModuleDef
모듈의 이름 버전 ID (GUID의 형태로 생성됩니다.) 등이 기록되어 있는 것을 확인 할 수 있습니다.
TypeRef
4개의 타입 참조가 보입니다. 그 중 Console Type에 대한 정보를 살펴봅시다. 타입의 이름(Name)과 이 타입이 어디에 있는지(Namespace)에 대한 정보를 포함하고 있네요.
TypeDef
모듈 내에 정의된 각 타입을 위한 항목을 포함한다고 했습니다. 각 엔트리는 타입의 이름(Name), 상위 타입(Namespace), 플래그(TypeDef Flags)등을 갖고 있습니다. Extends에 TypeRef Table Index 1이라고 되어 있네요. TypeRef의 1번째 인덱스는 위의 화면에서 보면 알겠지만 Object입니다. Object 클래스로부터 상속되어 확장되었다는 걸 알 수 있겠네요.
Method
위의 테이블에선 없던 항목이지만 Method라는 항목도 보이네요. 보시면 알겠지만 hi.exe 내의 메서드 정보를 포함하고 있습니다. .ctor은 뭔가요? 생성자입니다. .ctor이 호출되어야지 해당 객체가 메모리에서 생성된 위치를 가리키는 포인터 this라는 값을 갖게 됩니다.
MemberRef
모듈이 참조하는 각 멤버들의 참조 정보를 갖고 있네요. 3번째는 WriteLine입니다. Class정보를 보면 TypeRef Table Index 4 라고 되어 있네요. 위에서 확인해 보면 System.Console을 가리키는 것임을 알 수 있습니다. 다른 멤버들 역시 TypeRef의 다른 멤버들을 가리키고 있습니다.
그리고 밑에 보이는 CustomAttribute는 나중에 Attribute를 공부할 때 다시 한번 언급해 보기로 하구요. Assembly는 다음에 공부해야 할 Assembly에서 다시 한번 보면 될 것 같네요.
대략이나마 Metadata 정보를 확인해 보니까 정말 다양한 정보들이 체계적으로 기록되어 있다는 것을 확인해 볼 수 있었습니다.
생각보다 진도가 늦어지고 있네요. 그래도 이왕 공부하는 것 아주 완벽히는 아니더라도 이렇게 하나하나씩 살펴봐야지 공부하면 공부할수록 좀 더 깊이있는 이해가 필요한 부분에서 도움이 될 것이라고 생각합니다.
이 글을 보시는 분들 시간을 내서 직접 맨 위의 코드를 컴파일 해보시고 이런저런 메타데이터 정보를 한번 확인해 보세요~ CFF Explorer 라는 툴도 있지만 기본적로 지원하는 IL 역어셈블러 툴인 ILDasm.exe를 이용하셔도 위에서 언급한 메타데이터 정보들을 확인할 수 있습니다.
메타데이터 정보가 보이지요?
함수의 IL코드도 확인할 수 있습니다.
위에서 언급한 .ctor 코드도 볼 수 있습니다. System.Object의 생성자를 부르고 있네요. 이외에도 다양한 정보들을 확인해 볼 수 있으니까 꼭 한번씩 살펴보세요. :)
MSCorLib.dll
public sealed class Program
{
public static void Main()
{
System.Console.WriteLine("Hi");
}
}
{
public static void Main()
{
System.Console.WriteLine("Hi");
}
}
이런 코드를 하나 만들어 봅시다. C# 코드입니다. 우리는 CLR을 공부하려고 하는 것이지 C#을 공부하려는 건 아니지만 앞으로 C#을 이용하여 많은 코드를 작성할 예정입니다. 암튼 빌드 해봐야겠죠. 워워- VS 2010 IDE 아직 띄우지 마세요. 훌륭한 개발툴 깔아놓고서도 쓰지 않는게 억울하긴 하지만 지난번과 같이 Visual Studio Command Prompt를 실행해서 빌드를 해 봅시다.
csc.exe /out:hi.exe /t:exe /r:MSCorLib.dll hi.cs
.exe의 실행파일이 생성되고 실행 해 보면 Hi 라고 문자열을 출력합니다.
csc는 C#컴파일러 입니다. 인자를 살펴보면 /out 은 생성될 실행파일명을 지정하는 것이고 /t는 Target의 약자로 exe로 지정되었다는 것은 Win32 어플리케이션을 생성하겠다는 의미입니다. 그리고 /r은 Reference의 약자인데 코드에서 사용된 외부에서 정의된 타입의 참조를 어떤 모듈을 이용하여 해결하겠다는 의미입니다. 마지막으로 hi.cs는 컴파일할 소스 파일이지요.
여기서 우리가 자세히 살펴봐야 할 부분은 /r의 인자인 MSCorLib.dll 입니다.
우선 소스를 다시 한번 살펴 봅시다. 소스에서는 System.Console.WriteLine이라는 메서드를 사용하고 있습니다. 여기서 System.Console 이라는 타입은 C# 컴파일러에서는 정의되어 있는 타입이 아니기 때문에 위에서 보면 MSCorLib.dll이라는 어셈블리 모듈을 참조하여 컴파일을 하고 있습니다.
MSCorLib.dll은 특별한 파일로서 모든 기본 타입을 제공하고 있습니다. Byte, Char등의 타입부터 그 외에 기본적으로 사용하는 많은 타입들을 포함한 어셈블리입니다. 그야말로 핵심 모듈이지요. 그렇기 때문에 C#등의 컴파일러에서는 /r의 인자로 MSCorLib.dll 만큼은 생략해도 기본적으로 이 모듈은 참조하여 컴파일을 하게 됩니다.
결국
csc.exe /out:hi.exe /t:exe hi.cs
라고 해도 정상적으로 컴파일은 된다는 말이지요. /out 옵션과 /t옵션의 기본값도 원래는 소스파일명(확장자 제외한).exe 이고 /t도 기본이 exe이므로 본래는
csc.exe hi.cs
라고 해도 컴파일은 문제없이 잘 됩니다. csc 컴파일러의 인자 중에서는 기본 라이브러리 모듈은 MSCorLib.dll을 참조하지 말라고 지시하는 인자가 있는데 바로 /nostdlib 입니다.
만약에 hi.cs 를 /nostdlib 인자를 주어서 컴파일을 하면 어떻게 될까요.
당연히 컴파일이 실패합니다. 'System.Object' 타입을 찾을 수 없다고 하면서 말이지요. System.Object는 모든 타입의 기본입니다. 모든 타입은 System.Object로부터 파생이 됩니다. 기본 라이브러리 모듈을 참조하지 않았으니 모든 타입의 기본이 되는 System.Object 타입을 찾지 못하는 건 당연한 결과지요. 다시 말하지만 MSCorLib.dll 은 .NET Framework의 핵심 모듈입니다.
Metadata
자 우리는 hi.exe 라는 작은 프로그램을 생성하였습니다. 이제 이 생성된 파일을 좀 까봐야겠습니다. 지난번에 실행파일은 IL코드와 메타데이터가 포함되어 있다고 배웠습니다. 좀 더 정확히 말하자면 PE32(또는 PE32+)헤더와 CLR헤더 IL코드 그리고 메타데이터가 포함되어 있습니다. 한번 살펴봐야죠.
.NET에서 생성하는 PE32 포맷의 실행파일은 기존의 PE32 형식의 실행 파일과 거의 동일합니다. 거기에 .NET Metadata 관련 섹션 정보가 추가되어 있는 형식입니다.
생성된 파일의 Metadata 정보를 확인하기 전에 Metadata의 구조는 어떤식으로 되어 있는지 확인해 봐야겠습니다. Metadata의 구조는 Microsoft SDK에 포함되어 있는 CorHdr.h 라는 헤더 파일에 IMAGE_COR20_HEADER 라는 구조체로 정의가 되어 있습니다.
typedef struct IMAGE_COR20_HEADER
{
// Header versioning
DWORD cb;
WORD MajorRuntimeVersion;
WORD MinorRuntimeVersion;
// Symbol table and startup information
IMAGE_DATA_DIRECTORY MetaData;
DWORD Flags;
// The main program if it is an EXE (not used if a DLL?)
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint.
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint
// (depricated for DLLs, use modules constructors intead).
union {
DWORD EntryPointToken;
DWORD EntryPointRVA;
};
// This is the blob of managed resources. Fetched using code:AssemblyNative.GetResource and
// code:PEFile.GetResource and accessible from managed code from
// System.Assembly.GetManifestResourceStream. The meta data has a table that maps names to offsets into
// this blob, so logically the blob is a set of resources.
IMAGE_DATA_DIRECTORY Resources;
// IL assemblies can be signed with a public-private key to validate who created it. The signature goes
// here if this feature is used.
IMAGE_DATA_DIRECTORY StrongNameSignature;
IMAGE_DATA_DIRECTORY CodeManagerTable; // Depricated, not used
// Used for manged codee that has unmaanaged code inside it (or exports methods as unmanaged entry points)
IMAGE_DATA_DIRECTORY VTableFixups;
IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
// null for ordinary IL images. NGEN images it points at a code:CORCOMPILE_HEADER structure
IMAGE_DATA_DIRECTORY ManagedNativeHeader;
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;
{
// Header versioning
DWORD cb;
WORD MajorRuntimeVersion;
WORD MinorRuntimeVersion;
// Symbol table and startup information
IMAGE_DATA_DIRECTORY MetaData;
DWORD Flags;
// The main program if it is an EXE (not used if a DLL?)
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint.
// If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint
// (depricated for DLLs, use modules constructors intead).
union {
DWORD EntryPointToken;
DWORD EntryPointRVA;
};
// This is the blob of managed resources. Fetched using code:AssemblyNative.GetResource and
// code:PEFile.GetResource and accessible from managed code from
// System.Assembly.GetManifestResourceStream. The meta data has a table that maps names to offsets into
// this blob, so logically the blob is a set of resources.
IMAGE_DATA_DIRECTORY Resources;
// IL assemblies can be signed with a public-private key to validate who created it. The signature goes
// here if this feature is used.
IMAGE_DATA_DIRECTORY StrongNameSignature;
IMAGE_DATA_DIRECTORY CodeManagerTable; // Depricated, not used
// Used for manged codee that has unmaanaged code inside it (or exports methods as unmanaged entry points)
IMAGE_DATA_DIRECTORY VTableFixups;
IMAGE_DATA_DIRECTORY ExportAddressTableJumps;
// null for ordinary IL images. NGEN images it points at a code:CORCOMPILE_HEADER structure
IMAGE_DATA_DIRECTORY ManagedNativeHeader;
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;
좀 보기 힘들긴 하지만 위에서부터 하나하나 확인해보죠. 우리가 위에서 생성한 hi.exe 파일을 대상으로 확인하려고 합니다. 직접 헤더를 분석하는 툴을 만들어도 좋겠지만(^-^;;) CFF Explorer 라는 툴을 이용하여 헤더를 확인해 보기로 합니다.
CFF Explorer로 확인해 보면 .NET Directory 섹션부터 위의 헤더 정보와 동일한 이름의 정보들이 순차적으로 기록되어 있는 것을 확인할 수 있습니다. 굉장히 많은 정보 중에서 우리가 짚고 넘어가야 할 정보들은 메타데이터 정의 테이블과 메타데이터 참조 테이블입니다.
Corhdr.h 파일에서 메타 데이터 정의 테이블과 참조 테이블 리스트들의 이름들을 확인해 볼 수 있습니다.
// Token definitions
typedef mdToken mdModule; // Module token (roughly, a scope)
typedef mdToken mdTypeRef; // TypeRef reference (this or other scope)
typedef mdToken mdTypeDef; // TypeDef in this scope
typedef mdToken mdFieldDef; // Field in this scope
typedef mdToken mdMethodDef; // Method in this scope
typedef mdToken mdParamDef; // param token
typedef mdToken mdInterfaceImpl; // interface implementation token
typedef mdToken mdMemberRef; // MemberRef (this or other scope)
typedef mdToken mdCustomAttribute; // attribute token
typedef mdToken mdPermission; // DeclSecurity
typedef mdToken mdSignature; // Signature object
typedef mdToken mdEvent; // event token
typedef mdToken mdProperty; // property token
typedef mdToken mdModuleRef; // Module reference (for the imported modules)
여기서 확인해 봐야 할 테이블들은 다음과 같습니다.
메타 데이터 정의 테이블 리스트
메타 데이터 정의 테이블 이름 | 설명 |
ModuleDef | 모듈을 식별하기 위한 항목 |
TypeDef | 모듈 내의 정의된 각 타입을 위한 항목 |
ModuleDef | 모듈 내에 정의된 각 메서드를 위한 항목 |
FieldDef | 모듈 내에 정의된 모든 필드를 위한 항목 |
ParamDef | 모듈 내에 정의된 각 파라미터를 위한 항목 |
PropertyDef | 모듈 내에 정의된 각 속성을 위한 항목 |
EventDef | 모듈 내에 정의된 각 이벤트를 위한 항목 |
메타 데이터 참조 테이블 리스트
메타 데이터 참조 테이블 이름 | 설명 |
AssemblyRef | 모듈이 참조하는 각 어셈블리를 위한 항목 |
ModuleRef | 모듈이 참조하는 타입을 구현하는 각 PE 모듈을 위한 항목 |
TypeRef | 모듈이 참조하는 각 타입을 위한 항목 |
MemberRef | 모듈이 참조하는 각 멤버(필드 메서드 속성 이벤트 등)를 위한 항목 |
위의 두 테이블을 참조하여 파일의 메타 데이터 항목들을 살펴 봅니다.
ModuleDef
모듈의 이름 버전 ID (GUID의 형태로 생성됩니다.) 등이 기록되어 있는 것을 확인 할 수 있습니다.
TypeRef
4개의 타입 참조가 보입니다. 그 중 Console Type에 대한 정보를 살펴봅시다. 타입의 이름(Name)과 이 타입이 어디에 있는지(Namespace)에 대한 정보를 포함하고 있네요.
TypeDef
모듈 내에 정의된 각 타입을 위한 항목을 포함한다고 했습니다. 각 엔트리는 타입의 이름(Name), 상위 타입(Namespace), 플래그(TypeDef Flags)등을 갖고 있습니다. Extends에 TypeRef Table Index 1이라고 되어 있네요. TypeRef의 1번째 인덱스는 위의 화면에서 보면 알겠지만 Object입니다. Object 클래스로부터 상속되어 확장되었다는 걸 알 수 있겠네요.
Method
위의 테이블에선 없던 항목이지만 Method라는 항목도 보이네요. 보시면 알겠지만 hi.exe 내의 메서드 정보를 포함하고 있습니다. .ctor은 뭔가요? 생성자입니다. .ctor이 호출되어야지 해당 객체가 메모리에서 생성된 위치를 가리키는 포인터 this라는 값을 갖게 됩니다.
MemberRef
모듈이 참조하는 각 멤버들의 참조 정보를 갖고 있네요. 3번째는 WriteLine입니다. Class정보를 보면 TypeRef Table Index 4 라고 되어 있네요. 위에서 확인해 보면 System.Console을 가리키는 것임을 알 수 있습니다. 다른 멤버들 역시 TypeRef의 다른 멤버들을 가리키고 있습니다.
그리고 밑에 보이는 CustomAttribute는 나중에 Attribute를 공부할 때 다시 한번 언급해 보기로 하구요. Assembly는 다음에 공부해야 할 Assembly에서 다시 한번 보면 될 것 같네요.
대략이나마 Metadata 정보를 확인해 보니까 정말 다양한 정보들이 체계적으로 기록되어 있다는 것을 확인해 볼 수 있었습니다.
생각보다 진도가 늦어지고 있네요. 그래도 이왕 공부하는 것 아주 완벽히는 아니더라도 이렇게 하나하나씩 살펴봐야지 공부하면 공부할수록 좀 더 깊이있는 이해가 필요한 부분에서 도움이 될 것이라고 생각합니다.
이 글을 보시는 분들 시간을 내서 직접 맨 위의 코드를 컴파일 해보시고 이런저런 메타데이터 정보를 한번 확인해 보세요~ CFF Explorer 라는 툴도 있지만 기본적로 지원하는 IL 역어셈블러 툴인 ILDasm.exe를 이용하셔도 위에서 언급한 메타데이터 정보들을 확인할 수 있습니다.
메타데이터 정보가 보이지요?
함수의 IL코드도 확인할 수 있습니다.
위에서 언급한 .ctor 코드도 볼 수 있습니다. System.Object의 생성자를 부르고 있네요. 이외에도 다양한 정보들을 확인해 볼 수 있으니까 꼭 한번씩 살펴보세요. :)
참고 자료
CLR via C# 2nd Ed.
CLR via C# 2nd Ed.
'CLR' 카테고리의 다른 글
6. Assembly - GAC(Global Assembly Cache) (2) | 2010.02.02 |
---|---|
5. Assembly - Strongly named assemblies (0) | 2010.01.26 |
4. Assembly (2) | 2010.01.15 |
2. CLR! CLR! CLR! (3) | 2009.12.30 |
1. Hello 世界 (0) | 2009.12.23 |