| 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 "gcenv.h" |
| 7 | |
| 8 | #if defined(WIN64EXCEPTIONS) |
| 9 | |
| 10 | struct FindFirstInterruptiblePointState |
| 11 | { |
| 12 | unsigned offs; |
| 13 | unsigned endOffs; |
| 14 | unsigned returnOffs; |
| 15 | }; |
| 16 | |
| 17 | bool FindFirstInterruptiblePointStateCB( |
| 18 | UINT32 startOffset, |
| 19 | UINT32 stopOffset, |
| 20 | LPVOID hCallback) |
| 21 | { |
| 22 | FindFirstInterruptiblePointState* pState = (FindFirstInterruptiblePointState*)hCallback; |
| 23 | |
| 24 | _ASSERTE(startOffset < stopOffset); |
| 25 | _ASSERTE(pState->offs < pState->endOffs); |
| 26 | |
| 27 | if (stopOffset <= pState->offs) |
| 28 | { |
| 29 | // The range ends before the requested offset. |
| 30 | return false; |
| 31 | } |
| 32 | |
| 33 | // The offset is in the range. |
| 34 | if (startOffset <= pState->offs && |
| 35 | pState->offs < stopOffset) |
| 36 | { |
| 37 | pState->returnOffs = pState->offs; |
| 38 | return true; |
| 39 | } |
| 40 | |
| 41 | // The range is completely after the desired offset. We use the range start offset, if |
| 42 | // it comes before the given endOffs. We assume that the callback is called with ranges |
| 43 | // in increasing order, so earlier ones are reported before later ones. That is, if we |
| 44 | // get to this case, it will be the closest interruptible range after the requested |
| 45 | // offset. |
| 46 | |
| 47 | _ASSERTE(pState->offs < startOffset); |
| 48 | if (startOffset < pState->endOffs) |
| 49 | { |
| 50 | pState->returnOffs = startOffset; |
| 51 | return true; |
| 52 | } |
| 53 | |
| 54 | return false; |
| 55 | } |
| 56 | |
| 57 | // Find the first interruptible point in the range [offs .. endOffs) (the beginning of the range is inclusive, |
| 58 | // the end is exclusive). Return -1 if no such point exists. |
| 59 | unsigned FindFirstInterruptiblePoint(CrawlFrame* pCF, unsigned offs, unsigned endOffs) |
| 60 | { |
| 61 | #ifdef USE_GC_INFO_DECODER |
| 62 | GCInfoToken gcInfoToken = pCF->GetGCInfoToken(); |
| 63 | GcInfoDecoder gcInfoDecoder(gcInfoToken, DECODE_FOR_RANGES_CALLBACK); |
| 64 | |
| 65 | FindFirstInterruptiblePointState state; |
| 66 | state.offs = offs; |
| 67 | state.endOffs = endOffs; |
| 68 | state.returnOffs = -1; |
| 69 | |
| 70 | gcInfoDecoder.EnumerateInterruptibleRanges(&FindFirstInterruptiblePointStateCB, &state); |
| 71 | |
| 72 | return state.returnOffs; |
| 73 | #else |
| 74 | PORTABILITY_ASSERT("FindFirstInterruptiblePoint" ); |
| 75 | return -1; |
| 76 | #endif // USE_GC_INFO_DECODER |
| 77 | } |
| 78 | |
| 79 | #endif // WIN64EXCEPTIONS |
| 80 | |
| 81 | //----------------------------------------------------------------------------- |
| 82 | // Determine whether we should report the generic parameter context |
| 83 | // |
| 84 | // This is meant to detect following situations: |
| 85 | // |
| 86 | // When a ThreadAbortException is raised |
| 87 | // in the prolog of a managed method, before the location for the generics |
| 88 | // context has been initialized; when such a TAE is raised, we are open to a |
| 89 | // race with the GC (e.g. while creating the managed object for the TAE). |
| 90 | // The GC would cause a stack walk, and if we report the stack location for |
| 91 | // the generic param context at this time we'd crash. |
| 92 | // The long term solution is to avoid raising TAEs in any non-GC safe points, |
| 93 | // and to additionally ensure that we do not expose the runtime to TAE |
| 94 | // starvation. |
| 95 | // |
| 96 | // When we're in the process of resolution of an interface method and the |
| 97 | // interface method happens to have a default implementation. Normally, |
| 98 | // such methods require a generic context, but since we didn't resolve the |
| 99 | // method to an implementation yet, we don't have the right context (in fact, |
| 100 | // there's no context provided by the caller). |
| 101 | // See code:CEEInfo::getMethodSigInternal |
| 102 | // |
| 103 | inline bool SafeToReportGenericParamContext(CrawlFrame* pCF) |
| 104 | { |
| 105 | LIMITED_METHOD_CONTRACT; |
| 106 | |
| 107 | if (!pCF->IsFrameless() && pCF->GetFrame()->GetVTablePtr() == StubDispatchFrame::GetMethodFrameVPtr()) |
| 108 | { |
| 109 | return !((StubDispatchFrame*)pCF->GetFrame())->SuppressParamTypeArg(); |
| 110 | } |
| 111 | |
| 112 | if (!pCF->IsFrameless() || !(pCF->IsActiveFrame() || pCF->IsInterrupted())) |
| 113 | { |
| 114 | return true; |
| 115 | } |
| 116 | |
| 117 | #ifndef USE_GC_INFO_DECODER |
| 118 | |
| 119 | ICodeManager * pEECM = pCF->GetCodeManager(); |
| 120 | if (pEECM != NULL && pEECM->IsInPrologOrEpilog(pCF->GetRelOffset(), pCF->GetGCInfoToken(), NULL)) |
| 121 | { |
| 122 | return false; |
| 123 | } |
| 124 | |
| 125 | #else // USE_GC_INFO_DECODER |
| 126 | |
| 127 | GcInfoDecoder gcInfoDecoder(pCF->GetGCInfoToken(), |
| 128 | DECODE_PROLOG_LENGTH); |
| 129 | UINT32 prologLength = gcInfoDecoder.GetPrologSize(); |
| 130 | if (pCF->GetRelOffset() < prologLength) |
| 131 | { |
| 132 | return false; |
| 133 | } |
| 134 | |
| 135 | #endif // USE_GC_INFO_DECODER |
| 136 | |
| 137 | return true; |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | * GcEnumObject() |
| 142 | * |
| 143 | * This is the JIT compiler (or any remote code manager) |
| 144 | * GC enumeration callback |
| 145 | */ |
| 146 | |
| 147 | void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags) |
| 148 | { |
| 149 | Object ** ppObj = (Object **)pObj; |
| 150 | GCCONTEXT * pCtx = (GCCONTEXT *) pData; |
| 151 | |
| 152 | // Since we may be asynchronously walking another thread's stack, |
| 153 | // check (frequently) for stack-buffer-overrun corruptions after |
| 154 | // any long operation |
| 155 | if (pCtx->cf != NULL) |
| 156 | pCtx->cf->CheckGSCookies(); |
| 157 | |
| 158 | // |
| 159 | // Sanity check that the flags contain only these three values |
| 160 | // |
| 161 | assert((flags & ~(GC_CALL_INTERIOR|GC_CALL_PINNED|GC_CALL_CHECK_APP_DOMAIN)) == 0); |
| 162 | |
| 163 | // for interior pointers, we optimize the case in which |
| 164 | // it points into the current threads stack area |
| 165 | // |
| 166 | if (flags & GC_CALL_INTERIOR) |
| 167 | PromoteCarefully(pCtx->f, ppObj, pCtx->sc, flags); |
| 168 | else |
| 169 | (pCtx->f)(ppObj, pCtx->sc, flags); |
| 170 | } |
| 171 | |
| 172 | //----------------------------------------------------------------------------- |
| 173 | void GcReportLoaderAllocator(promote_func* fn, ScanContext* sc, LoaderAllocator *pLoaderAllocator) |
| 174 | { |
| 175 | CONTRACTL |
| 176 | { |
| 177 | NOTHROW; |
| 178 | GC_NOTRIGGER; |
| 179 | SO_TOLERANT; |
| 180 | MODE_COOPERATIVE; |
| 181 | } |
| 182 | CONTRACTL_END; |
| 183 | |
| 184 | if (pLoaderAllocator != NULL && pLoaderAllocator->IsCollectible()) |
| 185 | { |
| 186 | Object *refCollectionObject = OBJECTREFToObject(pLoaderAllocator->GetExposedObject()); |
| 187 | |
| 188 | #ifdef _DEBUG |
| 189 | Object *oldObj = refCollectionObject; |
| 190 | #endif |
| 191 | |
| 192 | _ASSERTE(refCollectionObject != NULL); |
| 193 | fn(&refCollectionObject, sc, CHECK_APP_DOMAIN); |
| 194 | |
| 195 | // We are reporting the location of a local variable, assert it doesn't change. |
| 196 | _ASSERTE(oldObj == refCollectionObject); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | //----------------------------------------------------------------------------- |
| 201 | StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData) |
| 202 | { |
| 203 | // |
| 204 | // KEEP IN SYNC WITH DacStackReferenceWalker::Callback in debug\daccess\daccess.cpp |
| 205 | // |
| 206 | |
| 207 | GCCONTEXT *gcctx = (GCCONTEXT*) pData; |
| 208 | |
| 209 | #ifdef FEATURE_APPDOMAIN_RESOURCE_MONITORING |
| 210 | if (g_fEnableARM) |
| 211 | { |
| 212 | gcctx->sc->pCurrentDomain = pCF->GetAppDomain(); |
| 213 | } |
| 214 | #endif //FEATURE_APPDOMAIN_RESOURCE_MONITORING |
| 215 | |
| 216 | MethodDesc *pMD = pCF->GetFunction(); |
| 217 | |
| 218 | #ifdef GC_PROFILING |
| 219 | gcctx->sc->pMD = pMD; |
| 220 | #endif //GC_PROFILING |
| 221 | |
| 222 | // Clear it on exit so that we never have a stale CrawlFrame |
| 223 | ResetPointerHolder<CrawlFrame*> rph(&gcctx->cf); |
| 224 | // put it somewhere so that GcEnumObject can get to it. |
| 225 | gcctx->cf = pCF; |
| 226 | |
| 227 | bool fReportGCReferences = true; |
| 228 | #if defined(WIN64EXCEPTIONS) |
| 229 | // We may have unwound this crawlFrame and thus, shouldn't report the invalid |
| 230 | // references it may contain. |
| 231 | fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences(); |
| 232 | #endif // defined(WIN64EXCEPTIONS) |
| 233 | |
| 234 | if (fReportGCReferences) |
| 235 | { |
| 236 | if (pCF->IsFrameless()) |
| 237 | { |
| 238 | ICodeManager * pCM = pCF->GetCodeManager(); |
| 239 | _ASSERTE(pCM != NULL); |
| 240 | |
| 241 | unsigned flags = pCF->GetCodeManagerFlags(); |
| 242 | |
| 243 | #ifdef _TARGET_X86_ |
| 244 | STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Scanning Frameless method %pM EIP = %p &EIP = %p\n" , |
| 245 | pMD, GetControlPC(pCF->GetRegisterSet()), pCF->GetRegisterSet()->PCTAddr); |
| 246 | #else |
| 247 | STRESS_LOG2(LF_GCROOTS, LL_INFO1000, "Scanning Frameless method %pM ControlPC = %p\n" , |
| 248 | pMD, GetControlPC(pCF->GetRegisterSet())); |
| 249 | #endif |
| 250 | |
| 251 | _ASSERTE(pMD != 0); |
| 252 | |
| 253 | #ifdef _DEBUG |
| 254 | LOG((LF_GCROOTS, LL_INFO1000, "Scanning Frame for method %s:%s\n" , |
| 255 | pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName)); |
| 256 | #endif // _DEBUG |
| 257 | |
| 258 | DWORD relOffsetOverride = NO_OVERRIDE_OFFSET; |
| 259 | #if defined(WIN64EXCEPTIONS) && defined(USE_GC_INFO_DECODER) |
| 260 | if (pCF->ShouldParentToFuncletUseUnwindTargetLocationForGCReporting()) |
| 261 | { |
| 262 | GCInfoToken gcInfoToken = pCF->GetGCInfoToken(); |
| 263 | GcInfoDecoder _gcInfoDecoder( |
| 264 | gcInfoToken, |
| 265 | DECODE_CODE_LENGTH |
| 266 | ); |
| 267 | |
| 268 | if(_gcInfoDecoder.WantsReportOnlyLeaf()) |
| 269 | { |
| 270 | // We're in a special case of unwinding from a funclet, and resuming execution in |
| 271 | // another catch funclet associated with same parent function. We need to report roots. |
| 272 | // Reporting at the original throw site gives incorrect liveness information. We choose to |
| 273 | // report the liveness information at the first interruptible instruction of the catch funclet |
| 274 | // that we are going to execute. We also only report stack slots, since no registers can be |
| 275 | // live at the first instruction of a handler, except the catch object, which the VM protects |
| 276 | // specially. If the catch funclet has not interruptible point, we fall back and just report |
| 277 | // what we used to: at the original throw instruction. This might lead to bad GC behavior |
| 278 | // if the liveness is not correct. |
| 279 | const EE_ILEXCEPTION_CLAUSE& ehClauseForCatch = pCF->GetEHClauseForCatch(); |
| 280 | relOffsetOverride = FindFirstInterruptiblePoint(pCF, ehClauseForCatch.HandlerStartPC, |
| 281 | ehClauseForCatch.HandlerEndPC); |
| 282 | _ASSERTE(relOffsetOverride != NO_OVERRIDE_OFFSET); |
| 283 | |
| 284 | STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Setting override offset = %u for method %pM ControlPC = %p\n" , |
| 285 | relOffsetOverride, pMD, GetControlPC(pCF->GetRegisterSet())); |
| 286 | } |
| 287 | |
| 288 | } |
| 289 | #endif // WIN64EXCEPTIONS && USE_GC_INFO_DECODER |
| 290 | |
| 291 | pCM->EnumGcRefs(pCF->GetRegisterSet(), |
| 292 | pCF->GetCodeInfo(), |
| 293 | flags, |
| 294 | GcEnumObject, |
| 295 | pData, |
| 296 | relOffsetOverride); |
| 297 | |
| 298 | } |
| 299 | else |
| 300 | { |
| 301 | Frame * pFrame = pCF->GetFrame(); |
| 302 | |
| 303 | STRESS_LOG3(LF_GCROOTS, LL_INFO1000, |
| 304 | "Scanning ExplicitFrame %p AssocMethod = %pM frameVTable = %pV\n" , |
| 305 | pFrame, pFrame->GetFunction(), *((void**) pFrame)); |
| 306 | pFrame->GcScanRoots( gcctx->f, gcctx->sc); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | |
| 311 | // If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it |
| 312 | // doesn't get collected and yank the method code out from under us). |
| 313 | |
| 314 | // Be careful to only promote the reference -- we can also be called to relocate the reference and |
| 315 | // that can lead to all sorts of problems since we could be racing for the relocation with the long |
| 316 | // weak handle we recover the reference from. Promoting the reference is enough, the handle in the |
| 317 | // reference will be relocated properly as long as we keep it alive till the end of the collection |
| 318 | // as long as the reference is actually maintained by the long weak handle. |
| 319 | if (pMD && gcctx->sc->promotion) |
| 320 | { |
| 321 | BOOL fMaybeCollectibleMethod = TRUE; |
| 322 | |
| 323 | // If this is a frameless method then the jitmanager can answer the question of whether |
| 324 | // or not this is LCG simply by looking at the heap where the code lives, however there |
| 325 | // is also the prestub case where we need to explicitly look at the MD for stuff that isn't |
| 326 | // ngen'd |
| 327 | if (pCF->IsFrameless()) |
| 328 | { |
| 329 | fMaybeCollectibleMethod = ExecutionManager::IsCollectibleMethod(pCF->GetMethodToken()); |
| 330 | } |
| 331 | |
| 332 | if (fMaybeCollectibleMethod && pMD->IsLCGMethod()) |
| 333 | { |
| 334 | Object *refResolver = OBJECTREFToObject(pMD->AsDynamicMethodDesc()->GetLCGMethodResolver()->GetManagedResolver()); |
| 335 | #ifdef _DEBUG |
| 336 | Object *oldObj = refResolver; |
| 337 | #endif |
| 338 | _ASSERTE(refResolver != NULL); |
| 339 | (*gcctx->f)(&refResolver, gcctx->sc, CHECK_APP_DOMAIN); |
| 340 | _ASSERTE(!pMD->IsSharedByGenericInstantiations()); |
| 341 | |
| 342 | // We are reporting the location of a local variable, assert it doesn't change. |
| 343 | _ASSERTE(oldObj == refResolver); |
| 344 | } |
| 345 | else |
| 346 | { |
| 347 | if (fMaybeCollectibleMethod) |
| 348 | { |
| 349 | GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMD->GetLoaderAllocator()); |
| 350 | } |
| 351 | |
| 352 | if (fReportGCReferences) |
| 353 | { |
| 354 | GenericParamContextType paramContextType = GENERIC_PARAM_CONTEXT_NONE; |
| 355 | |
| 356 | if (pCF->IsFrameless()) |
| 357 | { |
| 358 | // We need to grab the Context Type here because there are cases where the MethodDesc |
| 359 | // is shared, and thus indicates there should be an instantion argument, but the JIT |
| 360 | // was still allowed to optimize it away and we won't grab it below because we're not |
| 361 | // reporting any references from this frame. |
| 362 | paramContextType = pCF->GetCodeManager()->GetParamContextType(pCF->GetRegisterSet(), pCF->GetCodeInfo()); |
| 363 | } |
| 364 | else |
| 365 | { |
| 366 | if (pMD->RequiresInstMethodDescArg()) |
| 367 | paramContextType = GENERIC_PARAM_CONTEXT_METHODDESC; |
| 368 | else if (pMD->RequiresInstMethodTableArg()) |
| 369 | paramContextType = GENERIC_PARAM_CONTEXT_METHODTABLE; |
| 370 | } |
| 371 | |
| 372 | if (paramContextType != GENERIC_PARAM_CONTEXT_NONE && SafeToReportGenericParamContext(pCF)) |
| 373 | { |
| 374 | // Handle the case where the method is a static shared generic method and we need to keep the type |
| 375 | // of the generic parameters alive |
| 376 | if (paramContextType == GENERIC_PARAM_CONTEXT_METHODDESC) |
| 377 | { |
| 378 | MethodDesc *pMDReal = dac_cast<PTR_MethodDesc>(pCF->GetParamTypeArg()); |
| 379 | _ASSERTE((pMDReal != NULL) || !pCF->IsFrameless()); |
| 380 | if (pMDReal != NULL) |
| 381 | { |
| 382 | GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMDReal->GetLoaderAllocator()); |
| 383 | } |
| 384 | } |
| 385 | else if (paramContextType == GENERIC_PARAM_CONTEXT_METHODTABLE) |
| 386 | { |
| 387 | MethodTable *pMTReal = dac_cast<PTR_MethodTable>(pCF->GetParamTypeArg()); |
| 388 | _ASSERTE((pMTReal != NULL) || !pCF->IsFrameless()); |
| 389 | if (pMTReal != NULL) |
| 390 | { |
| 391 | GcReportLoaderAllocator(gcctx->f, gcctx->sc, pMTReal->GetLoaderAllocator()); |
| 392 | } |
| 393 | } |
| 394 | } |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | // Since we may be asynchronously walking another thread's stack, |
| 400 | // check (frequently) for stack-buffer-overrun corruptions after |
| 401 | // any long operation |
| 402 | pCF->CheckGSCookies(); |
| 403 | |
| 404 | return SWA_CONTINUE; |
| 405 | } |