Common Language Runtime Header
The fifteenth directory entry of the PE header contains the RVA and size of the runtime header in the image file. The runtime header, which contains all of the runtime-specific data entries and other information, should reside in a read-only, sharable section of the image file. The ILAsm compiler puts the common language runtime header in the .text section.
Header Structure
The common language runtime header is defined in CorHdr.h—a header file distributed as part of the Microsoft .NET Framework SDK—as follows:
typedef struct IMAGE_COR20_HEADER { ULONG cb; USHORT MajorRuntimeVersion; USHORT MinorRuntimeVersion; // Symbol table and startup information IMAGE_DATA_DIRECTORY MetaData; ULONG Flags; ULONG EntryPointToken; // Binding information IMAGE_DATA_DIRECTORY Resources; IMAGE_DATA_DIRECTORY StrongNameSignature; // Regular fixup and binding information IMAGE_DATA_DIRECTORY CodeManagerTable; IMAGE_DATA_DIRECTORY VTableFixups; IMAGE_DATA_DIRECTORY ExportAddressTableJumps; IMAGE_DATA_DIRECTORY ManagedNativeHeader; } IMAGE_COR20_HEADER;
Table 3-6 takes a closer look at the fields of the header.
Offset | Size | Field | Description |
0 | 4 | Cb | Size of the header in bytes. |
4 | 2 | MajorRuntimeVersion | Major portion of the minimum version of the runtime required to run the program. |
6 | 2 | MinorRuntimeVersion | Minor portion of the version of the runtime required to run the program. |
8 | 8 | MetaData | RVA and size of the metadata. |
16 | 4 | Flags | Binary flags, discussed in the following section. In ILAsm, this value can be specified explicitly by the directive .corflags <integer value> and/or the command-line option /FLAGS=<integer value>. The command-line option takes precedence over the directive. |
20 | 4 | EntryPointToken | Metadata identifier (token) of the entry point for the image file; can be 0 for DLL images. This field identifies a method belonging to this module or a module containing the entry point method. |
24 | 8 | Resources | RVA and size of managed resources. |
32 | 8 | StrongNameSignature | RVA and size of the hash data for this PE file, used by the loader for binding and versioning. |
40 | 8 | CodeManagerTable | RVA and size of the Code Manager table. In the first release of the runtime, this field is reserved and must be set to 0. |
48 | 8 | VTableFixups | RVA and size in bytes of an array of virtual table (v-table) fixups. Among current managed compilers, only the MC++ compiler and linker and the ILAsm compiler can produce this array. |
56 | 8 | ExportAddressTableJumps | RVA and size of an array of addresses of jump thunks. Among current managed compilers, only the MC++ compiler and linker can produce this table, which allows the export of unmanaged native methods embedded in the managed PE file. |
64 | 8 | ManagedNativeHeader | Reserved; set to 0. |
Flags Field
The Flags field of the common language runtime header can include one or more of the following flags:
COMIMAGE_FLAGS_ILONLY (0x00000001) The image file contains IL code only, with no embedded native unmanaged code except the startup stub. Because common language runtime aware operating systems (such as Windows XP) ignore the startup stub, for all practical purposes the file can be considered pure-IL. However, using this flag can cause certain ILAsm compiler specific problems when running under Windows XP. If this flag is set, Windows XP ignores not only the startup stub but also the .reloc section. The .reloc section can contain relocations for the beginning and end of the .tls section as well as relocations for what is referred to as data-on-data (that is, data constants that are pointers to other data constants). Among existing managed compilers, only the MC++ compiler and linker and the ILAsm compiler can produce these items. The MC++ compiler and linker never set this flag because the image file they generate is never pure-IL. Currently, the ILAsm compiler is the only one capable of producing pure-IL image files that might require a .reloc section. To resolve this problem, the ILAsm compiler, if TLS-based data or data-on-data is emitted, clears this flag and sets the COMIMAGE_FLAGS_32BITREQUIRED flag instead.
COMIMAGE_FLAGS_32BITREQUIRED (0x00000002) The image file can be loaded only into a 32-bit process. This flag is set when native unmanaged code is embedded in the PE file or when the .reloc section is not empty.
COMIMAGE_FLAGS_IL_LIBRARY (0x00000004) This flag is obsolete and should not be set. Setting it—as the ILAsm compiler allows, using the .corflags directive—will render your module unloadable.
COMIMAGE_FLAGS_STRONGNAMESIGNED (0x00000008) The image file is protected with a strong name signature. The strong name signature includes the public key and the signature hash and is a part of an assembly’s identity, along with the assembly name, version number, and the culture information. This flag is set when the strong name signing procedure is applied to the image file. No compiler, including ILAsm, can set this flag explicitly.
COMIMAGE_FLAGS_TRACKDEBUGDATA (0x00010000) The loader and the JIT (just-in-time) compiler are required to track debug information about the methods.
EntryPointToken Field
The EntryPointToken field of the common language runtime header contains a token (metadata identifier) of either a method definition (MethodDef) or a file reference (File). A MethodDef token identifies a method defined in the module (a managed PE file) as an entry point method. A File token is used in one case only: in the runtime header of the prime module of a multimodule assembly, when the entry point method is defined in another module (identified by the file reference) of this assembly. In this case, the module identified by the file reference must contain the respective MethodDef token in the EntryPointToken field of its runtime header.
EntryPointToken must be specified in runnable executables (EXE files). The ILAsm compiler, for example, does not even try to generate an EXE file if the source code does not define the entry point. The loader imposes limitations on the signature of the entry point method: the method must return an unsigned integer or void, and it must have at most one parameter of type string or string[ ] (vector of strings).
With nonrunnable executables (DLL files), it’s a different story. Pure-IL DLLs don’t need the entry point method defined, and the EntryPointToken field in their runtime headers should be set to 0.
Mixed-code DLLs—DLLs containing IL and embedded native code—generated by the MC++ compiler and linker must run the unmanaged native function DllMain immediately at the DLL invocation in order to perform the initialization necessary for the unmanaged native components of the DLL. The signature of this unmanaged function must be as follows:
int DllMain(HINSTANCE, DWORD, void *);
To be visible from the managed code and the runtime, the function DllMain must be declared as a platform invocation of an embedded native method (local P/Invoke, also known in enlightened circles as IJW—It Just Works). See Chapter 15 for details about the interoperation of managed and unmanaged code.
The method referred to by the EntryPointToken field of the common language runtime header has nothing to do with the function to which the AddressOfEntryPoint field of the PE header points. AddressOfEntryPoint always points to the runtime invocation stub, which is invisible to the runtime, is not reflected in metadata, and hence cannot have a token. | |
VTableFixups Field
The VTableFixups field of the runtime header is a data directory containing the RVA and the size of the image file’s v-table fixup table. When a managed method must be called from unmanaged code, the common language runtime creates a marshaling thunk for it, and the address of this thunk is placed in the respective address table. If the managed method is called from the unmanaged native code embedded in the managed PE file, the thunk address goes to a special internal v-table. If the managed method is exported as unmanaged and is consumed somewhere outside the managed PE file, the address of the respective v-table entry must also go to the Export Address table. At loading time (and in the disk image file), the entries of this v-table contain the respective method tokens.
These v-table fixups represent the initializing information necessary for the runtime to create the thunks and lay out the respective tables. v-table fixup is defined in CorHdr.h as follows:
typedef struct _IMAGE_COR_VTABLEFIXUP { ULONG RVA; USHORT Count; USHORT Type; } IMAGE_COR_VTABLEFIXUP;
In this definition, RVA points to the location of the v-table slot containing the respective method token(s). Count specifies the number of entries in the slot if multiple implementations of the same method exist, overriding one another. Type is a combination of the following flags, providing the runtime with information about the slot and what to do with it:
COR_VTABLE_32BIT (0x01) Each entry is 32 bits wide.
COR_VTABLE_64BIT (0x02) Each entry is 64 bits wide.
COR_VTABLE_FROM_UNMANAGED (0x04) The thunk created by the common language runtime must provide data marshaling between managed and unmanaged code.
COR_VTABLE_CALL_MOST_DERIVED (0x10) This flag is not currently used.
Obviously, the first two flags are mutually exclusive. The slots of the v-table must follow each other immediately—that is, the v-table must be contiguous.
Because the v-table should be fixed up after the image has been loaded into memory, this table is located in a read/write section. (In contrast, the v-table in an unmanaged image is located in a read-only section.)
Among existing managed compilers, only the MC++ compiler and linker and the ILAsm compiler can define the v-table and its fixups.
StrongNameSignature Field
The StrongNameSignature field of the common language runtime header contains the RVA and size of the strong name hash, which is used by the runtime to establish the authenticity of the image file. After the image file has been created, it is hashed using the public and private encryption keys provided by the producer of the image file, and the resulting hash blob is written into the space allocated inside the image file.
If even a single byte in the image file is subsequently modified, the authenticity check fails and the image file cannot be loaded. The strong name signature does not survive a round-tripping procedure; if you disassemble a strong-named module using the IL Disassembler and then reassemble it, the module must be strong name signed again.
The ILAsm compiler puts the strong name signature in the .text section of the image file.