1 # Standalone GC Loader Design
3 Author: Sean Gillespie (@swgillespie) - 2017
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.
10 Before diving in to the specification, it's useful to precisely define
11 some terms that will be used often in this document.
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).
28 ## Rationale and Goals of a Standalone GC
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
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.
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).
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.
55 ## Loading a Standalone GC
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.
63 Fundamentally, the algorithm for loading a standalone GC consists of these steps:
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.
72 The algorithm for initializing the DAC against a target process using a standalone GC consists of these steps:
74 1. Identify whether or not the target process is using a standalone GC at all. If not, no further
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.
81 Each one of these steps will be explained in detail below.
83 ### Identifying candidate shared libraries
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.
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.
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.
99 ### Verifying the version of a candidate GC
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:
107 uint32_t MajorVersion;
108 uint32_t MinorVersion;
109 uint32_t BuildVersion;
113 extern "C" void GC_VersionInfo(
114 /* Out */ VersionInfo*
118 The EE will call `GetProcAddress` on the library, looking for `GC_VersionInfo`. It is a fatal error if this symbol
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:
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
132 * Otherwise, accept (Backward compatibility). It is perfectly safe to use a GC whose MinorVersion exceeds the EE's
135 The build version and name are not considered and are provided only for display/debug purposes.
137 If this succeeds, the EE will transition to the next step in the loading sequence.
139 ### Initializing the GC
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:
145 extern "C" HRESULT GC_Initialize(
148 /* Out */ IGCHandleManager**,
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.
158 ### Initializing the DAC
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.
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:
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.
174 If a DAC rejects a loaded GC, it will return `E_FAIL` from DAC APIs that would otherwise need to interact with the
177 ## Outstanding Questions
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.
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.