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
10struct FindFirstInterruptiblePointState
11{
12 unsigned offs;
13 unsigned endOffs;
14 unsigned returnOffs;
15};
16
17bool 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.
59unsigned 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//
103inline 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
147void 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//-----------------------------------------------------------------------------
173void 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//-----------------------------------------------------------------------------
201StackWalkAction 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}