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