How the OS Loader will force .Net v1.0/1.1 executables to run under WOW64 on a 64-Bit Machine

[10/15, 2:04pm, fixed a couple typos; 10/15, 4:51pm, clarified a point]

Before you read this entry, you might want to read these two entries:

- https://blogs.msdn.com/joshwil/archive/2004/03/13/89163.aspx

- https://blogs.msdn.com/joshwil/archive/2004/03/11/88280.aspx

In case you skipped the link and kept on reading I’ll summarize the first post linked to (I however believe that they are really worth reading):

- “Bitness” is what we call an assembly’s ability to tell the OS and CLR what type of machine the assembly is safe to run on (32-bit vs. 64-bit, and on 64-bit: X64, IA64 or both).

- .Net 1.0 and 1.1 assemblies didn’t know anything about bitness

- There are things that you can do in a managed assembly which force you to need to bind it to one platform; these include: p/invoke, unsafe code, managed c++, etc…

The point is, in v1.0 and v1.1 we let you create assemblies that might very well have problems running on a 64bit platform, because of that we have decided that with .Net 2.0 (which is the first version of .Net to support 64-bit platforms natively) there will be an enforced loader policy such that 1.0 and 1.1 apps will be loaded by the 32-bit runtime on the machine.

We have gone back and forth on the right way to do this and have settled on the following:

- .Net 2.0 compilers will produce PE images with the IMAGE_COR20_HEADER.MinorRuntimeVersion set to 5, this matters in the case of MSIL (/platform:anycpu) images where the loader has the opportunity to choose whether the image should be loaded as a 32-bit or 64-bit image.

- MinorRuntimeVersion <5 will be used by the OS Loader to determine if the assembly is a .Net 2.0+ assembly or an older .Net assembly

- This will cause only .Net 2.0 executables to be loaded by the 64-bit runtime.

For reference, here is the definition of the IMAGE_COR20_HEADER:

// CLR 2.0 header structure.

typedef struct IMAGE_COR20_HEADER

{

       // Header versioning

       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;

       // Precompiled image info (internal use only - set to zero)

       IMAGE_DATA_DIRECTORY ManagedNativeHeader;

} IMAGE_COR20_HEADER;

 

It turns out that the 2.0 in the header structure name is kind of a misnomer and results from some pre-1.0 definitions of this structure. All released versions of the .Net runtime have had images that contain a IMAGE_COR20_HEADER structure. Additionally, all released versions of the .Net runtime have specified the MajorRuntimeVersion=2 and MinorRuntimeVersion=0. Kind of weird you might say for a v1.0/1.1 product? Yeah… That’s history for you…

What does this mean?

Well, it means that if you have a v1.0/1.1 assembly and run it on a 64-bit box with .Net 2.0 installed it will run under the 32-bit runtime in the WOW64. Whether it runs under a 1.1 32-bit runtime or the 2.0 32-bit runtime will be determined by the CLR’s loader policy, just as it would on a native 32-bit box. If you’re using v2.0 assemblies on a 64-bit machine and you have compiled them without the /platform switch or with /platform:anycpu (the default) then your image will load in the native runtime on whatever box you put it on. And of course the /platform:x86, /platform:x64, /platform:itanium switches work as they imply.

NOTE: /platform:anycpu doesn’t keep you from shooting yourself in the foot with a bad P/Invoke signature, unsafe code, etc… See this (https://blogs.msdn.com/joshwil/archive/2004/03/16/90612.aspx) blog entry for an example.

If you do have a managed v1.0/1.1 app which you believe to be 64-bit safe and whidbey compatible there is an easy way to make it run in 64-bit mode… You just have to whack the MinorRuntimeVersion to 5 in the IMAGE_COR20_HEADER for the image. Along these lines there will be a tool in the .Net 2.0 SDK called corflags.exe which will allow you to modify an image this way. A v1.0/1.1 image which has had its MinorRuntimeVersion whacked will still be compatible with the v1.0/1.1 runtimes (as per the version it was compiled against).

WARNING: If you don’t know what’s going on inside of some v1.0/1.1 image (if you didn’t write it say) you should be _VERY_ careful bumping it up to 64-bit as it may break in unexpected ways.

If you are a compiler writer and you want your application to run in 64-bit native mode under .Net 2.0 then you will need to produce images with this updated IMAGE_COR20_HEADER.

NOTE: .Net 2.0 assemblies that were compiled against the Beta 1 version of the framework (and the community drops up to now [10/15/04]) will act like v1.0/1.1 assemblies on newer builds of 64-bit OSes and load under the WOW64 in 32-bit mode.

What about v1.0/1.1 dlls?

If you make a .Net 2.0 executable and link against a v1.0/1.1 dll you will be able to load it into your process as if it was a .Net 2.0 MSIL assembly. If that v1.0/1.1 dll has code that isn’t safe to run in 64-bit mode it may crash.

Appendix: Why I think this is a good solution:

There are a number of ways in which we could have solved this problem. An easy one would have been to use the version string contained within the metadata for managed images. That string looks something like “v1.1.4322”, it represents the version of the framework which the assembly was compiled against, and.Net 2.0 assemblies will look something like “v2.0.X” (X still to be determined). You can argue that this makes sense, after all, Microsoft’s .Net 2.0 compilers (csc.exe, vbc.exe, cl.exe, etc…) will produce images that will be marked correctly. They need to since they support new features of the .Net 2.0 runtime (generics comes to mind) and bind against 2.0 frameworks libraries.

This however forces a tight binding between the runtime version and the compiler version, and doesn’t recognize the fact that there are plenty of assemblies compiled against the v1.0/1.1 frameworks that will run fine in 64-bit mode under v2.0. But even given that, its downfall is that it is a very Microsoft centric view, it assumes that the compilers will be updated at the same time as the runtime (which currently our compilers are). That tight binding wouldn’t recognize that there are compilers out there that only produce code which is safe to run in either 32-bit or 64-bit mode. Those compilers currently produce an image that works with the v1.0 or v1.1 runtime, and the compiler vendors may very well want to produce an image that also runs in 64-bit mode under the v2.0 runtime.

That’s the distinction, while Microsoft is producing compilers in this version of .Net that won’t create images which will run under any runtime prior to v2.0, other compiler vendors might very well want to produce images that run under prior versions (for deployments that maybe don’t have v2.0 installed yet) and yet still run in 64-bit native mode under v2.0 on a 64-bit machine.

This does put a great responsibility upon compiler vendors who chose to produce images that are marked this way, those compilers should only produce code which is safe to run in both 32-bit and 64-bit mode. Alternatively, they can do as our compilers do and let you shoot yourself in the foot if you want to by producing MSIL images with bad P/Invoke signatures and unsafe code… That puts the onus on the developer.