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// FILE: ProfilingEnumerators.cpp
6//
7// All enumerators returned by the profiling API to enumerate objects or to catch up on
8// the current CLR state (usually for attaching profilers) are defined in
9// ProfilingEnumerators.h,cpp.
10//
11// This cpp file contains implementations specific to the derived enumerator classes, as
12// well as helpers for iterating over AppDomains, assemblies, modules, etc., that have
13// been loaded enough that they may be made visible to profilers.
14//
15
16//
17
18#include "common.h"
19
20#ifdef PROFILING_SUPPORTED
21
22#include "proftoeeinterfaceimpl.h"
23#include "profilingenumerators.h"
24
25// ---------------------------------------------------------------------------------------
26// ProfilerFunctionEnum/ICorProfilerFunctionEnum implementation
27// ---------------------------------------------------------------------------------------
28
29BOOL ProfilerFunctionEnum::Init(BOOL fWithReJITIDs)
30{
31 CONTRACTL
32 {
33 // Yay!
34 NOTHROW;
35
36 // Yay!
37 // If we needs to get rejit ID, which requires a lock (which, in turn may switch us to
38 // preemptive mode).
39 if (fWithReJITIDs) GC_TRIGGERS; else GC_NOTRIGGER;
40
41 // Yay!
42 MODE_ANY;
43
44 // Depending on our GC mode, the jit manager may have to take a
45 // reader lock to prevent things from changing while reading...
46 CAN_TAKE_LOCK;
47
48 SO_NOT_MAINLINE;
49 } CONTRACTL_END;
50
51 EEJitManager::CodeHeapIterator heapIterator;
52 while(heapIterator.Next())
53 {
54 MethodDesc *pMD = heapIterator.GetMethod();
55
56 // On AMD64 JumpStub is used to call functions that is 2GB away. JumpStubs have a CodeHeader
57 // with NULL MethodDesc, are stored in code heap and are reported by EEJitManager::EnumCode.
58 if (pMD == NULL)
59 continue;
60
61 // There are two possible reasons to skip this MD.
62 //
63 // 1) If it has no metadata (i.e., LCG / IL stubs), then skip it
64 //
65 // 2) If it has no code compiled yet for it, then skip it.
66 //
67 if (pMD->IsNoMetadata() || !pMD->HasNativeCode())
68 {
69 continue;
70 }
71
72 COR_PRF_FUNCTION * element = m_elements.Append();
73 if (element == NULL)
74 {
75 return FALSE;
76 }
77 element->functionId = (FunctionID) pMD;
78
79 if (fWithReJITIDs)
80 {
81 // This guy causes triggering and locking, while the non-rejitid case does not.
82 element->reJitId = ReJitManager::GetReJitId(pMD, heapIterator.GetMethodCode());
83 }
84 else
85 {
86 element->reJitId = 0;
87 }
88 }
89
90 return TRUE;
91}
92
93// ---------------------------------------------------------------------------------------
94// Catch-up helpers
95//
96// #ProfilerEnumGeneral
97//
98// The following functions factor out the iteration code to ensure we only consider
99// AppDomains, assemblies, modules, etc., that the profiler can safely query about. The
100// parameters to these functions are of types that may have confusing syntax, but all
101// that's going on is that the caller may supply an object instance and a member function
102// on that object (non-static) to be called for each iterated item. This is just a
103// statically-typed way of doing the usual pattern of providing a function pointer for
104// the callback plus a void * context object to pass to the function. If the
105// caller-supplied callback returns anything other than S_OK, the iteration code will
106// stop iterating, and immediately propagate the callback's return value to the original
107// caller. Start looking at code:ProfilerModuleEnum::Init for an example of how these
108// helpers get used.
109//
110// The reason we have helpers to begin with is so we can centralize the logic that
111// enforces the following rather subtle invariants:
112//
113// * Provide enough entities that the profiler gets a complete set of entities from
114// the union of catch-up enumeration and "callbacks" (e.g., ModuleLoadFinished).
115// * Exclude entities that have unloaded to the point where it's no longer safe to
116// query information about them.
117//
118// The catch-up spec summarizes this via the following timeline for any given entity:
119//
120// Entity available in catch-up enumeration
121// < Entity's LoadFinished (or equivalent) callback is issued
122// < Entity NOT available from catch-up enumeration
123// < Entity's UnloadStarted (or equivalent) callback is issued
124//
125// These helpers avoid duplicate code in the ProfilerModuleEnum implementation, and will
126// also help avoid future duplicate code should we decide to provide more catch-up
127// enumerations for attaching profilers to find currently loaded AppDomains, Classes,
128// etc.
129//
130// Note: The debugging API has similar requirements around which entities at which stage
131// of loading are permitted to be enumerated over. See code:IDacDbiInterface#Enumeration
132// for debugger details. Note that profapi's needs are not exactly the same. For example,
133// Assemblies appear in the debugging API enumerations as soon as they begin to load,
134// whereas Assemblies (like all other entities) appear in the profiling API enumerations
135// once their load is complete (i.e., just before AssemblyLoadFinished). Also,
136// debuggers enumerate DomainModules and DomainAssemblies, whereas profilers enumerate
137// Modules and Assemblies.
138//
139// For information about other synchronization issues with profiler catch-up, see
140// code:ProfilingAPIUtility::LoadProfiler#ProfCatchUpSynchronization
141//
142// ---------------------------------------------------------------------------------------
143
144
145//---------------------------------------------------------------------------------------
146//
147// Iterates through exactly those AppDomains that should be visible to the profiler, and
148// calls a caller-supplied function to operate on each iterated AppDomain
149//
150// Arguments:
151// * callbackObj - Caller-supplied object containing the callback method to call for
152// each AppDomain
153// * callbackMethod - Caller-supplied method to call for each AppDomain. If this
154// method returns anything other than S_OK, then the iteration is aborted, and
155// callbackMethod's return value is returned to our caller.
156//
157
158template<typename CallbackObject>
159HRESULT IterateAppDomains(CallbackObject * callbackObj,
160 HRESULT (CallbackObject:: * callbackMethod)(AppDomain *))
161{
162 CONTRACTL
163 {
164 NOTHROW;
165 GC_TRIGGERS;
166 MODE_ANY;
167 CAN_TAKE_LOCK;
168 // (See comments in code:ProfToEEInterfaceImpl::EnumModules for info about contracts.)
169
170 SO_NOT_MAINLINE;
171 }
172 CONTRACTL_END;
173
174 // #ProfilerEnumAppDomains (See also code:#ProfilerEnumGeneral)
175 //
176 // When enumerating AppDomains, ensure this timeline:
177 // AD available in catch-up enumeration
178 // < AppDomainCreationFinished issued
179 // < AD NOT available from catch-up enumeration
180 //
181 // The AppDomainIterator constructor parameter m_bActive is set to be TRUE below,
182 // meaning only AppDomains in stage STAGE_ACTIVE or higher will be included
183 // in the iteration.
184 // * AppDomainCreationFinished (with S_OK hrStatus) is issued once the AppDomain
185 // reaches STAGE_ACTIVE.
186 AppDomainIterator appDomainIterator(TRUE);
187 while (appDomainIterator.Next())
188 {
189 AppDomain * pAppDomain = appDomainIterator.GetDomain();
190
191 // Of course, the AD could start unloading here, but if it does we're guaranteed
192 // the profiler has had a chance to see the Unload callback for the AD, and thus
193 // the profiler can block in that callback until it's done with the enumerator
194 // we provide.
195
196 // Call user-supplied callback, and cancel iteration if requested
197 HRESULT hr = (callbackObj->*callbackMethod)(pAppDomain);
198 if (hr != S_OK)
199 {
200 return hr;
201 }
202 }
203
204 return S_OK;
205}
206
207
208//---------------------------------------------------------------------------------------
209//
210// Iterates through exactly those Modules that should be visible to the profiler, and
211// calls a caller-supplied function to operate on each iterated Module. Any module that
212// is loaded domain-neutral is skipped.
213//
214// Arguments:
215// * pAppDomain - Only unshared modules loaded into this AppDomain will be iterated
216// * callbackObj - Caller-supplied object containing the callback method to call for
217// each Module
218// * callbackMethod - Caller-supplied method to call for each Module. If this
219// method returns anything other than S_OK, then the iteration is aborted, and
220// callbackMethod's return value is returned to our caller.
221//
222// Notes:
223// * In theory, this could be broken down into an unshared assembly iterator that
224// takes a callback, and an unshared module iterator (based on an input
225// assembly) that takes a callback. But that kind of granularity is unnecessary
226// now, and probably not useful in the future. If that turns out to be wrong,
227// this can still be broken down that way later on.
228//
229
230template<typename CallbackObject>
231HRESULT IterateUnsharedModules(AppDomain * pAppDomain,
232 CallbackObject * callbackObj,
233 HRESULT (CallbackObject:: * callbackMethod)(Module *))
234{
235 CONTRACTL
236 {
237 NOTHROW;
238 GC_TRIGGERS;
239 MODE_ANY;
240 CAN_TAKE_LOCK;
241 }
242 CONTRACTL_END;
243
244 // #ProfilerEnumAssemblies (See also code:#ProfilerEnumGeneral)
245 //
246 // When enumerating assemblies, ensure this timeline:
247 // Assembly available in catch-up enumeration
248 // < AssemblyLoadFinished issued
249 // < Assembly NOT available from catch-up enumeration
250 // < AssemblyUnloadStarted issued
251 //
252 // The IterateAssembliesEx parameter below ensures we will only include assemblies at
253 // load level >= FILE_LOAD_LOADLIBRARY.
254 // * AssemblyLoadFinished is issued once the Assembly reaches
255 // code:FILE_LOAD_LOADLIBRARY
256 // * AssemblyUnloadStarted is issued as a result of either:
257 // * Collectible assemblies unloading. Such assemblies will no longer be
258 // enumerable.
259 //
260 // Note: To determine what happens in a given load stage of a module or assembly,
261 // look at the switch statement in code:DomainFile::DoIncrementalLoad, and keep in
262 // mind that it takes cases on the *next* load stage; in other words, the actions
263 // that appear in a case for a given load stage are actually executed as we attempt
264 // to transition TO that load stage, and thus they actually execute while the module
265 // / assembly is still in the previous load stage.
266 //
267 // Note that the CLR may issue ModuleLoadFinished / AssemblyLoadFinished later, at
268 // FILE_LOAD_EAGER_FIXUPS stage, if for some reason MLF/ALF hadn't been issued
269 // earlier during FILE_LOAD_LOADLIBRARY. This does not affect the timeline, as either
270 // way the profiler receives the notification AFTER the assembly would appear in the
271 // enumeration.
272 //
273 // Although it's called an "AssemblyIterator", it actually iterates over
274 // DomainAssembly instances.
275 AppDomain::AssemblyIterator domainAssemblyIterator =
276 pAppDomain->IterateAssembliesEx(
277 (AssemblyIterationFlags) (kIncludeAvailableToProfilers | kIncludeExecution));
278 CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
279
280 while (domainAssemblyIterator.Next(pDomainAssembly.This()))
281 {
282 _ASSERTE(pDomainAssembly != NULL);
283 _ASSERTE(pDomainAssembly->GetAssembly() != NULL);
284
285 // #ProfilerEnumModules (See also code:#ProfilerEnumGeneral)
286 //
287 // When enumerating modules, ensure this timeline:
288 // Module available in catch-up enumeration
289 // < ModuleLoadFinished issued
290 // < Module NOT available from catch-up enumeration
291 // < ModuleUnloadStarted issued
292 //
293 // The IterateModules parameter below ensures only modules at level >=
294 // code:FILE_LOAD_LOADLIBRARY will be included in the iteration.
295 //
296 // Details for module callbacks are the same as those for assemblies, so see
297 // code:#ProfilerEnumAssemblies for info on how the timing works.
298 DomainModuleIterator domainModuleIterator =
299 pDomainAssembly->IterateModules(kModIterIncludeAvailableToProfilers);
300 while (domainModuleIterator.Next())
301 {
302 // Call user-supplied callback, and cancel iteration if requested
303 HRESULT hr = (callbackObj->*callbackMethod)(domainModuleIterator.GetModule());
304 if (hr != S_OK)
305 {
306 return hr;
307 }
308 }
309 }
310
311 return S_OK;
312}
313
314//---------------------------------------------------------------------------------------
315// ProfilerModuleEnum implementation
316//---------------------------------------------------------------------------------------
317
318
319//---------------------------------------------------------------------------------------
320// This is a helper class used by ProfilerModuleEnum when determining which shared
321// modules should be added to the enumerator. See code:ProfilerModuleEnum::Init for how
322// this gets used
323
324class IterateAppDomainsForSharedModule
325{
326public:
327 IterateAppDomainsForSharedModule(CDynArray< ModuleID > * pElements, Module * pModule)
328 : m_pElements(pElements), m_pModule(pModule)
329 {
330 LIMITED_METHOD_CONTRACT;
331 }
332
333 //---------------------------------------------------------------------------------------
334 // Callback passed to IterateAppDomains, that takes the currently iterated AppDomain,
335 // and adds m_pModule to the enumerator if it's loaded into the AppDomain. See
336 // code:ProfilerModuleEnum::Init for how this gets used.
337 //
338 // Arguments:
339 // * pAppDomain - Current AppDomain being iterated.
340 //
341 // Return Value:
342 // * S_OK = the iterator should continue after we return.
343 // * S_FALSE = we verified m_pModule is loaded into this AppDomain, so no need
344 // for the iterator to continue with the next AppDomain
345 // * error indicating a failure
346 //
347 HRESULT AddSharedModuleForAppDomain(AppDomain * pAppDomain)
348 {
349 CONTRACTL
350 {
351 NOTHROW;
352 GC_NOTRIGGER;
353 MODE_ANY;
354 CANNOT_TAKE_LOCK;
355 }
356 CONTRACTL_END;
357
358 DomainFile * pDomainFile = m_pModule->FindDomainFile(pAppDomain);
359 if ((pDomainFile == NULL) || !pDomainFile->IsAvailableToProfilers())
360 {
361 // This AD doesn't contain a fully loaded DomainFile for m_pModule. So continue
362 // iterating with the next AD
363 return S_OK;
364 }
365
366 ModuleID * pElement = m_pElements->Append();
367 if (pElement == NULL)
368 {
369 // Stop iteration with error
370 return E_OUTOFMEMORY;
371 }
372
373 // If we're here, we found a fully loaded DomainFile for m_pModule. So add
374 // m_pModule to our array, and no need to look at other other ADs for this
375 // m_pModule.
376 *pElement = (ModuleID) m_pModule;
377 return S_FALSE;
378 }
379
380private:
381 // List of ModuleIDs in the enumerator we're building
382 CDynArray< ModuleID > * m_pElements;
383
384 // Shared Module we're testing for load status in the iterated ADs.
385 Module * m_pModule;
386};
387
388
389//---------------------------------------------------------------------------------------
390//
391// Callback passed to IterateAppDomains, that takes the currently iterated AppDomain,
392// and then iterates through the unshared modules loaded into that AD. See
393// code:ProfilerModuleEnum::Init for how this gets used.
394//
395// Arguments:
396// * pAppDomain - Current AppDomain being iterated.
397//
398// Return Value:
399// * S_OK = the iterator should continue after we return.
400// * S_FALSE = we verified m_pModule is loaded into this AppDomain, so no need
401// for the iterator to continue with the next AppDomain
402// * error indicating a failure
403//
404
405HRESULT ProfilerModuleEnum::AddUnsharedModulesFromAppDomain(AppDomain * pAppDomain)
406{
407 CONTRACTL
408 {
409 NOTHROW;
410 GC_TRIGGERS;
411 MODE_ANY;
412 CAN_TAKE_LOCK;
413 }
414 CONTRACTL_END;
415
416 return IterateUnsharedModules<ProfilerModuleEnum>(
417 pAppDomain,
418 this,
419 &ProfilerModuleEnum::AddUnsharedModule);
420}
421
422
423//---------------------------------------------------------------------------------------
424//
425// Callback passed to IterateUnsharedModules, that takes the currently iterated unshared
426// Module, and adds it to the enumerator. See code:ProfilerModuleEnum::Init for how this
427// gets used.
428//
429// Arguments:
430// * pModule - Current Module being iterated.
431//
432// Return Value:
433// * S_OK = the iterator should continue after we return.
434// * error indicating a failure
435//
436HRESULT ProfilerModuleEnum::AddUnsharedModule(Module * pModule)
437{
438 CONTRACTL
439 {
440 NOTHROW;
441 GC_NOTRIGGER;
442 MODE_ANY;
443 CANNOT_TAKE_LOCK;
444 }
445 CONTRACTL_END;
446
447 ModuleID * pElement = m_elements.Append();
448 if (pElement == NULL)
449 {
450 return E_OUTOFMEMORY;
451 }
452 *pElement = (ModuleID) pModule;
453 return S_OK;
454}
455
456
457//---------------------------------------------------------------------------------------
458//
459// Populate the module enumerator that's about to be given to the profiler. This is
460// called from the ICorProfilerInfo3::EnumModules implementation.
461//
462// This code controls how the above iterator helpers and callbacks are used, so you might
463// want to look here first to understand how how the helpers and callbacks are used.
464//
465// Return Value:
466// HRESULT indicating success or failure.
467//
468HRESULT ProfilerModuleEnum::Init()
469{
470 CONTRACTL
471 {
472 NOTHROW;
473 GC_TRIGGERS;
474 MODE_ANY;
475 CAN_TAKE_LOCK;
476 // (See comments in code:ProfToEEInterfaceImpl::EnumModules for info about contracts.)
477
478 SO_NOT_MAINLINE;
479 }
480 CONTRACTL_END;
481
482 HRESULT hr = S_OK;
483
484 // When an assembly or module is loaded into an AppDomain, a separate DomainFile is
485 // created (one per pairing of the AppDomain with the module or assembly). This means
486 // that we'll create multiple DomainFiles for the same module if it is loaded
487 // domain-neutral (i.e., "shared"). The profiling API callbacks shield the profiler
488 // from this, and only report a given module the first time it's loaded. So a
489 // profiler sees only one ModuleLoadFinished for a module loaded domain-neutral, even
490 // though the module may be used by multiple AppDomains. The module enumerator must
491 // mirror the behavior of the profiling API callbacks, by avoiding duplicate Modules
492 // in the module list we return to the profiler. So first add unshared modules (non
493 // domain-neutral) to the enumerator, and then separately add any shared modules that
494 // were loaded into at least one AD.
495
496 // First, iterate through all ADs. For each one, call
497 // AddUnsharedModulesFromAppDomain, which iterates through all UNSHARED modules and
498 // adds them to the enumerator.
499 hr = IterateAppDomains<ProfilerModuleEnum>(
500 this,
501 &ProfilerModuleEnum::AddUnsharedModulesFromAppDomain);
502 if (FAILED(hr))
503 {
504 return hr;
505 }
506
507 return S_OK;
508}
509
510
511//---------------------------------------------------------------------------------------
512//
513// Callback passed to IterateAppDomains, that takes the currently iterated AppDomain,
514// and adds it to the enumerator if it has loaded the given module. See
515// code:IterateAppDomainContainingModule::PopulateArray for how this gets used.
516//
517// Arguments:
518// * pAppDomain - Current AppDomain being iterated.
519//
520// Return Value:
521// * S_OK = the iterator should continue after we return.
522// * error indicating a failure
523//
524HRESULT IterateAppDomainContainingModule::AddAppDomainContainingModule(AppDomain * pAppDomain)
525{
526 CONTRACTL
527 {
528 NOTHROW;
529 // This method iterates over AppDomains, which adds, then releases, a reference on
530 // each AppDomain iterated. This causes locking, and can cause triggering if the
531 // AppDomain gets destroyed as a result of the release. (See code:AppDomainIterator::Next
532 // and its call to code:AppDomain::Release.)
533 GC_TRIGGERS;
534 MODE_ANY;
535 CAN_TAKE_LOCK;
536 SO_NOT_MAINLINE;
537 }
538 CONTRACTL_END;
539
540 DomainFile * pDomainFile = m_pModule->FindDomainFile(pAppDomain);
541 if ((pDomainFile != NULL) && (pDomainFile->IsAvailableToProfilers()))
542 {
543 if (m_index < m_cAppDomainIds)
544 {
545 m_rgAppDomainIds[m_index] = reinterpret_cast<AppDomainID>(pAppDomain);
546 }
547
548 m_index++;
549 }
550
551 return S_OK;
552}
553
554
555//---------------------------------------------------------------------------------------
556//
557// Populate the array with AppDomains in which the given module has been loaded
558//
559// Return Value:
560// HRESULT indicating success or failure.
561//
562HRESULT IterateAppDomainContainingModule::PopulateArray()
563{
564 CONTRACTL
565 {
566 NOTHROW;
567 // This method iterates over AppDomains, which adds, then releases, a reference on
568 // each AppDomain iterated. This causes locking, and can cause triggering if the
569 // AppDomain gets destroyed as a result of the release. (See code:AppDomainIterator::Next
570 // and its call to code:AppDomain::Release.)
571 GC_TRIGGERS;
572 MODE_ANY;
573 CAN_TAKE_LOCK;
574 SO_NOT_MAINLINE;
575 }
576 CONTRACTL_END;
577
578 HRESULT hr = IterateAppDomains<IterateAppDomainContainingModule>(
579 this,
580 &IterateAppDomainContainingModule::AddAppDomainContainingModule);
581
582 *m_pcAppDomainIds = m_index;
583
584 return hr;
585}
586
587//---------------------------------------------------------------------------------------
588//
589// Populate the thread enumerator that's about to be given to the profiler. This is
590// called from the ICorProfilerInfo4::EnumThread implementation.
591//
592// Return Value:
593// HRESULT indicating success or failure.
594//
595HRESULT ProfilerThreadEnum::Init()
596{
597 CONTRACTL
598 {
599 NOTHROW;
600 GC_NOTRIGGER;
601 MODE_ANY;
602 CAN_TAKE_LOCK;
603 SO_NOT_MAINLINE;
604 }
605 CONTRACTL_END;
606
607 ThreadStoreLockHolder tsLock;
608
609 Thread * pThread = NULL;
610
611 //
612 // Walk through all the threads with the lock taken
613 // Because the thread enumeration status need to change before the ThreadCreated/ThreadDestroyed
614 // callback, we need to:
615 // 1. Include Thread::TS_FullyInitialized threads for ThreadCreated
616 // 2. Exclude Thread::TS_Dead | Thread::TS_ReportDead for ThreadDestroyed
617 //
618 while((pThread = ThreadStore::GetAllThreadList(
619 pThread,
620 Thread::TS_Dead | Thread::TS_ReportDead | Thread::TS_FullyInitialized,
621 Thread::TS_FullyInitialized
622 )))
623 {
624 if (pThread->IsGCSpecial())
625 continue;
626
627 *m_elements.Append() = (ThreadID) pThread;
628 }
629
630 return S_OK;
631}
632
633
634#endif // PROFILING_SUPPORTED
635