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.
15GPTR_IMPL_INIT(uint32_t, g_card_table, nullptr);
16GPTR_IMPL_INIT(uint8_t, g_lowest_address, nullptr);
17GPTR_IMPL_INIT(uint8_t, g_highest_address, nullptr);
18GVAL_IMPL_INIT(GCHeapType, g_heap_type, GC_HEAP_INVALID);
19uint8_t* g_ephemeral_low = (uint8_t*)1;
20uint8_t* g_ephemeral_high = (uint8_t*)~0;
21
22#ifdef FEATURE_MANUALLY_MANAGED_CARD_BUNDLES
23uint32_t* g_card_bundle_table = nullptr;
24#endif
25
26// This is the global GC heap, maintained by the VM.
27GPTR_IMPL(IGCHeap, g_pGCHeap);
28
29GcDacVars g_gc_dac_vars;
30GPTR_IMPL(GcDacVars, g_gcDacGlobals);
31
32#ifdef FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
33
34uint8_t* g_sw_ww_table = nullptr;
35bool g_sw_ww_enabled_for_gc_heap = false;
36
37#endif // FEATURE_USE_SOFTWARE_WRITE_WATCH_FOR_GC_HEAP
38
39gc_alloc_context g_global_alloc_context = {};
40
41enum 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.
54GC_LOAD_STATUS g_gc_load_status = GC_LOAD_STATUS_BEFORE_START;
55
56// The version of the GC that we have loaded.
57VersionInfo g_gc_version_info;
58
59// The module that contains the GC.
60HMODULE 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.
64extern "C" void GC_VersionInfo(/* Out */ VersionInfo* info);
65extern "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
74HMODULE GCHeapUtilities::GetGCModule()
75{
76 assert(g_gc_module);
77 return g_gc_module;
78}
79
80namespace
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.
91DangerousNonHostedSpinLock g_eventStashLock;
92
93GCEventLevel g_stashedLevel = GCEventLevel_None;
94GCEventKeyword g_stashedKeyword = GCEventKeyword_None;
95GCEventLevel g_stashedPrivateLevel = GCEventLevel_None;
96GCEventKeyword g_stashedPrivateKeyword = GCEventKeyword_None;
97
98BOOL 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.
105void 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
126void 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.
161HRESULT 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.
246HRESULT 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.
283HRESULT 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
308void 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