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 | // IBClogger.CPP |
5 | // |
6 | |
7 | // |
8 | // Infrastructure for recording touches of EE data structures |
9 | // |
10 | // |
11 | |
12 | |
13 | #include "common.h" |
14 | #ifdef IBCLOGGER_ENABLED |
15 | #include "method.hpp" |
16 | #include "corbbtprof.h" |
17 | #include "metadatatracker.h" |
18 | #include "field.h" |
19 | #include "typekey.h" |
20 | #include "ibclogger.h" |
21 | |
22 | //#ifdef _DEBUG |
23 | //#define DEBUG_IBCLOGGER |
24 | //#endif |
25 | |
26 | #ifdef DEBUG_IBCLOGGER |
27 | |
28 | #define DEBUG_PRINTF1(a) printf(a) |
29 | #define DEBUG_PRINTF2(a,b) printf(a,b) |
30 | #define DEBUG_PRINTF3(a,b,c) printf(a,b,c) |
31 | #define DEBUG_PRINTF4(a,b,c,d) printf(a,b,c,d) |
32 | #define DEBUG_PRINTF5(a,b,c,d,e) printf(a,b,c,d,e) |
33 | #else |
34 | #define DEBUG_PRINTF1(a) |
35 | #define DEBUG_PRINTF2(a,b) |
36 | #define DEBUG_PRINTF3(a,b,c) |
37 | #define DEBUG_PRINTF4(a,b,c,d) |
38 | #define DEBUG_PRINTF5(a,b,c,d,e) |
39 | #endif |
40 | |
41 | DWORD dwIBCLogCount = 0; |
42 | CrstStatic IBCLogger::m_sync; |
43 | |
44 | #ifdef _DEBUG |
45 | /*static*/ unsigned IbcCallback::s_highestId = 0; |
46 | #endif |
47 | |
48 | IBCLoggingDisabler::IBCLoggingDisabler() |
49 | { |
50 | m_pInfo = NULL; |
51 | m_fDisabled = false; |
52 | |
53 | if (g_IBCLogger.InstrEnabled()) |
54 | { |
55 | m_pInfo = GetThread()->GetIBCInfo(); |
56 | if (m_pInfo != NULL) |
57 | { |
58 | m_fDisabled = m_pInfo->DisableLogging(); |
59 | } |
60 | } |
61 | } |
62 | |
63 | IBCLoggingDisabler::IBCLoggingDisabler(bool ignore) |
64 | { |
65 | m_pInfo = NULL; |
66 | m_fDisabled = false; |
67 | |
68 | if (ignore == false) |
69 | { |
70 | if (g_IBCLogger.InstrEnabled()) |
71 | { |
72 | m_pInfo = GetThread()->GetIBCInfo(); |
73 | if (m_pInfo != NULL) |
74 | { |
75 | m_fDisabled = m_pInfo->DisableLogging(); |
76 | } |
77 | } |
78 | } |
79 | } |
80 | |
81 | IBCLoggingDisabler::IBCLoggingDisabler(ThreadLocalIBCInfo* pInfo) |
82 | { |
83 | LIMITED_METHOD_CONTRACT; |
84 | m_pInfo = pInfo; |
85 | |
86 | if (m_pInfo != NULL) |
87 | { |
88 | m_fDisabled = m_pInfo->DisableLogging(); |
89 | } |
90 | else |
91 | { |
92 | m_fDisabled = false; |
93 | } |
94 | } |
95 | |
96 | IBCLoggingDisabler::~IBCLoggingDisabler() |
97 | { |
98 | LIMITED_METHOD_CONTRACT; |
99 | if (m_fDisabled) |
100 | m_pInfo->EnableLogging(); |
101 | } |
102 | |
103 | IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker() |
104 | { |
105 | CONTRACTL |
106 | { |
107 | NOTHROW; |
108 | GC_NOTRIGGER; |
109 | MODE_ANY; |
110 | } |
111 | CONTRACTL_END; |
112 | |
113 | if (!m_fReleased) |
114 | { |
115 | GetThread()->FlushIBCInfo(); |
116 | } |
117 | } |
118 | |
119 | IBCLogger::IBCLogger() |
120 | : dwInstrEnabled(0) |
121 | { |
122 | LIMITED_METHOD_CONTRACT; |
123 | m_sync.Init(CrstIbcProfile, CrstFlags(CRST_UNSAFE_ANYMODE | CRST_REENTRANCY | CRST_DEBUGGER_THREAD)); |
124 | } |
125 | |
126 | IBCLogger::~IBCLogger() |
127 | { |
128 | WRAPPER_NO_CONTRACT; |
129 | |
130 | m_sync.Destroy(); |
131 | } |
132 | |
133 | void IBCLogger::LogAccessThreadSafeHelperStatic(const void * p, pfnIBCAccessCallback callback) |
134 | { |
135 | WRAPPER_NO_CONTRACT; |
136 | /* To make the logging callsite as small as possible keep the part that passes extra */ |
137 | /* argument to LogAccessThreadSafeHelper in separate non-inlined function */ |
138 | g_IBCLogger.LogAccessThreadSafeHelper(p, callback); |
139 | } |
140 | |
141 | void IBCLogger::LogAccessThreadSafeHelper(const void * p, pfnIBCAccessCallback callback) |
142 | { |
143 | WRAPPER_NO_CONTRACT; |
144 | SO_NOT_MAINLINE_FUNCTION; |
145 | CONTRACT_VIOLATION( HostViolation ); |
146 | |
147 | /* For the Global Class we may see p == NULL */ |
148 | if (p == NULL) |
149 | return; |
150 | |
151 | Thread * pThread = GetThread(); |
152 | |
153 | /* This could be called by the concurrent GC thread*/ |
154 | /* where GetThread() returns NULL. In such cases,*/ |
155 | /* we want to log data accessed by the GC, but we will just ignore it for now.*/ |
156 | if (pThread == NULL) |
157 | return; |
158 | |
159 | ThreadLocalIBCInfo* pInfo = pThread->GetIBCInfo(); |
160 | if (pInfo == NULL) |
161 | { |
162 | CONTRACT_VIOLATION( ThrowsViolation | FaultViolation); |
163 | pInfo = new ThreadLocalIBCInfo(); |
164 | pThread->SetIBCInfo(pInfo); |
165 | } |
166 | |
167 | // |
168 | // During certain events we disable IBC logging. |
169 | // This may be to prevent deadlocks or we might |
170 | // not want to have IBC logging during these events. |
171 | // |
172 | if ( !pInfo->IsLoggingDisabled() ) |
173 | { |
174 | CONTRACT_VIOLATION( ThrowsViolation | TakesLockViolation | FaultViolation); |
175 | pInfo->CallbackHelper(p, callback); |
176 | } |
177 | } |
178 | |
179 | CrstStatic* IBCLogger::GetSync() |
180 | { |
181 | CONTRACTL |
182 | { |
183 | THROWS; |
184 | GC_NOTRIGGER; |
185 | MODE_ANY; |
186 | SO_NOT_MAINLINE; |
187 | } |
188 | CONTRACTL_END; |
189 | |
190 | return &m_sync; |
191 | } |
192 | |
193 | void IBCLogger::DelayedCallbackPtr(pfnIBCAccessCallback callback, const void * pValue1, const void * pValue2 /*=NULL*/) |
194 | { |
195 | WRAPPER_NO_CONTRACT; |
196 | |
197 | ThreadLocalIBCInfo* pInfo = GetThread()->GetIBCInfo(); |
198 | |
199 | // record that we could not currently resolve this callback |
200 | pInfo->SetCallbackFailed(); |
201 | |
202 | // If we are processing the delayed list then we don't want or need to |
203 | // add this pair <callback, pValue> to the delay list. |
204 | if (pInfo->ProcessingDelayedList()) |
205 | { |
206 | return; |
207 | } |
208 | |
209 | // We could throw an out of memory exception |
210 | CONTRACT_VIOLATION( ThrowsViolation ); |
211 | |
212 | // Get our thread local hashtable |
213 | DelayCallbackTable * pTable = pInfo->GetPtrDelayList(); |
214 | |
215 | // Create IbcCallback in our stack frame to use as a key for the Lookup |
216 | IbcCallback key(callback, pValue1, pValue2); |
217 | |
218 | // Perform lookup of this key in our hashtable |
219 | IbcCallback * pEntry = pTable->Lookup(&key); |
220 | |
221 | // If we already have this pair <callback, pValue> in our table |
222 | // then just return, because we don't need to add a duplicate |
223 | if (pEntry != NULL) |
224 | { |
225 | // Print out a debug message if we are debugging this |
226 | DEBUG_PRINTF4("Did not add duplicate delayed ptr callback: pfn=0x%08x, pValue1=0x%8p, pValue2=0x%8p\n" , |
227 | pEntry->GetPfn(), pEntry->GetValue1(), pEntry->GetValue2()); |
228 | return; |
229 | } |
230 | // Now that we know that we will add a new entry into our hashtable |
231 | // We create a new IbcCallback in the heap to use as a persisted key |
232 | pEntry = new IbcCallback(callback, pValue1, pValue2); |
233 | |
234 | // Mark this key as new valid IbcCallback |
235 | pEntry->SetValid(); |
236 | |
237 | // Add the entry into our hashtable. |
238 | pTable->Add(pEntry); |
239 | |
240 | // Print out a debug message if we are debugging this |
241 | DEBUG_PRINTF4("Added a new delayed ptr callback: pfn=0x%08x, pValue1=0x%8p, pValue2=0x%8p\n" , |
242 | key.GetPfn(), key.GetValue1(), key.GetValue2()); |
243 | } |
244 | |
245 | // some of IBC probes never complete successfully at all. |
246 | // and there is no point for them to stay in the delay list forever, |
247 | // because it significantly slows down the IBC instrumentation. |
248 | // c_maxRetries: the maximun number of times the unsuccessful IBC probe is tried |
249 | // c_minCount: is the minimum number of entries in the delay list that we |
250 | // need before we will call ProcessDelayedCallbacks() |
251 | // c_minCountIncr: is the minimum number of entries in the delay list that we |
252 | // need to add before we will call ProcessDelayedCallbacks() again |
253 | // |
254 | static const int c_maxRetries = 10; |
255 | static const int c_minCount = 8; |
256 | static const int c_minCountIncr = 8; |
257 | |
258 | ThreadLocalIBCInfo::ThreadLocalIBCInfo() |
259 | { |
260 | LIMITED_METHOD_CONTRACT; |
261 | SO_NOT_MAINLINE_FUNCTION; |
262 | |
263 | m_fCallbackFailed = false; |
264 | m_fProcessingDelayedList = false; |
265 | m_fLoggingDisabled = false; |
266 | m_iMinCountToProcess = c_minCount; |
267 | m_pDelayList = NULL; |
268 | } |
269 | |
270 | ThreadLocalIBCInfo:: ~ThreadLocalIBCInfo() |
271 | { |
272 | WRAPPER_NO_CONTRACT; |
273 | |
274 | if (m_pDelayList != NULL) |
275 | { |
276 | // We have one last call to the CallbackHelper to |
277 | // flush out any remaining items on our delay list |
278 | // |
279 | // CONTRACT_VIOLATION( ThrowsViolation | TakesLockViolation ); |
280 | // CallbackHelper(NULL, NULL); |
281 | |
282 | DeleteDelayedCallbacks(); |
283 | } |
284 | } |
285 | |
286 | void ThreadLocalIBCInfo::DeleteDelayedCallbacks() |
287 | { |
288 | CONTRACTL |
289 | { |
290 | NOTHROW; |
291 | GC_NOTRIGGER; |
292 | MODE_ANY; |
293 | SO_NOT_MAINLINE; |
294 | } |
295 | CONTRACTL_END; |
296 | |
297 | for (DelayCallbackTable::Iterator elem = m_pDelayList->Begin(), |
298 | end = m_pDelayList->End(); |
299 | (elem != end); elem++) |
300 | { |
301 | IbcCallback * pCallback = const_cast<IbcCallback *>(*elem); |
302 | |
303 | _ASSERTE(pCallback->IsValid()); |
304 | |
305 | // free up each of the IbcCallback pointers that we allocated |
306 | pCallback->Invalidate(); |
307 | delete pCallback; |
308 | } |
309 | |
310 | delete m_pDelayList; |
311 | m_pDelayList = NULL; |
312 | } |
313 | |
314 | void ThreadLocalIBCInfo::FlushDelayedCallbacks() |
315 | { |
316 | CONTRACTL |
317 | { |
318 | NOTHROW; |
319 | GC_NOTRIGGER; |
320 | MODE_ANY; |
321 | SO_NOT_MAINLINE; |
322 | } |
323 | CONTRACTL_END; |
324 | |
325 | if (m_pDelayList != NULL) |
326 | { |
327 | CONTRACT_VIOLATION( ThrowsViolation ); |
328 | CallbackHelper(NULL, NULL); |
329 | |
330 | DeleteDelayedCallbacks(); |
331 | } |
332 | } |
333 | |
334 | DelayCallbackTable * ThreadLocalIBCInfo::GetPtrDelayList() |
335 | { |
336 | CONTRACTL |
337 | { |
338 | THROWS; |
339 | GC_NOTRIGGER; |
340 | MODE_ANY; |
341 | SO_NOT_MAINLINE; |
342 | } |
343 | CONTRACTL_END; |
344 | |
345 | if (m_pDelayList == NULL) |
346 | { |
347 | m_pDelayList = new DelayCallbackTable; |
348 | } |
349 | |
350 | return m_pDelayList; |
351 | } |
352 | |
353 | int ThreadLocalIBCInfo::ProcessDelayedCallbacks() |
354 | { |
355 | CONTRACTL |
356 | { |
357 | THROWS; |
358 | GC_NOTRIGGER; |
359 | MODE_ANY; |
360 | SO_NOT_MAINLINE; |
361 | } |
362 | CONTRACTL_END; |
363 | |
364 | int removedCount = 0; // Our return result |
365 | |
366 | _ASSERTE(m_pDelayList != NULL); |
367 | _ASSERTE(m_fProcessingDelayedList == false); |
368 | |
369 | m_fProcessingDelayedList = true; |
370 | |
371 | // Processing Delayed Callback list |
372 | DEBUG_PRINTF2("Processing Delayed Callback list: GetCount()=%d\n" , m_pDelayList->GetCount()); |
373 | |
374 | // try callbacks in the list |
375 | for (DelayCallbackTable::Iterator elem = m_pDelayList->Begin(), |
376 | end = m_pDelayList->End(); |
377 | (elem != end); elem++) |
378 | { |
379 | IbcCallback * pCallback = const_cast<IbcCallback *>(*elem); |
380 | |
381 | _ASSERTE(pCallback->IsValid()); |
382 | |
383 | // For each callback that we process we use the |
384 | // field m_fCallbackFailed to record wheather we |
385 | // failed or succeeded in resolving the callback |
386 | // |
387 | m_fCallbackFailed = false; |
388 | |
389 | pCallback->Invoke(); |
390 | |
391 | if (m_fCallbackFailed == false) |
392 | { |
393 | // Successfully proccessed a delayed callback |
394 | DEBUG_PRINTF5("Successfully processed a delayed callback: pfn=0x%08x, value1=0x%8p, value2=0x%8p, retries=%d\n" , |
395 | pCallback->GetPfn(), pCallback->GetValue1(), pCallback->GetValue2(), pCallback->GetTryCount()); |
396 | |
397 | m_pDelayList->Remove(pCallback); |
398 | pCallback->Invalidate(); |
399 | delete pCallback; |
400 | removedCount++; |
401 | } |
402 | else if (pCallback->IncrementTryCount() > c_maxRetries) |
403 | { |
404 | // Failed a delayed callback by hitting c_maxRetries |
405 | DEBUG_PRINTF4("Failed a delayed callback by hitting c_maxRetries: pfn=0x%08x, value1=0x%8p, value2=0x%8p\n" , |
406 | pCallback->GetPfn(), pCallback->GetValue1(), pCallback->GetValue2()); |
407 | |
408 | m_pDelayList->Remove(pCallback); |
409 | pCallback->Invalidate(); |
410 | delete pCallback; |
411 | removedCount++; |
412 | } |
413 | } |
414 | |
415 | // Done Processing Delayed Callback list |
416 | DEBUG_PRINTF3("Done Processing Delayed Callback list: removed %d items, %d remain\n" , |
417 | removedCount, m_pDelayList->GetCount()); |
418 | |
419 | _ASSERTE(m_fProcessingDelayedList == true); |
420 | m_fProcessingDelayedList = false; |
421 | |
422 | return removedCount; |
423 | } |
424 | |
425 | void ThreadLocalIBCInfo::CallbackHelper(const void * p, pfnIBCAccessCallback callback) |
426 | { |
427 | CONTRACTL |
428 | { |
429 | THROWS; |
430 | GC_NOTRIGGER; |
431 | MODE_ANY; |
432 | CAN_TAKE_LOCK; |
433 | SO_NOT_MAINLINE; |
434 | } |
435 | CONTRACTL_END; |
436 | |
437 | // Acquire the Crst lock before creating the IBCLoggingDisabler object. |
438 | // Only one thread at a time can be processing an IBC logging event. |
439 | CrstHolder lock(IBCLogger::GetSync()); |
440 | { |
441 | // @ToDo: methods called from here should assert that they have the lock that we just took |
442 | |
443 | IBCLoggingDisabler disableLogging( this ); // runs IBCLoggingDisabler::DisableLogging |
444 | |
445 | // Just in case the processing of delayed list was terminated with exception |
446 | m_fProcessingDelayedList = false; |
447 | |
448 | if (callback != NULL) |
449 | { |
450 | _ASSERTE(p != NULL); |
451 | |
452 | // For each callback that we process we use the |
453 | // field m_fCallbackFailed to record whether we |
454 | // failed or succeeded in resolving the callback |
455 | // |
456 | m_fCallbackFailed = false; |
457 | |
458 | callback(&g_IBCLogger, p, NULL); |
459 | |
460 | if (m_fCallbackFailed == false) |
461 | { |
462 | // If we were able to successfully process this ibc probe then |
463 | // the chances are good that the delayed probes will succeed too. |
464 | // Thus it may be worth proccessing the delayed call back list. |
465 | // We will process this list if it currently has at least |
466 | // MinCountToProcess items in the delay list. |
467 | // |
468 | int delayListAfter = (m_pDelayList == NULL) ? 0 : m_pDelayList->GetCount(); |
469 | if (delayListAfter >= GetMinCountToProcess()) |
470 | { |
471 | int numRemoved = ProcessDelayedCallbacks(); |
472 | if (numRemoved > 0) |
473 | { |
474 | // Reset the min count back down to the number that we still have remaining |
475 | m_iMinCountToProcess = m_pDelayList->GetCount(); |
476 | } |
477 | |
478 | // we increase the minCount by the min count increment so |
479 | // that we have to add a few new items to the delay list |
480 | // before we retry ProcessDelayedCallbacks() again. |
481 | IncMinCountToProcess(c_minCountIncr); |
482 | } |
483 | } |
484 | } |
485 | else // (callback == NULL) -- This is a special case |
486 | { |
487 | _ASSERTE(p == NULL); |
488 | |
489 | // We just need to call ProcessDelayedCallbacks() unconditionally |
490 | if (m_pDelayList->GetCount() > 0) |
491 | { |
492 | ProcessDelayedCallbacks(); |
493 | } |
494 | } |
495 | |
496 | // runs IBCLoggingDisabler::~IBCLoggingDisabler |
497 | // which runs IBCLoggingDisabler::EnableLogging |
498 | } |
499 | } |
500 | |
501 | |
502 | void IBCLogger::LogMethodAccessHelper(const MethodDesc* pMD, ULONG flagNum) |
503 | { |
504 | CONTRACTL |
505 | { |
506 | NOTHROW; |
507 | GC_NOTRIGGER; |
508 | MODE_ANY; |
509 | SO_NOT_MAINLINE; |
510 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
511 | } |
512 | CONTRACTL_END; |
513 | |
514 | { |
515 | // Don't set the ReadMethodCode flag for EE implemented methods such as Invoke |
516 | if ((flagNum == ReadMethodCode) && pMD->IsEEImpl()) |
517 | return; |
518 | |
519 | // we cannot log before the ObjectClass or StringClass are loaded |
520 | if (g_pObjectClass == NULL || g_pStringClass == NULL) |
521 | goto DelayCallback; |
522 | |
523 | RelativeFixupPointer<PTR_MethodTable> * ppMT = pMD->GetMethodTablePtr(); |
524 | if (ppMT->IsNull()) |
525 | goto DelayCallback; |
526 | |
527 | TADDR pMaybeTaggedMT = ppMT->GetValueMaybeTagged((TADDR)ppMT); |
528 | if (CORCOMPILE_IS_POINTER_TAGGED(pMaybeTaggedMT)) |
529 | goto DelayCallback; |
530 | |
531 | MethodTable *pMT = (MethodTable *)pMaybeTaggedMT; |
532 | if (!pMT->IsRestored_NoLogging()) |
533 | goto DelayCallback; |
534 | |
535 | LogMethodTableAccessHelper(pMT); |
536 | |
537 | Module *pModule = pMT->GetModule(); |
538 | |
539 | if (MethodDescAccessInstrEnabled()) |
540 | { |
541 | mdToken token; |
542 | if ( pMD->HasClassOrMethodInstantiation_NoLogging() ) |
543 | { |
544 | // We will need to defer the Logging if we cannot compute the PreferredZapModule |
545 | |
546 | // |
547 | // If we are creating a generic type or method we can have null TypeHandle args |
548 | // TFS: 749998 |
549 | // We can also have unrestored MethodTables in our Instantiation args during FixupNativeEntry |
550 | // |
551 | Instantiation classInst = pMD->GetClassInstantiation(); |
552 | Instantiation methodInst = pMD->GetMethodInstantiation(); |
553 | for (DWORD i = 0; i < classInst.GetNumArgs(); i++) |
554 | { |
555 | TypeHandle thArg = classInst[i]; |
556 | if (thArg.IsNull() || thArg.IsEncodedFixup() || !thArg.IsRestored_NoLogging()) |
557 | goto DelayCallback; |
558 | } |
559 | for (DWORD i = 0; i < methodInst.GetNumArgs(); i++) |
560 | { |
561 | TypeHandle thArg = methodInst[i]; |
562 | if (thArg.IsNull() || thArg.IsEncodedFixup() || !thArg.IsRestored_NoLogging()) |
563 | goto DelayCallback; |
564 | } |
565 | |
566 | Module *pPZModule = Module::GetPreferredZapModuleForMethodDesc(pMD); |
567 | token = pPZModule->LogInstantiatedMethod(pMD, flagNum); |
568 | if (!IsNilToken(token)) |
569 | { |
570 | pPZModule->LogTokenAccess(token, MethodProfilingData, flagNum); |
571 | } |
572 | } |
573 | else |
574 | { |
575 | token = pMD->GetMemberDef_NoLogging(); |
576 | pModule->LogTokenAccess(token, MethodProfilingData, flagNum); |
577 | } |
578 | } |
579 | return; |
580 | } |
581 | |
582 | DelayCallback: |
583 | DelayedCallbackPtr(LogMethodAccessWrapper, pMD, (void *)(SIZE_T)flagNum); |
584 | } |
585 | |
586 | void IBCLogger::LogMethodAccessWrapper(IBCLogger* pLogger, const void * pValue1, const void * pValue2) |
587 | { |
588 | WRAPPER_NO_CONTRACT; |
589 | pLogger->LogMethodAccessHelper((MethodDesc *)pValue1, (ULONG)(SIZE_T)pValue2); |
590 | } |
591 | |
592 | void IBCLogger::LogMethodDescAccessHelper(const MethodDesc *pMD) |
593 | { |
594 | WRAPPER_NO_CONTRACT; |
595 | SO_NOT_MAINLINE_FUNCTION; |
596 | |
597 | LogMethodAccessHelper(pMD, ReadMethodDesc); |
598 | } |
599 | |
600 | void IBCLogger::LogMethodDescWriteAccessHelper(MethodDesc *pMD) |
601 | { |
602 | WRAPPER_NO_CONTRACT; |
603 | SO_NOT_MAINLINE_FUNCTION; |
604 | |
605 | LogMethodAccessHelper(pMD, ReadMethodDesc); |
606 | LogMethodAccessHelper(pMD, WriteMethodDesc); |
607 | } |
608 | |
609 | void IBCLogger::LogMethodPrecodeAccessHelper(MethodDesc *pMD) |
610 | { |
611 | WRAPPER_NO_CONTRACT; |
612 | SO_NOT_MAINLINE_FUNCTION; |
613 | |
614 | LogMethodAccessHelper(pMD, ReadMethodPrecode); |
615 | } |
616 | |
617 | void IBCLogger::LogMethodPrecodeWriteAccessHelper(MethodDesc *pMD) |
618 | { |
619 | WRAPPER_NO_CONTRACT; |
620 | SO_NOT_MAINLINE_FUNCTION; |
621 | |
622 | LogMethodAccessHelper(pMD, ReadMethodPrecode); |
623 | LogMethodAccessHelper(pMD, WriteMethodPrecode); |
624 | } |
625 | |
626 | // Log access to method code or method header |
627 | void IBCLogger::LogMethodCodeAccessHelper(MethodDesc *pMD) |
628 | { |
629 | CONTRACTL |
630 | { |
631 | NOTHROW; |
632 | GC_NOTRIGGER; |
633 | MODE_ANY; |
634 | SO_NOT_MAINLINE; |
635 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
636 | } |
637 | CONTRACTL_END; |
638 | |
639 | LogMethodAccessHelper(pMD, ReadMethodCode); |
640 | } |
641 | |
642 | // Log access to the method code and method header for NDirect calls |
643 | void IBCLogger::LogNDirectCodeAccessHelper(MethodDesc *pMD) |
644 | { |
645 | CONTRACTL |
646 | { |
647 | NOTHROW; |
648 | GC_NOTRIGGER; |
649 | MODE_ANY; |
650 | SO_NOT_MAINLINE; |
651 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
652 | } |
653 | CONTRACTL_END; |
654 | |
655 | LogMethodAccessHelper(pMD, ReadMethodDesc); |
656 | LogMethodAccessHelper(pMD, ReadMethodCode); |
657 | } |
658 | |
659 | |
660 | // Log access to method gc info |
661 | void IBCLogger::LogMethodGCInfoAccessHelper(MethodDesc *pMD) |
662 | { |
663 | WRAPPER_NO_CONTRACT; |
664 | SO_NOT_MAINLINE_FUNCTION; |
665 | |
666 | _ASSERTE(InstrEnabled()); |
667 | |
668 | LogMethodAccessHelper(pMD, ReadGCInfo); |
669 | LogMethodAccessHelper(pMD, CommonReadGCInfo); |
670 | } |
671 | |
672 | // Log access to method table |
673 | void IBCLogger::LogMethodTableAccessHelper(MethodTable const * pMT) |
674 | { |
675 | WRAPPER_NO_CONTRACT; |
676 | SO_NOT_MAINLINE_FUNCTION; |
677 | |
678 | LogTypeAccessHelper(pMT, ReadMethodTable); |
679 | } |
680 | |
681 | // Log access to method table |
682 | void IBCLogger::LogTypeMethodTableAccessHelper(const TypeHandle *th) |
683 | { |
684 | WRAPPER_NO_CONTRACT; |
685 | SO_NOT_MAINLINE_FUNCTION; |
686 | |
687 | LogTypeAccessHelper(*th, ReadMethodTable); |
688 | } |
689 | |
690 | // Log write access to method table |
691 | void IBCLogger::LogTypeMethodTableWriteableAccessHelper(const TypeHandle *th) |
692 | { |
693 | WRAPPER_NO_CONTRACT; |
694 | SO_NOT_MAINLINE_FUNCTION; |
695 | |
696 | LogTypeAccessHelper(*th, ReadTypeDesc); |
697 | LogTypeAccessHelper(*th, WriteTypeDesc); |
698 | } |
699 | |
700 | // Log access via method table, to a token-based type or an instantiated type. |
701 | void IBCLogger::LogTypeAccessHelper(TypeHandle th, ULONG flagNum) |
702 | { |
703 | CONTRACTL |
704 | { |
705 | NOTHROW; |
706 | GC_NOTRIGGER; |
707 | MODE_ANY; |
708 | SO_NOT_MAINLINE; |
709 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
710 | } |
711 | CONTRACTL_END; |
712 | |
713 | CONTRACT_VIOLATION( ThrowsViolation ); |
714 | |
715 | idTypeSpec token = idTypeSpecNil; |
716 | Module* pPreferredZapModule = NULL; |
717 | |
718 | if (th.IsNull() || th.IsEncodedFixup()) |
719 | return; |
720 | |
721 | // we cannot do any logging before the ObjectClass and StringClass are loaded |
722 | if (g_pObjectClass == NULL || g_pStringClass == NULL) |
723 | goto DelayCallback; |
724 | |
725 | if (!th.IsRestored_NoLogging()) |
726 | goto DelayCallback; |
727 | |
728 | // |
729 | // We assign the pPreferredZapModule and the token, then fall out to the LogTokenAccess |
730 | // |
731 | // Logging accesses to TypeDescs is done by blob and we create a special IBC token for the blob |
732 | if (th.IsTypeDesc()) |
733 | { |
734 | pPreferredZapModule = Module::GetPreferredZapModuleForTypeHandle(th); |
735 | |
736 | token = pPreferredZapModule->LogInstantiatedType(th, flagNum); |
737 | } |
738 | else |
739 | { |
740 | MethodTable *pMT = th.AsMethodTable(); |
741 | |
742 | if (pMT->IsArray()) |
743 | { |
744 | pPreferredZapModule = Module::GetPreferredZapModuleForMethodTable(pMT); |
745 | |
746 | token = pPreferredZapModule->LogInstantiatedType(th, flagNum); |
747 | } |
748 | else |
749 | { |
750 | Module* pModule = pMT->GetModule(); |
751 | |
752 | // Instantiations of generic types (like other parameterized types like arrays) |
753 | // need to be handled specially. Generic instantiations do not have a ready-made token |
754 | // in the loader module and need special handling |
755 | // |
756 | if (pMT->HasInstantiation() && // Is this any of List<T>, List<Blah<T>>, or List<String>? |
757 | !pMT->IsGenericTypeDefinition() && // Ignore the type definition (List<T>) as it corresponds to the typeDef token |
758 | !pMT->ContainsGenericVariables()) // We more or less don't save these anyway, apart from the GenericTypeDefinition |
759 | { |
760 | Instantiation inst = pMT->GetInstantiation(); |
761 | |
762 | // This function can get called from BuildMethodTableThrowing(). The instantiation info is not yet set then |
763 | if (!inst.IsEmpty() && !inst[0].IsNull()) |
764 | { |
765 | pPreferredZapModule = Module::GetPreferredZapModuleForMethodTable(pMT); |
766 | |
767 | token = pPreferredZapModule->LogInstantiatedType(th, flagNum); |
768 | } |
769 | } |
770 | else |
771 | { |
772 | pPreferredZapModule = pModule; |
773 | token = pMT->GetCl_NoLogging(); |
774 | } |
775 | } |
776 | } |
777 | |
778 | if (!IsNilToken(token)) |
779 | pPreferredZapModule->LogTokenAccess(token, TypeProfilingData, flagNum); |
780 | |
781 | return; |
782 | |
783 | DelayCallback: |
784 | DelayedCallbackPtr(LogTypeAccessWrapper, th.AsPtr(), (void *)(SIZE_T)flagNum); |
785 | } |
786 | |
787 | void IBCLogger::LogTypeAccessWrapper(IBCLogger* pLogger, const void * pValue, const void * pValue2) |
788 | { |
789 | CONTRACTL |
790 | { |
791 | NOTHROW; |
792 | GC_NOTRIGGER; |
793 | SO_TOLERANT; |
794 | MODE_ANY; |
795 | } |
796 | CONTRACTL_END; |
797 | pLogger->LogTypeAccessHelper(TypeHandle::FromPtr((void *)pValue), (ULONG)(SIZE_T)pValue2); |
798 | } |
799 | |
800 | // Log access to method tables which are private (i.e. methodtables that are updated in the ngen image) |
801 | void IBCLogger::LogMethodTableWriteableDataAccessHelper(MethodTable const * pMT) |
802 | { |
803 | WRAPPER_NO_CONTRACT; |
804 | SO_NOT_MAINLINE_FUNCTION; |
805 | |
806 | LogTypeAccessHelper(pMT, ReadMethodTable); |
807 | LogTypeAccessHelper(pMT, ReadMethodTableWriteableData); |
808 | } |
809 | |
810 | // Log access to method tables which are private (i.e. methodtables that are updated in the ngen image) |
811 | void IBCLogger::LogMethodTableWriteableDataWriteAccessHelper(MethodTable *pMT) |
812 | { |
813 | WRAPPER_NO_CONTRACT; |
814 | SO_NOT_MAINLINE_FUNCTION; |
815 | |
816 | LogTypeAccessHelper(pMT, ReadMethodTable); |
817 | LogTypeAccessHelper(pMT, WriteMethodTableWriteableData); |
818 | } |
819 | |
820 | void IBCLogger::LogMethodTableNonVirtualSlotsAccessHelper(MethodTable const * pMT) |
821 | { |
822 | WRAPPER_NO_CONTRACT; |
823 | SO_NOT_MAINLINE_FUNCTION; |
824 | |
825 | LogTypeAccessHelper(pMT, ReadMethodTable); |
826 | LogTypeAccessHelper(pMT, ReadNonVirtualSlots); |
827 | } |
828 | |
829 | // Log access to EEClass |
830 | void IBCLogger::LogEEClassAndMethodTableAccessHelper(MethodTable * pMT) |
831 | { |
832 | WRAPPER_NO_CONTRACT; |
833 | SO_NOT_MAINLINE_FUNCTION; |
834 | |
835 | if (pMT == NULL) |
836 | return; |
837 | |
838 | LogTypeAccessHelper(pMT, ReadMethodTable); |
839 | |
840 | if (!pMT->IsCanonicalMethodTable()) { |
841 | pMT = pMT->GetCanonicalMethodTable(); |
842 | LogTypeAccessHelper(pMT, ReadMethodTable); |
843 | } |
844 | |
845 | LogTypeAccessHelper(pMT, ReadEEClass); |
846 | } |
847 | |
848 | // Log write to EEClass |
849 | void IBCLogger::LogEEClassCOWTableAccessHelper(MethodTable * pMT) |
850 | { |
851 | WRAPPER_NO_CONTRACT; |
852 | SO_NOT_MAINLINE_FUNCTION; |
853 | |
854 | if (pMT == NULL) |
855 | return; |
856 | |
857 | LogTypeAccessHelper(pMT, ReadMethodTable); |
858 | |
859 | if (!pMT->IsCanonicalMethodTable()) { |
860 | pMT = pMT->GetCanonicalMethodTable(); |
861 | LogTypeAccessHelper(pMT, ReadMethodTable); |
862 | } |
863 | |
864 | LogTypeAccessHelper(pMT, ReadEEClass); |
865 | LogTypeAccessHelper(pMT, WriteEEClass); |
866 | } |
867 | |
868 | // Log access to FieldDescs list in EEClass |
869 | void IBCLogger::LogFieldDescsAccessHelper(FieldDesc * pFD) |
870 | { |
871 | WRAPPER_NO_CONTRACT; |
872 | SO_NOT_MAINLINE_FUNCTION; |
873 | |
874 | MethodTable * pMT = pFD->GetApproxEnclosingMethodTable_NoLogging(); |
875 | |
876 | LogTypeAccessHelper(pMT, ReadMethodTable); |
877 | |
878 | if (!pMT->IsCanonicalMethodTable()) { |
879 | pMT = pMT->GetCanonicalMethodTable(); |
880 | LogTypeAccessHelper(pMT, ReadMethodTable); |
881 | } |
882 | |
883 | LogTypeAccessHelper(pMT, ReadFieldDescs); |
884 | } |
885 | |
886 | void IBCLogger::LogDispatchMapAccessHelper(MethodTable *pMT) |
887 | { |
888 | WRAPPER_NO_CONTRACT; |
889 | SO_NOT_MAINLINE_FUNCTION; |
890 | |
891 | LogTypeAccessHelper(pMT, ReadMethodTable); |
892 | LogTypeAccessHelper(pMT, ReadDispatchMap); |
893 | } |
894 | |
895 | void IBCLogger::LogDispatchTableAccessHelper(MethodTable *pMT) |
896 | { |
897 | WRAPPER_NO_CONTRACT; |
898 | SO_NOT_MAINLINE_FUNCTION; |
899 | |
900 | LogTypeAccessHelper(pMT, ReadMethodTable); |
901 | LogTypeAccessHelper(pMT, ReadDispatchMap); |
902 | LogTypeAccessHelper(pMT, ReadDispatchTable); |
903 | } |
904 | |
905 | void IBCLogger::LogDispatchTableSlotAccessHelper(DispatchSlot *pDS) |
906 | { |
907 | WRAPPER_NO_CONTRACT; |
908 | SO_NOT_MAINLINE_FUNCTION; |
909 | |
910 | if (pDS->IsNull()) |
911 | return; |
912 | |
913 | MethodDesc *pMD = MethodTable::GetMethodDescForSlotAddress(pDS->GetTarget()); |
914 | MethodTable *pMT = pMD->GetMethodTable_NoLogging(); |
915 | LogDispatchTableAccessHelper(pMT); |
916 | } |
917 | |
918 | // Log write to EEClass |
919 | void IBCLogger::LogFieldMarshalersReadAccessHelper(MethodTable * pMT) |
920 | { |
921 | WRAPPER_NO_CONTRACT; |
922 | SO_NOT_MAINLINE_FUNCTION; |
923 | |
924 | if (pMT == NULL) |
925 | return; |
926 | |
927 | LogTypeAccessHelper(pMT, ReadMethodTable); |
928 | |
929 | if (!pMT->IsCanonicalMethodTable()) { |
930 | pMT = pMT->GetCanonicalMethodTable(); |
931 | LogTypeAccessHelper(pMT, ReadMethodTable); |
932 | } |
933 | |
934 | LogTypeAccessHelper(pMT, ReadEEClass); |
935 | LogTypeAccessHelper(pMT, ReadFieldMarshalers); |
936 | } |
937 | |
938 | // Log access to cctor info table |
939 | void IBCLogger::LogCCtorInfoReadAccessHelper(MethodTable *pMT) |
940 | { |
941 | WRAPPER_NO_CONTRACT; |
942 | SO_NOT_MAINLINE_FUNCTION; |
943 | LogTypeAccessHelper(pMT, ReadCCtorInfo); |
944 | } |
945 | |
946 | |
947 | void IBCLogger::LogTypeHashTableAccessHelper(const TypeHandle *th) |
948 | { |
949 | WRAPPER_NO_CONTRACT; |
950 | SO_NOT_MAINLINE_FUNCTION; |
951 | |
952 | LogTypeAccessHelper(*th, ReadTypeHashTable); |
953 | } |
954 | |
955 | // Log access to class hash table |
956 | void IBCLogger::LogClassHashTableAccessHelper(EEClassHashEntry *pEntry) |
957 | { |
958 | CONTRACTL |
959 | { |
960 | NOTHROW; |
961 | GC_NOTRIGGER; |
962 | MODE_ANY; |
963 | SO_NOT_MAINLINE; |
964 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
965 | } |
966 | CONTRACTL_END; |
967 | |
968 | // ExecutionManager::FindZapModule may enter the host (if we were hosted), but it's |
969 | // ok since we're just logging IBC data. |
970 | CONTRACT_VIOLATION( HostViolation ); |
971 | |
972 | Module *pModule = ExecutionManager::FindZapModule(dac_cast<TADDR>(pEntry)); |
973 | if (pModule == NULL) |
974 | { |
975 | // if FindZapModule returns NULL, it always will return NULL |
976 | // so there is no point in adding a DelayedCallback here. |
977 | return; |
978 | } |
979 | |
980 | // we cannot log before the ObjectClass or StringClass are loaded |
981 | if (g_pObjectClass == NULL || g_pStringClass == NULL) |
982 | goto DelayCallback; |
983 | |
984 | HashDatum datum; |
985 | datum = pEntry->GetData(); |
986 | mdToken token; |
987 | if ((((ULONG_PTR) datum) & EECLASSHASH_TYPEHANDLE_DISCR) == 0) |
988 | { |
989 | TypeHandle t = TypeHandle::FromPtr(datum); |
990 | _ASSERTE(!t.IsNull()); |
991 | MethodTable *pMT = t.GetMethodTable(); |
992 | if (pMT == NULL) |
993 | goto DelayCallback; |
994 | |
995 | token = pMT->GetCl_NoLogging(); |
996 | } |
997 | else if (((ULONG_PTR)datum & EECLASSHASH_MDEXPORT_DISCR) == 0) |
998 | { |
999 | DWORD dwDatum = (DWORD)(DWORD_PTR)(datum); // <TODO> WIN64 - Pointer Truncation</TODO> |
1000 | token = ((dwDatum >> 1) & 0x00ffffff) | mdtTypeDef; |
1001 | } |
1002 | else |
1003 | return; |
1004 | |
1005 | pModule->LogTokenAccess(token, TypeProfilingData, ReadClassHashTable); |
1006 | return; |
1007 | |
1008 | DelayCallback: |
1009 | DelayedCallbackPtr(LogClassHashTableAccessWrapper, pEntry); |
1010 | } |
1011 | |
1012 | // Log access to meta data |
1013 | void IBCLogger::LogMetaDataAccessHelper(const void * addr) |
1014 | { |
1015 | CONTRACTL |
1016 | { |
1017 | NOTHROW; |
1018 | GC_NOTRIGGER; |
1019 | MODE_ANY; |
1020 | SO_NOT_MAINLINE; |
1021 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
1022 | } |
1023 | CONTRACTL_END; |
1024 | |
1025 | // ExecutionManager::FindZapModule may enter the host (if we were hosted), but it's |
1026 | // ok since we're just logging IBC data. |
1027 | CONTRACT_VIOLATION( HostViolation ); |
1028 | |
1029 | #if METADATATRACKER_ENABLED |
1030 | if (Module *pModule = ExecutionManager::FindZapModule(dac_cast<TADDR>(addr))) |
1031 | { |
1032 | mdToken token = MetaDataTracker::MapAddrToToken(addr); |
1033 | |
1034 | pModule->LogTokenAccess(token, ProfilingFlags_MetaData); |
1035 | pModule->LogTokenAccess(token, CommonMetaData); |
1036 | return; |
1037 | } |
1038 | #endif //METADATATRACKER_ENABLED |
1039 | |
1040 | // if FindZapModule returns NULL, it always will return NULL |
1041 | // so there is no point in adding a DelayedCallback here. |
1042 | } |
1043 | |
1044 | // Log a search to meta data |
1045 | // See the comment above CMiniMdRW::GetHotMetadataTokensSearchAware |
1046 | void IBCLogger::LogMetaDataSearchAccessHelper(const void * result) |
1047 | { |
1048 | CONTRACTL |
1049 | { |
1050 | NOTHROW; |
1051 | GC_NOTRIGGER; |
1052 | MODE_ANY; |
1053 | SO_NOT_MAINLINE; |
1054 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
1055 | } |
1056 | CONTRACTL_END; |
1057 | |
1058 | // ExecutionManager::FindZapModule may enter the host (if we were hosted), but it's |
1059 | // ok since we're just logging IBC data. |
1060 | CONTRACT_VIOLATION( HostViolation ); |
1061 | |
1062 | #if METADATATRACKER_ENABLED |
1063 | if (Module *pModule = ExecutionManager::FindZapModule(dac_cast<TADDR>(result))) |
1064 | { |
1065 | mdToken token = MetaDataTracker::MapAddrToToken(result); |
1066 | |
1067 | pModule->LogTokenAccess(token, ProfilingFlags_MetaData); |
1068 | pModule->LogTokenAccess(token, CommonMetaData); |
1069 | pModule->LogTokenAccess(token, ProfilingFlags_MetaDataSearch); |
1070 | return; |
1071 | } |
1072 | #endif //METADATATRACKER_ENABLED |
1073 | |
1074 | // if FindZapModule returns NULL, it always will return NULL |
1075 | // so there is no point in adding a DelayedCallback here. |
1076 | } |
1077 | |
1078 | // Log access to method list associated with a CER |
1079 | void IBCLogger::LogCerMethodListReadAccessHelper(MethodDesc *pMD) |
1080 | { |
1081 | CONTRACTL |
1082 | { |
1083 | NOTHROW; |
1084 | GC_NOTRIGGER; |
1085 | MODE_ANY; |
1086 | SO_NOT_MAINLINE; |
1087 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
1088 | } |
1089 | CONTRACTL_END; |
1090 | |
1091 | LogMethodAccessHelper(pMD, ReadCerMethodList); |
1092 | } |
1093 | |
1094 | void IBCLogger::LogRidMapAccessHelper( RidMapLogData data ) |
1095 | { |
1096 | WRAPPER_NO_CONTRACT; |
1097 | SO_NOT_MAINLINE_FUNCTION; |
1098 | |
1099 | data.First()->LogTokenAccess( data.Second(), RidMap ); |
1100 | } |
1101 | |
1102 | // Log access to RVA data |
1103 | void IBCLogger::LogRVADataAccessHelper(FieldDesc *pFD) |
1104 | { |
1105 | CONTRACTL |
1106 | { |
1107 | NOTHROW; |
1108 | GC_NOTRIGGER; |
1109 | MODE_ANY; |
1110 | SO_NOT_MAINLINE; |
1111 | PRECONDITION(g_IBCLogger.InstrEnabled()); |
1112 | } |
1113 | CONTRACTL_END; |
1114 | |
1115 | // we cannot log before the ObjectClass or StringClass are loaded |
1116 | if (g_pObjectClass == NULL || g_pStringClass == NULL) |
1117 | goto DelayCallback; |
1118 | |
1119 | if (CORCOMPILE_IS_POINTER_TAGGED(SIZE_T(pFD))) |
1120 | return; |
1121 | |
1122 | MethodTable * pMT; |
1123 | pMT = pFD->GetApproxEnclosingMethodTable(); |
1124 | |
1125 | if (!pMT->IsRestored_NoLogging()) |
1126 | goto DelayCallback; |
1127 | |
1128 | if (pMT->HasInstantiation()) |
1129 | return; |
1130 | |
1131 | pMT->GetModule()->LogTokenAccess(pFD->GetMemberDef(), TypeProfilingData, RVAFieldData); |
1132 | return; |
1133 | |
1134 | DelayCallback: |
1135 | DelayedCallbackPtr(LogRVADataAccessWrapper, pFD); |
1136 | } |
1137 | |
1138 | |
1139 | #define LOADORDER_INSTR 0x00000001 |
1140 | #define RID_ACCESSORDER_INSTR 0x00000002 |
1141 | #define METHODDESC_ACCESS_INSTR 0x00000004 |
1142 | #define ALL_INSTR (LOADORDER_INSTR | RID_ACCESSORDER_INSTR | METHODDESC_ACCESS_INSTR) |
1143 | |
1144 | void IBCLogger::EnableAllInstr() |
1145 | { |
1146 | LIMITED_METHOD_CONTRACT; |
1147 | #if METADATATRACKER_ENABLED |
1148 | MetaDataTracker::Enable(); |
1149 | MetaDataTracker::s_IBCLogMetaDataAccess = IBCLogger::LogMetaDataAccessStatic; |
1150 | MetaDataTracker::s_IBCLogMetaDataSearch = IBCLogger::LogMetaDataSearchAccessStatic; |
1151 | #endif //METADATATRACKER_ENABLED |
1152 | dwInstrEnabled = ALL_INSTR; |
1153 | } |
1154 | |
1155 | void IBCLogger::DisableAllInstr() |
1156 | { |
1157 | LIMITED_METHOD_CONTRACT; |
1158 | dwInstrEnabled = 0; |
1159 | } |
1160 | |
1161 | void IBCLogger::DisableRidAccessOrderInstr() |
1162 | { |
1163 | LIMITED_METHOD_CONTRACT; |
1164 | dwInstrEnabled &= (~RID_ACCESSORDER_INSTR); |
1165 | } |
1166 | |
1167 | void IBCLogger::DisableMethodDescAccessInstr() |
1168 | { |
1169 | LIMITED_METHOD_CONTRACT; |
1170 | dwInstrEnabled &= (~METHODDESC_ACCESS_INSTR); |
1171 | } |
1172 | |
1173 | BOOL IBCLogger::MethodDescAccessInstrEnabled() |
1174 | { |
1175 | LIMITED_METHOD_CONTRACT; |
1176 | return (dwInstrEnabled & METHODDESC_ACCESS_INSTR); |
1177 | } |
1178 | |
1179 | BOOL IBCLogger::RidAccessInstrEnabled() |
1180 | { |
1181 | LIMITED_METHOD_CONTRACT; |
1182 | return (dwInstrEnabled & RID_ACCESSORDER_INSTR); |
1183 | } |
1184 | |
1185 | #endif // IBCLOGGER_ENABLED |
1186 | |