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
6/****************************************************************************/
7/* gccover.cpp */
8/****************************************************************************/
9
10/* This file holds code that is designed to test GC pointer tracking in
11 fully interruptible code. We basically do a GC everywhere we can in
12 jitted code
13 */
14/****************************************************************************/
15
16
17#include "common.h"
18
19#ifdef HAVE_GCCOVER
20
21#pragma warning(disable:4663)
22
23#include "eeconfig.h"
24#include "gms.h"
25#include "utsem.h"
26#include "gccover.h"
27#include "virtualcallstub.h"
28#include "threadsuspend.h"
29
30#if defined(_TARGET_AMD64_) || defined(_TARGET_ARM_)
31#include "gcinfodecoder.h"
32#endif
33
34#include "disassembler.h"
35
36/****************************************************************************/
37
38MethodDesc* AsMethodDesc(size_t addr);
39static SLOT getTargetOfCall(SLOT instrPtr, PCONTEXT regs, SLOT*nextInstr);
40bool isCallToStopForGCJitHelper(SLOT instrPtr);
41#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
42static void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID codeStart);
43static bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 stopOffset, LPVOID codeStart);
44#endif
45
46// There is a call target instruction, try to find the MethodDesc for where target points to.
47// Returns nullptr if it can't find it.
48static MethodDesc* getTargetMethodDesc(PCODE target)
49{
50 MethodDesc* targetMD = ExecutionManager::GetCodeMethodDesc(target);
51 if (targetMD != nullptr)
52 {
53 // It is JIT/NGened call.
54 return targetMD;
55 }
56
57 VirtualCallStubManager::StubKind vsdStubKind = VirtualCallStubManager::SK_UNKNOWN;
58 VirtualCallStubManager *pVSDStubManager = VirtualCallStubManager::FindStubManager(target, &vsdStubKind);
59 if (vsdStubKind != VirtualCallStubManager::SK_BREAKPOINT && vsdStubKind != VirtualCallStubManager::SK_UNKNOWN)
60 {
61 // It is a VSD stub manager.
62 DispatchToken token = VirtualCallStubManager::GetTokenFromStubQuick(pVSDStubManager, target, vsdStubKind);
63 _ASSERTE(token.IsValid());
64 return VirtualCallStubManager::GetInterfaceMethodDescFromToken(token);
65 }
66 if (RangeSectionStubManager::GetStubKind(target) == STUB_CODE_BLOCK_PRECODE)
67 {
68 // The address looks like a value stub, try to get the method descriptor.
69 return MethodDesc::GetMethodDescFromStubAddr(target, TRUE);
70 }
71
72 return nullptr;
73}
74
75
76void SetupAndSprinkleBreakpoints(
77 MethodDesc * pMD,
78 EECodeInfo * pCodeInfo,
79 IJitManager::MethodRegionInfo methodRegionInfo,
80 BOOL fZapped
81 )
82{
83 // Allocate room for the GCCoverageInfo and copy of the method instructions
84 size_t memSize = sizeof(GCCoverageInfo) + methodRegionInfo.hotSize + methodRegionInfo.coldSize;
85 GCCoverageInfo* gcCover = (GCCoverageInfo*)(void*) pMD->GetLoaderAllocatorForCode()->GetHighFrequencyHeap()->AllocAlignedMem(memSize, CODE_SIZE_ALIGN);
86
87 memset(gcCover, 0, sizeof(GCCoverageInfo));
88
89 gcCover->methodRegion = methodRegionInfo;
90 gcCover->codeMan = pCodeInfo->GetCodeManager();
91 gcCover->gcInfoToken = pCodeInfo->GetGCInfoToken();
92 gcCover->callerThread = 0;
93 gcCover->doingEpilogChecks = true;
94
95 gcCover->lastMD = pMD; /* pass pMD to SprinkleBreakpoints */
96
97 gcCover->SprinkleBreakpoints(gcCover->savedCode,
98 gcCover->methodRegion.hotStartAddress,
99 gcCover->methodRegion.hotSize,
100 0,
101 fZapped);
102
103 // This is not required for ARM* as the above call does the work for both hot & cold regions
104#if !defined(_TARGET_ARM_) && !defined(_TARGET_ARM64_)
105 if (gcCover->methodRegion.coldSize != 0)
106 {
107 gcCover->SprinkleBreakpoints(gcCover->savedCode + gcCover->methodRegion.hotSize,
108 gcCover->methodRegion.coldStartAddress,
109 gcCover->methodRegion.coldSize,
110 gcCover->methodRegion.hotSize,
111 fZapped);
112 }
113#endif
114
115 gcCover->lastMD = NULL; /* clear lastMD */
116
117 _ASSERTE(!pMD->m_GcCover);
118 *EnsureWritablePages(&pMD->m_GcCover) = gcCover;
119}
120
121void SetupAndSprinkleBreakpointsForJittedMethod(MethodDesc * pMD,
122 PCODE codeStart
123 )
124{
125 EECodeInfo codeInfo(codeStart);
126 _ASSERTE(codeInfo.IsValid());
127 _ASSERTE(codeInfo.GetRelOffset() == 0);
128
129 IJitManager::MethodRegionInfo methodRegionInfo;
130 codeInfo.GetMethodRegionInfo(&methodRegionInfo);
131
132 _ASSERTE(PCODEToPINSTR(codeStart) == methodRegionInfo.hotStartAddress);
133
134#ifdef _DEBUG
135 if (!g_pConfig->SkipGCCoverage(pMD->GetModule()->GetSimpleName()))
136#endif
137 SetupAndSprinkleBreakpoints(pMD,
138 &codeInfo,
139 methodRegionInfo,
140 FALSE
141 );
142}
143
144/****************************************************************************/
145/* called when a method is first jitted when GCStress level 4 or 8 is on */
146
147void SetupGcCoverage(MethodDesc* pMD, BYTE* methodStartPtr) {
148
149#ifdef _DEBUG
150 if (!g_pConfig->ShouldGcCoverageOnMethod(pMD->m_pszDebugMethodName)) {
151 return;
152 }
153#endif
154
155 // Ideally we would assert here that m_GcCover is NULL.
156 //
157 // However, we can't do that (at least not yet), because we may
158 // invoke this method more than once on a given
159 // MethodDesc. Examples include prejitted methods and rejitted
160 // methods.
161 //
162 // In the prejit case, we can't safely re-instrument an already
163 // instrumented method. By bailing out here, we will use the
164 // original instrumentation, which should still be valid as
165 // the method code has not changed.
166 //
167 // In the rejit case, the old method code may still be active and
168 // instrumented, so we need to preserve that gc cover info. By
169 // bailing out here we will skip instrumenting the rejitted native
170 // code, and since the rejitted method does not get instrumented
171 // we should be able to tolerate that the gc cover info does not
172 // match.
173 if (pMD->m_GcCover)
174 {
175 return;
176 }
177
178 PCODE codeStart = (PCODE) methodStartPtr;
179 SetupAndSprinkleBreakpointsForJittedMethod(pMD, codeStart);
180}
181
182#ifdef FEATURE_PREJIT
183
184void SetupGcCoverageForNativeMethod(MethodDesc* pMD,
185 PCODE codeStart,
186 IJitManager::MethodRegionInfo& methodRegionInfo
187 )
188{
189
190 EECodeInfo codeInfo(codeStart);
191 _ASSERTE(codeInfo.IsValid());
192 _ASSERTE(codeInfo.GetRelOffset() == 0);
193
194 _ASSERTE(PCODEToPINSTR(codeStart) == methodRegionInfo.hotStartAddress);
195
196 SetupAndSprinkleBreakpoints(pMD,
197 &codeInfo,
198 methodRegionInfo,
199 TRUE
200 );
201}
202
203void SetupGcCoverageForNativeImage(Module* module)
204{
205 // Disable IBC logging here because of NGen image is not fully initialized yet. Eager bound
206 // indirection cells are not initialized yet and so IBC logging would crash while attempting to dereference them.
207 IBCLoggingDisabler disableLogging;
208
209#if 0
210 // Debug code
211 LPWSTR wszSetupGcCoverage = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_SetupGcCoverage);
212
213 if (!wszSetupGcCoverage)
214 {
215 printf("wszSetupGcCoverage is NULL. Will not SetupGcCoverage for any module.\n");
216 return;
217 }
218 else
219 {
220 if ((wcscmp(W("*"), wszSetupGcCoverage) == 0) || // "*" means will gcstress all modules
221 (wcsstr(module->GetDebugName(), wszSetupGcCoverage) != NULL))
222 {
223 printf("[%ws] matched %ws\n", wszSetupGcCoverage, module->GetDebugName());
224 // Fall through
225 }
226 else
227 {
228 printf("[%ws] NOT match %ws\n", wszSetupGcCoverage, module->GetDebugName());
229 return;
230 }
231 }
232#endif
233
234#ifdef _DEBUG
235 if (g_pConfig->SkipGCCoverage(module->GetSimpleName()))
236 return;
237#endif
238
239 MethodIterator mi(module);
240 while (mi.Next())
241 {
242 PTR_MethodDesc pMD = mi.GetMethodDesc();
243 PCODE pMethodStart = mi.GetMethodStartAddress();
244
245 IJitManager::MethodRegionInfo methodRegionInfo;
246 mi.GetMethodRegionInfo(&methodRegionInfo);
247
248 SetupGcCoverageForNativeMethod(pMD, pMethodStart, methodRegionInfo);
249 }
250}
251#endif
252
253#ifdef _TARGET_AMD64_
254
255class GCCoverageRangeEnumerator
256{
257private:
258
259 ICodeManager *m_pCodeManager;
260 GCInfoToken m_pvGCTable;
261 BYTE *m_codeStart;
262 BYTE *m_codeEnd;
263 BYTE *m_curFuncletEnd;
264 BYTE *m_nextFunclet;
265
266
267 BYTE* GetNextFunclet ()
268 {
269 if (m_nextFunclet == NULL)
270 return m_codeEnd;
271
272 BYTE *pCurFunclet = (BYTE*)EECodeInfo::findNextFunclet(m_nextFunclet, m_codeEnd - m_nextFunclet, (LPVOID*)&m_curFuncletEnd);
273 m_nextFunclet = (pCurFunclet != NULL) ? m_curFuncletEnd : NULL;
274
275 if (pCurFunclet == NULL)
276 return m_codeEnd;
277
278 LOG((LF_JIT, LL_INFO1000, "funclet range %p-%p\n", pCurFunclet, m_curFuncletEnd));
279
280 //
281 // workaround - adjust the funclet end address to exclude uninterruptible
282 // code at the end of each funclet. The jit currently puts data like
283 // jump tables in the code portion of the allocation, instead of the
284 // read-only portion.
285 //
286 // TODO: If the entire range is uninterruptible, we should skip the
287 // entire funclet.
288 //
289 unsigned ofsLastInterruptible = m_pCodeManager->FindEndOfLastInterruptibleRegion(
290 static_cast<unsigned int>(pCurFunclet - m_codeStart),
291 static_cast<unsigned int>(m_curFuncletEnd - m_codeStart),
292 m_pvGCTable);
293
294 if (ofsLastInterruptible)
295 {
296 m_curFuncletEnd = m_codeStart + ofsLastInterruptible;
297 LOG((LF_JIT, LL_INFO1000, "adjusted end to %p\n", m_curFuncletEnd));
298 }
299
300 return pCurFunclet;
301 }
302
303
304public:
305
306 GCCoverageRangeEnumerator (ICodeManager *pCodeManager, GCInfoToken pvGCTable, BYTE *codeStart, SIZE_T codeSize)
307 {
308 m_pCodeManager = pCodeManager;
309 m_pvGCTable = pvGCTable;
310 m_codeStart = codeStart;
311 m_codeEnd = codeStart + codeSize;
312 m_nextFunclet = codeStart;
313
314 GetNextFunclet();
315 }
316
317 // Checks that the given pointer is inside of a range where gc should be
318 // tested. If not, increments the pointer until it is, and returns the
319 // new pointer.
320 BYTE *EnsureInRange (BYTE *cur)
321 {
322 if (cur >= m_curFuncletEnd)
323 {
324 cur = GetNextFunclet();
325 }
326
327 return cur;
328 }
329
330 BYTE *SkipToNextRange ()
331 {
332 return GetNextFunclet();
333 }
334};
335
336#endif // _TARGET_AMD64_
337
338// When Sprinking break points, we must make sure that certain calls to
339// Thread-suspension routines inlined into the managed method are not
340// converted to GC-Stress points. Otherwise, this will lead to race
341// conditions with the GC.
342//
343// For example, for an inlined PInvoke stub, the JIT generates the following code
344//
345// call CORINFO_HELP_INIT_PINVOKE_FRAME // Obtain the thread pointer
346//
347// mov byte ptr[rsi + 12], 0 // Switch to preemptive mode [thread->premptiveGcDisabled = 0]
348// call rax // The actual native call, in preemptive mode
349// mov byte ptr[rsi + 12], 1 // Switch the thread to Cooperative mode
350// cmp dword ptr[(reloc 0x7ffd1bb77148)], 0 // if(g_TrapReturningThreads)
351// je SHORT G_M40565_IG05
352// call[CORINFO_HELP_STOP_FOR_GC] // Call JIT_RareDisableHelper()
353//
354//
355// For the SprinkleBreakPoints() routine, the JIT_RareDisableHelper() itself will
356// look like an ordinary indirect call/safepoint. So, it may rewrite it with
357// a TRAP to perform GC
358//
359// call CORINFO_HELP_INIT_PINVOKE_FRAME // Obtain the thread pointer
360//
361// mov byte ptr[rsi + 12], 0 // Switch to preemptive mode [thread->premptiveGcDisabled = 0]
362// cli // INTERRUPT_INSTR_CALL
363// mov byte ptr[rsi + 12], 1 // Switch the thread to Cooperative mode
364// cmp dword ptr[(reloc 0x7ffd1bb77148)], 0 // if(g_TrapReturningThreads)
365// je SHORT G_M40565_IG05
366// cli // INTERRUPT_INSTR_CALL
367//
368//
369// Now, a managed thread (T) can race with the GC as follows:
370// 1) At the first safepoint, we notice that T is in preemptive mode during the call for GCStress
371// So, it is put it in cooperative mode for the purpose of GCStress(fPremptiveGcDisabledForGcStress)
372// 2) We DoGCStress(). Start off background GC in a different thread.
373// 3) Then the thread T is put back to preemptive mode (because that's where it was).
374// Thread T continues execution along with the GC thread.
375// 4) The Jitted code puts thread T to cooperative mode, as part of PInvoke epilog
376// 5) Now instead of CORINFO_HELP_STOP_FOR_GC(), we hit the GCStress trap and start
377// another round of GCStress while in Cooperative mode.
378// 6) Now, thread T can modify the stack (ex: RedirectionFrame setup) while the GC thread is scanning it.
379//
380// This problem can be avoided by not inserting traps-for-GC in place of calls to CORINFO_HELP_STOP_FOR_GC()
381//
382// How do we identify the calls to CORINFO_HELP_STOP_FOR_GC()?
383// Since this is a GCStress only requirement, its not worth special identification in the GcInfo
384// Since CORINFO_HELP_STOP_FOR_GC() calls are realized as indirect calls by the JIT, we cannot identify
385// them by address at the time of SprinkleBreakpoints().
386// So, we actually let the SprinkleBreakpoints() replace the call to CORINFO_HELP_STOP_FOR_GC() with a trap,
387// and revert it back to the original instruction the first time we hit the trap in OnGcCoverageInterrupt().
388//
389// Similarly, inserting breakpoints can be avoided for JIT_PollGC() and JIT_StressGC().
390
391#if defined(_TARGET_ARM_) || defined(_TARGET_AMD64_)
392extern "C" FCDECL0(VOID, JIT_RareDisableHelper);
393#else
394FCDECL0(VOID, JIT_RareDisableHelper);
395#endif
396
397/****************************************************************************/
398/* sprinkle interupt instructions that will stop on every GCSafe location
399 regionOffsetAdj - Represents the offset of the current region
400 from the beginning of the method (is 0 for hot region)
401*/
402
403void GCCoverageInfo::SprinkleBreakpoints(
404 BYTE * saveAddr,
405 PCODE pCode,
406 size_t codeSize,
407 size_t regionOffsetAdj,
408 BOOL fZapped)
409{
410#if (defined(_TARGET_X86_) || defined(_TARGET_AMD64_)) && USE_DISASSEMBLER
411
412 BYTE * codeStart = (BYTE *)pCode;
413
414 memcpy(saveAddr, codeStart, codeSize);
415
416 // For prejitted code we have to remove the write-protect on the code page
417 if (fZapped)
418 {
419 DWORD oldProtect;
420 ClrVirtualProtect(codeStart, codeSize, PAGE_EXECUTE_READWRITE, &oldProtect);
421 }
422
423 SLOT cur;
424 BYTE* codeEnd = codeStart + codeSize;
425
426 EECodeInfo codeInfo((PCODE)codeStart);
427
428 static ConfigDWORD fGcStressOnDirectCalls; // ConfigDWORD must be a static variable
429
430
431#ifdef _TARGET_AMD64_
432 GCCoverageRangeEnumerator rangeEnum(codeMan, gcInfoToken, codeStart, codeSize);
433
434 GcInfoDecoder safePointDecoder(gcInfoToken, (GcInfoDecoderFlags)0, 0);
435 bool fSawPossibleSwitch = false;
436#endif
437
438 cur = codeStart;
439 Disassembler disassembler;
440
441 // When we find a direct call instruction and we are partially-interruptible
442 // we determine the target and place a breakpoint after the call
443 // to simulate the hijack
444 // However, we need to wait until we disassemble the instruction
445 // after the call in order to put the breakpoint or we'll mess up
446 // the disassembly
447 // This variable is non-null if the previous instruction was a direct call,
448 // and we have found it's target MethodDesc
449 MethodDesc* prevDirectCallTargetMD = NULL;
450
451 /* TODO. Simulating the hijack could cause problems in cases where the
452 return register is not always a valid GC ref on the return offset.
453 That could happen if we got to the return offset via a branch
454 and not via return from the preceding call. However, this has not been
455 an issue so far.
456
457 Example:
458 mov eax, someval
459 test eax, eax
460 jCC AFTERCALL
461 call MethodWhichReturnsGCobject // return value is not used
462 AFTERCALL:
463 */
464
465 while (cur < codeEnd)
466 {
467 _ASSERTE(*cur != INTERRUPT_INSTR && *cur != INTERRUPT_INSTR_CALL);
468
469 MethodDesc* targetMD = NULL;
470 InstructionType instructionType;
471 size_t len = disassembler.DisassembleInstruction(cur, codeEnd - cur, &instructionType);
472
473#ifdef _TARGET_AMD64_
474 // REVISIT_TODO apparently the jit does not use the entire RUNTIME_FUNCTION range
475 // for code. It uses some for switch tables. Because the first few offsets
476 // may be decodable as instructions, we can't reason about where we should
477 // encounter invalid instructions. However, we do not want to silently skip
478 // large chunks of methods just becuase the JIT started emitting a new
479 // instruction, so only assume it is a switch table if we've seen the switch
480 // code (an indirect unconditional jump)
481 if ((len == 0) && fSawPossibleSwitch)
482 {
483 LOG((LF_JIT, LL_WARNING, "invalid instruction at %p (possibly start of switch table)\n", cur));
484 cur = rangeEnum.SkipToNextRange();
485 prevDirectCallTargetMD = NULL;
486 fSawPossibleSwitch = false;
487 continue;
488 }
489#endif
490
491 _ASSERTE(len > 0);
492 _ASSERTE(len <= (size_t)(codeEnd-cur));
493
494 switch(instructionType)
495 {
496 case InstructionType::Call_IndirectUnconditional:
497#ifdef _TARGET_AMD64_
498 if(safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj)))
499#endif
500 {
501 *cur = INTERRUPT_INSTR_CALL; // return value. May need to protect
502 }
503 break;
504
505 case InstructionType::Call_DirectUnconditional:
506 if(fGcStressOnDirectCalls.val(CLRConfig::INTERNAL_GcStressOnDirectCalls))
507 {
508#ifdef _TARGET_AMD64_
509 if(safePointDecoder.IsSafePoint((UINT32)(cur + len - codeStart + regionOffsetAdj)))
510#endif
511 {
512 SLOT nextInstr;
513 SLOT target = getTargetOfCall(cur, NULL, &nextInstr);
514
515 if (target != 0)
516 {
517 // JIT_RareDisableHelper() is expected to be an indirect call.
518 // If we encounter a direct call (in future), skip the call
519 _ASSERTE(target != (SLOT)JIT_RareDisableHelper);
520 targetMD = getTargetMethodDesc((PCODE)target);
521 }
522 }
523 }
524 break;
525
526#ifdef _TARGET_AMD64_
527 case InstructionType::Branch_IndirectUnconditional:
528 fSawPossibleSwitch = true;
529 break;
530#endif
531
532 default:
533 // Clang issues an error saying that some enum values are not handled in the switch, that's intended
534 break;
535 }
536
537 if (prevDirectCallTargetMD != 0)
538 {
539 if (prevDirectCallTargetMD->ReturnsObject(true) != MetaSig::RETNONOBJ)
540 *cur = INTERRUPT_INSTR_PROTECT_RET;
541 else
542 *cur = INTERRUPT_INSTR;
543 }
544
545 // For fully interruptible code, we end up whacking every instruction
546 // to INTERRUPT_INSTR. For non-fully interruptible code, we end
547 // up only touching the call instructions (specially so that we
548 // can really do the GC on the instruction just after the call).
549 _ASSERTE(FitsIn<DWORD>((cur - codeStart) + regionOffsetAdj));
550 if (codeMan->IsGcSafe(&codeInfo, static_cast<DWORD>((cur - codeStart) + regionOffsetAdj)))
551 *cur = INTERRUPT_INSTR;
552
553#ifdef _TARGET_X86_
554 // we will whack every instruction in the prolog and epilog to make certain
555 // our unwinding logic works there.
556 if (codeMan->IsInPrologOrEpilog((cur - codeStart) + (DWORD)regionOffsetAdj, gcInfoToken, NULL)) {
557 *cur = INTERRUPT_INSTR;
558 }
559#endif
560
561 // If we couldn't find the method desc targetMD is zero
562 prevDirectCallTargetMD = targetMD;
563
564 cur += len;
565
566#ifdef _TARGET_AMD64_
567 SLOT newCur = rangeEnum.EnsureInRange(cur);
568 if(newCur != cur)
569 {
570 prevDirectCallTargetMD = NULL;
571 cur = newCur;
572 fSawPossibleSwitch = false;
573 }
574#endif
575 }
576
577 // If we are not able to place an interrupt at the first instruction, this means that
578 // we are partially interruptible with no prolog. Just don't bother to do the
579 // the epilog checks, since the epilog will be trival (a single return instr)
580 assert(codeSize > 0);
581 if ((regionOffsetAdj==0) && (*codeStart != INTERRUPT_INSTR))
582 doingEpilogChecks = false;
583
584#elif defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
585 //Save the method code from hotRegion
586 memcpy(saveAddr, (BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize);
587
588 if (methodRegion.coldSize > 0)
589 {
590 //Save the method code from coldRegion
591 memcpy(saveAddr+methodRegion.hotSize, (BYTE*)methodRegion.coldStartAddress, methodRegion.coldSize);
592 }
593
594 // For prejitted code we have to remove the write-protect on the code page
595 if (fZapped)
596 {
597 DWORD oldProtect;
598 ClrVirtualProtect((BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize, PAGE_EXECUTE_READWRITE, &oldProtect);
599
600 if (methodRegion.coldSize > 0)
601 {
602 ClrVirtualProtect((BYTE*)methodRegion.coldStartAddress, methodRegion.coldSize, PAGE_EXECUTE_READWRITE, &oldProtect);
603 }
604 }
605
606 GcInfoDecoder safePointDecoder(gcInfoToken, (GcInfoDecoderFlags)0, 0);
607
608 assert(methodRegion.hotSize > 0);
609
610#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
611 safePointDecoder.EnumerateSafePoints(&replaceSafePointInstructionWithGcStressInstr,this);
612#endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
613
614 safePointDecoder.EnumerateInterruptibleRanges(&replaceInterruptibleRangesWithGcStressInstr, this);
615
616 FlushInstructionCache(GetCurrentProcess(), (BYTE*)methodRegion.hotStartAddress, methodRegion.hotSize);
617
618 if (methodRegion.coldSize > 0)
619 {
620 FlushInstructionCache(GetCurrentProcess(), (BYTE*)methodRegion.coldStartAddress, methodRegion.coldSize);
621 }
622
623#else
624 _ASSERTE(!"not implemented for platform");
625#endif // _TARGET_X86_
626}
627
628#if defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
629
630#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED
631
632void replaceSafePointInstructionWithGcStressInstr(UINT32 safePointOffset, LPVOID pGCCover)
633{
634 PCODE pCode = NULL;
635 IJitManager::MethodRegionInfo *ptr = &(((GCCoverageInfo*)pGCCover)->methodRegion);
636
637 //Get code address from offset
638 if (safePointOffset < ptr->hotSize)
639 pCode = ptr->hotStartAddress + safePointOffset;
640 else if(safePointOffset - ptr->hotSize < ptr->coldSize)
641 {
642 SIZE_T coldOffset = safePointOffset - ptr->hotSize;
643 pCode = ptr->coldStartAddress + coldOffset;
644 }
645 else
646 {
647 //For some methods( eg MCCTest.MyClass.GetSum2 in test file jit\jit64\mcc\interop\mcc_i07.il) gcinfo points to a safepoint
648 //beyond the length of the method. So commenting the below assert.
649 //_ASSERTE(safePointOffset - ptr->hotSize < ptr->coldSize);
650 return;
651 }
652
653 SLOT instrPtr = (BYTE*)PCODEToPINSTR(pCode);
654
655 // For code sequences of the type
656 // BL func1
657 // BL func2 // Safe point 1
658 // mov r1 r0 // Safe point 2
659 // Both the above safe points instruction must be replaced with gcStress instruction.
660 // However as the first safe point is already replaced with gcstress instruction, decoding of the call
661 // instruction will fail when processing for the 2nd safe point. Therefore saved instruction must be used instead of
662 // instrPtr for decoding the call instruction.
663 SLOT savedInstrPtr = ((GCCoverageInfo*)pGCCover)->savedCode + safePointOffset;
664
665 //Determine if instruction before the safe point is call using immediate (BLX Imm) or call by register (BLX Rm)
666 BOOL instructionIsACallThroughRegister = FALSE;
667 BOOL instructionIsACallThroughImmediate = FALSE;
668#if defined(_TARGET_ARM_)
669
670 // call by register instruction is two bytes (BL<c> Reg T1 encoding)
671 WORD instr = *((WORD*)savedInstrPtr - 1);
672
673 instr = instr & 0xff87;
674 if((instr ^ 0x4780) == 0)
675 // It is call by register
676 instructionIsACallThroughRegister = TRUE;
677
678 // call using immediate instructions are 4 bytes (BL<c> <label> T1 encoding)
679 instr = *((WORD*)savedInstrPtr - 2);
680 instr = instr & 0xf800;
681 if((instr ^ 0xf000) == 0)
682 if((*(((WORD*)savedInstrPtr)-1) & 0xd000) == 0xd000)
683 // It is call by immediate
684 instructionIsACallThroughImmediate = TRUE;
685#elif defined(_TARGET_ARM64_)
686 DWORD instr = *((DWORD*)savedInstrPtr - 1);
687
688 // Is the call through a register or an immediate offset
689 // BL
690 // Encoding: 0x94000000 & [imm26]
691 if ((instr & 0xFC000000) == 0x94000000)
692 {
693 instructionIsACallThroughImmediate = TRUE;
694 }
695 // BLR
696 // Encoding: 0xD63F0000 & (Rn<<5)
697 else if ((instr & 0xFFFFFC1F) == 0xD63F0000)
698 {
699 instructionIsACallThroughRegister = TRUE;
700 }
701#endif
702 // safe point must always be after a call instruction
703 // and cannot be both call by register & immediate
704 // The safe points are also marked at jump calls( a special variant of
705 // tail call). However that call site will never appear on the stack.
706 // So commenting the assert for now. As for such places the previous
707 // instruction will not be a call instruction.
708 //_ASSERTE(instructionIsACallThroughRegister ^ instructionIsACallThroughImmediate);
709
710 if(instructionIsACallThroughRegister)
711 {
712 // If it is call by register then cannot know MethodDesc so replace the call instruction with illegal instruction
713 // safe point will be replaced with appropiate illegal instruction at execution time when reg value is known
714#if defined(_TARGET_ARM_)
715 *((WORD*)instrPtr - 1) = INTERRUPT_INSTR_CALL;
716#elif defined(_TARGET_ARM64_)
717 *((DWORD*)instrPtr - 1) = INTERRUPT_INSTR_CALL;
718#endif
719 }
720 else if(instructionIsACallThroughImmediate)
721 {
722 // If it is call by immediate then find the methodDesc
723 SLOT nextInstr;
724 SLOT target = getTargetOfCall((SLOT)((WORD*)savedInstrPtr-2), NULL, &nextInstr);
725
726 if (target != 0)
727 {
728 //Target is calculated wrt the saved instruction pointer
729 //Find the real target wrt the real instruction pointer
730 int delta = static_cast<int>(target - savedInstrPtr);
731 target = delta + instrPtr;
732
733 MethodDesc* targetMD = getTargetMethodDesc((PCODE)target);
734
735 if (targetMD != 0)
736 {
737
738 // The instruction about to be replaced cannot already be a gcstress instruction
739#if defined(_TARGET_ARM_)
740 size_t instrLen = GetARMInstructionLength(instrPtr);
741 if (instrLen == 2)
742 {
743 _ASSERTE(*((WORD*)instrPtr) != INTERRUPT_INSTR &&
744 *((WORD*)instrPtr) != INTERRUPT_INSTR_CALL &&
745 *((WORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET);
746 }
747 else
748 {
749 _ASSERTE(*((DWORD*)instrPtr) != INTERRUPT_INSTR_32 &&
750 *((DWORD*)instrPtr) != INTERRUPT_INSTR_CALL_32 &&
751 *((DWORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET_32);
752 }
753#elif defined(_TARGET_ARM64_)
754 {
755 _ASSERTE(*((DWORD*)instrPtr) != INTERRUPT_INSTR &&
756 *((DWORD*)instrPtr) != INTERRUPT_INSTR_CALL &&
757 *((DWORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET);
758 }
759#endif
760 //
761 // When applying GC coverage breakpoints at native image load time, the code here runs
762 // before eager fixups are applied for the module being loaded. The direct call target
763 // never requires restore, however it is possible that it is initially in an invalid state
764 // and remains invalid until one or more eager fixups are applied.
765 //
766 // MethodDesc::ReturnsObject() consults the method signature, meaning it consults the
767 // metadata in the owning module. For generic instantiations stored in non-preferred
768 // modules, reaching the owning module requires following the module override pointer for
769 // the enclosing MethodTable. In this case, the module override pointer is generally
770 // invalid until an associated eager fixup is applied.
771 //
772 // In situations like this, MethodDesc::ReturnsObject() will try to dereference an
773 // unresolved fixup and will AV.
774 //
775 // Given all of this, skip the MethodDesc::ReturnsObject() call by default to avoid
776 // unexpected AVs. This implies leaving out the GC coverage breakpoints for direct calls
777 // unless COMPlus_GcStressOnDirectCalls=1 is explicitly set in the environment.
778 //
779
780 static ConfigDWORD fGcStressOnDirectCalls;
781
782 if (fGcStressOnDirectCalls.val(CLRConfig::INTERNAL_GcStressOnDirectCalls))
783 {
784 // If the method returns an object then should protect the return object
785 if (targetMD->ReturnsObject(true) != MetaSig::RETNONOBJ)
786 {
787 // replace with corresponding 2 or 4 byte illegal instruction (which roots the return value)
788#if defined(_TARGET_ARM_)
789 if (instrLen == 2)
790 *((WORD*)instrPtr) = INTERRUPT_INSTR_PROTECT_RET;
791 else
792 *((DWORD*)instrPtr) = INTERRUPT_INSTR_PROTECT_RET_32;
793#elif defined(_TARGET_ARM64_)
794 *((DWORD*)instrPtr) = INTERRUPT_INSTR_PROTECT_RET;
795#endif
796 }
797 else // method does not return an objectref
798 {
799 // replace with corresponding 2 or 4 byte illegal instruction
800#if defined(_TARGET_ARM_)
801 if (instrLen == 2)
802 *((WORD*)instrPtr) = INTERRUPT_INSTR;
803 else
804 *((DWORD*)instrPtr) = INTERRUPT_INSTR_32;
805#elif defined(_TARGET_ARM64_)
806 *((DWORD*)instrPtr) = INTERRUPT_INSTR;
807#endif
808 }
809 }
810 }
811 }
812 }
813}
814#endif
815
816//Replaces the provided interruptible range with corresponding 2 or 4 byte gcStress illegal instruction
817bool replaceInterruptibleRangesWithGcStressInstr (UINT32 startOffset, UINT32 stopOffset, LPVOID pGCCover)
818{
819 PCODE pCode = NULL;
820 SLOT rangeStart = NULL;
821 SLOT rangeStop = NULL;
822
823 //Interruptible range can span accross hot & cold region
824 int acrossHotRegion = 1; // 1 means range is not across end of hot region & 2 is when it is across end of hot region
825
826 //Find the code addresses from offsets
827 IJitManager::MethodRegionInfo *ptr = &(((GCCoverageInfo*)pGCCover)->methodRegion);
828 if (startOffset < ptr->hotSize)
829 {
830 pCode = ptr->hotStartAddress + startOffset;
831 rangeStart = (BYTE*)PCODEToPINSTR(pCode);
832
833 if(stopOffset <= ptr->hotSize)
834 {
835 pCode = ptr->hotStartAddress + stopOffset;
836 rangeStop = (BYTE*)PCODEToPINSTR(pCode);
837 }
838 else
839 {
840 //Interruptible range is spanning across hot & cold region
841 pCode = ptr->hotStartAddress + ptr->hotSize;
842 rangeStop = (BYTE*)PCODEToPINSTR(pCode);
843 acrossHotRegion++;
844 }
845 }
846 else
847 {
848 SIZE_T coldOffset = startOffset - ptr->hotSize;
849 _ASSERTE(coldOffset < ptr->coldSize);
850 pCode = ptr->coldStartAddress + coldOffset;
851 rangeStart = (BYTE*)PCODEToPINSTR(pCode);
852
853 coldOffset = stopOffset - ptr->hotSize;
854 _ASSERTE(coldOffset <= ptr->coldSize);
855 pCode = ptr->coldStartAddress + coldOffset;
856 rangeStop = (BYTE*)PCODEToPINSTR(pCode);
857 }
858
859 // Need to do two iterations if interruptible range spans across hot & cold region
860 while(acrossHotRegion--)
861 {
862 SLOT instrPtr = rangeStart;
863 while(instrPtr < rangeStop)
864 {
865
866 // The instruction about to be replaced cannot already be a gcstress instruction
867#if defined(_TARGET_ARM_)
868 size_t instrLen = GetARMInstructionLength(instrPtr);
869 if (instrLen == 2)
870 {
871 _ASSERTE(*((WORD*)instrPtr) != INTERRUPT_INSTR &&
872 *((WORD*)instrPtr) != INTERRUPT_INSTR_CALL &&
873 *((WORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET);
874 }
875 else
876 {
877 _ASSERTE(*((DWORD*)instrPtr) != INTERRUPT_INSTR_32 &&
878 *((DWORD*)instrPtr) != INTERRUPT_INSTR_CALL_32 &&
879 *((DWORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET_32);
880 }
881
882 if (instrLen == 2)
883 *((WORD*)instrPtr) = INTERRUPT_INSTR;
884 else
885 {
886 // Do not replace with gcstress interrupt instruction at call to JIT_RareDisableHelper
887 if(!isCallToStopForGCJitHelper(instrPtr))
888 *((DWORD*)instrPtr) = INTERRUPT_INSTR_32;
889 }
890
891 instrPtr += instrLen;
892#elif defined(_TARGET_ARM64_)
893 {
894 _ASSERTE(*((DWORD*)instrPtr) != INTERRUPT_INSTR &&
895 *((DWORD*)instrPtr) != INTERRUPT_INSTR_CALL &&
896 *((DWORD*)instrPtr) != INTERRUPT_INSTR_PROTECT_RET);
897 }
898
899 // Do not replace with gcstress interrupt instruction at call to JIT_RareDisableHelper
900 if(!isCallToStopForGCJitHelper(instrPtr))
901 *((DWORD*)instrPtr) = INTERRUPT_INSTR;
902 instrPtr += 4;
903#endif
904
905 }
906
907 if(acrossHotRegion)
908 {
909 //Set rangeStart & rangeStop for the second iteration
910 _ASSERTE(acrossHotRegion==1);
911 rangeStart = (BYTE*)PCODEToPINSTR(ptr->coldStartAddress);
912 pCode = ptr->coldStartAddress + stopOffset - ptr->hotSize;
913 rangeStop = (BYTE*)PCODEToPINSTR(pCode);
914 }
915 }
916 return FALSE;
917}
918#endif
919
920// Is this a call instruction to JIT_RareDisableHelper()
921// We cannot insert GCStress instruction at this call
922// For arm64 & arm (R2R) call to jithelpers happens via a stub.
923// For other architectures call does not happen via stub.
924// For other architectures we can get the target directly by calling getTargetOfCall().
925// This is not the case for arm64/arm so need to decode the stub
926// instruction to find the actual jithelper target.
927// For other architecture we detect call to JIT_RareDisableHelper
928// in function OnGcCoverageInterrupt() since getTargetOfCall() can
929// get the actual jithelper target.
930bool isCallToStopForGCJitHelper(SLOT instrPtr)
931{
932#if defined(_TARGET_ARM64_)
933 if (((*reinterpret_cast<DWORD*>(instrPtr)) & 0xFC000000) == 0x94000000) // Do we have a BL instruction?
934 {
935 // call through immediate
936 int imm26 = ((*((DWORD*)instrPtr)) & 0x03FFFFFF)<<2;
937 // SignExtend the immediate value.
938 imm26 = (imm26 << 4) >> 4;
939 DWORD* target = (DWORD*) (instrPtr + imm26);
940 // Call to jithelpers happens via jumpstub
941 if(*target == 0x58000050 /* ldr xip0, PC+8*/ && *(target+1) == 0xd61f0200 /* br xip0 */)
942 {
943 // get the actual jithelper target
944 target = *(((DWORD**)target) + 1);
945 if((TADDR)target == GetEEFuncEntryPoint(JIT_RareDisableHelper))
946 {
947 return true;
948 }
949 }
950 }
951#elif defined(_TARGET_ARM_)
952 if((instrPtr[1] & 0xf8) == 0xf0 && (instrPtr[3] & 0xc0) == 0xc0) // call using imm
953 {
954 int imm32 = GetThumb2BlRel24((UINT16 *)instrPtr);
955 WORD* target = (WORD*) (instrPtr + 4 + imm32);
956 // Is target a stub
957 if(*target == 0xf8df && *(target+1) == 0xf000) // ldr pc, [pc+4]
958 {
959 //get actual target
960 target = *((WORD**)target + 1);
961 if((TADDR)target == GetEEFuncEntryPoint(JIT_RareDisableHelper))
962 {
963 return true;
964 }
965 }
966 }
967#endif
968 return false;
969}
970
971static size_t getRegVal(unsigned regNum, PCONTEXT regs)
972{
973 return *getRegAddr(regNum, regs);
974}
975
976/****************************************************************************/
977static SLOT getTargetOfCall(SLOT instrPtr, PCONTEXT regs, SLOT*nextInstr) {
978
979 BYTE sibindexadj = 0;
980 BYTE baseadj = 0;
981 WORD displace = 0;
982
983 // In certain situations, the instruction bytes are read from a different
984 // location than the actual bytes being executed.
985 // When decoding the instructions of a method which is sprinkled with
986 // TRAP instructions for GCStress, we decode the bytes from a copy
987 // of the instructions stored before the traps-for-gc were inserted.
988 // Hoiwever, the PC-relative addressing/displacement of the CALL-target
989 // will still be with respect to the currently executing PC.
990 // So, if a register context is available, we pick the PC from it
991 // (for address calculation purposes only).
992
993 SLOT PC = (regs) ? (SLOT)GetIP(regs) : instrPtr;
994
995#ifdef _TARGET_ARM_
996 if((instrPtr[1] & 0xf0) == 0xf0) // direct call
997 {
998 int imm32 = GetThumb2BlRel24((UINT16 *)instrPtr);
999 *nextInstr = instrPtr + 4;
1000 return PC + 4 + imm32;
1001 }
1002 else if(((instrPtr[1] & 0x47) == 0x47) & ((instrPtr[0] & 0x80) == 0x80)) // indirect call
1003 {
1004 *nextInstr = instrPtr + 2;
1005 unsigned int regnum = (instrPtr[0] & 0x78) >> 3;
1006 return (BYTE *)getRegVal(regnum, regs);
1007 }
1008 else
1009 {
1010 return 0; // Not a call.
1011 }
1012#elif defined(_TARGET_ARM64_)
1013 if (((*reinterpret_cast<DWORD*>(instrPtr)) & 0xFC000000) == 0x94000000)
1014 {
1015 // call through immediate
1016 int imm26 = ((*((DWORD*)instrPtr)) & 0x03FFFFFF)<<2;
1017 // SignExtend the immediate value.
1018 imm26 = (imm26 << 4) >> 4;
1019 *nextInstr = instrPtr + 4;
1020 return PC + imm26;
1021 }
1022 else if (((*reinterpret_cast<DWORD*>(instrPtr)) & 0xFFFFC1F) == 0xD63F0000)
1023 {
1024 // call through register
1025 *nextInstr = instrPtr + 4;
1026 unsigned int regnum = ((*(DWORD*)instrPtr) >> 5) & 0x1F;
1027 return (BYTE *)getRegVal(regnum, regs);
1028 }
1029 else
1030 {
1031 return 0; // Fail
1032 }
1033#endif
1034
1035#ifdef _TARGET_AMD64_
1036
1037 if ((instrPtr[0] & 0xf0) == REX_PREFIX_BASE)
1038 {
1039 static_assert_no_msg(REX_SIB_BASE_EXT == REX_MODRM_RM_EXT);
1040 if (instrPtr[0] & REX_SIB_BASE_EXT)
1041 baseadj = 8;
1042
1043 if (instrPtr[0] & REX_SIB_INDEX_EXT)
1044 sibindexadj = 8;
1045
1046 instrPtr++;
1047 }
1048
1049#endif // _TARGET_AMD64_
1050
1051 if (instrPtr[0] == 0xE8) { // Direct Relative Near
1052 *nextInstr = instrPtr + 5;
1053
1054 size_t base = (size_t) PC + 5;
1055
1056 INT32 displacement = (INT32) (
1057 ((UINT32)instrPtr[1]) +
1058 (((UINT32)instrPtr[2]) << 8) +
1059 (((UINT32)instrPtr[3]) << 16) +
1060 (((UINT32)instrPtr[4]) << 24)
1061 );
1062
1063 // Note that the signed displacement is sign-extended
1064 // to 64-bit on AMD64
1065 return((SLOT)(base + (SSIZE_T)displacement));
1066 }
1067
1068 if (instrPtr[0] == 0xFF) { // Indirect Absolute Near
1069
1070 _ASSERTE(regs);
1071
1072 BYTE mod = (instrPtr[1] & 0xC0) >> 6;
1073 BYTE rm = (instrPtr[1] & 0x7);
1074 SLOT result;
1075
1076 switch (mod) {
1077 case 0:
1078 case 1:
1079 case 2:
1080
1081 if (rm == 4) {
1082
1083 //
1084 // Get values from the SIB byte
1085 //
1086 BYTE ss = (instrPtr[2] & 0xC0) >> 6;
1087 BYTE index = (instrPtr[2] & 0x38) >> 3;
1088 BYTE base = (instrPtr[2] & 0x7);
1089
1090 //
1091 // Get starting value
1092 //
1093 if ((mod == 0) && (base == 5)) {
1094 result = 0;
1095 } else {
1096 result = (BYTE *)getRegVal(baseadj + base, regs);
1097 }
1098
1099 //
1100 // Add in the [index]
1101 //
1102 if (index != 0x4) {
1103 result = result + (getRegVal(sibindexadj + index, regs) << ss);
1104 }
1105
1106 //
1107 // Finally add in the offset
1108 //
1109 if (mod == 0) {
1110
1111 if (base == 5) {
1112 result = result + *((int *)&instrPtr[3]);
1113 displace += 7;
1114 } else {
1115 displace += 3;
1116 }
1117
1118 } else if (mod == 1) {
1119
1120 result = result + *((char *)&instrPtr[3]);
1121 displace += 4;
1122
1123 } else { // == 2
1124
1125 result = result + *((int *)&instrPtr[3]);
1126 displace += 7;
1127
1128 }
1129
1130 } else {
1131
1132 //
1133 // Get the value we need from the register.
1134 //
1135
1136 if ((mod == 0) && (rm == 5)) {
1137#ifdef _TARGET_AMD64_
1138 // at this point instrPtr should be pointing at the beginning
1139 // of the byte sequence for the call instruction. the operand
1140 // is a RIP-relative address from the next instruction, so to
1141 // calculate the address of the next instruction we need to
1142 // jump forward 6 bytes: 1 for the opcode, 1 for the ModRM byte,
1143 // and 4 for the operand. see AMD64 Programmer's Manual Vol 3.
1144 result = PC + 6;
1145#else
1146 result = 0;
1147#endif // _TARGET_AMD64_
1148 } else {
1149 result = (SLOT)getRegVal(baseadj + rm, regs);
1150 }
1151
1152 if (mod == 0) {
1153
1154 if (rm == 5) {
1155 result = result + *((int *)&instrPtr[2]);
1156 displace += 6;
1157 } else {
1158 displace += 2;
1159 }
1160
1161 } else if (mod == 1) {
1162
1163 result = result + *((char *)&instrPtr[2]);
1164 displace += 3;
1165
1166 } else { // == 2
1167
1168 result = result + *((int *)&instrPtr[2]);
1169 displace += 6;
1170
1171 }
1172
1173 }
1174
1175 //
1176 // Now dereference thru the result to get the resulting IP.
1177 //
1178 result = (SLOT)(*((SLOT *)result));
1179
1180 break;
1181
1182 case 3:
1183 default:
1184
1185 result = (SLOT)getRegVal(baseadj + rm, regs);
1186 displace += 2;
1187 break;
1188
1189 }
1190
1191 *nextInstr = instrPtr + displace;
1192 return result;
1193
1194 }
1195
1196 return(0); // Fail
1197}
1198
1199/****************************************************************************/
1200
1201#ifdef _TARGET_X86_
1202
1203void checkAndUpdateReg(DWORD& origVal, DWORD curVal, bool gcHappened) {
1204 if (origVal == curVal)
1205 return;
1206
1207 // If these asserts go off, they indicate either that unwinding out of a epilog is wrong or that
1208 // the validation infrastructure has got a bug.
1209
1210 _ASSERTE(gcHappened); // If the register values are different, a GC must have happened
1211 _ASSERTE(GCHeapUtilities::GetGCHeap()->IsHeapPointer((BYTE*) size_t(origVal))); // And the pointers involved are on the GCHeap
1212 _ASSERTE(GCHeapUtilities::GetGCHeap()->IsHeapPointer((BYTE*) size_t(curVal)));
1213 origVal = curVal; // this is now the best estimate of what should be returned.
1214}
1215
1216#endif // _TARGET_X86_
1217
1218
1219int GCcoverCount = 0;
1220
1221void* forceStack[8];
1222
1223/****************************************************************************/
1224
1225bool IsGcCoverageInterrupt(LPVOID ip)
1226{
1227 // Determine if the IP is valid for a GC marker first, before trying to dereference it to check the instruction
1228
1229 EECodeInfo codeInfo(reinterpret_cast<PCODE>(ip));
1230 if (!codeInfo.IsValid())
1231 {
1232 return false;
1233 }
1234
1235 GCCoverageInfo *gcCover = codeInfo.GetMethodDesc()->m_GcCover;
1236 if (gcCover == nullptr)
1237 {
1238 return false;
1239 }
1240
1241 // Now it's safe to dereference the IP to check the instruction
1242#if defined(_TARGET_ARM64_)
1243 UINT32 instructionCode = *reinterpret_cast<UINT32 *>(ip);
1244#elif defined(_TARGET_ARM_)
1245 UINT16 instructionCode = *reinterpret_cast<UINT16 *>(ip);
1246#else
1247 UINT8 instructionCode = *reinterpret_cast<UINT8 *>(ip);
1248#endif
1249 switch (instructionCode)
1250 {
1251 case INTERRUPT_INSTR:
1252 case INTERRUPT_INSTR_CALL:
1253 case INTERRUPT_INSTR_PROTECT_RET:
1254 return true;
1255
1256 default:
1257 // Another thread may have already changed the code back to the original
1258 return instructionCode == gcCover->savedCode[codeInfo.GetRelOffset()];
1259 }
1260}
1261
1262// Remove the GcCoverage interrupt instruction, and restore the
1263// original instruction. Only one instruction must be used,
1264// because multiple threads can be executing the same code stream.
1265
1266void RemoveGcCoverageInterrupt(TADDR instrPtr, BYTE * savedInstrPtr)
1267{
1268#ifdef _TARGET_ARM_
1269 if (GetARMInstructionLength(savedInstrPtr) == 2)
1270 *(WORD *)instrPtr = *(WORD *)savedInstrPtr;
1271 else
1272 *(DWORD *)instrPtr = *(DWORD *)savedInstrPtr;
1273#elif defined(_TARGET_ARM64_)
1274 *(DWORD *)instrPtr = *(DWORD *)savedInstrPtr;
1275#else
1276 *(BYTE *)instrPtr = *savedInstrPtr;
1277#endif
1278
1279 FlushInstructionCache(GetCurrentProcess(), (LPCVOID)instrPtr, 4);
1280}
1281
1282BOOL OnGcCoverageInterrupt(PCONTEXT regs)
1283{
1284 SO_NOT_MAINLINE_FUNCTION;
1285
1286 // So that you can set counted breakpoint easily;
1287 GCcoverCount++;
1288 forceStack[0]= &regs; // This is so I can see it fastchecked
1289
1290 PCODE controlPc = GetIP(regs);
1291 TADDR instrPtr = PCODEToPINSTR(controlPc);
1292
1293 forceStack[0] = &instrPtr; // This is so I can see it fastchecked
1294
1295 EECodeInfo codeInfo(controlPc);
1296 if (!codeInfo.IsValid())
1297 return(FALSE);
1298
1299 MethodDesc* pMD = codeInfo.GetMethodDesc();
1300 DWORD offset = codeInfo.GetRelOffset();
1301
1302 forceStack[1] = &pMD; // This is so I can see it fastchecked
1303 forceStack[2] = &offset; // This is so I can see it fastchecked
1304
1305 GCCoverageInfo* gcCover = pMD->m_GcCover;
1306 forceStack[3] = &gcCover; // This is so I can see it fastchecked
1307 if (gcCover == 0)
1308 return(FALSE); // we aren't doing code gcCoverage on this function
1309
1310 BYTE * savedInstrPtr = &gcCover->savedCode[offset];
1311
1312 // If this trap instruction is taken in place of CORINFO_HELP_STOP_FOR_GC()
1313 // Do not start a GC, but continue with the original instruction.
1314 // See the comments above SprinkleBreakpoints() function.
1315 SLOT nextInstr;
1316 SLOT target = getTargetOfCall(savedInstrPtr, regs, &nextInstr);
1317
1318 if (target == (SLOT)JIT_RareDisableHelper) {
1319 RemoveGcCoverageInterrupt(instrPtr, savedInstrPtr);
1320 return TRUE;
1321 }
1322
1323 Thread* pThread = GetThread();
1324 _ASSERTE(pThread);
1325
1326#if defined(USE_REDIRECT_FOR_GCSTRESS) && !defined(PLATFORM_UNIX)
1327 // If we're unable to redirect, then we simply won't test GC at this
1328 // location.
1329 if (!pThread->CheckForAndDoRedirectForGCStress(regs))
1330 {
1331 RemoveGcCoverageInterrupt(instrPtr, savedInstrPtr);
1332 }
1333
1334#else // !USE_REDIRECT_FOR_GCSTRESS
1335
1336#ifdef _DEBUG
1337 if (!g_pConfig->SkipGCCoverage(pMD->GetModule()->GetSimpleName()))
1338#endif
1339 DoGcStress(regs, pMD);
1340
1341#endif // !USE_REDIRECT_FOR_GCSTRESS
1342
1343 return TRUE;
1344}
1345
1346// There are some code path in DoGcStress to return without doing a GC but we
1347// now relies on EE suspension to update the GC STRESS instruction.
1348// We need to do a extra EE suspension/resume even without GC.
1349FORCEINLINE void UpdateGCStressInstructionWithoutGC ()
1350{
1351 ThreadSuspend::SuspendEE(ThreadSuspend::SUSPEND_OTHER);
1352 ThreadSuspend::RestartEE(TRUE, TRUE);
1353}
1354
1355/****************************************************************************/
1356
1357void DoGcStress (PCONTEXT regs, MethodDesc *pMD)
1358{
1359 PCODE controlPc = GetIP(regs);
1360 TADDR instrPtr = PCODEToPINSTR(controlPc);
1361
1362 if (!pMD)
1363 {
1364 pMD = ExecutionManager::GetCodeMethodDesc(controlPc);
1365 if (!pMD)
1366 return;
1367 }
1368
1369 GCCoverageInfo *gcCover = pMD->m_GcCover;
1370
1371 EECodeInfo codeInfo(controlPc);
1372 _ASSERTE(codeInfo.GetMethodDesc() == pMD);
1373 DWORD offset = codeInfo.GetRelOffset();
1374
1375 Thread *pThread = GetThread();
1376
1377#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_)
1378
1379 BYTE instrVal = *(BYTE *)instrPtr;
1380 forceStack[6] = &instrVal; // This is so I can see it fastchecked
1381
1382 if (instrVal != INTERRUPT_INSTR &&
1383 instrVal != INTERRUPT_INSTR_CALL &&
1384 instrVal != INTERRUPT_INSTR_PROTECT_RET) {
1385 _ASSERTE(instrVal == gcCover->savedCode[offset]); // someone beat us to it.
1386 return; // Someone beat us to it, just go on running
1387 }
1388
1389 bool atCall = (instrVal == INTERRUPT_INSTR_CALL);
1390 bool afterCallProtect = (instrVal == INTERRUPT_INSTR_PROTECT_RET);
1391
1392#elif defined(_TARGET_ARM_)
1393
1394 WORD instrVal = *(WORD*)instrPtr;
1395 forceStack[6] = &instrVal; // This is so I can see it fastchecked
1396
1397 size_t instrLen = GetARMInstructionLength(instrVal);
1398
1399 bool atCall;
1400 bool afterCallProtect;
1401
1402 if (instrLen == 2)
1403 {
1404 if (instrVal != INTERRUPT_INSTR &&
1405 instrVal != INTERRUPT_INSTR_CALL &&
1406 instrVal != INTERRUPT_INSTR_PROTECT_RET) {
1407 _ASSERTE(instrVal == *(WORD*)(gcCover->savedCode + offset)); // someone beat us to it.
1408 return; // Someone beat us to it, just go on running
1409 }
1410
1411 atCall = (instrVal == INTERRUPT_INSTR_CALL);
1412 afterCallProtect = (instrVal == INTERRUPT_INSTR_PROTECT_RET);
1413 }
1414 else
1415 {
1416 _ASSERTE(instrLen == 4);
1417
1418 DWORD instrVal32 = *(DWORD*)instrPtr;
1419
1420 if (instrVal32 != INTERRUPT_INSTR_32 &&
1421 instrVal32 != INTERRUPT_INSTR_CALL_32 &&
1422 instrVal32 != INTERRUPT_INSTR_PROTECT_RET_32) {
1423 _ASSERTE(instrVal32 == *(DWORD*)(gcCover->savedCode + offset)); // someone beat us to it.
1424 return; // Someone beat us to it, just go on running
1425 }
1426
1427 atCall = (instrVal32 == INTERRUPT_INSTR_CALL_32);
1428 afterCallProtect = (instrVal32 == INTERRUPT_INSTR_PROTECT_RET_32);
1429 }
1430#elif defined(_TARGET_ARM64_)
1431 DWORD instrVal = *(DWORD *)instrPtr;
1432 forceStack[6] = &instrVal; // This is so I can see it fastchecked
1433
1434 if (instrVal != INTERRUPT_INSTR &&
1435 instrVal != INTERRUPT_INSTR_CALL &&
1436 instrVal != INTERRUPT_INSTR_PROTECT_RET) {
1437 _ASSERTE(instrVal == *(DWORD *)(gcCover->savedCode + offset)); // someone beat us to it.
1438 return; // Someone beat us to it, just go on running
1439 }
1440
1441 bool atCall = (instrVal == INTERRUPT_INSTR_CALL);
1442 bool afterCallProtect = (instrVal == INTERRUPT_INSTR_PROTECT_RET);
1443
1444#endif // _TARGET_*
1445
1446#ifdef _TARGET_X86_
1447 /* are we at the very first instruction? If so, capture the register state */
1448 bool bShouldUpdateProlog = true;
1449 if (gcCover->doingEpilogChecks) {
1450 if (offset == 0) {
1451 if (gcCover->callerThread == 0) {
1452 if (FastInterlockCompareExchangePointer(&gcCover->callerThread, pThread, 0) == 0) {
1453 gcCover->callerRegs = *regs;
1454 gcCover->gcCount = GCHeapUtilities::GetGCHeap()->GetGcCount();
1455 bShouldUpdateProlog = false;
1456 }
1457 }
1458 else {
1459 // We have been in this routine before. Give up on epilog checking because
1460 // it is hard to insure that the saved caller register state is correct
1461 // This also has the effect of only doing the checking once per routine
1462 // (Even if there are multiple epilogs)
1463 gcCover->doingEpilogChecks = false;
1464 }
1465 }
1466
1467 // If some other thread removes interrupt points, we abandon epilog testing
1468 // for this routine since the barrier at the begining of the routine may not
1469 // be up anymore, and thus the caller context is now not guaranteed to be correct.
1470 // This should happen only very rarely so is not a big deal.
1471 if (gcCover->callerThread != pThread)
1472 gcCover->doingEpilogChecks = false;
1473 }
1474
1475 instrVal = gcCover->savedCode[offset];
1476#endif // _TARGET_X86_
1477
1478
1479 // <GCStress instruction update race>
1480 // Remove the interrupt instruction the next time we suspend the EE,
1481 // which should happen below in the call to StressHeap(). This is
1482 // done with the EE suspended so that we do not race with the executing
1483 // code on some other thread. If we allow that race, we may sometimes
1484 // get a STATUS_ACCESS_VIOLATION instead of the expected
1485 // STATUS_PRIVILEGED_INSTRUCTION because the OS has to inspect the code
1486 // stream to determine which exception code to raise. As a result, some
1487 // thread may take the exception due to the HLT, but by the time the OS
1488 // inspects the code stream, the HLT may be replaced with the original
1489 // code and it will just raise a STATUS_ACCESS_VIOLATION.
1490#ifdef _TARGET_X86_
1491 // only restore the original instruction if:
1492 // this is not the first instruction in the method's prolog, or
1493 // if it is, only if this is the second time we run in this method
1494 // note that if this is the second time in the prolog we've already disabled epilog checks
1495 if (offset != 0 || bShouldUpdateProlog)
1496#endif
1497 pThread->PostGCStressInstructionUpdate((BYTE*)instrPtr, &gcCover->savedCode[offset]);
1498
1499#ifdef _TARGET_X86_
1500 /* are we in a prolog or epilog? If so just test the unwind logic
1501 but don't actually do a GC since the prolog and epilog are not
1502 GC safe points */
1503 if (gcCover->codeMan->IsInPrologOrEpilog(offset, gcCover->gcInfoToken, NULL))
1504 {
1505 // We are not at a GC safe point so we can't Suspend EE (Suspend EE will yield to GC).
1506 // But we still have to update the GC Stress instruction. We do it directly without suspending
1507 // other threads, which means a race on updating is still possible. But for X86 the window of
1508 // race is so small that we could ignore it. We need a better solution if the race becomes a real problem.
1509 // see details about <GCStress instruction update race> in comments above
1510 pThread->CommitGCStressInstructionUpdate ();
1511
1512 REGDISPLAY regDisp;
1513 CONTEXT copyRegs = *regs;
1514
1515 pThread->Thread::InitRegDisplay(&regDisp, &copyRegs, true);
1516 pThread->UnhijackThread();
1517
1518 CodeManState codeManState;
1519 codeManState.dwIsSet = 0;
1520
1521 // unwind out of the prolog or epilog
1522 gcCover->codeMan->UnwindStackFrame(&regDisp,
1523 &codeInfo, UpdateAllRegs, &codeManState, NULL);
1524
1525 // Note we always doing the unwind, since that at does some checking (that we
1526 // unwind to a valid return address), but we only do the precise checking when
1527 // we are certain we have a good caller state
1528 if (gcCover->doingEpilogChecks) {
1529 // Confirm that we recovered our register state properly
1530 _ASSERTE(regDisp.PCTAddr == TADDR(gcCover->callerRegs.Esp));
1531
1532 // If a GC happened in this function, then the registers will not match
1533 // precisely. However there is still checks we can do. Also we can update
1534 // the saved register to its new value so that if a GC does not happen between
1535 // instructions we can recover (and since GCs are not allowed in the
1536 // prologs and epilogs, we get get complete coverage except for the first
1537 // instruction in the epilog (TODO: fix it for the first instr Case)
1538
1539 _ASSERTE(pThread->PreemptiveGCDisabled()); // Epilogs should be in cooperative mode, no GC can happen right now.
1540 bool gcHappened = gcCover->gcCount != GCHeapUtilities::GetGCHeap()->GetGcCount();
1541 checkAndUpdateReg(gcCover->callerRegs.Edi, *regDisp.GetEdiLocation(), gcHappened);
1542 checkAndUpdateReg(gcCover->callerRegs.Esi, *regDisp.GetEsiLocation(), gcHappened);
1543 checkAndUpdateReg(gcCover->callerRegs.Ebx, *regDisp.GetEbxLocation(), gcHappened);
1544 checkAndUpdateReg(gcCover->callerRegs.Ebp, *regDisp.GetEbpLocation(), gcHappened);
1545
1546 gcCover->gcCount = GCHeapUtilities::GetGCHeap()->GetGcCount();
1547
1548 }
1549 return;
1550 }
1551#endif // _TARGET_X86_
1552
1553#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) || defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
1554
1555 /* In non-fully interrruptable code, if the EIP is just after a call instr
1556 means something different because it expects that that we are IN the
1557 called method, not actually at the instruction just after the call. This
1558 is important, because until the called method returns, IT is responsible
1559 for protecting the return value. Thus just after a call instruction
1560 we have to protect EAX if the method being called returns a GC pointer.
1561
1562 To figure this out, we need to stop AT the call so we can determine the
1563 target (and thus whether it returns a GC pointer), and then place the
1564 a different interrupt instruction so that the GCCover harness protects
1565 EAX before doing the GC). This effectively simulates a hijack in
1566 non-fully interruptible code */
1567
1568 /* TODO. Simulating the hijack could cause problems in cases where the
1569 return register is not always a valid GC ref on the return offset.
1570 That could happen if we got to the return offset via a branch
1571 and not via return from the preceding call. However, this has not been
1572 an issue so far.
1573
1574 Example:
1575 mov eax, someval
1576 test eax, eax
1577 jCC AFTERCALL
1578 call MethodWhichReturnsGCobject // return value is not used
1579 AFTERCALL:
1580 */
1581
1582 if (atCall) {
1583 // We need to update the GC Stress instruction. With partially-interruptible code
1584 // the call instruction is not a GC safe point so we can't use
1585 // StressHeap or UpdateGCStressInstructionWithoutGC to take care of updating;
1586 // So we just update the instruction directly. There are still chances for a race,
1587 // but it's not been a problem so far.
1588 // see details about <GCStress instruction update race> in comments above
1589 pThread->CommitGCStressInstructionUpdate ();
1590 BYTE* nextInstr;
1591 SLOT target = getTargetOfCall((BYTE*) instrPtr, regs, (BYTE**)&nextInstr);
1592 if (target != 0)
1593 {
1594 if (!pThread->PreemptiveGCDisabled())
1595 {
1596 // We are in preemtive mode in JITTed code. This implies that we are into IL stub
1597 // close to PINVOKE method. This call will never return objectrefs.
1598#ifdef _TARGET_ARM_
1599 size_t instrLen = GetARMInstructionLength(nextInstr);
1600 if (instrLen == 2)
1601 *(WORD*)nextInstr = INTERRUPT_INSTR;
1602 else
1603 *(DWORD*)nextInstr = INTERRUPT_INSTR_32;
1604#elif defined(_TARGET_ARM64_)
1605 *(DWORD*)nextInstr = INTERRUPT_INSTR;
1606#else
1607 *nextInstr = INTERRUPT_INSTR;
1608#endif
1609 }
1610 else
1611 {
1612 MethodDesc* targetMD = getTargetMethodDesc((PCODE)target);
1613
1614 if (targetMD != 0)
1615 {
1616 // Mark that we are performing a stackwalker like operation on the current thread.
1617 // This is necessary to allow the ReturnsObject function to work without triggering any loads
1618 ClrFlsValueSwitch _threadStackWalking(TlsIdx_StackWalkerWalkingThread, pThread);
1619
1620 // @Todo: possible race here, might need to be fixed if it become a problem.
1621 // It could become a problem if 64bit does partially interrupt work.
1622 // OK, we have the MD, mark the instruction after the CALL
1623 // appropriately
1624#ifdef _TARGET_ARM_
1625 size_t instrLen = GetARMInstructionLength(nextInstr);
1626 if (targetMD->ReturnsObject(true) != MetaSig::RETNONOBJ)
1627 if (instrLen == 2)
1628 *(WORD*)nextInstr = INTERRUPT_INSTR_PROTECT_RET;
1629 else
1630 *(DWORD*)nextInstr = INTERRUPT_INSTR_PROTECT_RET_32;
1631 else
1632 if (instrLen == 2)
1633 *(WORD*)nextInstr = INTERRUPT_INSTR;
1634 else
1635 *(DWORD*)nextInstr = INTERRUPT_INSTR_32;
1636#elif defined(_TARGET_ARM64_)
1637 if (targetMD->ReturnsObject(true) != MetaSig::RETNONOBJ)
1638 *(DWORD *)nextInstr = INTERRUPT_INSTR_PROTECT_RET;
1639 else
1640 *(DWORD *)nextInstr = INTERRUPT_INSTR;
1641#else
1642 if (targetMD->ReturnsObject(true) != MetaSig::RETNONOBJ)
1643 *nextInstr = INTERRUPT_INSTR_PROTECT_RET;
1644 else
1645 *nextInstr = INTERRUPT_INSTR;
1646#endif
1647 }
1648 }
1649 }
1650
1651 // Must flush instruction cache before returning as instruction has been modified.
1652 // Note this needs to reach beyond the call by up to 4 bytes.
1653 FlushInstructionCache(GetCurrentProcess(), (LPCVOID)instrPtr, 10);
1654
1655 // It's not GC safe point, the GC Stress instruction is
1656 // already commited and interrupt is already put at next instruction so we just return.
1657 return;
1658 }
1659#else
1660 PORTABILITY_ASSERT("DoGcStress - NYI on this platform");
1661#endif // _TARGET_*
1662
1663 bool enableWhenDone = false;
1664 if (!pThread->PreemptiveGCDisabled())
1665 {
1666 pThread->DisablePreemptiveGC();
1667 enableWhenDone = true;
1668 }
1669
1670
1671#if 0
1672 // TODO currently disabled. we only do a GC once per instruction location.
1673
1674 /* note that for multiple threads, we can loose track and
1675 forget to set reset the interrupt after we executed
1676 an instruction, so some instruction points will not be
1677 executed twice, but we still ge350t very good coverage
1678 (perfect for single threaded cases) */
1679
1680 /* if we have not run this instruction in the past */
1681 /* remember to wack it to an INTERUPT_INSTR again */
1682
1683 if (!gcCover->IsBitSetForOffset(offset)) {
1684 // gcCover->curInstr = instrPtr;
1685 gcCover->SetBitForOffset(offset);
1686 }
1687#endif // 0
1688
1689
1690#if !defined(USE_REDIRECT_FOR_GCSTRESS)
1691 //
1692 // If we redirect for gc stress, we don't need this frame on the stack,
1693 // the redirection will push a resumable frame.
1694 //
1695 FrameWithCookie<ResumableFrame> frame(regs);
1696 frame.Push(pThread);
1697#endif // USE_REDIRECT_FOR_GCSTRESS
1698
1699#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) || defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
1700 FrameWithCookie<GCFrame> gcFrame;
1701 DWORD_PTR retVal = 0;
1702
1703 if (afterCallProtect) // Do I need to protect return value?
1704 {
1705#ifdef _TARGET_AMD64_
1706 retVal = regs->Rax;
1707#elif defined(_TARGET_X86_)
1708 retVal = regs->Eax;
1709#elif defined(_TARGET_ARM_)
1710 retVal = regs->R0;
1711#elif defined(_TARGET_ARM64_)
1712 retVal = regs->X0;
1713#else
1714 PORTABILITY_ASSERT("DoGCStress - return register");
1715#endif
1716 gcFrame.Init(pThread, (OBJECTREF*) &retVal, 1, TRUE);
1717 }
1718#endif // _TARGET_*
1719
1720 if (gcCover->lastMD != pMD)
1721 {
1722 LOG((LF_GCROOTS, LL_INFO100000, "GCCOVER: Doing GC at method %s::%s offset 0x%x\n",
1723 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, offset));
1724 gcCover->lastMD =pMD;
1725 }
1726 else
1727 {
1728 LOG((LF_GCROOTS, LL_EVERYTHING, "GCCOVER: Doing GC at method %s::%s offset 0x%x\n",
1729 pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, offset));
1730 }
1731
1732 //-------------------------------------------------------------------------
1733 // Do the actual stress work
1734 //
1735
1736 // BUG(github #10318) - when not using allocation contexts, the alloc lock
1737 // must be acquired here. Until fixed, this assert prevents random heap corruption.
1738 assert(GCHeapUtilities::UseThreadAllocationContexts());
1739 GCHeapUtilities::GetGCHeap()->StressHeap(GetThread()->GetAllocContext());
1740
1741 // StressHeap can exit early w/o forcing a SuspendEE to trigger the instruction update
1742 // We can not rely on the return code to determine if the instruction update happened
1743 // Use HasPendingGCStressInstructionUpdate() to be certain.
1744 if(pThread->HasPendingGCStressInstructionUpdate())
1745 UpdateGCStressInstructionWithoutGC ();
1746
1747 // Must flush instruction cache before returning as instruction has been modified.
1748 FlushInstructionCache(GetCurrentProcess(), (LPCVOID)instrPtr, 4);
1749
1750 CONSISTENCY_CHECK(!pThread->HasPendingGCStressInstructionUpdate());
1751
1752#if defined(_TARGET_X86_) || defined(_TARGET_AMD64_) || defined(_TARGET_ARM_) || defined(_TARGET_ARM64_)
1753 if (afterCallProtect)
1754 {
1755#ifdef _TARGET_AMD64_
1756 regs->Rax = retVal;
1757#elif defined(_TARGET_X86_)
1758 regs->Eax = retVal;
1759#elif defined(_TARGET_ARM_)
1760 regs->R0 = retVal;
1761#elif defined(_TARGET_ARM64_)
1762 regs->X[0] = retVal;
1763#else
1764 PORTABILITY_ASSERT("DoGCStress - return register");
1765#endif
1766 gcFrame.Pop();
1767 }
1768#endif // _TARGET_*
1769
1770#if !defined(USE_REDIRECT_FOR_GCSTRESS)
1771 frame.Pop(pThread);
1772#endif // USE_REDIRECT_FOR_GCSTRESS
1773
1774 if (enableWhenDone)
1775 {
1776 BOOL b = GC_ON_TRANSITIONS(FALSE); // Don't do a GCStress 3 GC here
1777 pThread->EnablePreemptiveGC();
1778 GC_ON_TRANSITIONS(b);
1779 }
1780
1781 return;
1782
1783}
1784
1785#endif // HAVE_GCCOVER
1786
1787