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// DebugInfoStore
5
6
7
8#include "common.h"
9#include "debuginfostore.h"
10#include "nibblestream.h"
11
12
13#ifdef _DEBUG
14// For debug builds only.
15static bool Dbg_ShouldUseCookies()
16{
17 SUPPORTS_DAC;
18
19 // Normally we want this as false b/c it would bloat the image.
20 // But give us a hook to enable it in case we need it.
21 return false;
22}
23#endif
24
25//-----------------------------------------------------------------------------
26// We have "Transfer" objects that sit on top of the streams.
27// The objects look identical, but one serializes and the other deserializes.
28// This lets the compression + restoration routines share all their compression
29// logic and just swap out Transfer objects.
30//
31// It's not ideal that we have a lot of redundancy maintaining both Transfer
32// objects, but at least the compiler can enforce that the Reader & Writer are
33// in sync. It can't enforce that a 2 separate routines for Compression &
34// restoration are in sync.
35//
36// We could have the TransferReader + Writer be polymorphic off a base class,
37// but the virtual function calls will be extra overhead. May as well use
38// templates and let the compiler resolve it all statically at compile time.
39//-----------------------------------------------------------------------------
40
41
42//-----------------------------------------------------------------------------
43// Serialize to a NibbleWriter stream.
44//-----------------------------------------------------------------------------
45class TransferWriter
46{
47public:
48 TransferWriter(NibbleWriter & w) : m_w(w)
49 {
50 }
51
52 // Write an raw U32 in nibble encoded form.
53 void DoEncodedU32(DWORD dw) { m_w.WriteEncodedU32(dw); }
54
55 // Use to encode a monotonically increasing delta.
56 void DoEncodedDeltaU32(DWORD & dw, DWORD dwLast)
57 {
58 CONTRACTL
59 {
60 THROWS;
61 GC_NOTRIGGER;
62 MODE_ANY;
63 }
64 CONTRACTL_END;
65 _ASSERTE(dw >= dwLast);
66 DWORD dwDelta = dw - dwLast;
67 m_w.WriteEncodedU32(dwDelta);
68 }
69
70
71 // Some U32 may have a few sentinal negative values .
72 // We adjust it to be a real U32 and then encode that.
73 // dwAdjust should be the lower bound on the enum.
74 void DoEncodedAdjustedU32(DWORD dw, DWORD dwAdjust)
75 {
76 //_ASSERTE(dwAdjust < 0); // some negative lower bound.
77 m_w.WriteEncodedU32(dw - dwAdjust);
78 }
79
80 // Typesafe versions of EncodeU32.
81 void DoEncodedSourceType(ICorDebugInfo::SourceTypes & dw) { m_w.WriteEncodedU32(dw); }
82 void DoEncodedVarLocType(ICorDebugInfo::VarLocType & dw) { m_w.WriteEncodedU32(dw); }
83 void DoEncodedUnsigned(unsigned & dw) { m_w.WriteEncodedU32(dw); }
84
85 // Stack offsets are aligned on a DWORD boundary, so that lets us shave off 2 bits.
86 void DoEncodedStackOffset(signed & dwOffset)
87 {
88 CONTRACTL
89 {
90 THROWS;
91 GC_NOTRIGGER;
92 MODE_ANY;
93 }
94 CONTRACTL_END;
95#ifdef _TARGET_X86_
96 _ASSERTE(dwOffset % sizeof(DWORD) == 0); // should be dword aligned. That'll save us 2 bits.
97 m_w.WriteEncodedI32(dwOffset / sizeof(DWORD));
98#else
99 // Non x86 platforms don't need it to be dword aligned.
100 m_w.WriteEncodedI32(dwOffset);
101#endif
102 }
103
104 void DoEncodedRegIdx(ICorDebugInfo::RegNum & reg) { m_w.WriteEncodedU32(reg); }
105
106 // For debugging purposes, inject cookies into the Compression.
107 void DoCookie(BYTE b) {
108#ifdef _DEBUG
109 if (Dbg_ShouldUseCookies())
110 {
111 m_w.WriteNibble(b);
112 }
113#endif
114 }
115
116protected:
117 NibbleWriter & m_w;
118
119};
120
121//-----------------------------------------------------------------------------
122// Deserializer that sits on top of a NibbleReader
123// This class interface matches TransferWriter exactly. See that for details.
124//-----------------------------------------------------------------------------
125class TransferReader
126{
127public:
128 TransferReader(NibbleReader & r) : m_r(r)
129 {
130 SUPPORTS_DAC;
131 }
132
133 void DoEncodedU32(DWORD & dw)
134 {
135 SUPPORTS_DAC;
136 dw = m_r.ReadEncodedU32();
137 }
138
139 // Use to decode a monotonically increasing delta.
140 // dwLast was the last value; we update it to the current value on output.
141 void DoEncodedDeltaU32(DWORD & dw, DWORD dwLast)
142 {
143 SUPPORTS_DAC;
144 DWORD dwDelta = m_r.ReadEncodedU32();
145 dw = dwLast + dwDelta;
146 }
147
148 void DoEncodedAdjustedU32(DWORD & dw, DWORD dwAdjust)
149 {
150 SUPPORTS_DAC;
151 //_ASSERTE(dwAdjust < 0);
152 dw = m_r.ReadEncodedU32() + dwAdjust;
153 }
154
155 void DoEncodedSourceType(ICorDebugInfo::SourceTypes & dw)
156 {
157 SUPPORTS_DAC;
158 dw = (ICorDebugInfo::SourceTypes) m_r.ReadEncodedU32();
159 }
160
161 void DoEncodedVarLocType(ICorDebugInfo::VarLocType & dw)
162 {
163 SUPPORTS_DAC;
164 dw = (ICorDebugInfo::VarLocType) m_r.ReadEncodedU32();
165 }
166
167 void DoEncodedUnsigned(unsigned & dw)
168 {
169 SUPPORTS_DAC;
170 dw = (unsigned) m_r.ReadEncodedU32();
171 }
172
173
174 // Stack offsets are aligned on a DWORD boundary, so that lets us shave off 2 bits.
175 void DoEncodedStackOffset(signed & dwOffset)
176 {
177 SUPPORTS_DAC;
178#ifdef _TARGET_X86_
179 dwOffset = m_r.ReadEncodedI32() * sizeof(DWORD);
180#else
181 // Non x86 platforms don't need it to be dword aligned.
182 dwOffset = m_r.ReadEncodedI32();
183#endif
184 }
185
186 void DoEncodedRegIdx(ICorDebugInfo::RegNum & reg)
187 {
188 SUPPORTS_DAC;
189 reg = (ICorDebugInfo::RegNum) m_r.ReadEncodedU32();
190 }
191
192 // For debugging purposes, inject cookies into the Compression.
193 void DoCookie(BYTE b)
194 {
195 SUPPORTS_DAC;
196
197#ifdef _DEBUG
198 if (Dbg_ShouldUseCookies())
199 {
200 BYTE b2 = m_r.ReadNibble();
201 _ASSERTE(b == b2);
202 }
203#endif
204 }
205
206
207protected:
208 NibbleReader & m_r;
209};
210
211
212#if defined(_DEBUG) && !defined(DACCESS_COMPILE)
213// Perf tracking
214static int g_CDI_TotalMethods = 0;
215static int g_CDI_bMethodTotalUncompress = 0;
216static int g_CDI_bMethodTotalCompress = 0;
217
218static int g_CDI_bVarsTotalUncompress = 0;
219static int g_CDI_bVarsTotalCompress = 0;
220#endif
221
222//-----------------------------------------------------------------------------
223// Serialize Bounds info.
224//-----------------------------------------------------------------------------
225template <class T>
226void DoBounds(
227 T trans, // transfer object.
228 ULONG32 cMap,
229 ICorDebugInfo::OffsetMapping *pMap
230)
231{
232 CONTRACTL
233 {
234 THROWS;
235 GC_NOTRIGGER;
236 MODE_ANY;
237 SUPPORTS_DAC;
238 }
239 CONTRACTL_END;
240
241
242 // Bounds info contains (Native Offset, IL Offset, flags)
243 // - Sorted by native offset (so use a delta encoding for that).
244 // - IL offsets aren't sorted, but they should be close to each other (so a signed delta encoding)
245 // They may also include a sentinel value from MappingTypes.
246 // - flags is 3 indepedent bits.
247
248 // Loop through and transfer each Entry in the Mapping.
249 DWORD dwLastNativeOffset = 0;
250 for(DWORD i = 0; i < cMap; i++)
251 {
252 ICorDebugInfo::OffsetMapping * pBound = &pMap[i];
253
254 trans.DoEncodedDeltaU32(pBound->nativeOffset, dwLastNativeOffset);
255 dwLastNativeOffset = pBound->nativeOffset;
256
257
258 trans.DoEncodedAdjustedU32(pBound->ilOffset, (DWORD) ICorDebugInfo::MAX_MAPPING_VALUE);
259
260 trans.DoEncodedSourceType(pBound->source);
261
262 trans.DoCookie(0xA);
263 }
264}
265
266
267
268// Helper to write a compressed Native Var Info
269template<class T>
270void DoNativeVarInfo(
271 T trans,
272 ICorDebugInfo::NativeVarInfo * pVar
273)
274{
275 CONTRACTL
276 {
277 THROWS;
278 GC_NOTRIGGER;
279 MODE_ANY;
280 SUPPORTS_DAC;
281 }
282 CONTRACTL_END;
283
284
285 // Each Varinfo has a:
286 // - native start +End offset. We can use a delta for the end offset.
287 // - Il variable number. These are usually small.
288 // - VarLoc information. This is a tagged variant.
289 // The entries aren't sorted in any particular order.
290 trans.DoCookie(0xB);
291 trans.DoEncodedU32(pVar->startOffset);
292
293
294 trans.DoEncodedDeltaU32(pVar->endOffset, pVar->startOffset);
295
296 // record var number.
297 trans.DoEncodedAdjustedU32(pVar->varNumber, (DWORD) ICorDebugInfo::MAX_ILNUM);
298
299
300 // Now write the VarLoc... This is a variant like structure and so we'll get different
301 // compressioned depending on what we've got.
302 trans.DoEncodedVarLocType(pVar->loc.vlType);
303
304 switch(pVar->loc.vlType)
305 {
306 case ICorDebugInfo::VLT_REG:
307 case ICorDebugInfo::VLT_REG_FP: // fall through
308 case ICorDebugInfo::VLT_REG_BYREF: // fall through
309 trans.DoEncodedRegIdx(pVar->loc.vlReg.vlrReg);
310 break;
311
312 case ICorDebugInfo::VLT_STK:
313 case ICorDebugInfo::VLT_STK_BYREF: // fall through
314 trans.DoEncodedRegIdx(pVar->loc.vlStk.vlsBaseReg);
315 trans.DoEncodedStackOffset(pVar->loc.vlStk.vlsOffset);
316 break;
317
318 case ICorDebugInfo::VLT_REG_REG:
319 trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg1);
320 trans.DoEncodedRegIdx(pVar->loc.vlRegReg.vlrrReg2);
321 break;
322
323 case ICorDebugInfo::VLT_REG_STK:
324 trans.DoEncodedRegIdx(pVar->loc.vlRegStk.vlrsReg);
325 trans.DoEncodedRegIdx(pVar->loc.vlRegStk.vlrsStk.vlrssBaseReg);
326 trans.DoEncodedStackOffset(pVar->loc.vlRegStk.vlrsStk.vlrssOffset);
327 break;
328
329 case ICorDebugInfo::VLT_STK_REG:
330 trans.DoEncodedStackOffset(pVar->loc.vlStkReg.vlsrStk.vlsrsOffset);
331 trans.DoEncodedRegIdx(pVar->loc.vlStkReg.vlsrStk.vlsrsBaseReg);
332 trans.DoEncodedRegIdx(pVar->loc.vlStkReg.vlsrReg);
333 break;
334
335 case ICorDebugInfo::VLT_STK2:
336 trans.DoEncodedRegIdx(pVar->loc.vlStk2.vls2BaseReg);
337 trans.DoEncodedStackOffset(pVar->loc.vlStk2.vls2Offset);
338 break;
339
340 case ICorDebugInfo::VLT_FPSTK:
341 trans.DoEncodedUnsigned(pVar->loc.vlFPstk.vlfReg);
342 break;
343
344 case ICorDebugInfo::VLT_FIXED_VA:
345 trans.DoEncodedUnsigned(pVar->loc.vlFixedVarArg.vlfvOffset);
346 break;
347
348 default:
349 _ASSERTE(!"Unknown varloc type!");
350 break;
351 }
352
353
354 trans.DoCookie(0xC);
355}
356
357
358#ifndef DACCESS_COMPILE
359
360void CompressDebugInfo::CompressBoundaries(
361 IN ULONG32 cMap,
362 IN ICorDebugInfo::OffsetMapping *pMap,
363 IN OUT NibbleWriter *pWriter
364)
365{
366 CONTRACTL
367 {
368 THROWS;
369 GC_NOTRIGGER;
370 MODE_ANY;
371 }
372 CONTRACTL_END;
373
374 _ASSERTE(pWriter != NULL);
375 _ASSERTE((pMap == NULL) == (cMap == 0));
376
377 if (cMap != 0)
378 {
379 pWriter->WriteEncodedU32(cMap);
380
381 TransferWriter t(*pWriter);
382 DoBounds(t, cMap, pMap);
383
384 pWriter->Flush();
385 }
386
387#ifdef _DEBUG
388 DWORD cbBlob;
389 PVOID pBlob = pWriter->GetBlob(&cbBlob);
390
391 // Track perf #s for compression...
392 g_CDI_TotalMethods++;
393 g_CDI_bMethodTotalUncompress += sizeof(ICorDebugInfo::OffsetMapping) * cMap;
394 g_CDI_bMethodTotalCompress += (int) cbBlob;
395#endif // _DEBUG
396}
397
398
399void CompressDebugInfo::CompressVars(
400 IN ULONG32 cVars,
401 IN ICorDebugInfo::NativeVarInfo *vars,
402 IN OUT NibbleWriter *pWriter
403)
404{
405 CONTRACTL
406 {
407 THROWS;
408 GC_NOTRIGGER;
409 MODE_ANY;
410 }
411 CONTRACTL_END;
412
413 _ASSERTE(pWriter != NULL);
414 _ASSERTE((cVars == 0) == (vars == NULL));
415
416 if (cVars != 0)
417 {
418 pWriter->WriteEncodedU32(cVars);
419
420 TransferWriter t(*pWriter);
421 for(ULONG32 i = 0; i < cVars; i ++)
422 {
423 DoNativeVarInfo(t, &vars[i]);
424 }
425
426 pWriter->Flush();
427 }
428
429#ifdef _DEBUG
430 DWORD cbBlob;
431 PVOID pBlob = pWriter->GetBlob(&cbBlob);
432
433 g_CDI_bVarsTotalUncompress += cVars * sizeof(ICorDebugInfo::NativeVarInfo);
434 g_CDI_bVarsTotalCompress += (int) cbBlob;
435#endif
436}
437
438PTR_BYTE CompressDebugInfo::CompressBoundariesAndVars(
439 IN ICorDebugInfo::OffsetMapping * pOffsetMapping,
440 IN ULONG iOffsetMapping,
441 IN ICorDebugInfo::NativeVarInfo * pNativeVarInfo,
442 IN ULONG iNativeVarInfo,
443 IN OUT SBuffer * pDebugInfoBuffer,
444 IN LoaderHeap * pLoaderHeap
445 )
446{
447 CONTRACTL {
448 THROWS; // compression routines throw
449 PRECONDITION((iOffsetMapping == 0) == (pOffsetMapping == NULL));
450 PRECONDITION((iNativeVarInfo == 0) == (pNativeVarInfo == NULL));
451 PRECONDITION((pDebugInfoBuffer != NULL) ^ (pLoaderHeap != NULL));
452 } CONTRACTL_END;
453
454 // Actually do the compression. These will throw on oom.
455 NibbleWriter boundsBuffer;
456 DWORD cbBounds = 0;
457 PVOID pBounds = NULL;
458 if (iOffsetMapping > 0)
459 {
460 CompressDebugInfo::CompressBoundaries(iOffsetMapping, pOffsetMapping, &boundsBuffer);
461 pBounds = boundsBuffer.GetBlob(&cbBounds);
462 }
463
464 NibbleWriter varsBuffer;
465 DWORD cbVars = 0;
466 PVOID pVars = NULL;
467 if (iNativeVarInfo > 0)
468 {
469 CompressDebugInfo::CompressVars(iNativeVarInfo, pNativeVarInfo, &varsBuffer);
470 pVars = varsBuffer.GetBlob(&cbVars);
471 }
472
473 // Now write it all out to the buffer in a compact fashion.
474 NibbleWriter w;
475 w.WriteEncodedU32(cbBounds);
476 w.WriteEncodedU32(cbVars);
477 w.Flush();
478
479 DWORD cbHeader;
480 PVOID pHeader = w.GetBlob(&cbHeader);
481
482 S_UINT32 cbFinalSize = S_UINT32(cbHeader) + S_UINT32(cbBounds) + S_UINT32(cbVars);
483 if (cbFinalSize.IsOverflow())
484 ThrowHR(COR_E_OVERFLOW);
485
486 BYTE *ptrStart = NULL;
487 if (pLoaderHeap != NULL)
488 {
489 ptrStart = (BYTE *)(void *)pLoaderHeap->AllocMem(S_SIZE_T(cbFinalSize.Value()));
490 }
491 else
492 {
493 // Create a conservatively large buffer to hold all the data.
494 ptrStart = pDebugInfoBuffer->OpenRawBuffer(cbFinalSize.Value());
495 }
496 _ASSERTE(ptrStart != NULL); // throws on oom.
497
498 BYTE *ptr = ptrStart;
499
500 memcpy(ptr, pHeader, cbHeader);
501 ptr += cbHeader;
502
503 memcpy(ptr, pBounds, cbBounds);
504 ptr += cbBounds;
505
506 memcpy(ptr, pVars, cbVars);
507 ptr += cbVars;
508
509 if (pLoaderHeap != NULL)
510 {
511 return ptrStart;
512 }
513 else
514 {
515 pDebugInfoBuffer->CloseRawBuffer(cbFinalSize.Value());
516 return NULL;
517 }
518}
519
520#endif // DACCESS_COMPILE
521
522//-----------------------------------------------------------------------------
523// Compression routines
524// DAC only needs to run the uncompression routines.
525//-----------------------------------------------------------------------------
526
527//-----------------------------------------------------------------------------
528// Uncompression (restore) routines
529//-----------------------------------------------------------------------------
530
531// Uncompress data supplied by Compress functions.
532void CompressDebugInfo::RestoreBoundariesAndVars(
533 IN FP_IDS_NEW fpNew, IN void * pNewData,
534 IN PTR_BYTE pDebugInfo,
535 OUT ULONG32 * pcMap, // number of entries in ppMap
536 OUT ICorDebugInfo::OffsetMapping **ppMap, // pointer to newly allocated array
537 OUT ULONG32 *pcVars,
538 OUT ICorDebugInfo::NativeVarInfo **ppVars
539 )
540{
541 CONTRACTL
542 {
543 THROWS; // reading from nibble stream may throw on invalid data.
544 GC_NOTRIGGER;
545 MODE_ANY;
546 SUPPORTS_DAC;
547 }
548 CONTRACTL_END;
549
550 if (pcMap != NULL) *pcMap = 0;
551 if (ppMap != NULL) *ppMap = NULL;
552 if (pcVars != NULL) *pcVars = 0;
553 if (ppVars != NULL) *ppVars = NULL;
554
555 NibbleReader r(pDebugInfo, 12 /* maximum size of compressed 2 UINT32s */);
556
557 ULONG cbBounds = r.ReadEncodedU32();
558 ULONG cbVars = r.ReadEncodedU32();
559
560 PTR_BYTE addrBounds = pDebugInfo + r.GetNextByteIndex();
561 PTR_BYTE addrVars = addrBounds + cbBounds;
562
563 if ((pcMap != NULL || ppMap != NULL) && (cbBounds != 0))
564 {
565 NibbleReader r(addrBounds, cbBounds);
566 TransferReader t(r);
567
568 UINT32 cNumEntries = r.ReadEncodedU32();
569 _ASSERTE(cNumEntries > 0);
570
571 if (pcMap != NULL)
572 *pcMap = cNumEntries;
573
574 if (ppMap != NULL)
575 {
576 ICorDebugInfo::OffsetMapping * pMap = reinterpret_cast<ICorDebugInfo::OffsetMapping *>
577 (fpNew(pNewData, cNumEntries * sizeof(ICorDebugInfo::OffsetMapping)));
578 if (pMap == NULL)
579 {
580 ThrowOutOfMemory();
581 }
582 *ppMap = pMap;
583
584 // Main decompression routine.
585 DoBounds(t, cNumEntries, pMap);
586 }
587 }
588
589 if ((pcVars != NULL || ppVars != NULL) && (cbVars != 0))
590 {
591 NibbleReader r(addrVars, cbVars);
592 TransferReader t(r);
593
594 UINT32 cNumEntries = r.ReadEncodedU32();
595 _ASSERTE(cNumEntries > 0);
596
597 if (pcVars != NULL)
598 *pcVars = cNumEntries;
599
600 if (ppVars != NULL)
601 {
602 ICorDebugInfo::NativeVarInfo * pVars = reinterpret_cast<ICorDebugInfo::NativeVarInfo *>
603 (fpNew(pNewData, cNumEntries * sizeof(ICorDebugInfo::NativeVarInfo)));
604 if (pVars == NULL)
605 {
606 ThrowOutOfMemory();
607 }
608 *ppVars = pVars;
609
610 for(UINT32 i = 0; i < cNumEntries; i++)
611 {
612 DoNativeVarInfo(t, &pVars[i]);
613 }
614 }
615 }
616}
617
618#ifdef DACCESS_COMPILE
619void CompressDebugInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags, PTR_BYTE pDebugInfo)
620{
621 CONTRACTL
622 {
623 NOTHROW;
624 GC_NOTRIGGER;
625 SUPPORTS_DAC;
626 }
627 CONTRACTL_END;
628
629 NibbleReader r(pDebugInfo, 12 /* maximum size of compressed 2 UINT32s */);
630
631 ULONG cbBounds = r.ReadEncodedU32();
632 ULONG cbVars = r.ReadEncodedU32();
633
634 DacEnumMemoryRegion(dac_cast<TADDR>(pDebugInfo), r.GetNextByteIndex() + cbBounds + cbVars);
635}
636#endif // DACCESS_COMPILE
637
638// Init given a starting address from the start of code.
639void DebugInfoRequest::InitFromStartingAddr(MethodDesc * pMD, PCODE addrCode)
640{
641 CONTRACTL
642 {
643 NOTHROW;
644 GC_NOTRIGGER;
645 MODE_ANY;
646 SUPPORTS_DAC;
647 }
648 CONTRACTL_END;
649
650 _ASSERTE(pMD != NULL);
651 _ASSERTE(addrCode != NULL);
652
653 this->m_pMD = pMD;
654 this->m_addrStart = addrCode;
655}
656
657
658//-----------------------------------------------------------------------------
659// Impl for DebugInfoManager's IDebugInfoStore
660//-----------------------------------------------------------------------------
661BOOL DebugInfoManager::GetBoundariesAndVars(
662 const DebugInfoRequest & request,
663 IN FP_IDS_NEW fpNew, IN void * pNewData,
664 OUT ULONG32 * pcMap,
665 OUT ICorDebugInfo::OffsetMapping ** ppMap,
666 OUT ULONG32 * pcVars,
667 OUT ICorDebugInfo::NativeVarInfo ** ppVars)
668{
669 CONTRACTL
670 {
671 THROWS;
672 WRAPPER(GC_TRIGGERS); // depends on fpNew
673 SUPPORTS_DAC;
674 }
675 CONTRACTL_END;
676
677 IJitManager* pJitMan = ExecutionManager::FindJitMan(request.GetStartAddress());
678 if (pJitMan == NULL)
679 {
680 return FALSE; // no info available.
681 }
682
683 return pJitMan->GetBoundariesAndVars(request, fpNew, pNewData, pcMap, ppMap, pcVars, ppVars);
684}
685
686#ifdef DACCESS_COMPILE
687void DebugInfoManager::EnumMemoryRegionsForMethodDebugInfo(CLRDataEnumMemoryFlags flags, MethodDesc * pMD)
688{
689 CONTRACTL
690 {
691 NOTHROW;
692 GC_NOTRIGGER;
693 SUPPORTS_DAC;
694 }
695 CONTRACTL_END;
696
697 PCODE addrCode = pMD->GetNativeCode();
698 if (addrCode == NULL)
699 {
700 return;
701 }
702
703 IJitManager* pJitMan = ExecutionManager::FindJitMan(addrCode);
704 if (pJitMan == NULL)
705 {
706 return; // no info available.
707 }
708
709 pJitMan->EnumMemoryRegionsForMethodDebugInfo(flags, pMD);
710}
711#endif
712