I guess my question is about the CLR Loader. I want to understand the mechanics behind CorFlags.exe /32BIT+
functionality.
We know that when one starts an assembly compiled with the Any CPU flag set on 64-bit Windows, it starts as a 64-bit process. If one run CorFlags /32BIT+
on that assembly, it will start as a 32-bit process. I think this is a fascinating feature.
I have so many questions about it:
- How is it implemented?
- Does the OS Loader get involved?
- Is possible to build a custom application (I guess an unmanaged one) that loads 32-bit or 64-bit CLR at a wish?
Is there an article, book, blog, etc that explains the inner workings of this feature?
This isn't well documented in any place I know of, I can only point you to a relevant MSDN article. Yes, your assumption is correct, the loader in Windows XP and up has awareness of managed executables. It automatically loads the .NET loader shim (c:\windows\system32\mscoree.dll), the relevant entrypoint is _CorValidateImage(). The Remarks section in the linked MSDN article describes the mechanism that turns a 32-bit .exe file into a 64-bit process:
In Windows XP and later versions, the operating system loader checks for managed modules by examining the COM Descriptor Directory bit in the common object file format (COFF) header. A set bit indicates a managed module. If the loader detects a managed module, it loads MsCorEE.dll and calls _CorValidateImage, which performs the following actions:
- Confirms that the image is a valid managed module.
- Changes the entry point in the image to an entry point in the common language runtime (CLR).
- For 64-bit versions of Windows, modifies the image that is in memory by transforming it from PE32 to PE32+ format.
- Returns to the loader when the managed module images are loaded.
For executable images, the operating system loader then calls the
_CorExeMain function, regardless of the entry point specified in the executable. For DLL assembly images, the loader calls the _CorDllMain
function.
_CorExeMain or _CorDllMain performs the following actions:
- Initializes the CLR.
- Locates the managed entry point from the assembly's CLR header.
- Begins execution.
The loader calls the _CorImageUnloading function when managed module
images are unloaded. However, this function does not perform any
action; it just returns.
To add on Hans' answer, there is also some Windows kernel mode code that responds to that flag. Every loaded executable has a kernel structure, SECTION_IMAGE_INFORMATION
, associated with it. Here's its symbol information:
0: kd> dt nt!_SECTION_IMAGE_INFORMATION
+0x000 TransferAddress : Ptr64 Void
+0x008 ZeroBits : Uint4B
+0x010 MaximumStackSize : Uint8B
+0x018 CommittedStackSize : Uint8B
+0x020 SubSystemType : Uint4B
+0x024 SubSystemMinorVersion : Uint2B
+0x026 SubSystemMajorVersion : Uint2B
+0x024 SubSystemVersion : Uint4B
+0x028 GpValue : Uint4B
+0x02c ImageCharacteristics : Uint2B
+0x02e DllCharacteristics : Uint2B
+0x030 Machine : Uint2B
+0x032 ImageContainsCode : UChar
+0x033 ImageFlags : UChar
+0x033 ComPlusNativeReady : Pos 0, 1 Bit
+0x033 ComPlusILOnly : Pos 1, 1 Bit
+0x033 ImageDynamicallyRelocated : Pos 2, 1 Bit
+0x033 ImageMappedFlat : Pos 3, 1 Bit
+0x033 BaseBelow4gb : Pos 4, 1 Bit
+0x033 Reserved : Pos 5, 3 Bits
The flags ComPlusILOnly
and ComPlusNativeReady
are related to .NET, ComPlusILOnly
simply tells if the assembly is CIL only (not mixed or native - in which case the assembly is already architecture specific), and ComPlusNativeReady
is 1 only if /32BIT+ is not set (32BITREQ or 32BITPREF in newer CorFlags version). Those flags are checked during nt!PspAllocateProcess
and based on them a 32-bit
or 64-bit
process is created.
I wrote about it with some details.