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 | // ProfilingHelper.cpp |
6 | // |
7 | |
8 | // |
9 | // Implementation of helper classes used for miscellaneous purposes within the profiling |
10 | // API |
11 | // |
12 | // ====================================================================================== |
13 | |
14 | // |
15 | // #LoadUnloadCallbackSynchronization |
16 | // |
17 | // There is synchronization around loading profilers, unloading profilers, and issuing |
18 | // callbacks to profilers, to ensure that we know when it's safe to detach profilers or |
19 | // to call into profilers. The synchronization scheme is intentionally lockless on the |
20 | // mainline path (issuing callbacks into the profiler), with heavy locking on the |
21 | // non-mainline path (loading / unloading profilers). |
22 | // |
23 | // PROTECTED DATA |
24 | // |
25 | // The synchronization protects the following data: |
26 | // |
27 | // * ProfilingAPIDetach::s_profilerDetachInfo |
28 | // * (volatile) g_profControlBlock.curProfStatus.m_profStatus |
29 | // * (volatile) g_profControlBlock.pProfInterface |
30 | // * latter implies the profiler DLL's load status is protected as well, as |
31 | // pProfInterface changes between non-NULL and NULL as a profiler DLL is |
32 | // loaded and unloaded, respectively. |
33 | // |
34 | // SYNCHRONIZATION COMPONENTS |
35 | // |
36 | // * Simple Crst: code:ProfilingAPIUtility::s_csStatus |
37 | // * Lockless, volatile per-thread counters: code:EvacuationCounterHolder |
38 | // * Profiler status transition invariants and CPU buffer flushing: |
39 | // code:CurrentProfilerStatus::Set |
40 | // |
41 | // WRITERS |
42 | // |
43 | // The above data is considered to be "written to" when a profiler is loaded or unloaded, |
44 | // or the status changes (see code:ProfilerStatus), or a request to detach the profiler |
45 | // is received (see code:ProfilingAPIDetach::RequestProfilerDetach), or the DetachThread |
46 | // consumes or modifies the contents of code:ProfilingAPIDetach::s_profilerDetachInfo. |
47 | // All these cases are serialized with each other by the simple Crst: |
48 | // code:ProfilingAPIUtility::s_csStatus |
49 | // |
50 | // READERS |
51 | // |
52 | // Readers are the mainline case and are lockless. A "reader" is anyone who wants to |
53 | // issue a profiler callback. Readers are scattered throughout the runtime, and have the |
54 | // following format: |
55 | // { |
56 | // BEGIN_PIN_PROFILER(CORProfilerTrackAppDomainLoads()); |
57 | // g_profControlBlock.pProfInterface->AppDomainCreationStarted(MyAppDomainID); |
58 | // END_PIN_PROFILER(); |
59 | // } |
60 | // The BEGIN / END macros do the following: |
61 | // * Evaluate the expression argument (e.g., CORProfilerTrackAppDomainLoads()). This is a |
62 | // "dirty read" as the profiler could be detached at any moment during or after that |
63 | // evaluation. |
64 | // * If true, push a code:EvacuationCounterHolder on the stack, which increments the |
65 | // per-thread evacuation counter (not interlocked). |
66 | // * Re-evaluate the expression argument. This time, it's a "clean read" (see below for |
67 | // why). |
68 | // * If still true, execute the statements inside the BEGIN/END block. Inside that block, |
69 | // the profiler is guaranteed to remain loaded, because the evacuation counter |
70 | // remains nonzero (again, see below). |
71 | // * Once the BEGIN/END block is exited, the evacuation counter is decremented, and the |
72 | // profiler is unpinned and allowed to detach. |
73 | // |
74 | // READER / WRITER COORDINATION |
75 | // |
76 | // The above ensures that a reader never touches g_profControlBlock.pProfInterface and |
77 | // all it embodies (including the profiler DLL code and callback implementations) unless |
78 | // the reader was able to increment its thread's evacuation counter AND re-verify that |
79 | // the profiler's status is still active (the status check is included in the macro's |
80 | // expression argument, such as CORProfilerTrackAppDomainLoads()). |
81 | // |
82 | // At the same time, a profiler DLL is never unloaded (nor |
83 | // g_profControlBlock.pProfInterface deleted and NULLed out) UNLESS the writer performs |
84 | // these actions: |
85 | // * (a) Set the profiler's status to a non-active state like kProfStatusDetaching or |
86 | // kProfStatusNone |
87 | // * (b) Call FlushProcessWriteBuffers() |
88 | // * (c) Grab thread store lock, iterate through all threads, and verify each per-thread |
89 | // evacuation counter is zero. |
90 | // |
91 | // The above steps are why it's considered a "clean read" if a reader first increments |
92 | // its evacuation counter and then checks the profiler status. Once the writer flushes |
93 | // the CPU buffers (b), the reader will see the updated status (from a) and know not to |
94 | // use g_profControlBlock.pProfInterface. And if the reader clean-reads the status before |
95 | // the buffers were flushed, then the reader will have incremented its evacuation counter |
96 | // first, which the writer will be sure to see in (c). For more details about how the |
97 | // evacuation counters work, see code:ProfilingAPIDetach::IsProfilerEvacuated. |
98 | // |
99 | // WHEN ARE BEGIN/END_PIN_PROFILER REQUIRED? |
100 | // |
101 | // In general, any time you access g_profControlBlock.pProfInterface, you must be inside |
102 | // a BEGIN/END_PIN_PROFILER block. This is pretty much always true throughout the EE, but |
103 | // there are some exceptions inside the profiling API code itself, where the BEGIN / END |
104 | // macros are unnecessary: |
105 | // * If you are inside a public ICorProfilerInfo function's implementation, the |
106 | // profiler is already pinned. This is because the profiler called the Info |
107 | // function from either: |
108 | // * a callback implemented inside of g_profControlBlock.pProfInterface, in which |
109 | // case the BEGIN/END macros are already in place around the call to that |
110 | // callback, OR |
111 | // * a hijacked thread or a thread of the profiler's own creation. In either |
112 | // case, it's the profiler's responsibility to end hijacking and end its own |
113 | // threads before requesting a detach. So the profiler DLL is guaranteed not |
114 | // to disappear while hijacking or profiler-created threads are in action. |
115 | // * If you're executing while code:ProfilingAPIUtility::s_csStatus is held, then |
116 | // you're explicitly serialized against all code that might unload the profiler's |
117 | // DLL and delete g_profControlBlock.pProfInterface. So the profiler is therefore |
118 | // still guaranteed not to disappear. |
119 | // * If slow ELT helpers, fast ELT hooks, or profiler-instrumented code is on the |
120 | // stack, then the profiler cannot be detached yet anyway. Today, we outright |
121 | // refuse a detach request from a profiler that instrumented code or enabled ELT. |
122 | // Once rejit / revert is implemented, the evacuation checks will ensure all |
123 | // instrumented code (including ELT) are reverted and off all stacks before |
124 | // attempting to unload the profielr. |
125 | |
126 | |
127 | #include "common.h" |
128 | |
129 | #ifdef PROFILING_SUPPORTED |
130 | |
131 | #include "eeprofinterfaces.h" |
132 | #include "eetoprofinterfaceimpl.h" |
133 | #include "eetoprofinterfaceimpl.inl" |
134 | #include "corprof.h" |
135 | #include "proftoeeinterfaceimpl.h" |
136 | #include "proftoeeinterfaceimpl.inl" |
137 | #include "profilinghelper.h" |
138 | #include "profilinghelper.inl" |
139 | #include "eemessagebox.h" |
140 | |
141 | |
142 | #ifdef FEATURE_PROFAPI_ATTACH_DETACH |
143 | #include "profattach.h" |
144 | #include "profdetach.h" |
145 | #endif // FEATURE_PROFAPI_ATTACH_DETACH |
146 | |
147 | #include "utilcode.h" |
148 | |
149 | #ifndef FEATURE_PAL |
150 | #include "securitywrapper.h" |
151 | #endif // !FEATURE_PAL |
152 | |
153 | //--------------------------------------------------------------------------------------- |
154 | // Normally, this would go in profilepriv.inl, but it's not easily inlineable because of |
155 | // the use of BEGIN/END_PIN_PROFILER |
156 | // |
157 | // Return Value: |
158 | // TRUE iff security transparency checks in full trust assemblies should be disabled |
159 | // due to the profiler. |
160 | // |
161 | BOOL CORProfilerBypassSecurityChecks() |
162 | { |
163 | CONTRACTL |
164 | { |
165 | NOTHROW; |
166 | GC_NOTRIGGER; |
167 | CANNOT_TAKE_LOCK; |
168 | SO_NOT_MAINLINE; |
169 | } |
170 | CONTRACTL_END; |
171 | |
172 | { |
173 | BEGIN_PIN_PROFILER(CORProfilerPresent()); |
174 | |
175 | // V2 profiler binaries, for compatibility purposes, should bypass transparency |
176 | // checks in full trust assemblies. |
177 | if (!(&g_profControlBlock)->pProfInterface->IsCallback3Supported()) |
178 | return TRUE; |
179 | |
180 | // V4 profiler binaries must opt in to bypasssing transparency checks in full trust |
181 | // assemblies. |
182 | if (((&g_profControlBlock)->dwEventMask & COR_PRF_DISABLE_TRANSPARENCY_CHECKS_UNDER_FULL_TRUST) != 0) |
183 | return TRUE; |
184 | |
185 | END_PIN_PROFILER(); |
186 | } |
187 | |
188 | // All other cases, including no profiler loaded at all: Don't bypass |
189 | return FALSE; |
190 | } |
191 | |
192 | // ---------------------------------------------------------------------------- |
193 | // CurrentProfilerStatus methods |
194 | |
195 | |
196 | //--------------------------------------------------------------------------------------- |
197 | // |
198 | // Updates the value indicating the profiler's current status |
199 | // |
200 | // Arguments: |
201 | // profStatus - New value (from enum ProfilerStatus) to set. |
202 | // |
203 | // Notes: |
204 | // Sets the status under a lock, and performs a debug-only check to verify that the |
205 | // status transition is a legal one. Also performs a FlushStoreBuffers() after |
206 | // changing the status when necessary. |
207 | // |
208 | |
209 | void CurrentProfilerStatus::Set(ProfilerStatus newProfStatus) |
210 | { |
211 | CONTRACTL |
212 | { |
213 | NOTHROW; |
214 | GC_TRIGGERS; |
215 | MODE_ANY; |
216 | CAN_TAKE_LOCK; |
217 | } |
218 | CONTRACTL_END; |
219 | |
220 | _ASSERTE(ProfilingAPIUtility::GetStatusCrst() != NULL); |
221 | |
222 | { |
223 | // Need to serialize attempts to transition the profiler status. For example, a |
224 | // profiler in one thread could request a detach, while the CLR in another |
225 | // thread is transitioning the profiler from kProfStatusInitializing* to |
226 | // kProfStatusActive |
227 | CRITSEC_Holder csh(ProfilingAPIUtility::GetStatusCrst()); |
228 | |
229 | // Based on what the old status is, verify the new status is a legal transition. |
230 | switch(m_profStatus) |
231 | { |
232 | default: |
233 | _ASSERTE(!"Unknown ProfilerStatus" ); |
234 | break; |
235 | |
236 | case kProfStatusNone: |
237 | _ASSERTE((newProfStatus == kProfStatusInitializingForStartupLoad) || |
238 | (newProfStatus == kProfStatusInitializingForAttachLoad)); |
239 | break; |
240 | |
241 | case kProfStatusDetaching: |
242 | _ASSERTE(newProfStatus == kProfStatusNone); |
243 | break; |
244 | |
245 | case kProfStatusInitializingForStartupLoad: |
246 | case kProfStatusInitializingForAttachLoad: |
247 | _ASSERTE((newProfStatus == kProfStatusActive) || |
248 | (newProfStatus == kProfStatusNone)); |
249 | break; |
250 | |
251 | case kProfStatusActive: |
252 | _ASSERTE((newProfStatus == kProfStatusNone) || |
253 | (newProfStatus == kProfStatusDetaching)); |
254 | break; |
255 | } |
256 | |
257 | m_profStatus = newProfStatus; |
258 | } |
259 | |
260 | #if !defined(DACCESS_COMPILE) |
261 | if (((newProfStatus == kProfStatusNone) || |
262 | (newProfStatus == kProfStatusDetaching) || |
263 | (newProfStatus == kProfStatusActive))) |
264 | { |
265 | // Flush the store buffers on all CPUs, to ensure other threads see that |
266 | // g_profControlBlock.curProfStatus has changed. The important status changes to |
267 | // flush are: |
268 | // * to kProfStatusNone or kProfStatusDetaching so other threads know to stop |
269 | // making calls into the profiler |
270 | // * to kProfStatusActive, to ensure callbacks can be issued by the time an |
271 | // attaching profiler receives ProfilerAttachComplete(), so the profiler |
272 | // can safely perform catchup at that time (see |
273 | // code:#ProfCatchUpSynchronization). |
274 | // |
275 | ::FlushProcessWriteBuffers(); |
276 | } |
277 | #endif // !defined(DACCESS_COMPILE) |
278 | } |
279 | |
280 | |
281 | //--------------------------------------------------------------------------------------- |
282 | // ProfilingAPIUtility members |
283 | |
284 | |
285 | // See code:#LoadUnloadCallbackSynchronization. |
286 | CRITSEC_COOKIE ProfilingAPIUtility::s_csStatus = NULL; |
287 | |
288 | |
289 | SidBuffer * ProfilingAPIUtility::s_pSidBuffer = NULL; |
290 | |
291 | // ---------------------------------------------------------------------------- |
292 | // ProfilingAPIUtility::AppendSupplementaryInformation |
293 | // |
294 | // Description: |
295 | // Helper to the event logging functions to append the process ID and string |
296 | // resource ID to the end of the message. |
297 | // |
298 | // Arguments: |
299 | // * iStringResource - [in] String resource ID to append to message. |
300 | // * pString - [in/out] On input, the string to log so far. On output, the original |
301 | // string with the process ID info appended. |
302 | // |
303 | |
304 | // static |
305 | void ProfilingAPIUtility::AppendSupplementaryInformation(int iStringResource, SString * pString) |
306 | { |
307 | CONTRACTL |
308 | { |
309 | THROWS; |
310 | GC_TRIGGERS; |
311 | MODE_ANY; |
312 | |
313 | // This loads resource strings, which takes locks. |
314 | CAN_TAKE_LOCK; |
315 | } |
316 | CONTRACTL_END; |
317 | |
318 | StackSString supplementaryInformation; |
319 | |
320 | if (!supplementaryInformation.LoadResource( |
321 | CCompRC::Debugging, |
322 | IDS_PROF_SUPPLEMENTARY_INFO |
323 | )) |
324 | { |
325 | // Resource not found; should never happen. |
326 | return; |
327 | } |
328 | |
329 | pString->Append(W(" " )); |
330 | pString->AppendPrintf( |
331 | supplementaryInformation, |
332 | GetCurrentProcessId(), |
333 | iStringResource); |
334 | } |
335 | |
336 | //--------------------------------------------------------------------------------------- |
337 | // |
338 | // Helper function to log publicly-viewable errors about profiler loading and |
339 | // initialization. |
340 | // |
341 | // |
342 | // Arguments: |
343 | // * iStringResourceID - resource ID of string containing message to log |
344 | // * wEventType - same constant used in win32 to specify the type of event: |
345 | // usually EVENTLOG_ERROR_TYPE, EVENTLOG_WARNING_TYPE, or |
346 | // EVENTLOG_INFORMATION_TYPE |
347 | // * insertionArgs - 0 or more values to be inserted into the string to be logged |
348 | // (>0 only if iStringResourceID contains format arguments (%)). |
349 | // |
350 | |
351 | // static |
352 | void ProfilingAPIUtility::LogProfEventVA( |
353 | int iStringResourceID, |
354 | WORD wEventType, |
355 | va_list insertionArgs) |
356 | { |
357 | CONTRACTL |
358 | { |
359 | THROWS; |
360 | GC_TRIGGERS; |
361 | MODE_ANY; |
362 | |
363 | // This loads resource strings, which takes locks. |
364 | CAN_TAKE_LOCK; |
365 | } |
366 | CONTRACTL_END; |
367 | |
368 | StackSString messageFromResource; |
369 | StackSString messageToLog; |
370 | |
371 | if (!messageFromResource.LoadResource( |
372 | CCompRC::Debugging, |
373 | iStringResourceID |
374 | )) |
375 | { |
376 | // Resource not found; should never happen. |
377 | return; |
378 | } |
379 | |
380 | messageToLog.VPrintf(messageFromResource, insertionArgs); |
381 | |
382 | AppendSupplementaryInformation(iStringResourceID, &messageToLog); |
383 | |
384 | // Ouput debug strings for diagnostic messages. |
385 | WszOutputDebugString(messageToLog); |
386 | } |
387 | |
388 | // See code:ProfilingAPIUtility.LogProfEventVA for description of arguments. |
389 | // static |
390 | void ProfilingAPIUtility::LogProfError(int iStringResourceID, ...) |
391 | { |
392 | CONTRACTL |
393 | { |
394 | THROWS; |
395 | GC_TRIGGERS; |
396 | MODE_ANY; |
397 | |
398 | // This loads resource strings, which takes locks. |
399 | CAN_TAKE_LOCK; |
400 | } |
401 | CONTRACTL_END; |
402 | |
403 | va_list insertionArgs; |
404 | va_start(insertionArgs, iStringResourceID); |
405 | LogProfEventVA( |
406 | iStringResourceID, |
407 | EVENTLOG_ERROR_TYPE, |
408 | insertionArgs); |
409 | va_end(insertionArgs); |
410 | } |
411 | |
412 | // See code:ProfilingAPIUtility.LogProfEventVA for description of arguments. |
413 | // static |
414 | void ProfilingAPIUtility::LogProfInfo(int iStringResourceID, ...) |
415 | { |
416 | CONTRACTL |
417 | { |
418 | THROWS; |
419 | GC_TRIGGERS; |
420 | MODE_ANY; |
421 | |
422 | // This loads resource strings, which takes locks. |
423 | CAN_TAKE_LOCK; |
424 | } |
425 | CONTRACTL_END; |
426 | |
427 | va_list insertionArgs; |
428 | va_start(insertionArgs, iStringResourceID); |
429 | LogProfEventVA( |
430 | iStringResourceID, |
431 | EVENTLOG_INFORMATION_TYPE, |
432 | insertionArgs); |
433 | va_end(insertionArgs); |
434 | } |
435 | |
436 | #ifdef PROF_TEST_ONLY_FORCE_ELT |
437 | // Special forward-declarations of the profiling API's slow-path enter/leave/tailcall |
438 | // hooks. These need to be forward-declared here so that they may be referenced in |
439 | // InitializeProfiling() below solely for the debug-only, test-only code to allow |
440 | // enter/leave/tailcall to be turned on at startup without a profiler. See |
441 | // code:ProfControlBlock#TestOnlyELT |
442 | EXTERN_C void STDMETHODCALLTYPE ProfileEnterNaked(UINT_PTR clientData); |
443 | EXTERN_C void STDMETHODCALLTYPE ProfileLeaveNaked(UINT_PTR clientData); |
444 | EXTERN_C void STDMETHODCALLTYPE ProfileTailcallNaked(UINT_PTR clientData); |
445 | #endif //PROF_TEST_ONLY_FORCE_ELT |
446 | |
447 | // ---------------------------------------------------------------------------- |
448 | // ProfilingAPIUtility::InitializeProfiling |
449 | // |
450 | // This is the top-most level of profiling API initialization, and is called directly by |
451 | // EEStartupHelper() (in ceemain.cpp). This initializes internal structures relating to the |
452 | // Profiling API. This also orchestrates loading the profiler and initializing it (if |
453 | // its GUID is specified in the environment). |
454 | // |
455 | // Return Value: |
456 | // HRESULT indicating success or failure. This is generally very lenient about internal |
457 | // failures, as we don't want them to prevent the startup of the app: |
458 | // S_OK = Environment didn't request a profiler, or |
459 | // Environment did request a profiler, and it was loaded successfully |
460 | // S_FALSE = There was a problem loading the profiler, but that shouldn't prevent the app |
461 | // from starting up |
462 | // else (failure) = There was a serious problem that should be dealt with by the caller |
463 | // |
464 | // Notes: |
465 | // This function (or one of its callees) will log an error to the event log |
466 | // if there is a failure |
467 | // |
468 | // Assumptions: |
469 | // InitializeProfiling is called during startup, AFTER the host has initialized its |
470 | // settings and the config variables have been read, but BEFORE the finalizer thread |
471 | // has entered its first wait state. ASSERTs are placed in |
472 | // code:ProfilingAPIAttachDetach::Initialize (which is called by this function, and |
473 | // which depends on these assumptions) to verify. |
474 | |
475 | // static |
476 | HRESULT ProfilingAPIUtility::InitializeProfiling() |
477 | { |
478 | CONTRACTL |
479 | { |
480 | THROWS; |
481 | GC_TRIGGERS; |
482 | |
483 | // This causes events to be logged, which loads resource strings, |
484 | // which takes locks. |
485 | CAN_TAKE_LOCK; |
486 | |
487 | MODE_PREEMPTIVE; |
488 | } |
489 | CONTRACTL_END; |
490 | |
491 | InitializeLogging(); |
492 | |
493 | // NULL out / initialize members of the global profapi structure |
494 | g_profControlBlock.Init(); |
495 | |
496 | if (IsCompilationProcess()) |
497 | { |
498 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling disabled for ngen process.\n" )); |
499 | return S_OK; |
500 | } |
501 | |
502 | AttemptLoadProfilerForStartup(); |
503 | // For now, the return value from AttemptLoadProfilerForStartup is of no use to us. |
504 | // Any event has been logged already by AttemptLoadProfilerForStartup, and |
505 | // regardless of whether a profiler got loaded, we still need to continue. |
506 | |
507 | |
508 | #ifdef PROF_TEST_ONLY_FORCE_ELT |
509 | // Test-only, debug-only code to enable ELT on startup regardless of whether a |
510 | // startup profiler is loaded. See code:ProfControlBlock#TestOnlyELT. |
511 | DWORD dwEnableSlowELTHooks = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TestOnlyEnableSlowELTHooks); |
512 | if (dwEnableSlowELTHooks != 0) |
513 | { |
514 | (&g_profControlBlock)->fTestOnlyForceEnterLeave = TRUE; |
515 | SetJitHelperFunction(CORINFO_HELP_PROF_FCN_ENTER, (void *) ProfileEnterNaked); |
516 | SetJitHelperFunction(CORINFO_HELP_PROF_FCN_LEAVE, (void *) ProfileLeaveNaked); |
517 | SetJitHelperFunction(CORINFO_HELP_PROF_FCN_TAILCALL, (void *) ProfileTailcallNaked); |
518 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Enabled test-only slow ELT hooks.\n" )); |
519 | } |
520 | #endif //PROF_TEST_ONLY_FORCE_ELT |
521 | |
522 | #ifdef PROF_TEST_ONLY_FORCE_OBJECT_ALLOCATED |
523 | // Test-only, debug-only code to enable ObjectAllocated callbacks on startup regardless of whether a |
524 | // startup profiler is loaded. See code:ProfControlBlock#TestOnlyObjectAllocated. |
525 | DWORD dwEnableObjectAllocated = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TestOnlyEnableObjectAllocatedHook); |
526 | if (dwEnableObjectAllocated != 0) |
527 | { |
528 | (&g_profControlBlock)->fTestOnlyForceObjectAllocated = TRUE; |
529 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Enabled test-only object ObjectAllocated hooks.\n" )); |
530 | } |
531 | #endif //PROF_TEST_ONLY_FORCE_ELT |
532 | |
533 | |
534 | #ifdef _DEBUG |
535 | // Test-only, debug-only code to allow attaching profilers to call ICorProfilerInfo inteface, |
536 | // which would otherwise be disallowed for attaching profilers |
537 | DWORD dwTestOnlyEnableICorProfilerInfo = CLRConfig::GetConfigValue(CLRConfig::INTERNAL_TestOnlyEnableICorProfilerInfo); |
538 | if (dwTestOnlyEnableICorProfilerInfo != 0) |
539 | { |
540 | (&g_profControlBlock)->fTestOnlyEnableICorProfilerInfo = TRUE; |
541 | } |
542 | #endif // _DEBUG |
543 | |
544 | return S_OK; |
545 | } |
546 | |
547 | |
548 | // ---------------------------------------------------------------------------- |
549 | // ProfilingAPIUtility::ProfilerCLSIDFromString |
550 | // |
551 | // Description: |
552 | // Takes a string form of a CLSID (or progid, believe it or not), and returns the |
553 | // corresponding CLSID structure. |
554 | // |
555 | // Arguments: |
556 | // * wszClsid - [in / out] CLSID string to convert. This may also be a progid. This |
557 | // ensures our behavior is backward-compatible with previous CLR versions. I don't |
558 | // know why previous versions allowed the user to set a progid in the environment, |
559 | // but well whatever. On [out], this string is normalized in-place (e.g., |
560 | // double-quotes around progid are removed). |
561 | // * pClsid - [out] CLSID structure corresponding to wszClsid |
562 | // |
563 | // Return Value: |
564 | // HRESULT indicating success or failure. |
565 | // |
566 | // Notes: |
567 | // * An event is logged if there is a failure. |
568 | // |
569 | |
570 | // static |
571 | HRESULT ProfilingAPIUtility::ProfilerCLSIDFromString( |
572 | __inout_z LPWSTR wszClsid, |
573 | CLSID * pClsid) |
574 | { |
575 | CONTRACTL |
576 | { |
577 | THROWS; |
578 | GC_TRIGGERS; |
579 | |
580 | // This causes events to be logged, which loads resource strings, |
581 | // which takes locks. |
582 | CAN_TAKE_LOCK; |
583 | |
584 | MODE_PREEMPTIVE; |
585 | } |
586 | CONTRACTL_END; |
587 | |
588 | _ASSERTE(wszClsid != NULL); |
589 | _ASSERTE(pClsid != NULL); |
590 | |
591 | HRESULT hr; |
592 | |
593 | // Translate the string into a CLSID |
594 | if (*wszClsid == W('{')) |
595 | { |
596 | hr = IIDFromString(wszClsid, pClsid); |
597 | } |
598 | else |
599 | { |
600 | #ifndef FEATURE_PAL |
601 | WCHAR *szFrom, *szTo; |
602 | |
603 | #ifdef _PREFAST_ |
604 | #pragma warning(push) |
605 | #pragma warning(disable:26000) // "espX thinks there is an overflow here, but there isn't any" |
606 | #endif |
607 | for (szFrom=szTo=wszClsid; *szFrom; ) |
608 | { |
609 | if (*szFrom == W('"')) |
610 | { |
611 | ++szFrom; |
612 | continue; |
613 | } |
614 | *szTo++ = *szFrom++; |
615 | } |
616 | *szTo = 0; |
617 | hr = CLSIDFromProgID(wszClsid, pClsid); |
618 | #ifdef _PREFAST_ |
619 | #pragma warning(pop) |
620 | #endif /*_PREFAST_*/ |
621 | |
622 | #else // !FEATURE_PAL |
623 | // ProgID not supported on FEATURE_PAL |
624 | hr = E_INVALIDARG; |
625 | #endif // !FEATURE_PAL |
626 | } |
627 | |
628 | if (FAILED(hr)) |
629 | { |
630 | LOG(( |
631 | LF_CORPROF, |
632 | LL_INFO10, |
633 | "**PROF: Invalid CLSID or ProgID (%S). hr=0x%x.\n" , |
634 | wszClsid, |
635 | hr)); |
636 | ProfilingAPIUtility::LogProfError(IDS_E_PROF_BAD_CLSID, wszClsid, hr); |
637 | return hr; |
638 | } |
639 | |
640 | return S_OK; |
641 | } |
642 | |
643 | // ---------------------------------------------------------------------------- |
644 | // ProfilingAPIUtility::AttemptLoadProfilerForStartup |
645 | // |
646 | // Description: |
647 | // Checks environment or registry to see if the app is configured to run with a |
648 | // profiler loaded on startup. If so, this calls LoadProfiler() to load it up. |
649 | // |
650 | // Arguments: |
651 | // |
652 | // Return Value: |
653 | // * S_OK: Startup-profiler has been loaded |
654 | // * S_FALSE: No profiler is configured for startup load |
655 | // * else, HRESULT indicating failure that occurred |
656 | // |
657 | // Assumptions: |
658 | // * This should be called on startup, after g_profControlBlock is initialized, but |
659 | // before any attach infrastructure is initialized. This ensures we don't receive |
660 | // an attach request while startup-loading a profiler. |
661 | // |
662 | // Notes: |
663 | // * This or its callees will ensure an event is logged on failure (though will be |
664 | // silent if no profiler is configured for startup load (which causes S_FALSE to |
665 | // be returned) |
666 | // |
667 | |
668 | // static |
669 | HRESULT ProfilingAPIUtility::AttemptLoadProfilerForStartup() |
670 | { |
671 | CONTRACTL |
672 | { |
673 | THROWS; |
674 | GC_TRIGGERS; |
675 | |
676 | // This causes events to be logged, which loads resource strings, |
677 | // which takes locks. |
678 | CAN_TAKE_LOCK; |
679 | |
680 | MODE_PREEMPTIVE; |
681 | } |
682 | CONTRACTL_END; |
683 | |
684 | HRESULT hr; |
685 | |
686 | // Find out if profiling is enabled |
687 | DWORD fProfEnabled = 0; |
688 | |
689 | fProfEnabled = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_ENABLE_PROFILING); |
690 | |
691 | // If profiling is not enabled, return. |
692 | if (fProfEnabled == 0) |
693 | { |
694 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling not enabled.\n" )); |
695 | return S_FALSE; |
696 | } |
697 | |
698 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Initializing Profiling Services.\n" )); |
699 | |
700 | // Get the CLSID of the profiler to CoCreate |
701 | NewArrayHolder<WCHAR> wszClsid(NULL); |
702 | NewArrayHolder<WCHAR> wszProfilerDLL(NULL); |
703 | |
704 | IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER, &wszClsid)); |
705 | |
706 | #if defined(_TARGET_X86_) |
707 | IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER_PATH_32, &wszProfilerDLL)); |
708 | #elif defined(_TARGET_AMD64_) |
709 | IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER_PATH_64, &wszProfilerDLL)); |
710 | #endif |
711 | if(wszProfilerDLL == NULL) |
712 | { |
713 | IfFailRet(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_CORECLR_PROFILER_PATH, &wszProfilerDLL)); |
714 | } |
715 | |
716 | // If the environment variable doesn't exist, profiling is not enabled. |
717 | if (wszClsid == NULL) |
718 | { |
719 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling flag set, but required " |
720 | "environment variable does not exist.\n" )); |
721 | |
722 | LogProfError(IDS_E_PROF_NO_CLSID); |
723 | |
724 | return S_FALSE; |
725 | } |
726 | |
727 | if ((wszProfilerDLL != NULL) && (wcslen(wszProfilerDLL) >= MAX_LONGPATH)) |
728 | { |
729 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling flag set, but COR_PROFILER_PATH was not set properly.\n" )); |
730 | |
731 | LogProfError(IDS_E_PROF_BAD_PATH); |
732 | |
733 | return S_FALSE; |
734 | } |
735 | |
736 | #ifdef FEATURE_PAL |
737 | // If the environment variable doesn't exist, profiling is not enabled. |
738 | if (wszProfilerDLL == NULL) |
739 | { |
740 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling flag set, but required " |
741 | "environment variable does not exist.\n" )); |
742 | |
743 | LogProfError(IDS_E_PROF_BAD_PATH); |
744 | |
745 | return S_FALSE; |
746 | } |
747 | #endif // FEATURE_PAL |
748 | |
749 | CLSID clsid; |
750 | hr = ProfilingAPIUtility::ProfilerCLSIDFromString(wszClsid, &clsid); |
751 | if (FAILED(hr)) |
752 | { |
753 | // ProfilerCLSIDFromString already logged an event if there was a failure |
754 | return hr; |
755 | } |
756 | |
757 | hr = LoadProfiler( |
758 | kStartupLoad, |
759 | &clsid, |
760 | wszClsid, |
761 | wszProfilerDLL, |
762 | NULL, // No client data for startup load |
763 | 0); // No client data for startup load |
764 | if (FAILED(hr)) |
765 | { |
766 | // A failure in either the CLR or the profiler prevented it from |
767 | // loading. Event has been logged. Propagate hr |
768 | return hr; |
769 | } |
770 | |
771 | return S_OK; |
772 | } |
773 | |
774 | |
775 | //--------------------------------------------------------------------------------------- |
776 | // |
777 | // Performs lazy initialization that need not occur on startup, but does need to occur |
778 | // before trying to load a profiler. |
779 | // |
780 | // Return Value: |
781 | // HRESULT indicating success or failure. |
782 | // |
783 | |
784 | HRESULT ProfilingAPIUtility::PerformDeferredInit() |
785 | { |
786 | CONTRACTL |
787 | { |
788 | THROWS; |
789 | GC_TRIGGERS; |
790 | CAN_TAKE_LOCK; |
791 | MODE_ANY; |
792 | } |
793 | CONTRACTL_END; |
794 | |
795 | #ifdef FEATURE_PROFAPI_ATTACH_DETACH |
796 | // Initialize internal resources for detaching |
797 | HRESULT hr = ProfilingAPIDetach::Initialize(); |
798 | if (FAILED(hr)) |
799 | { |
800 | LOG(( |
801 | LF_CORPROF, |
802 | LL_ERROR, |
803 | "**PROF: Unable to initialize resources for detaching. hr=0x%x.\n" , |
804 | hr)); |
805 | return hr; |
806 | } |
807 | #endif // FEATURE_PROFAPI_ATTACH_DETACH |
808 | |
809 | if (s_csStatus == NULL) |
810 | { |
811 | s_csStatus = ClrCreateCriticalSection( |
812 | CrstProfilingAPIStatus, |
813 | (CrstFlags) (CRST_REENTRANCY | CRST_TAKEN_DURING_SHUTDOWN)); |
814 | if (s_csStatus == NULL) |
815 | { |
816 | return E_OUTOFMEMORY; |
817 | } |
818 | } |
819 | |
820 | return S_OK; |
821 | } |
822 | |
823 | // ---------------------------------------------------------------------------- |
824 | // ProfilingAPIUtility::LoadProfiler |
825 | // |
826 | // Description: |
827 | // Outermost common code for loading the profiler DLL. Both startup and attach code |
828 | // paths use this. |
829 | // |
830 | // Arguments: |
831 | // * loadType - Startup load or attach load? |
832 | // * pClsid - Profiler's CLSID |
833 | // * wszClsid - Profiler's CLSID (or progid) in string form, for event log messages |
834 | // * wszProfilerDLL - Profiler's DLL path |
835 | // * pvClientData - For attach loads, this is the client data the trigger wants to |
836 | // pass to the profiler DLL |
837 | // * cbClientData - For attach loads, size of client data in bytes |
838 | // * dwConcurrentGCWaitTimeoutInMs - Time out for wait operation on concurrent GC. Attach scenario only |
839 | // |
840 | // Return Value: |
841 | // HRESULT indicating success or failure of the load |
842 | // |
843 | // Notes: |
844 | // * On failure, this function or a callee will have logged an event |
845 | // |
846 | |
847 | // static |
848 | HRESULT ProfilingAPIUtility::LoadProfiler( |
849 | LoadType loadType, |
850 | const CLSID * pClsid, |
851 | LPCWSTR wszClsid, |
852 | LPCWSTR wszProfilerDLL, |
853 | LPVOID pvClientData, |
854 | UINT cbClientData, |
855 | DWORD dwConcurrentGCWaitTimeoutInMs) |
856 | { |
857 | CONTRACTL |
858 | { |
859 | THROWS; |
860 | GC_TRIGGERS; |
861 | |
862 | // This causes events to be logged, which loads resource strings, |
863 | // which takes locks. |
864 | CAN_TAKE_LOCK; |
865 | |
866 | MODE_ANY; |
867 | } |
868 | CONTRACTL_END; |
869 | |
870 | if (g_fEEShutDown) |
871 | { |
872 | return CORPROF_E_RUNTIME_UNINITIALIZED; |
873 | } |
874 | |
875 | enum ProfilerCompatibilityFlag |
876 | { |
877 | // Default: disable V2 profiler |
878 | kDisableV2Profiler = 0x0, |
879 | |
880 | // Enable V2 profilers |
881 | kEnableV2Profiler = 0x1, |
882 | |
883 | // Disable Profiling |
884 | kPreventLoad = 0x2, |
885 | }; |
886 | |
887 | ProfilerCompatibilityFlag profilerCompatibilityFlag = kDisableV2Profiler; |
888 | NewArrayHolder<WCHAR> wszProfilerCompatibilitySetting(NULL); |
889 | |
890 | if (loadType == kStartupLoad) |
891 | { |
892 | CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ProfAPI_ProfilerCompatibilitySetting, &wszProfilerCompatibilitySetting); |
893 | if (wszProfilerCompatibilitySetting != NULL) |
894 | { |
895 | if (SString::_wcsicmp(wszProfilerCompatibilitySetting, W("EnableV2Profiler" )) == 0) |
896 | { |
897 | profilerCompatibilityFlag = kEnableV2Profiler; |
898 | } |
899 | else if (SString::_wcsicmp(wszProfilerCompatibilitySetting, W("PreventLoad" )) == 0) |
900 | { |
901 | profilerCompatibilityFlag = kPreventLoad; |
902 | } |
903 | } |
904 | |
905 | if (profilerCompatibilityFlag == kPreventLoad) |
906 | { |
907 | LOG((LF_CORPROF, LL_INFO10, "**PROF: COMPlus_ProfAPI_ProfilerCompatibilitySetting is set to PreventLoad. " |
908 | "Profiler will not be loaded.\n" )); |
909 | |
910 | LogProfInfo(IDS_PROF_PROFILER_DISABLED, |
911 | CLRConfig::EXTERNAL_ProfAPI_ProfilerCompatibilitySetting.name, |
912 | wszProfilerCompatibilitySetting.GetValue(), |
913 | wszClsid); |
914 | |
915 | return S_OK; |
916 | } |
917 | } |
918 | |
919 | HRESULT hr; |
920 | |
921 | hr = PerformDeferredInit(); |
922 | if (FAILED(hr)) |
923 | { |
924 | LOG(( |
925 | LF_CORPROF, |
926 | LL_ERROR, |
927 | "**PROF: ProfilingAPIUtility::PerformDeferredInit failed. hr=0x%x.\n" , |
928 | hr)); |
929 | LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, hr); |
930 | return hr; |
931 | } |
932 | |
933 | // Valid loadType? |
934 | _ASSERTE((loadType == kStartupLoad) || (loadType == kAttachLoad)); |
935 | |
936 | // If a nonzero client data size is reported, there'd better be client data! |
937 | _ASSERTE((cbClientData == 0) || (pvClientData != NULL)); |
938 | |
939 | // Client data is currently only specified on attach |
940 | _ASSERTE((pvClientData == NULL) || (loadType == kAttachLoad)); |
941 | |
942 | // Don't be telling me to load a profiler if there already is one. |
943 | _ASSERTE(g_profControlBlock.curProfStatus.Get() == kProfStatusNone); |
944 | |
945 | // Create the ProfToEE interface to provide to the profiling services |
946 | NewHolder<ProfToEEInterfaceImpl> pProfEE(new (nothrow) ProfToEEInterfaceImpl()); |
947 | if (pProfEE == NULL) |
948 | { |
949 | LOG((LF_CORPROF, LL_ERROR, "**PROF: Unable to allocate ProfToEEInterfaceImpl.\n" )); |
950 | LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, E_OUTOFMEMORY); |
951 | return E_OUTOFMEMORY; |
952 | } |
953 | |
954 | // Initialize the interface |
955 | hr = pProfEE->Init(); |
956 | if (FAILED(hr)) |
957 | { |
958 | LOG((LF_CORPROF, LL_ERROR, "**PROF: ProfToEEInterface::Init failed.\n" )); |
959 | LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, hr); |
960 | return hr; |
961 | } |
962 | |
963 | // Provide the newly created and inited interface |
964 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiling code being provided with EE interface.\n" )); |
965 | |
966 | // Create a new EEToProf object |
967 | NewHolder<EEToProfInterfaceImpl> pEEProf(new (nothrow) EEToProfInterfaceImpl()); |
968 | if (pEEProf == NULL) |
969 | { |
970 | LOG((LF_CORPROF, LL_ERROR, "**PROF: Unable to allocate EEToProfInterfaceImpl.\n" )); |
971 | LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, E_OUTOFMEMORY); |
972 | return E_OUTOFMEMORY; |
973 | } |
974 | |
975 | #ifdef FEATURE_PROFAPI_ATTACH_DETACH |
976 | // We're about to load the profiler, so first make sure we successfully create the |
977 | // DetachThread and abort the load of the profiler if we can't. This ensures we don't |
978 | // load a profiler unless we're prepared to detach it later. |
979 | hr = ProfilingAPIDetach::CreateDetachThread(); |
980 | if (FAILED(hr)) |
981 | { |
982 | LOG(( |
983 | LF_CORPROF, |
984 | LL_ERROR, |
985 | "**PROF: Unable to create DetachThread. hr=0x%x.\n" , |
986 | hr)); |
987 | ProfilingAPIUtility::LogProfError(IDS_E_PROF_INTERNAL_INIT, wszClsid, hr); |
988 | return hr; |
989 | } |
990 | #endif // FEATURE_PROFAPI_ATTACH_DETACH |
991 | |
992 | // Initialize internal state of our EEToProfInterfaceImpl. This also loads the |
993 | // profiler itself, but does not yet call its Initalize() callback |
994 | hr = pEEProf->Init(pProfEE, pClsid, wszClsid, wszProfilerDLL, (loadType == kAttachLoad), dwConcurrentGCWaitTimeoutInMs); |
995 | if (FAILED(hr)) |
996 | { |
997 | LOG((LF_CORPROF, LL_ERROR, "**PROF: EEToProfInterfaceImpl::Init failed.\n" )); |
998 | // EEToProfInterfaceImpl::Init logs an event log error on failure |
999 | return hr; |
1000 | } |
1001 | |
1002 | // EEToProfInterfaceImpl::Init takes over the ownership of pProfEE when Init succeeds, and |
1003 | // EEToProfInterfaceImpl::~EEToProfInterfaceImpl is responsible for releasing the resource pointed |
1004 | // by pProfEE. Calling SuppressRelease here is necessary to avoid double release that |
1005 | // the resource pointed by pProfEE are released by both pProfEE and pEEProf's destructor. |
1006 | pProfEE.SuppressRelease(); |
1007 | pProfEE = NULL; |
1008 | |
1009 | if (loadType == kAttachLoad) // V4 profiler from attach |
1010 | { |
1011 | // Profiler must support ICorProfilerCallback3 to be attachable |
1012 | if (!pEEProf->IsCallback3Supported()) |
1013 | { |
1014 | LogProfError(IDS_E_PROF_NOT_ATTACHABLE, wszClsid); |
1015 | return CORPROF_E_PROFILER_NOT_ATTACHABLE; |
1016 | } |
1017 | } |
1018 | else if (!pEEProf->IsCallback3Supported()) // V2 profiler from startup |
1019 | { |
1020 | if (profilerCompatibilityFlag == kDisableV2Profiler) |
1021 | { |
1022 | LOG((LF_CORPROF, LL_INFO10, "**PROF: COMPlus_ProfAPI_ProfilerCompatibilitySetting is set to DisableV2Profiler (the default). " |
1023 | "V2 profilers are not allowed, so that the configured V2 profiler is going to be unloaded.\n" )); |
1024 | |
1025 | LogProfInfo(IDS_PROF_V2PROFILER_DISABLED, wszClsid); |
1026 | return S_OK; |
1027 | } |
1028 | |
1029 | _ASSERTE(profilerCompatibilityFlag == kEnableV2Profiler); |
1030 | |
1031 | LOG((LF_CORPROF, LL_INFO10, "**PROF: COMPlus_ProfAPI_ProfilerCompatibilitySetting is set to EnableV2Profiler. " |
1032 | "The configured V2 profiler is going to be initialized.\n" )); |
1033 | |
1034 | LogProfInfo(IDS_PROF_V2PROFILER_ENABLED, |
1035 | CLRConfig::EXTERNAL_ProfAPI_ProfilerCompatibilitySetting.name, |
1036 | wszProfilerCompatibilitySetting.GetValue(), |
1037 | wszClsid); |
1038 | } |
1039 | |
1040 | _ASSERTE(s_csStatus != NULL); |
1041 | { |
1042 | // All modification of the profiler's status and |
1043 | // g_profControlBlock.pProfInterface need to be serialized against each other, |
1044 | // in particular, this code should be serialized against detach and unloading |
1045 | // code. |
1046 | CRITSEC_Holder csh(s_csStatus); |
1047 | |
1048 | // We've successfully allocated and initialized the callback wrapper object and the |
1049 | // Info interface implementation objects. The profiler DLL is therefore also |
1050 | // successfully loaded (but not yet Initialized). Transfer ownership of the |
1051 | // callback wrapper object to globals (thus suppress a release when the local |
1052 | // vars go out of scope). |
1053 | // |
1054 | // Setting this state now enables us to call into the profiler's Initialize() |
1055 | // callback (which we do immediately below), and have it successfully call |
1056 | // back into us via the Info interface (ProfToEEInterfaceImpl) to perform its |
1057 | // initialization. |
1058 | g_profControlBlock.pProfInterface = pEEProf.GetValue(); |
1059 | pEEProf.SuppressRelease(); |
1060 | pEEProf = NULL; |
1061 | |
1062 | // Set global status to reflect the proper type of Init we're doing (attach vs |
1063 | // startup) |
1064 | g_profControlBlock.curProfStatus.Set( |
1065 | (loadType == kStartupLoad) ? |
1066 | kProfStatusInitializingForStartupLoad : |
1067 | kProfStatusInitializingForAttachLoad); |
1068 | } |
1069 | |
1070 | // Now that the profiler is officially loaded and in Init status, call into the |
1071 | // profiler's appropriate Initialize() callback. Note that if the profiler fails this |
1072 | // call, we should abort the rest of the profiler loading, and reset our state so we |
1073 | // appear as if we never attempted to load the profiler. |
1074 | |
1075 | if (loadType == kStartupLoad) |
1076 | { |
1077 | hr = g_profControlBlock.pProfInterface->Initialize(); |
1078 | } |
1079 | else |
1080 | { |
1081 | _ASSERTE(loadType == kAttachLoad); |
1082 | _ASSERTE(g_profControlBlock.pProfInterface->IsCallback3Supported()); |
1083 | hr = g_profControlBlock.pProfInterface->InitializeForAttach(pvClientData, cbClientData); |
1084 | } |
1085 | |
1086 | if (FAILED(hr)) |
1087 | { |
1088 | LOG(( |
1089 | LF_CORPROF, |
1090 | LL_INFO10, |
1091 | "**PROF: Profiler failed its Initialize callback. hr=0x%x.\n" , |
1092 | hr)); |
1093 | |
1094 | // If we timed out due to waiting on concurrent GC to finish, it is very likely this is |
1095 | // the reason InitializeForAttach callback failed even though we cannot be sure and we cannot |
1096 | // cannot assume hr is going to be CORPROF_E_TIMEOUT_WAITING_FOR_CONCURRENT_GC. |
1097 | // The best we can do in this case is to report this failure anyway. |
1098 | if (g_profControlBlock.pProfInterface->HasTimedOutWaitingForConcurrentGC()) |
1099 | { |
1100 | ProfilingAPIUtility::LogProfError(IDS_E_PROF_TIMEOUT_WAITING_FOR_CONCURRENT_GC, dwConcurrentGCWaitTimeoutInMs, wszClsid); |
1101 | } |
1102 | |
1103 | // Check for known failure types, to customize the event we log |
1104 | if ((loadType == kAttachLoad) && |
1105 | ((hr == CORPROF_E_PROFILER_NOT_ATTACHABLE) || (hr == E_NOTIMPL))) |
1106 | { |
1107 | _ASSERTE(g_profControlBlock.pProfInterface->IsCallback3Supported()); |
1108 | |
1109 | // Profiler supports ICorProfilerCallback3, but explicitly doesn't support |
1110 | // Attach loading. So log specialized event |
1111 | LogProfError(IDS_E_PROF_NOT_ATTACHABLE, wszClsid); |
1112 | |
1113 | // Normalize (CORPROF_E_PROFILER_NOT_ATTACHABLE || E_NOTIMPL) down to |
1114 | // CORPROF_E_PROFILER_NOT_ATTACHABLE |
1115 | hr = CORPROF_E_PROFILER_NOT_ATTACHABLE; |
1116 | } |
1117 | else if (hr == CORPROF_E_PROFILER_CANCEL_ACTIVATION) |
1118 | { |
1119 | // Profiler didn't encounter a bad error, but is voluntarily choosing not to |
1120 | // profile this runtime. Profilers that need to set system environment |
1121 | // variables to be able to profile services may use this HRESULT to avoid |
1122 | // profiling all the other managed apps on the box. |
1123 | LogProfInfo(IDS_PROF_CANCEL_ACTIVATION, wszClsid); |
1124 | } |
1125 | else |
1126 | { |
1127 | LogProfError(IDS_E_PROF_INIT_CALLBACK_FAILED, wszClsid, hr); |
1128 | } |
1129 | |
1130 | // Profiler failed; reset everything. This will automatically reset |
1131 | // g_profControlBlock and will unload the profiler's DLL. |
1132 | TerminateProfiling(); |
1133 | return hr; |
1134 | } |
1135 | |
1136 | #ifdef FEATURE_MULTICOREJIT |
1137 | |
1138 | // Disable multicore JIT when profiling is enabled |
1139 | if (g_profControlBlock.dwEventMask & COR_PRF_MONITOR_JIT_COMPILATION) |
1140 | { |
1141 | MulticoreJitManager::DisableMulticoreJit(); |
1142 | } |
1143 | |
1144 | #endif |
1145 | |
1146 | // Indicate that profiling is properly initialized. On an attach-load, this will |
1147 | // force a FlushStoreBuffers(), which is important for catch-up synchronization (see |
1148 | // code:#ProfCatchUpSynchronization) |
1149 | g_profControlBlock.curProfStatus.Set(kProfStatusActive); |
1150 | |
1151 | LOG(( |
1152 | LF_CORPROF, |
1153 | LL_INFO10, |
1154 | "**PROF: Profiler successfully loaded and initialized.\n" )); |
1155 | |
1156 | LogProfInfo(IDS_PROF_LOAD_COMPLETE, wszClsid); |
1157 | |
1158 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Profiler created and enabled.\n" )); |
1159 | |
1160 | if (loadType == kStartupLoad) |
1161 | { |
1162 | // For startup profilers only: If the profiler is interested in tracking GC |
1163 | // events, then we must disable concurrent GC since concurrent GC can allocate |
1164 | // and kill objects without relocating and thus not doing a heap walk. |
1165 | if (CORProfilerTrackGC()) |
1166 | { |
1167 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Turning off concurrent GC at startup.\n" )); |
1168 | // Previously we would use SetGCConcurrent(0) to indicate to the GC that it shouldn't even |
1169 | // attempt to use concurrent GC. The standalone GC feature create a cycle during startup, |
1170 | // where the profiler couldn't set startup flags for the GC. To overcome this, we call |
1171 | // TempraryDisableConcurrentGC and never enable it again. This has a perf cost, since the |
1172 | // GC will create concurrent GC data structures, but it is acceptable in the context of |
1173 | // this kind of profiling. |
1174 | GCHeapUtilities::GetGCHeap()->TemporaryDisableConcurrentGC(); |
1175 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Concurrent GC has been turned off at startup.\n" )); |
1176 | } |
1177 | } |
1178 | |
1179 | if (loadType == kAttachLoad) |
1180 | { |
1181 | // #ProfCatchUpSynchronization |
1182 | // |
1183 | // Now that callbacks are enabled (and all threads are aware), tell an attaching |
1184 | // profiler that it's safe to request catchup information. |
1185 | // |
1186 | // There's a race we're preventing that's worthwhile to spell out. An attaching |
1187 | // profiler should be able to get a COMPLETE set of data through the use of |
1188 | // callbacks unioned with the use of catch-up enumeration Info functions. To |
1189 | // achieve this, we must ensure that there is no "hole"--any new data the |
1190 | // profiler seeks must be available from a callback or a catch-up info function |
1191 | // (or both, as dupes are ok). That means that: |
1192 | // |
1193 | // * callbacks must be enabled on other threads NO LATER THAN the profiler begins |
1194 | // requesting catch-up information on this thread |
1195 | // * Abbreviate: callbacks <= catch-up. |
1196 | // |
1197 | // Otherwise, if catch-up < callbacks, then it would be possible to have this: |
1198 | // |
1199 | // * catch-up < new data arrives < callbacks. |
1200 | // |
1201 | // In this nightmare scenario, the new data would not be accessible from the |
1202 | // catch-up calls made by the profiler (cuz the profiler made the calls too |
1203 | // early) or the callbacks made into the profiler (cuz the callbacks were enabled |
1204 | // too late). That's a hole, and that's bad. So we ensure callbacks <= catch-up |
1205 | // by the following order of operations: |
1206 | // |
1207 | // * This thread: |
1208 | // * a: Set (volatile) currentProfStatus = kProfStatusActive (done above) and |
1209 | // event mask bits (profiler did this in Initialize() callback above, |
1210 | // when it called SetEventMask) |
1211 | // * b: Flush CPU buffers (done automatically when we set status to |
1212 | // kProfStatusActive) |
1213 | // * c: CLR->Profiler call: ProfilerAttachComplete() (below). Inside this |
1214 | // call: |
1215 | // * Profiler->CLR calls: Catch-up Info functions |
1216 | // * Other threads: |
1217 | // * a: New data (thread, JIT info, etc.) is created |
1218 | // * b: This new data is now available to a catch-up Info call |
1219 | // * c: currentProfStatus & event mask bits are accurately visible to thread |
1220 | // in determining whether to make a callback |
1221 | // * d: Read currentProfStatus & event mask bits and make callback |
1222 | // (CLR->Profiler) if necessary |
1223 | // |
1224 | // So as long as OtherThreads.c <= ThisThread.c we're ok. This means other |
1225 | // threads must be able to get a clean read of the (volatile) currentProfStatus & |
1226 | // event mask bits BEFORE this thread calls ProfilerAttachComplete(). Use of the |
1227 | // "volatile" keyword ensures that compiler optimizations and (w/ VC2005+ |
1228 | // compilers) the CPU's instruction reordering optimizations at runtime are |
1229 | // disabled enough such that they do not hinder the order above. Use of |
1230 | // FlushStoreBuffers() ensures that multiple caches on multiple CPUs do not |
1231 | // hinder the order above (by causing other threads to get stale reads of the |
1232 | // volatiles). |
1233 | // |
1234 | // For more information about catch-up enumerations and exactly which entities, |
1235 | // and which stage of loading, are permitted to appear in the enumerations, see |
1236 | // code:ProfilerFunctionEnum::Init#ProfilerEnumGeneral |
1237 | |
1238 | { |
1239 | BEGIN_PIN_PROFILER(CORProfilerPresent()); |
1240 | g_profControlBlock.pProfInterface->ProfilerAttachComplete(); |
1241 | END_PIN_PROFILER(); |
1242 | } |
1243 | } |
1244 | return S_OK; |
1245 | } |
1246 | |
1247 | |
1248 | //--------------------------------------------------------------------------------------- |
1249 | // |
1250 | // This is the top-most level of profiling API teardown, and is called directly by |
1251 | // EEShutDownHelper() (in ceemain.cpp). This cleans up internal structures relating to |
1252 | // the Profiling API. If we're not in process teardown, then this also releases the |
1253 | // profiler COM object and frees the profiler DLL |
1254 | // |
1255 | |
1256 | // static |
1257 | void ProfilingAPIUtility::TerminateProfiling() |
1258 | { |
1259 | CONTRACTL |
1260 | { |
1261 | NOTHROW; |
1262 | GC_TRIGGERS; |
1263 | MODE_ANY; |
1264 | CAN_TAKE_LOCK; |
1265 | } |
1266 | CONTRACTL_END; |
1267 | |
1268 | if (IsAtProcessExit()) |
1269 | { |
1270 | // We're tearing down the process so don't bother trying to clean everything up. |
1271 | // There's no reliable way to verify other threads won't be trying to re-enter |
1272 | // the profiler anyway, so cleaning up here could cause AVs. |
1273 | return; |
1274 | } |
1275 | |
1276 | _ASSERTE(s_csStatus != NULL); |
1277 | { |
1278 | // We're modifying status and possibly unloading the profiler DLL below, so |
1279 | // serialize this code with any other loading / unloading / detaching code. |
1280 | CRITSEC_Holder csh(s_csStatus); |
1281 | |
1282 | |
1283 | #ifdef FEATURE_PROFAPI_ATTACH_DETACH |
1284 | if (ProfilingAPIDetach::GetEEToProfPtr() != NULL) |
1285 | { |
1286 | // The profiler is still being referenced by |
1287 | // ProfilingAPIDetach::s_profilerDetachInfo, so don't try to release and |
1288 | // unload it. This can happen if Shutdown and Detach race, and Shutdown wins. |
1289 | // For example, we could be called as part of Shutdown, but the profiler |
1290 | // called RequestProfilerDetach near shutdown time as well (or even earlier |
1291 | // but remains un-evacuated as shutdown begins). Whatever the cause, just |
1292 | // don't unload the profiler here (as part of shutdown), and let the Detach |
1293 | // Thread deal with it (if it gets the chance). |
1294 | // |
1295 | // Note: Since this check occurs inside s_csStatus, we don't have to worry |
1296 | // that ProfilingAPIDetach::GetEEToProfPtr() will suddenly change during the |
1297 | // code below. |
1298 | // |
1299 | // FUTURE: For reattach-with-neutered-profilers feature crew, change the |
1300 | // above to scan through list of detaching profilers to make sure none of |
1301 | // them give a GetEEToProfPtr() equal to g_profControlBlock.pProfInterface. |
1302 | return; |
1303 | } |
1304 | |
1305 | if (g_profControlBlock.curProfStatus.Get() == kProfStatusActive) |
1306 | { |
1307 | g_profControlBlock.curProfStatus.Set(kProfStatusDetaching); |
1308 | |
1309 | // Profiler was active when TerminateProfiling() was called, so we're unloading |
1310 | // it due to shutdown. But other threads may still be trying to enter profiler |
1311 | // callbacks (e.g., ClassUnloadStarted() can get called during shutdown). Now |
1312 | // that the status has been changed to kProfStatusDetaching, no new threads will |
1313 | // attempt to enter the profiler. But use the detach evacuation counters to see |
1314 | // if other threads already began to enter the profiler. |
1315 | if (!ProfilingAPIDetach::IsProfilerEvacuated()) |
1316 | { |
1317 | // Other threads might be entering the profiler, so just skip cleanup |
1318 | return; |
1319 | } |
1320 | } |
1321 | #endif // FEATURE_PROFAPI_ATTACH_DETACH |
1322 | |
1323 | // If we have a profiler callback wrapper and / or info implementation |
1324 | // active, then terminate them. |
1325 | |
1326 | if (g_profControlBlock.pProfInterface.Load() != NULL) |
1327 | { |
1328 | // This destructor takes care of releasing the profiler's ICorProfilerCallback* |
1329 | // interface, and unloading the DLL when we're not in process teardown. |
1330 | delete g_profControlBlock.pProfInterface; |
1331 | g_profControlBlock.pProfInterface.Store(NULL); |
1332 | } |
1333 | |
1334 | // NOTE: Intentionally not deleting s_pSidBuffer. Doing so can cause annoying races |
1335 | // with other threads that lazily create and initialize it when needed. (Example: |
1336 | // it's used to fill out the "User" field of profiler event log entries.) Keeping |
1337 | // s_pSidBuffer around after a profiler detaches and before a new one attaches |
1338 | // consumes a bit more memory unnecessarily, but it'll get paged out if another |
1339 | // profiler doesn't attach. |
1340 | |
1341 | // NOTE: Similarly, intentionally not destroying / NULLing s_csStatus. If |
1342 | // s_csStatus is already initialized, we can reuse it each time we do another |
1343 | // attach / detach, so no need to destroy it. |
1344 | |
1345 | // If we disabled concurrent GC and somehow failed later during the initialization |
1346 | if (g_profControlBlock.fConcurrentGCDisabledForAttach) |
1347 | { |
1348 | // We know for sure GC has been fully initialized as we've turned off concurrent GC before |
1349 | _ASSERTE(IsGarbageCollectorFullyInitialized()); |
1350 | GCHeapUtilities::GetGCHeap()->TemporaryEnableConcurrentGC(); |
1351 | g_profControlBlock.fConcurrentGCDisabledForAttach = FALSE; |
1352 | } |
1353 | |
1354 | // #ProfileResetSessionStatus Reset all the status variables that are for the current |
1355 | // profiling attach session. |
1356 | // When you are adding new status in g_profControlBlock, you need to think about whether |
1357 | // your new status is per-session, or consistent across sessions |
1358 | g_profControlBlock.ResetPerSessionStatus(); |
1359 | |
1360 | g_profControlBlock.curProfStatus.Set(kProfStatusNone); |
1361 | } |
1362 | } |
1363 | |
1364 | #ifndef FEATURE_PAL |
1365 | |
1366 | // ---------------------------------------------------------------------------- |
1367 | // ProfilingAPIUtility::GetCurrentProcessUserSid |
1368 | // |
1369 | // Description: |
1370 | // Generates a SID of the current user from the current process's token. SID is |
1371 | // returned in an [out] param, and is also cached for future use. The SID is used for |
1372 | // two purposes: event log entries (for filling out the User field) and the ACL used |
1373 | // on the globally named pipe object for attaching profilers. |
1374 | // |
1375 | // Arguments: |
1376 | // * ppsid - [out] Generated (or cached) SID |
1377 | // |
1378 | // Return Value: |
1379 | // HRESULT indicating success or failure. |
1380 | // |
1381 | |
1382 | // static |
1383 | HRESULT ProfilingAPIUtility::GetCurrentProcessUserSid(PSID * ppsid) |
1384 | { |
1385 | CONTRACTL |
1386 | { |
1387 | NOTHROW; |
1388 | GC_NOTRIGGER; |
1389 | MODE_ANY; |
1390 | } |
1391 | CONTRACTL_END; |
1392 | |
1393 | if (s_pSidBuffer == NULL) |
1394 | { |
1395 | HRESULT hr; |
1396 | NewHolder<SidBuffer> pSidBuffer(new (nothrow) SidBuffer); |
1397 | if (pSidBuffer == NULL) |
1398 | { |
1399 | return E_OUTOFMEMORY; |
1400 | } |
1401 | |
1402 | // This gets the SID of the user from the process token |
1403 | hr = pSidBuffer->InitFromProcessUserNoThrow(GetCurrentProcessId()); |
1404 | if (FAILED(hr)) |
1405 | { |
1406 | return hr; |
1407 | } |
1408 | |
1409 | if (FastInterlockCompareExchangePointer( |
1410 | &s_pSidBuffer, |
1411 | pSidBuffer.GetValue(), |
1412 | NULL) == NULL) |
1413 | { |
1414 | // Lifetime successfully transferred to s_pSidBuffer, so don't delete it here |
1415 | pSidBuffer.SuppressRelease(); |
1416 | } |
1417 | } |
1418 | |
1419 | _ASSERTE(s_pSidBuffer != NULL); |
1420 | _ASSERTE(s_pSidBuffer->GetSid().RawSid() != NULL); |
1421 | *ppsid = s_pSidBuffer->GetSid().RawSid(); |
1422 | return S_OK; |
1423 | } |
1424 | |
1425 | #endif // !FEATURE_PAL |
1426 | |
1427 | #endif // PROFILING_SUPPORTED |
1428 | |