Quiz: What managed code runs before managed Main() in your program startup path?
Answers:
I note "managed code", because obviously the CLR startup code gets executed, as does other native startup code.
1) The common answer is static constructors referenced from Main().
2) The less common answer would be managed code in the CLR's startup path. While much of the CLR is implemented in native code (mscorwks.dll), we try to migrate parts of the CLR itself into managed and into the BCL (mscorlib.dll).
You can verify this in MDbg. MDbg normally tries to identify the Main() method and set a breakpoint there and stop there. However, the 'ca nt' command tells Mdbg to stop when a thread first enters managed code. Here's an MDbg transcript:
C:\temp>mdbgMDbg (Managed debugger) v2.0.50727.42 (RTM.050727-4200) started.Copyright (C) Microsoft Corporation. All rights reserved. For information about commands type "help";to exit program type "quit". mdbg> ca nt <-- stop when thread first hits managed codemdbg> run x.exeSTOP: Thread CreatedIP: 0 @ System.Security.PermissionSet..cctor - MAPPING_PROLOG[p#:0, t#:0] mdbg> whereThread [#:0]*0. System.Security.PermissionSet..cctor (source line information unavailable)[p#:0, t#:0] mdbg> goSTOP: Breakpoint Hit22: {[p#:0, t#:0] mdbg> whereThread [#:0]*0. Program.Main (x.cs:22)[p#:0, t#:0] mdbg>
C:\temp>mdbgMDbg (Managed debugger) v2.0.50727.42 (RTM.050727-4200) started.Copyright (C) Microsoft Corporation. All rights reserved.
For information about commands type "help";to exit program type "quit".
mdbg> ca nt <-- stop when thread first hits managed codemdbg> run x.exeSTOP: Thread CreatedIP: 0 @ System.Security.PermissionSet..cctor - MAPPING_PROLOG[p#:0, t#:0] mdbg> whereThread [#:0]*0. System.Security.PermissionSet..cctor (source line information unavailable)[p#:0, t#:0] mdbg> goSTOP: Breakpoint Hit22: {[p#:0, t#:0] mdbg> whereThread [#:0]*0. Program.Main (x.cs:22)[p#:0, t#:0] mdbg>
So in this case, you can see that System.Security.PermissionSet() is being run before Main. When we continue past that, we hit the breakpoint that Mdbg set on the main() method.
3) And there bonus answer is: any code that the compiler injects before the call to Main().
The user's Main() method is not necessarily the real entry point for a module. That's actually why managed pdbs specify a "user entry point" method (see the <EntryPoint> tag in http://blogs.msdn.com/jmstall/archive/2005/08/25/pdb2xml.aspx ).
The "real" entry point for the CLR has ".entrypoint" in the IL. The user's entry point is specified in the PDB. Eg, in the ILDasm for the main method:
.method private hidebysig static void Main() cil managed{ .entrypoint ...
Why? This gives languages additional flexibility to uphold language semantics and do setup before the user's Main() method is called.
4) And a very obscure answer: failure code, such as exception constructors. (I alluded to this here)
At this point, it should be clear that there's no reason that Main() has to be the first code that runs. So we could brainstorm other strange cases.
A real example with MC++
C# is so clean that it's not an ideal language for demonstrating wonky features of the CLR. So let's turn our attention to MC++.
Compile the following MC++ app with /clr:pure (so it's 100% IL and no suspicious native stuff):
// mcpp_console.cpp : main project file. #include "stdafx.h" using namespace System; int main(array<System::String ^> ^args) { Console::WriteLine(L"Hello World"); return 0; }
F10 into main, and you can see that Main() is not the first managed code on the stack:
> mcpp_console.exe!main + 0x15 bytes C++ mcpp_console.exe!mainCRTStartupStrArray + 0xb8 bytes C++
You can verify that mainCRTStartupStrArray in this case is indeed managed code.
This example should make it clear that that managed case of 'code running before main' can inherit much of the properties that the native case had.
A bonus experiment:
Here's a bonus experiment. Take a trivial C# app:
// Test using System; class Foo { static void Main() { Console.WriteLine("Main"); } static void Test() { Console.WriteLine("Test"); } }
Compile and run it, and it prints "Main".
C:\temp>csc t.cs & t.exeMicrosoft (R) Visual C# 2005 Compiler version 8.00.50727.1378for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727Copyright (C) Microsoft Corporation 2001-2005. All rights reserved. Main
C:\temp>csc t.cs & t.exeMicrosoft (R) Visual C# 2005 Compiler version 8.00.50727.1378for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.
Main
Now use ILasm/ildasm round-tripping to move the .entrypoint to Test.
C:\temp>ildasm t.exe /out=t.il
Edit t.il to move .entrypoint from 'main' to 'test'.
C:\temp>ilasm t.il Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.1378Copyright (c) Microsoft Corporation. All rights reserved.Assembling 't.il' to EXE --> 't.exe'Source file is ANSI Assembled method Foo::MainAssembled method Foo::TestAssembled method Foo::.ctorCreating PE file Emitting classes:Class 1: Foo Emitting fields and methods:GlobalClass 1 Methods: 3; Emitting events and properties:GlobalClass 1Writing PE fileOperation completed successfully
C:\temp>ilasm t.il
Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.1378Copyright (c) Microsoft Corporation. All rights reserved.Assembling 't.il' to EXE --> 't.exe'Source file is ANSI
Assembled method Foo::MainAssembled method Foo::TestAssembled method Foo::.ctorCreating PE file
Emitting classes:Class 1: Foo
Emitting fields and methods:GlobalClass 1 Methods: 3;
Emitting events and properties:GlobalClass 1Writing PE fileOperation completed successfully
And run it. You see you've changed the entry point and Main() doesn't execute now.
C:\temp>t.exeTest