13

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:

  1. How is it implemented?
  2. Does the OS Loader get involved?
  3. 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?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nullptr Dev
  • 311
  • 2
  • 10

2 Answers2

7

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks for the quick answer. This is a good starting point. I wanted to find out how clr deals with .reloc sections. I dug in sscli, mostly in pedecoder.h/pewriter.cpp and found my answers. Still there are many questions (e.g. what about Windows 2000 x64) but I guess I will find the answers in sscli. – Nullptr Dev May 01 '12 at 05:35
  • That's an easy one, Windows 2000 x64 has last been seen used by the great white Yeti. – Hans Passant May 01 '12 at 10:02
  • 1
    Wow. I wonder if there is any way to take advantage of this "special awareness" to create proper fat (native code) binaries for Windows. – Fowl Jul 04 '12 at 03:48
  • Tried that, found no answer. Probably, this magic won't work at all if no .NET is installed in the OS. Which renders this useless for native executables. – hypersw Jan 15 '15 at 20:37
2

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
argaz
  • 1,458
  • 10
  • 15
  • Many thanks!!! I've got confused in calculating some offsets of fields this structure using obsoleted information in Windows NT/2000 Native API Reference. – Ta Thanh Dinh Jan 07 '18 at 02:18