Update PgoData to release-20180530-0051 (#18194)
[platform/upstream/coreclr.git] / Documentation / design-docs / standalone-gc-loading.md
1 # Standalone GC Loader Design
2
3 Author: Sean Gillespie (@swgillespie) - 2017
4
5 This document aims to provide a specification for how a standalone GC is
6 to be loaded and what is to happen in the case of version mismatches.
7
8 ## Definitions
9
10 Before diving in to the specification, it's useful to precisely define
11 some terms that will be used often in this document.
12
13 * The **Execution Engine**, or **EE** - The component of the CLR responsible for *executing* programs.
14   This is an intentionally vague definition. The GC does not care how (or even *if*) programs are
15   compiled or executed, so it is up to the EE to invoke the GC whenever an executing
16   program does something that requires the GC's attention. The EE is notable because the implementation
17   of an execution engine varies widely between runtimes; the CoreRT EE is primarily in managed code
18   (C#), while CoreCLR (and the .NET Framework)'s EE is primarily in C++.
19 * The **GC**, or **Garbage Collector** - The component of the CLR responsible for allocating managed
20   objects and reclaming unused memory. It is written in C++ and the code is shared by multiple runtimes.
21   (That is, CoreCLR/CoreRT may have different execution engines, but they share the *same* GC code.)
22 * The **DAC**, or **Data Access Component** - A subset of the execution engine that is compiled in
23   such a way that it can be run *out of process*, when debugging .NET code using a debugger. The DAC
24   is used by higher-level components such as SOS (Son of Strike, a windbg/lldb debugger extension for
25   debugging managed code) and the DBI (a COM interface). The full details about the DAC are covered in
26   [this document](https://github.com/dotnet/coreclr/blob/master/Documentation/botr/dac-notes.md).
27
28 ## Rationale and Goals of a Standalone GC
29
30 A GC that is "standalone" is one that is able to be built as a dynamic shared library and loaded
31 dynamically at startup. This definition is useful because it provides a number of benefits
32 to the codebase:
33
34 * A standalone GC that can be loaded dynamically at runtime can also be *substituted* easily by
35   loading any GC-containing dynamic shared library specified by a user. This is especially interesting
36   for prototyping and testing GC changes since it would be possible to make changes to the GC
37   without having to re-compile the runtime.
38 * A standalone GC that can be *built* as a dynamic shared library imposes a strong requirement that
39   the interfaces that the GC uses to interact with other runtime components be complete and
40   correct. A standalone GC will not link successfully if it refers to symbols defined within
41   the EE. This makes the GC codebase significantly easier to share between different execution
42   engine implementations; as long as the GC implements its side of the interface and the EE
43   implements its side of the interface, we can expect that changes within the GC itself
44   will be more portable to other runtime implementations.
45
46 Worth noting is that the JIT (both RyuJIT and the legacy JIT(s) before it) can be built standalone
47 and have realized these same benefits. The existence of an interface and an implementation loadable
48 from shared libraries has enabled RyuJIT in particular to be used as the code generator for both the
49 CoreRT compiler and crossgen, while still being flexible enough to be tested using tools that implement
50 very non-standard execution engines such as [SuperPMI](https://github.com/dotnet/coreclr/blob/master/src/ToolBox/superpmi/readme.txt).
51
52 The below loading protocol is inspired directly by the JIT loader and many aspects of the GC loader are identical
53 to what the JIT does when loading dynamic shared libraries.
54
55 ## Loading a Standalone GC
56
57 Given that it is possible to create a GC that resides in a dynamic shared library, it is important
58 that the runtime have a protocol for locating and loading such GCs. The JIT is capable of being loaded
59 in this manner and, because of this, a significant amount of prior art exists for loading components
60 for shared libraries from the file system. This specification is based heavily on the ways that a
61 standalone JIT can be loaded.
62
63 Fundamentally, the algorithm for loading a standalone GC consists of these steps:
64
65 0. Identify whether or not we should be using a standalone GC at all.
66 1. Identify *where* the standalone GC will be loaded from.
67 3. Load the dynamic shared library and ask it to identify itself (name and version).
68 4. Check that the version numbers are compatible.
69 5. If so, initialize the GC and continue on with EE startup. If not, reject the dynamic shared library
70    and raise an appropriate user-visible error.
71
72 The algorithm for initializing the DAC against a target process using a standalone GC consists of these steps:
73
74 1. Identify whether or not the target process is using a standalone GC at all. If not, no further
75    checks are necessary.
76 2. If so, inspect the version number of the standalone GC in the target process and determine whether
77    or not the DAC is compatible with that version. If not, present a notification of some kind
78    that the debugging experience will be degraded.
79 3. Continue onwards.
80
81 Each one of these steps will be explained in detail below.
82
83 ### Identifying candidate shared libraries
84
85 The question of whether or not the EE should attempt to locate and load a standalone GC
86 is answered by the EE's configuration system (`EEConfig`). EEConfig has the ability to
87 query configuration information from environment variables. Using this subsystem, users
88 can specify a specific environment variable to indicate that they are interested in
89 loading a standalone GC.
90
91 There is one environment variable that governs the behavior of the standalone GC loader:
92 `COMPlus_GCName`. It should be set to be a path to a dynamic shared library containing
93 the GC that the EE intends to load. Its presence informs the EE that, first, a standalone GC
94 is to be loaded and, second, precisely where the EE should load it from.
95
96 The EE will call `LoadLibrary` using the path given by `COMPlus_GCName`.
97 If this succeeds, the EE will move to the next step in the loading process.
98
99 ### Verifying the version of a candidate GC
100
101 Once the EE has successfully loaded a candidate GC dynamic shared library, it must then check that the candidate GC is
102 version-compatible with the version of the EE that is doing the loading. It does this in three phases. First, the
103 candidate GC must expose a function with the given name and signature:
104
105 ```c++
106 struct VersionInfo {
107   uint32_t MajorVersion;
108   uint32_t MinorVersion;
109   uint32_t BuildVersion;
110   const char* Name;
111 };
112
113 extern "C" void GC_VersionInfo(
114   /* Out */ VersionInfo*
115 );
116 ```
117
118 The EE will call `GetProcAddress` on the library, looking for `GC_VersionInfo`. It is a fatal error if this symbol
119 is not found.
120
121 Next, the EE will call this function and receive back a `VersionInfo` structure. Each EE capable of loading 
122 standalone GCs has a major version number and minor version number that is obtained from the version of 
123 `gcinterface.h` that the EE built against. It will compare these numbers against the numbers it receives from 
124 `GC_VersionInfo` in this way:
125
126 * If the EE's MajorVersion is not equal to the MajorVersion obtained from the candidate GC, reject. Major version    changes occur when there are breaking changes in the EE/GC interface and it is not possible to interoperate with 
127   incompatible interfaces. A change is considered breaking if it alters the semantics of an existing method or if 
128   it deletes or renames existing methods so that VTable layouts are not compatible.
129 * If the EE's MinorVersion is greater than the MinorVersion obtained from the candidate GC, accept
130   (Forward compatability). The EE must take care not to call any new APIs that are not present in the version of
131   the candidate GC.
132 * Otherwise, accept (Backward compatibility). It is perfectly safe to use a GC whose MinorVersion exceeds the EE's 
133   MinorVersion.
134
135 The build version and name are not considered and are provided only for display/debug purposes.
136
137 If this succeeds, the EE will transition to the next step in the loading sequence.
138
139 ### Initializing the GC
140
141 Once the EE has verified that the version of the candidate GC is valid, it then proceeds to initialize the
142 GC. It does so by loading (via `GetProcAddress`) and executing a function with this signature:
143
144 ```c++
145 extern "C" HRESULT GC_Initialize(
146   /* In  */ IGCToCLR*,
147   /* Out */ IGCHeap**.
148   /* Out */ IGCHandleManager**,
149   /* Out */ GcDacVars*
150 );
151 ```
152
153 The EE will provide its implementation of `IGCToCLR` to the GC and the GC will provide its implementations of
154 `IGCHeap`, `IGCHandleManager`, and `GcDacVars` to the EE. From here, if `GC_Initialize` returns a successful
155 HRESULT, the GC is considered initialized and the remainder of EE startup continues. If `GC_Initialize` returns
156 an error HRESULT, the initialization has failed.
157
158 ### Initializing the DAC
159
160 The existence of a standalone GC is a debuggee process has implications for how the DAC is loaded and
161 initializes itself. The DAC has access to implementation details of the GC that are not normally exposed as part
162 of the `GC/EE` interfaces, and as such it is versioned separately.
163
164 When the DAC is being initialized and it loads the `GcDacVars` structure from the debuggee process's memory, it
165 must check the major and minor versions of the DAC, which are itself DAC variables exposed by a standalone GC.
166 It then decides whether or not the loaded GC is compatible with the DAC that is currently executing. It does this
167 in the same manner that the EE does:
168
169 * If the major versions of the DAC and loaded GC do not agree, reject.
170 * If the minor version of the DAC is greater than the minor version of the GC, accept but take care
171   not to invoke any new code paths not present in the target GC.
172 * If the minor version of the DAC is less than or equal to the minor version of the GC, accept.
173
174 If a DAC rejects a loaded GC, it will return `E_FAIL` from DAC APIs that would otherwise need to interact with the
175 GC.
176
177 ## Outstanding Questions
178
179 How can we provide the most useful error message when a standalone GC fails to load? In the past it has been difficult
180 to determine what preciscely has gone wrong with `coreclr_initialize` returns a HRESULT and no indication of what occured.
181
182 Same question for the DAC - Is `E_FAIL` the best we can do? If we could define our own error for DAC/GC version
183 mismatches, that would be nice; however, that is technically a breaking change in the DAC.