1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | #include "common.h" |
6 | #include "gcheaputilities.h" |
7 | #include "gcenv.ee.h" |
8 | #include "appdomain.hpp" |
9 | |
10 | |
11 | // These globals are variables used within the GC and maintained |
12 | // by the EE for use in write barriers. It is the responsibility |
13 | // of the GC to communicate updates to these globals to the EE through |
14 | // GCToEEInterface::StompWriteBarrierResize and GCToEEInterface::StompWriteBarrierEphemeral. |
15 | GPTR_IMPL_INIT(uint32_t, g_card_table, nullptr); |
16 | GPTR_IMPL_INIT(uint8_t, g_lowest_address, nullptr); |
17 | GPTR_IMPL_INIT(uint8_t, g_highest_address, nullptr); |
18 | GVAL_IMPL_INIT(GCHeapType, g_heap_type, GC_HEAP_INVALID); |
19 | uint8_t* g_ephemeral_low = (uint8_t*)1; |
20 | uint8_t* g_ephemeral_high = (uint8_t*)~0; |
21 | |
22 | #ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES |
23 | uint32_t* g_card_bundle_table = nullptr; |
24 | #endif |
25 | |
26 | // This is the global GC heap, maintained by the VM. |
27 | GPTR_IMPL(IGCHeap, g_pGCHeap); |
28 | |
29 | GcDacVars g_gc_dac_vars; |
30 | GPTR_IMPL(GcDacVars, g_gcDacGlobals); |
31 | |
32 | #ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP |
33 | |
34 | uint8_t* g_sw_ww_table = nullptr; |
35 | bool g_sw_ww_enabled_for_gc_heap = false; |
36 | |
37 | #endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP |
38 | |
39 | gc_alloc_context g_global_alloc_context = {}; |
40 | |
41 | enum GC_LOAD_STATUS { |
42 | GC_LOAD_STATUS_BEFORE_START, |
43 | GC_LOAD_STATUS_START, |
44 | GC_LOAD_STATUS_DONE_LOAD, |
45 | GC_LOAD_STATUS_GET_VERSIONINFO, |
46 | GC_LOAD_STATUS_CALL_VERSIONINFO, |
47 | GC_LOAD_STATUS_DONE_VERSION_CHECK, |
48 | GC_LOAD_STATUS_GET_INITIALIZE, |
49 | GC_LOAD_STATUS_LOAD_COMPLETE |
50 | }; |
51 | |
52 | // Load status of the GC. If GC loading fails, the value of this |
53 | // global indicates where the failure occured. |
54 | GC_LOAD_STATUS g_gc_load_status = GC_LOAD_STATUS_BEFORE_START; |
55 | |
56 | // The version of the GC that we have loaded. |
57 | VersionInfo g_gc_version_info; |
58 | |
59 | // The module that contains the GC. |
60 | HMODULE g_gc_module; |
61 | |
62 | // GC entrypoints for the the linked-in GC. These symbols are invoked |
63 | // directly if we are not using a standalone GC. |
64 | extern "C" void GC_VersionInfo(/* Out */ VersionInfo* info); |
65 | extern "C" HRESULT GC_Initialize( |
66 | /* In */ IGCToCLR* clrToGC, |
67 | /* Out */ IGCHeap** gcHeap, |
68 | /* Out */ IGCHandleManager** gcHandleManager, |
69 | /* Out */ GcDacVars* gcDacVars |
70 | ); |
71 | |
72 | #ifndef DACCESS_COMPILE |
73 | |
74 | HMODULE GCHeapUtilities::GetGCModule() |
75 | { |
76 | assert(g_gc_module); |
77 | return g_gc_module; |
78 | } |
79 | |
80 | namespace |
81 | { |
82 | |
83 | // This block of code contains all of the state necessary to handle incoming |
84 | // EtwCallbacks before the GC has been initialized. This is a tricky problem |
85 | // because EtwCallbacks can appear at any time, even when we are just about |
86 | // finished initializing the GC. |
87 | // |
88 | // The below lock is taken by the "main" thread (the thread in EEStartup) and |
89 | // the "ETW" thread, the one calling EtwCallback. EtwCallback may or may not |
90 | // be called on the main thread. |
91 | DangerousNonHostedSpinLock g_eventStashLock; |
92 | |
93 | GCEventLevel g_stashedLevel = GCEventLevel_None; |
94 | GCEventKeyword g_stashedKeyword = GCEventKeyword_None; |
95 | GCEventLevel g_stashedPrivateLevel = GCEventLevel_None; |
96 | GCEventKeyword g_stashedPrivateKeyword = GCEventKeyword_None; |
97 | |
98 | BOOL g_gcEventTracingInitialized = FALSE; |
99 | |
100 | // FinalizeLoad is called by the main thread to complete initialization of the GC. |
101 | // At this point, the GC has provided us with an IGCHeap instance and we are preparing |
102 | // to "publish" it by assigning it to g_pGCHeap. |
103 | // |
104 | // This function can proceed concurrently with StashKeywordAndLevel below. |
105 | void FinalizeLoad(IGCHeap* gcHeap, IGCHandleManager* handleMgr, HMODULE gcModule) |
106 | { |
107 | g_pGCHeap = gcHeap; |
108 | |
109 | { |
110 | DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock); |
111 | |
112 | // Ultimately, g_eventStashLock ensures that no two threads call ControlEvents at any |
113 | // point in time. |
114 | g_pGCHeap->ControlEvents(g_stashedKeyword, g_stashedLevel); |
115 | g_pGCHeap->ControlPrivateEvents(g_stashedPrivateKeyword, g_stashedPrivateLevel); |
116 | g_gcEventTracingInitialized = TRUE; |
117 | } |
118 | |
119 | g_pGCHandleManager = handleMgr; |
120 | g_gcDacGlobals = &g_gc_dac_vars; |
121 | g_gc_load_status = GC_LOAD_STATUS_LOAD_COMPLETE; |
122 | g_gc_module = gcModule; |
123 | LOG((LF_GC, LL_INFO100, "GC load successful\n" )); |
124 | } |
125 | |
126 | void StashKeywordAndLevel(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level) |
127 | { |
128 | DangerousNonHostedSpinLockHolder lockHolder(&g_eventStashLock); |
129 | if (!g_gcEventTracingInitialized) |
130 | { |
131 | if (isPublicProvider) |
132 | { |
133 | g_stashedKeyword = keywords; |
134 | g_stashedLevel = level; |
135 | } |
136 | else |
137 | { |
138 | g_stashedPrivateKeyword = keywords; |
139 | g_stashedPrivateLevel = level; |
140 | } |
141 | } |
142 | else |
143 | { |
144 | if (isPublicProvider) |
145 | { |
146 | g_pGCHeap->ControlEvents(keywords, level); |
147 | } |
148 | else |
149 | { |
150 | g_pGCHeap->ControlPrivateEvents(keywords, level); |
151 | } |
152 | } |
153 | } |
154 | |
155 | // Loads and initializes a standalone GC, given the path to the GC |
156 | // that we should load. Returns S_OK on success and the failed HRESULT |
157 | // on failure. |
158 | // |
159 | // See Documentation/design-docs/standalone-gc-loading.md for details |
160 | // on the loading protocol in use here. |
161 | HRESULT LoadAndInitializeGC(LPWSTR standaloneGcLocation) |
162 | { |
163 | LIMITED_METHOD_CONTRACT; |
164 | |
165 | #ifndef FEATURE_STANDALONE_GC |
166 | LOG((LF_GC, LL_FATALERROR, "EE not built with the ability to load standalone GCs" )); |
167 | return E_FAIL; |
168 | #else |
169 | LOG((LF_GC, LL_INFO100, "Loading standalone GC from path %S\n" , standaloneGcLocation)); |
170 | HMODULE hMod = CLRLoadLibrary(standaloneGcLocation); |
171 | if (!hMod) |
172 | { |
173 | HRESULT err = GetLastError(); |
174 | LOG((LF_GC, LL_FATALERROR, "Load of %S failed\n" , standaloneGcLocation)); |
175 | return err; |
176 | } |
177 | |
178 | // a standalone GC dispatches virtually on GCToEEInterface, so we must instantiate |
179 | // a class for the GC to use. |
180 | IGCToCLR* gcToClr = new (nothrow) standalone::GCToEEInterface(); |
181 | if (!gcToClr) |
182 | { |
183 | return E_OUTOFMEMORY; |
184 | } |
185 | |
186 | g_gc_load_status = GC_LOAD_STATUS_DONE_LOAD; |
187 | GC_VersionInfoFunction versionInfo = (GC_VersionInfoFunction)GetProcAddress(hMod, "GC_VersionInfo" ); |
188 | if (!versionInfo) |
189 | { |
190 | HRESULT err = GetLastError(); |
191 | LOG((LF_GC, LL_FATALERROR, "Load of `GC_VersionInfo` from standalone GC failed\n" )); |
192 | return err; |
193 | } |
194 | |
195 | g_gc_load_status = GC_LOAD_STATUS_GET_VERSIONINFO; |
196 | versionInfo(&g_gc_version_info); |
197 | g_gc_load_status = GC_LOAD_STATUS_CALL_VERSIONINFO; |
198 | |
199 | if (g_gc_version_info.MajorVersion != GC_INTERFACE_MAJOR_VERSION) |
200 | { |
201 | LOG((LF_GC, LL_FATALERROR, "Loaded GC has incompatible major version number (expected %d, got %d)\n" , |
202 | GC_INTERFACE_MAJOR_VERSION, g_gc_version_info.MajorVersion)); |
203 | return E_FAIL; |
204 | } |
205 | |
206 | if (g_gc_version_info.MinorVersion < GC_INTERFACE_MINOR_VERSION) |
207 | { |
208 | LOG((LF_GC, LL_INFO100, "Loaded GC has lower minor version number (%d) than EE was compiled against (%d)\n" , |
209 | g_gc_version_info.MinorVersion, GC_INTERFACE_MINOR_VERSION)); |
210 | } |
211 | |
212 | LOG((LF_GC, LL_INFO100, "Loaded GC identifying itself with name `%s`\n" , g_gc_version_info.Name)); |
213 | g_gc_load_status = GC_LOAD_STATUS_DONE_VERSION_CHECK; |
214 | GC_InitializeFunction initFunc = (GC_InitializeFunction)GetProcAddress(hMod, "GC_Initialize" ); |
215 | if (!initFunc) |
216 | { |
217 | HRESULT err = GetLastError(); |
218 | LOG((LF_GC, LL_FATALERROR, "Load of `GC_Initialize` from standalone GC failed\n" )); |
219 | return err; |
220 | } |
221 | |
222 | g_gc_load_status = GC_LOAD_STATUS_GET_INITIALIZE; |
223 | IGCHeap* heap; |
224 | IGCHandleManager* manager; |
225 | HRESULT initResult = initFunc(gcToClr, &heap, &manager, &g_gc_dac_vars); |
226 | if (initResult == S_OK) |
227 | { |
228 | FinalizeLoad(heap, manager, hMod); |
229 | } |
230 | else |
231 | { |
232 | LOG((LF_GC, LL_FATALERROR, "GC initialization failed with HR = 0x%X\n" , initResult)); |
233 | } |
234 | |
235 | return initResult; |
236 | #endif // FEATURE_STANDALONE_GC |
237 | } |
238 | |
239 | // Initializes a non-standalone GC. The protocol for initializing a non-standalone GC |
240 | // is similar to loading a standalone one, except that the GC_VersionInfo and |
241 | // GC_Initialize symbols are linked to directory and thus don't need to be loaded. |
242 | // |
243 | // The major and minor versions are still checked in debug builds - it must be the case |
244 | // that the GC and EE agree on a shared version number because they are built from |
245 | // the same sources. |
246 | HRESULT InitializeDefaultGC() |
247 | { |
248 | LIMITED_METHOD_CONTRACT; |
249 | |
250 | LOG((LF_GC, LL_INFO100, "Standalone GC location not provided, using provided GC\n" )); |
251 | |
252 | g_gc_load_status = GC_LOAD_STATUS_DONE_LOAD; |
253 | GC_VersionInfo(&g_gc_version_info); |
254 | g_gc_load_status = GC_LOAD_STATUS_CALL_VERSIONINFO; |
255 | |
256 | // the default GC builds with the rest of the EE. By definition, it must have been |
257 | // built with the same interface version. |
258 | assert(g_gc_version_info.MajorVersion == GC_INTERFACE_MAJOR_VERSION); |
259 | assert(g_gc_version_info.MinorVersion == GC_INTERFACE_MINOR_VERSION); |
260 | g_gc_load_status = GC_LOAD_STATUS_DONE_VERSION_CHECK; |
261 | |
262 | IGCHeap* heap; |
263 | IGCHandleManager* manager; |
264 | HRESULT initResult = GC_Initialize(nullptr, &heap, &manager, &g_gc_dac_vars); |
265 | if (initResult == S_OK) |
266 | { |
267 | FinalizeLoad(heap, manager, GetModuleInst()); |
268 | } |
269 | else |
270 | { |
271 | LOG((LF_GC, LL_FATALERROR, "GC initialization failed with HR = 0x%X\n" , initResult)); |
272 | } |
273 | |
274 | |
275 | return initResult; |
276 | } |
277 | |
278 | } // anonymous namespace |
279 | |
280 | // Loads (if necessary) and initializes the GC. If using a standalone GC, |
281 | // it loads the library containing it and dynamically loads the GC entry point. |
282 | // If using a non-standalone GC, it invokes the GC entry point directly. |
283 | HRESULT GCHeapUtilities::LoadAndInitialize() |
284 | { |
285 | LIMITED_METHOD_CONTRACT; |
286 | |
287 | // we should only call this once on startup. Attempting to load a GC |
288 | // twice is an error. |
289 | assert(g_pGCHeap == nullptr); |
290 | |
291 | // we should not have attempted to load a GC already. Attempting a |
292 | // load after the first load already failed is an error. |
293 | assert(g_gc_load_status == GC_LOAD_STATUS_BEFORE_START); |
294 | g_gc_load_status = GC_LOAD_STATUS_START; |
295 | |
296 | LPWSTR standaloneGcLocation = nullptr; |
297 | CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCName, &standaloneGcLocation); |
298 | if (!standaloneGcLocation) |
299 | { |
300 | return InitializeDefaultGC(); |
301 | } |
302 | else |
303 | { |
304 | return LoadAndInitializeGC(standaloneGcLocation); |
305 | } |
306 | } |
307 | |
308 | void GCHeapUtilities::RecordEventStateChange(bool isPublicProvider, GCEventKeyword keywords, GCEventLevel level) |
309 | { |
310 | CONTRACTL { |
311 | MODE_ANY; |
312 | NOTHROW; |
313 | GC_NOTRIGGER; |
314 | CAN_TAKE_LOCK; |
315 | } CONTRACTL_END; |
316 | |
317 | StashKeywordAndLevel(isPublicProvider, keywords, level); |
318 | } |
319 | |
320 | #endif // DACCESS_COMPILE |
321 | |