Different IL when compiling .NET assemblies on different Windows versions?

 

Someone asked if you compile a .NET assembly on two different operating systems (ex: Windows 2003 Server versus Windows XP) could there possibly be any differences between the resulting assemblies?

Given that we know that .NET applications compile to an intermediate language and run inside of the CLR we can assume that the assemblies are identical. But are they? Let’s test it.

I created a default C# windows application (File->New Project->C# Project->Windows Application) on my tablet and compiled the application. I copied the source to a Win2k3 box and compiled the same exact source code there. I then used ILDASM.EXE to dump the IL for the two binaries. If you read my blog entry about restoring lost code you should be familiar with this technique.

SIDE NOTE:   To get a complete listing of the IL and metadata for the assemblies I launched ILDASM with the /adv switch which enables additional options for dumping and viewing metadata.

I then used the trusty WinDiff tool to do a file comparison of the two IL files.

ADDITIONAL SIDE NOTE:   You can also use Word to do a file comparison I found out today. Open the document (text file, etc.), go to the Tools menu and select “Compare and Merge Documents…”

WinDiff identified several discrepancies between the files. I was actually expecting a few differences most likely related to file size and a timestamp for file generation but was surprised by what I saw. Here are the file differences:

// Microsoft (R) .NET Framework IL Disassembler. Version 1.1.4322.573

// Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

// PE Header:

// Subsystem: 00000002

// Native entry point address: 0000285e

// Native entry point address: 0000287e

// Image base: 00400000

// Section alignment: 00002000

// File alignment: 00001000

// Stack reserve size: 00100000

// Stack commit size: 00001000

// Directories: 00000010

// 0 [0 ] address [size] of Export Directory:

// 2810 [4b ] address [size] of Import Directory:         

// 2830 [4b ] address [size] of Import Directory:         

// 4000 [878 ] address [size] of Resource Directory:

// 0 [0 ] address [size] of Exception Directory:

// 0 [0 ] address [size] of Security Directory:

// 6000 [c ] address [size] of Base Relocation Table:

// 21ac [1c ] address [size] of Debug Directory:

// 0 [0 ] address [size] of Architecture Specific:

// 0 [0 ] address [size] of Global Pointer:

// 0 [0 ] address [size] of TLS Directory:

// 0 [0 ] address [size] of Load Config Directory:

// 0 [0 ] address [size] of Bound Import Directory:

// 2000 [8 ] address [size] of Import Address Table:

// 0 [0 ] address [size] of Delay Load IAT:

// 2008 [48 ] address [size] of CLR Header:

// Import Address Table

// mscoree.dll

// 00002000 Import Address Table

// 0000284e Import Name Table

// 0000286e Import Name Table

// 0 time date stamp

// 0 Index of first forwarder reference

//

// 0 _CorExeMain

.

// Delay Load Import Address Table

// No data.

// CLR Header:

// 72 Header Size

// 2 Major Runtime Version

// 0 Minor Runtime Version

// 1 Flags

// 6000004 Entrypoint Token

// 2224 [5ec ] address [size] of Metadata Directory:       

// 2244 [5ec ] address [size] of Metadata Directory:       

// 20ec [c0 ] address [size] of Resources Directory:

// 0 [0 ] address [size] of Strong Name Signature:

// 0 [0 ] address [size] of CodeManager Table:

// 0 [0 ] address [size] of VTableFixups Directory:

// 0 [0 ] address [size] of Export Address Table:

// 0 [0 ] address [size] of Precompile Header:

// Code Manager Table:

// default

// Export Address Table Jumps:

// No data..

..

.

. .assembly /*20000001*/ WindowsApplication1

{

  .custom /*0C000001:0A000007*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyCopyrightAttribute/* 01000009 */::.ctor(string) /* 0A000007 */ = ( 01 00 00 00 00 )

  .custom /*0C000002:0A000001*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyKeyNameAttribute/* 01000003 */::.ctor(string) /* 0A000001 */ = ( 01 00 00 00 00 )

  .custom /*0C000003:0A000002*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyKeyFileAttribute/* 01000004 */::.ctor(string) /* 0A000002 */ = ( 01 00 00 00 00 )

  .custom /*0C000004:0A000003*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyDelaySignAttribute/* 01000005 */::.ctor(bool) /* 0A000003 */ = ( 01 00 00 00 00 )

  .custom /*0C000005:0A000006*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyTrademarkAttribute/* 01000008 */::.ctor(string) /* 0A000006 */ = ( 01 00 00 00 00 )

  .custom /*0C000006:0A00000A*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyConfigurationAttribute/* 0100000C */::.ctor(string) /* 0A00000A */ = ( 01 00 00 00 00 )

  // --- The following custom attribute is added automatically, do not uncomment -------

  // .custom /*0C000007:0A00000D*/ instance void [mscorlib/* 23000003 */]System.Diagnostics.DebuggableAttribute/* 0100000F */::.ctor(bool,

  // bool) /* 0A00000D */ = ( 01 00 01 01 00 00 )

  .custom /*0C000008:0A000009*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyCompanyAttribute/* 0100000B */::.ctor(string) /* 0A000009 */ = ( 01 00 00 00 00 )

  .custom /*0C000009:0A000008*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyProductAttribute/* 0100000A */::.ctor(string) /* 0A000008 */ = ( 01 00 00 00 00 )

  .custom /*0C00000A:0A00000B*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyDescriptionAttribute/* 0100000D */::.ctor(string) /* 0A00000B */ = ( 01 00 00 00 00 )

  .custom /*0C00000B:0A00000C*/ instance void [mscorlib/* 23000003 */]System.Reflection.AssemblyTitleAttribute/* 0100000E */::.ctor(string) /* 0A00000C */ = ( 01 00 00 00 00 )

  .hash algorithm 0x00008004

  .ver 1:0:1540:591

  .ver 1:0:1540:526

}

.

.

. .module WindowsApplication1.exe

// MVID: {D0722759-767F-4DB0-8280-A3E80637CE26}

// MVID: {414187D5-EACD-480D-964C-E787190ACC40}/

.imagebase 0x00400000

.subsystem 0x00000002

.file alignment 4096

.corflags 0x00000001

// Image base: 0x06cf0000

// Image base: 0x06ba0000

.

.

.

.

As you can see there were six differences between the IL of the two compiled binaries. The only two I could readily explain without any research is the assembly version and the MVID.

  • In the AssemblyInfo file for my project the Assembly version attribute is marked to as having an auto-increment version number ([assembly: AssemblyVersion("1.0.*")]). This change and the remaining changes are all metadata for the assembly.
  • The MVID is known as the Module Version ID. This is a unique GUID that identifies the assembly, this value is changed every time the assembly is recompiled.

The metadata is used by the CLR for various things such as to figure out where in the binary the starting address is of the 1st instruction, also referred to as the entry point, for the managed code, etc. If you want more information on both PE headers and .NET assembly metadata read the following two great MSDN articles:

PE Header - Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format .

               

.NET Assembly MetaData - Avoiding DLL Hell: Introducing Application Metadata in the Microsoft .NET Framework