The 68 things the CLR does before executing a single line of your code (*)

Because the CLR is a managed environment there are several components within the runtime that need to be initialised before any of your code can be executed. This post will take a look at the EE (Execution Engine) start-up routine and examine the initialisation process in detail.

(*) 68 is only a rough guide, it depends on which version of the runtime you are using, which features are enabled and a few other things

‘Hello World’

Imagine you have the simplest possible C# program, what has to happen before the CLR prints ‘Hello World’ out to the console?

using System ; namespace ConsoleApplication { public class Program { public static void Main ( string [] args ) { Console . WriteLine ( "Hello World!" ); } } }

The code path into the EE (Execution Engine)

When a .NET executable runs, control gets into the EE via the following code path:

_CorExeMain() (the external entry point) call to _CorExeMainInternal() _CorExeMainInternal() call to EnsureEEStarted() EnsureEEStarted() call to EEStartup() EEStartup() call to EEStartupHelper() EEStartupHelper()

(if you’re interested in what happens before this, i.e. how a CLR Host can start-up the runtime, see my previous post ‘How the dotnet CLI tooling runs your code’)

And so we end up in EEStartupHelper() , which at a high-level does the following (from a comment in ceemain.cpp):

EEStartup is responsible for all the one time initialization of the runtime.

Some of the highlights of what it does include Creates the default and shared, appdomains.

Loads mscorlib.dll and loads up the fundamental types (System.Object …)

The main phases in EE (Execution Engine) start-up routine

But let’s look at what it does in detail, the lists below contain all the individual function calls made from EEStartupHelper() (~500 L.O.C). To make them easier to understand, we’ll split them up into separate phases:

Phase 1 - Set-up the infrastructure that needs to be in place before anything else can run

that needs to be in place before anything else can run Phase 2 - Initialise the core, low-level components

components Phase 3 - Start-up the low-level components , i.e. error handling, profiling API, debugging

, i.e. error handling, profiling API, debugging Phase 4 - Start the main components , i.e. Garbage Collector (GC), AppDomains, Security

, i.e. Garbage Collector (GC), AppDomains, Security Phase 5 - Final setup and then notify other components that the EE has started

Note some items in the list below are only included if a particular feature is defined at build-time, these are indicated by the inclusion on an ifdef statement. Also note that the links take you to the code for the function being called, not the line of code within EEStartupHelper() .

Phase 1 - Set-up the infrastructure that needs to be in place before anything else can run

Phase 2 - Initialise the core, low-level components

Phase 3 - Start-up the low-level components, i.e. error handling, profiling API, debugging

Phase 4 - Start the main components, i.e. Garbage Collector (GC), AppDomains, Security

Start up security system, that handles Code Access Security (CAS) - Security::Start() which in turn calls SecurityPolicy::Start() Wire-up an event to allow synchronisation of AppDomain unloads - AppDomain::CreateADUnloadStartEvent() Initialise the ‘Stack Probes’ used to setup stack guards InitStackProbes() ( #ifdef FEATURE_STACK_PROBE ) Initialise the GC and create the heaps that it uses - InitializeGarbageCollector() Initialise the tables used to hold the locations of pinned objects - InitializePinHandleTable() Inform the debugger about the DefaultDomain, so it can interact with it - SystemDomain::System()->PublishAppDomainAndInformDebugger(..) ( #ifdef DEBUGGING_SUPPORTED ) Initialise the existing OOB Assembly List (no idea?) - ExistingOobAssemblyList::Init() ( #ifndef FEATURE_CORECLR ) Actually initialise the System Domain (which contains mscorlib), so that it can start executing - SystemDomain::System()->Init()

Phase 5 Final setup and then notify other components that the EE has started

Tell the profiler we’ve stated up - SystemDomain::NotifyProfilerStartup() ( #ifdef PROFILING_SUPPORTED ) Pre-create a thread to handle AppDomain unloads - AppDomain::CreateADUnloadWorker() ( #ifndef CROSSGEN_COMPILE ) Set a flag to confirm that ‘initialisation’ of the EE succeeded - g_fEEInit = false Load the System Assemblies (‘mscorlib’) into the Default Domain - SystemDomain::System()->DefaultDomain()->LoadSystemAssemblies() Set-up all the shared static variables (and String.Empty ) in the Default Domain - SystemDomain::System()->DefaultDomain()->SetupSharedStatics(), they are all contained in the internal class SharedStatics.cs Set-up the stack sampler feature, that identifies ‘hot’ methods in your code - StackSampler::Init() ( #ifdef FEATURE_STACK_SAMPLING ) Perform any once-only SafeHandle initialization - SafeHandle::Init() ( #ifndef CROSSGEN_COMPILE ) Set flags to indicate that the CLR has successfully started - g_fEEStarted = TRUE , g_EEStartupStatus = S_OK and hr = S_OK Write to the log ===================EEStartup Completed===================

Once this is all done, the CLR is now ready to execute your code!!

Executing your code

Your code will be executed (after first being ‘JITted’) via the following code flow:

Discuss this post on Hacker News and /r/programming

Further information

The CLR provides a huge amount of log information if you create a debug build and then enable the right environment variables. The links below take you to the various logs produced when running a simple ‘hello world’ program (shown at the top of this post), they give you an pretty good idea of the different things that the CLR is doing behind-the-scenes.