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.
5 // FILE: ProfilingEnumerators.cpp
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.
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.
20 #ifdef PROFILING_SUPPORTED
22 #include "proftoeeinterfaceimpl.h"
23 #include "profilingenumerators.h"
25 // ---------------------------------------------------------------------------------------
26 // ProfilerFunctionEnum/ICorProfilerFunctionEnum implementation
27 // ---------------------------------------------------------------------------------------
29 BOOL ProfilerFunctionEnum::Init(BOOL fWithReJITIDs)
37 // If we needs to get rejit ID, which requires a lock (which, in turn may switch us to
39 if (fWithReJITIDs) GC_TRIGGERS; else GC_NOTRIGGER;
44 // Depending on our GC mode, the jit manager may have to take a
45 // reader lock to prevent things from changing while reading...
50 EEJitManager::CodeHeapIterator heapIterator;
51 while(heapIterator.Next())
53 MethodDesc *pMD = heapIterator.GetMethod();
55 // On AMD64 JumpStub is used to call functions that is 2GB away. JumpStubs have a CodeHeader
56 // with NULL MethodDesc, are stored in code heap and are reported by EEJitManager::EnumCode.
60 // There are two possible reasons to skip this MD.
62 // 1) If it has no metadata (i.e., LCG / IL stubs), then skip it
64 // 2) If it has no code compiled yet for it, then skip it.
66 if (pMD->IsNoMetadata() || !pMD->HasNativeCode())
71 COR_PRF_FUNCTION * element = m_elements.Append();
76 element->functionId = (FunctionID) pMD;
80 // This guy causes triggering and locking, while the non-rejitid case does not.
81 element->reJitId = ReJitManager::GetReJitId(pMD, heapIterator.GetMethodCode());
92 // ---------------------------------------------------------------------------------------
95 // #ProfilerEnumGeneral
97 // The following functions factor out the iteration code to ensure we only consider
98 // AppDomains, assemblies, modules, etc., that the profiler can safely query about. The
99 // parameters to these functions are of types that may have confusing syntax, but all
100 // that's going on is that the caller may supply an object instance and a member function
101 // on that object (non-static) to be called for each iterated item. This is just a
102 // statically-typed way of doing the usual pattern of providing a function pointer for
103 // the callback plus a void * context object to pass to the function. If the
104 // caller-supplied callback returns anything other than S_OK, the iteration code will
105 // stop iterating, and immediately propagate the callback's return value to the original
106 // caller. Start looking at code:ProfilerModuleEnum::Init for an example of how these
109 // The reason we have helpers to begin with is so we can centralize the logic that
110 // enforces the following rather subtle invariants:
112 // * Provide enough entities that the profiler gets a complete set of entities from
113 // the union of catch-up enumeration and "callbacks" (e.g., ModuleLoadFinished).
114 // * Exclude entities that have unloaded to the point where it's no longer safe to
115 // query information about them.
117 // The catch-up spec summarizes this via the following timeline for any given entity:
119 // Entity available in catch-up enumeration
120 // < Entity's LoadFinished (or equivalent) callback is issued
121 // < Entity NOT available from catch-up enumeration
122 // < Entity's UnloadStarted (or equivalent) callback is issued
124 // These helpers avoid duplicate code in the ProfilerModuleEnum implementation, and will
125 // also help avoid future duplicate code should we decide to provide more catch-up
126 // enumerations for attaching profilers to find currently loaded AppDomains, Classes,
129 // Note: The debugging API has similar requirements around which entities at which stage
130 // of loading are permitted to be enumerated over. See code:IDacDbiInterface#Enumeration
131 // for debugger details. Note that profapi's needs are not exactly the same. For example,
132 // Assemblies appear in the debugging API enumerations as soon as they begin to load,
133 // whereas Assemblies (like all other entities) appear in the profiling API enumerations
134 // once their load is complete (i.e., just before AssemblyLoadFinished). Also,
135 // debuggers enumerate DomainModules and DomainAssemblies, whereas profilers enumerate
136 // Modules and Assemblies.
138 // For information about other synchronization issues with profiler catch-up, see
139 // code:ProfilingAPIUtility::LoadProfiler#ProfCatchUpSynchronization
141 // ---------------------------------------------------------------------------------------
144 //---------------------------------------------------------------------------------------
146 // Iterates through exactly those AppDomains that should be visible to the profiler, and
147 // calls a caller-supplied function to operate on each iterated AppDomain
150 // * callbackObj - Caller-supplied object containing the callback method to call for
152 // * callbackMethod - Caller-supplied method to call for each AppDomain. If this
153 // method returns anything other than S_OK, then the iteration is aborted, and
154 // callbackMethod's return value is returned to our caller.
157 template<typename CallbackObject>
158 HRESULT IterateAppDomains(CallbackObject * callbackObj,
159 HRESULT (CallbackObject:: * callbackMethod)(AppDomain *))
167 // (See comments in code:ProfToEEInterfaceImpl::EnumModules for info about contracts.)
171 // #ProfilerEnumAppDomains (See also code:#ProfilerEnumGeneral)
173 // When enumerating AppDomains, ensure this timeline:
174 // AD available in catch-up enumeration
175 // < AppDomainCreationFinished issued
176 // < AD NOT available from catch-up enumeration
178 // * AppDomainCreationFinished (with S_OK hrStatus) is issued once the AppDomain
179 // reaches STAGE_ACTIVE.
180 AppDomain * pAppDomain = ::GetAppDomain();
181 if (pAppDomain->IsActive())
184 // Of course, the AD could start unloading here, but if it does we're guaranteed
185 // the profiler has had a chance to see the Unload callback for the AD, and thus
186 // the profiler can block in that callback until it's done with the enumerator
189 // Call user-supplied callback, and cancel iteration if requested
190 HRESULT hr = (callbackObj->*callbackMethod)(pAppDomain);
201 //---------------------------------------------------------------------------------------
203 // Iterates through exactly those Modules that should be visible to the profiler, and
204 // calls a caller-supplied function to operate on each iterated Module. Any module that
205 // is loaded domain-neutral is skipped.
208 // * pAppDomain - Only unshared modules loaded into this AppDomain will be iterated
209 // * callbackObj - Caller-supplied object containing the callback method to call for
211 // * callbackMethod - Caller-supplied method to call for each Module. If this
212 // method returns anything other than S_OK, then the iteration is aborted, and
213 // callbackMethod's return value is returned to our caller.
216 // * In theory, this could be broken down into an unshared assembly iterator that
217 // takes a callback, and an unshared module iterator (based on an input
218 // assembly) that takes a callback. But that kind of granularity is unnecessary
219 // now, and probably not useful in the future. If that turns out to be wrong,
220 // this can still be broken down that way later on.
223 template<typename CallbackObject>
224 HRESULT IterateUnsharedModules(AppDomain * pAppDomain,
225 CallbackObject * callbackObj,
226 HRESULT (CallbackObject:: * callbackMethod)(Module *))
237 // #ProfilerEnumAssemblies (See also code:#ProfilerEnumGeneral)
239 // When enumerating assemblies, ensure this timeline:
240 // Assembly available in catch-up enumeration
241 // < AssemblyLoadFinished issued
242 // < Assembly NOT available from catch-up enumeration
243 // < AssemblyUnloadStarted issued
245 // The IterateAssembliesEx parameter below ensures we will only include assemblies at
246 // load level >= FILE_LOAD_LOADLIBRARY.
247 // * AssemblyLoadFinished is issued once the Assembly reaches
248 // code:FILE_LOAD_LOADLIBRARY
249 // * AssemblyUnloadStarted is issued as a result of either:
250 // * Collectible assemblies unloading. Such assemblies will no longer be
253 // Note: To determine what happens in a given load stage of a module or assembly,
254 // look at the switch statement in code:DomainFile::DoIncrementalLoad, and keep in
255 // mind that it takes cases on the *next* load stage; in other words, the actions
256 // that appear in a case for a given load stage are actually executed as we attempt
257 // to transition TO that load stage, and thus they actually execute while the module
258 // / assembly is still in the previous load stage.
260 // Note that the CLR may issue ModuleLoadFinished / AssemblyLoadFinished later, at
261 // FILE_LOAD_EAGER_FIXUPS stage, if for some reason MLF/ALF hadn't been issued
262 // earlier during FILE_LOAD_LOADLIBRARY. This does not affect the timeline, as either
263 // way the profiler receives the notification AFTER the assembly would appear in the
266 // Although it's called an "AssemblyIterator", it actually iterates over
267 // DomainAssembly instances.
268 AppDomain::AssemblyIterator domainAssemblyIterator =
269 pAppDomain->IterateAssembliesEx(
270 (AssemblyIterationFlags) (kIncludeAvailableToProfilers | kIncludeExecution));
271 CollectibleAssemblyHolder<DomainAssembly *> pDomainAssembly;
273 while (domainAssemblyIterator.Next(pDomainAssembly.This()))
275 _ASSERTE(pDomainAssembly != NULL);
276 _ASSERTE(pDomainAssembly->GetAssembly() != NULL);
278 // #ProfilerEnumModules (See also code:#ProfilerEnumGeneral)
280 // When enumerating modules, ensure this timeline:
281 // Module available in catch-up enumeration
282 // < ModuleLoadFinished issued
283 // < Module NOT available from catch-up enumeration
284 // < ModuleUnloadStarted issued
286 // The IterateModules parameter below ensures only modules at level >=
287 // code:FILE_LOAD_LOADLIBRARY will be included in the iteration.
289 // Details for module callbacks are the same as those for assemblies, so see
290 // code:#ProfilerEnumAssemblies for info on how the timing works.
291 DomainModuleIterator domainModuleIterator =
292 pDomainAssembly->IterateModules(kModIterIncludeAvailableToProfilers);
293 while (domainModuleIterator.Next())
295 // Call user-supplied callback, and cancel iteration if requested
296 HRESULT hr = (callbackObj->*callbackMethod)(domainModuleIterator.GetModule());
307 //---------------------------------------------------------------------------------------
308 // ProfilerModuleEnum implementation
309 //---------------------------------------------------------------------------------------
312 //---------------------------------------------------------------------------------------
314 // Callback passed to IterateAppDomains, that takes the currently iterated AppDomain,
315 // and then iterates through the unshared modules loaded into that AD. See
316 // code:ProfilerModuleEnum::Init for how this gets used.
319 // * pAppDomain - Current AppDomain being iterated.
322 // * S_OK = the iterator should continue after we return.
323 // * S_FALSE = we verified m_pModule is loaded into this AppDomain, so no need
324 // for the iterator to continue with the next AppDomain
325 // * error indicating a failure
328 HRESULT ProfilerModuleEnum::AddUnsharedModulesFromAppDomain(AppDomain * pAppDomain)
339 return IterateUnsharedModules<ProfilerModuleEnum>(
342 &ProfilerModuleEnum::AddUnsharedModule);
346 //---------------------------------------------------------------------------------------
348 // Callback passed to IterateUnsharedModules, that takes the currently iterated unshared
349 // Module, and adds it to the enumerator. See code:ProfilerModuleEnum::Init for how this
353 // * pModule - Current Module being iterated.
356 // * S_OK = the iterator should continue after we return.
357 // * error indicating a failure
359 HRESULT ProfilerModuleEnum::AddUnsharedModule(Module * pModule)
370 ModuleID * pElement = m_elements.Append();
371 if (pElement == NULL)
373 return E_OUTOFMEMORY;
375 *pElement = (ModuleID) pModule;
380 //---------------------------------------------------------------------------------------
382 // Populate the module enumerator that's about to be given to the profiler. This is
383 // called from the ICorProfilerInfo3::EnumModules implementation.
385 // This code controls how the above iterator helpers and callbacks are used, so you might
386 // want to look here first to understand how how the helpers and callbacks are used.
389 // HRESULT indicating success or failure.
391 HRESULT ProfilerModuleEnum::Init()
399 // (See comments in code:ProfToEEInterfaceImpl::EnumModules for info about contracts.)
406 // When an assembly or module is loaded into an AppDomain, a separate DomainFile is
407 // created (one per pairing of the AppDomain with the module or assembly). This means
408 // that we'll create multiple DomainFiles for the same module if it is loaded
409 // domain-neutral (i.e., "shared"). The profiling API callbacks shield the profiler
410 // from this, and only report a given module the first time it's loaded. So a
411 // profiler sees only one ModuleLoadFinished for a module loaded domain-neutral, even
412 // though the module may be used by multiple AppDomains. The module enumerator must
413 // mirror the behavior of the profiling API callbacks, by avoiding duplicate Modules
414 // in the module list we return to the profiler. So first add unshared modules (non
415 // domain-neutral) to the enumerator, and then separately add any shared modules that
416 // were loaded into at least one AD.
418 // First, iterate through all ADs. For each one, call
419 // AddUnsharedModulesFromAppDomain, which iterates through all UNSHARED modules and
420 // adds them to the enumerator.
421 hr = IterateAppDomains<ProfilerModuleEnum>(
423 &ProfilerModuleEnum::AddUnsharedModulesFromAppDomain);
433 //---------------------------------------------------------------------------------------
435 // Callback passed to IterateAppDomains, that takes the currently iterated AppDomain,
436 // and adds it to the enumerator if it has loaded the given module. See
437 // code:IterateAppDomainContainingModule::PopulateArray for how this gets used.
440 // * pAppDomain - Current AppDomain being iterated.
443 // * S_OK = the iterator should continue after we return.
444 // * error indicating a failure
446 HRESULT IterateAppDomainContainingModule::AddAppDomainContainingModule(AppDomain * pAppDomain)
451 // This method iterates over AppDomains, which adds, then releases, a reference on
452 // each AppDomain iterated. This causes locking, and can cause triggering if the
453 // AppDomain gets destroyed as a result of the release. (See code:AppDomainIterator::Next
454 // and its call to code:AppDomain::Release.)
461 DomainFile * pDomainFile = m_pModule->GetDomainFile();
462 if ((pDomainFile != NULL) && (pDomainFile->IsAvailableToProfilers()))
464 if (m_index < m_cAppDomainIds)
466 m_rgAppDomainIds[m_index] = reinterpret_cast<AppDomainID>(pAppDomain);
476 //---------------------------------------------------------------------------------------
478 // Populate the array with AppDomains in which the given module has been loaded
481 // HRESULT indicating success or failure.
483 HRESULT IterateAppDomainContainingModule::PopulateArray()
488 // This method iterates over AppDomains, which adds, then releases, a reference on
489 // each AppDomain iterated. This causes locking, and can cause triggering if the
490 // AppDomain gets destroyed as a result of the release. (See code:AppDomainIterator::Next
491 // and its call to code:AppDomain::Release.)
498 HRESULT hr = IterateAppDomains<IterateAppDomainContainingModule>(
500 &IterateAppDomainContainingModule::AddAppDomainContainingModule);
502 *m_pcAppDomainIds = m_index;
507 //---------------------------------------------------------------------------------------
509 // Populate the thread enumerator that's about to be given to the profiler. This is
510 // called from the ICorProfilerInfo4::EnumThread implementation.
513 // HRESULT indicating success or failure.
515 HRESULT ProfilerThreadEnum::Init()
526 // If a profiler has requested that the runtime suspend to do stack snapshots, it
527 // will be holding the ThreadStore lock already
528 ThreadStoreLockHolder tsLock(!g_profControlBlock.fProfilerRequestedRuntimeSuspend);
530 Thread * pThread = NULL;
533 // Walk through all the threads with the lock taken
534 // Because the thread enumeration status need to change before the ThreadCreated/ThreadDestroyed
535 // callback, we need to:
536 // 1. Include Thread::TS_FullyInitialized threads for ThreadCreated
537 // 2. Exclude Thread::TS_Dead | Thread::TS_ReportDead for ThreadDestroyed
539 while((pThread = ThreadStore::GetAllThreadList(
541 Thread::TS_Dead | Thread::TS_ReportDead | Thread::TS_FullyInitialized,
542 Thread::TS_FullyInitialized
545 if (pThread->IsGCSpecial())
548 *m_elements.Append() = (ThreadID) pThread;
551 _ASSERTE(ThreadStore::HoldingThreadStore() || g_profControlBlock.fProfilerRequestedRuntimeSuspend);
556 #endif // PROFILING_SUPPORTED