| 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 | |