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 | // ProfAttach.cpp |
6 | // |
7 | |
8 | // |
9 | // Definitions of functions that help with attaching and detaching profilers |
10 | // |
11 | |
12 | // ====================================================================================== |
13 | |
14 | |
15 | #include "common.h" |
16 | |
17 | #ifdef FEATURE_PROFAPI_ATTACH_DETACH |
18 | |
19 | #include <sddl.h> // Windows security descriptor language |
20 | #include <SecurityUtil.h> |
21 | #include "eeprofinterfaces.h" |
22 | #include "eetoprofinterfaceimpl.h" |
23 | #include "corprof.h" |
24 | #include "proftoeeinterfaceimpl.h" |
25 | #include "proftoeeinterfaceimpl.inl" |
26 | #include "profilinghelper.h" |
27 | #include "profilinghelper.inl" |
28 | #include "profattach.h" |
29 | #include "profattach.inl" |
30 | #include "securitywrapper.h" |
31 | #include "profattachserver.h" |
32 | #include "profattachserver.inl" |
33 | #include "profattachclient.h" |
34 | #include "profdetach.h" |
35 | |
36 | PSECURITY_DESCRIPTOR ProfilingAPIAttachDetach::s_pSecurityDescriptor = NULL; |
37 | HANDLE ProfilingAPIAttachDetach::s_hAttachEvent = NULL; |
38 | ProfilingAPIAttachDetach::AttachThreadingMode ProfilingAPIAttachDetach::s_attachThreadingMode = |
39 | ProfilingAPIAttachDetach::kUninitialized; |
40 | BOOL ProfilingAPIAttachDetach::s_fInitializeCalled = FALSE; |
41 | |
42 | |
43 | // ---------------------------------------------------------------------------- |
44 | // ProfilingAPIAttachDetach::OverlappedResultHolder implementation. See |
45 | // code:ProfilingAPIAttachDetach::OverlappedResultHolder for more information |
46 | // |
47 | |
48 | // ---------------------------------------------------------------------------- |
49 | // ProfilingAPIAttachDetach::OverlappedResultHolder::Initialize |
50 | // |
51 | // Description: |
52 | // Call this first! This initializes the contained OVERLAPPED structure |
53 | // |
54 | // Return Value: |
55 | // Returns E_OUTOFMEMORY if OVERLAPPED structure could not be allocated. |
56 | // Else S_OK. |
57 | // |
58 | |
59 | HRESULT ProfilingAPIAttachDetach::OverlappedResultHolder::Initialize() |
60 | { |
61 | CONTRACTL |
62 | { |
63 | NOTHROW; |
64 | GC_NOTRIGGER; |
65 | SO_INTOLERANT; |
66 | MODE_ANY; |
67 | } |
68 | CONTRACTL_END; |
69 | |
70 | Assign(new (nothrow) OVERLAPPED); |
71 | if (m_value == NULL) |
72 | { |
73 | return E_OUTOFMEMORY; |
74 | } |
75 | |
76 | memset(m_value, 0, sizeof(OVERLAPPED)); |
77 | return S_OK; |
78 | } |
79 | |
80 | // ---------------------------------------------------------------------------- |
81 | // ProfilingAPIAttachDetach::OverlappedResultHolder::Wait |
82 | // |
83 | // Description: |
84 | // Uses the contained OVERLAPPED structure (pointed to by m_value) to call |
85 | // WaitForSingleObject to wait for an overlapped read or write on the pipe to complete |
86 | // (or timeout). |
87 | // |
88 | // Arguments: |
89 | // * dwMillisecondsMax - [in] Timeout for the wait |
90 | // * hPipe - [in] Handle to the pipe object carrying out the request (may be either a |
91 | // server or client pipe handle). |
92 | // * pcbReceived - [out] Number of bytes received from the overlapped request |
93 | // |
94 | // Return Value: |
95 | // HRESULT indicating success or failure |
96 | // |
97 | // Assumptions: |
98 | // * Must call code:ProfilingAPIAttachDetach::OverlappedResultHolder::Initialize first |
99 | |
100 | HRESULT ProfilingAPIAttachDetach::OverlappedResultHolder::Wait( |
101 | DWORD dwMillisecondsMax, |
102 | HANDLE hPipe, |
103 | DWORD * pcbReceived) |
104 | { |
105 | CONTRACTL |
106 | { |
107 | THROWS; |
108 | GC_TRIGGERS; |
109 | MODE_PREEMPTIVE; |
110 | CAN_TAKE_LOCK; |
111 | } |
112 | CONTRACTL_END; |
113 | |
114 | _ASSERTE(IsValidHandle(hPipe)); |
115 | _ASSERTE(m_value != NULL); |
116 | _ASSERTE(pcbReceived != NULL); |
117 | |
118 | HRESULT hr = E_UNEXPECTED; |
119 | |
120 | // Since the OVERLAPPED structure referenced by m_value contains a NULL event, the OS |
121 | // will signal hPipe itself when the operation is complete |
122 | switch (WaitForSingleObject(hPipe, dwMillisecondsMax)) |
123 | { |
124 | default: |
125 | _ASSERTE(!"Unexpected return from WaitForSingleObject()" ); |
126 | hr = E_UNEXPECTED; |
127 | break; |
128 | |
129 | case WAIT_FAILED: |
130 | hr = HRESULT_FROM_GetLastError(); |
131 | break; |
132 | |
133 | case WAIT_TIMEOUT: |
134 | hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); |
135 | break; |
136 | |
137 | case WAIT_OBJECT_0: |
138 | // Operation finished in time. Get the results |
139 | if (!GetOverlappedResult( |
140 | hPipe, |
141 | m_value, |
142 | pcbReceived, |
143 | TRUE)) // bWait: operation is done, so this returns immediately anyway |
144 | { |
145 | hr = HRESULT_FROM_GetLastError(); |
146 | } |
147 | else |
148 | { |
149 | hr = S_OK; |
150 | } |
151 | break; |
152 | } |
153 | |
154 | // The gymnastics below are to ensure that Windows is done with the overlapped |
155 | // structure, so we know it's safe to allow the base class (NewHolder) to free it |
156 | // when the destructor is called. |
157 | |
158 | if (SUCCEEDED(hr)) |
159 | { |
160 | // Operation successful, so we're done with the OVERLAPPED structure pointed to |
161 | // by m_value and may return |
162 | return hr; |
163 | } |
164 | |
165 | _ASSERTE(FAILED(hr)); |
166 | |
167 | // There was a failure waiting for or retrieving the result. Cancel the operation and |
168 | // wait again for verification that the operation is completed or canceled. |
169 | |
170 | // Note that we're ignoring whether CancelIo succeeds or fails, as our action is the |
171 | // same either way: Wait on the pipe again to verify that no active operation remains. |
172 | CancelIo(hPipe); |
173 | |
174 | if (WaitForSingleObject(hPipe, dwMillisecondsMax) == WAIT_OBJECT_0) |
175 | { |
176 | // Typical case: The wait returns successfully and quickly, so we have |
177 | // verification that the OVERLAPPED structured pointed to by m_value is done |
178 | // being used. |
179 | return hr; |
180 | } |
181 | |
182 | // Atypical case: For all our trying, we're unable to force this request to end |
183 | // before returning. Therefore, we're intentionally leaking the OVERLAPPED structured |
184 | // pointed to by m_value, as Windows may write to it at a later time. |
185 | SuppressRelease(); |
186 | return hr; |
187 | } |
188 | |
189 | |
190 | // ---------------------------------------------------------------------------- |
191 | // ProfilingAPIAttachDetach::ProfilingAPIAttachThreadStart |
192 | // |
193 | // Description: |
194 | // Thread proc for AttachThread. Serves as simple try/catch wrapper around |
195 | // ProfilingAPIAttachThreadMain |
196 | // |
197 | // Arguments: |
198 | // * LPVOID thread proc param is ignored |
199 | // |
200 | // Return Value: |
201 | // Just returns 0 always. |
202 | // |
203 | |
204 | // static |
205 | DWORD WINAPI ProfilingAPIAttachDetach::ProfilingAPIAttachThreadStart(LPVOID) |
206 | { |
207 | CONTRACTL |
208 | { |
209 | NOTHROW; |
210 | GC_TRIGGERS; |
211 | MODE_PREEMPTIVE; |
212 | CAN_TAKE_LOCK; |
213 | } |
214 | CONTRACTL_END; |
215 | |
216 | // At start of this thread, set its type so SOS !threads and anyone else knows who we |
217 | // are. |
218 | ClrFlsSetThreadType(ThreadType_ProfAPI_Attach); |
219 | |
220 | LOG(( |
221 | LF_CORPROF, |
222 | LL_INFO10, |
223 | "**PROF: AttachThread created and executing.\n" )); |
224 | |
225 | // This try block is a last-ditch stop-gap to prevent an unhandled exception on the |
226 | // AttachThread from bringing down the process. Note that if the unhandled |
227 | // exception is a terminal one, then hey, sure, let's tear everything down. Also |
228 | // note that any naughtiness in the profiler (e.g., throwing an exception from its |
229 | // Initialize callback) should already be handled before we pop back to here, so this |
230 | // is just being super paranoid. |
231 | EX_TRY |
232 | { |
233 | // Don't care about return value, thread proc will just return 0 regardless |
234 | ProfilingAPIAttachThreadMain(); |
235 | } |
236 | EX_CATCH |
237 | { |
238 | _ASSERTE(!"Unhandled exception on profiling API attach / detach thread" ); |
239 | } |
240 | EX_END_CATCH(RethrowTerminalExceptions); |
241 | |
242 | LOG(( |
243 | LF_CORPROF, |
244 | LL_INFO10, |
245 | "**PROF: AttachThread exiting.\n" )); |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | // ---------------------------------------------------------------------------- |
251 | // ProfilingAPIAttachDetach::ProfilingAPIAttachThreadMain |
252 | // |
253 | // Description: |
254 | // Main code for AttachThread. Includes all attach functionality. |
255 | // |
256 | // Return Value: |
257 | // S_OK if a profiler ever attached, error HRESULT otherwise |
258 | // |
259 | |
260 | // static |
261 | HRESULT ProfilingAPIAttachDetach::ProfilingAPIAttachThreadMain() |
262 | { |
263 | CONTRACTL |
264 | { |
265 | THROWS; |
266 | GC_TRIGGERS; |
267 | MODE_PREEMPTIVE; |
268 | CAN_TAKE_LOCK; |
269 | } |
270 | CONTRACTL_END; |
271 | |
272 | HRESULT hr; |
273 | |
274 | ProfilingAPIAttachServer attachServer; |
275 | hr = attachServer.ExecutePipeRequests(); |
276 | if (FAILED(hr)) |
277 | { |
278 | // No profiler got attached, so we're done |
279 | return hr; |
280 | } |
281 | |
282 | // If we made it here, a profiler was successfully attached. It would be nice to be |
283 | // able to assert g_profControlBlock.curProfStatus.Get() == kProfStatusActive, but |
284 | // that's prone to a theoretical race: the profiler might have attached and detached |
285 | // by the time we get here. |
286 | |
287 | return S_OK; |
288 | } |
289 | |
290 | // ---------------------------------------------------------------------------- |
291 | // ProfilingAPIAttachDetach::InitSecurityAttributes |
292 | // |
293 | // Description: |
294 | // Initializes a SECURITY_ATTRIBUTES struct using the result of |
295 | // code:ProfilingAPIAttachDetach::GetSecurityDescriptor |
296 | // |
297 | // Arguments: |
298 | // * pSecAttrs - [in/out] SECURITY_ATTRIBUTES struct to initialize |
299 | // * cbSecAttrs - Size in bytes of *pSecAttrs |
300 | // |
301 | // Return Value: |
302 | // HRESULT indicating success or failure |
303 | // |
304 | |
305 | // static |
306 | HRESULT ProfilingAPIAttachDetach::InitSecurityAttributes( |
307 | SECURITY_ATTRIBUTES * pSecAttrs, |
308 | DWORD cbSecAttrs) |
309 | { |
310 | CONTRACTL |
311 | { |
312 | THROWS; |
313 | GC_TRIGGERS; |
314 | MODE_PREEMPTIVE; |
315 | CAN_TAKE_LOCK; |
316 | } |
317 | CONTRACTL_END; |
318 | |
319 | PSECURITY_DESCRIPTOR psd = NULL; |
320 | HRESULT hr = GetSecurityDescriptor(&psd); |
321 | if (FAILED(hr)) |
322 | { |
323 | return hr; |
324 | } |
325 | |
326 | _ASSERTE(psd != NULL); |
327 | memset(pSecAttrs, 0, cbSecAttrs); |
328 | pSecAttrs->nLength = cbSecAttrs; |
329 | pSecAttrs->lpSecurityDescriptor = psd; |
330 | pSecAttrs->bInheritHandle = FALSE; |
331 | |
332 | return S_OK; |
333 | } |
334 | |
335 | //--------------------------------------------------------------------------------------- |
336 | // |
337 | // Helper function that gets the string (SDDL) form of the mandatory SID for this |
338 | // process. This encodes the integrity level of the process for use in security |
339 | // descriptors. The integrity level is capped at "high". See code:#HighGoodEnough. |
340 | // |
341 | // Arguments: |
342 | // * pwszIntegritySidString - [out] On return will point to a buffer allocated by |
343 | // Windows that contains the string representation of the SID. If |
344 | // GetIntegritySidString succeeds, the caller is responsible for freeing |
345 | // *pwszIntegritySidString via LocalFree(). |
346 | // |
347 | // Return Value: |
348 | // HRESULT indicating success or failure. |
349 | // |
350 | // |
351 | |
352 | static HRESULT GetIntegritySidString(__out LPWSTR * pwszIntegritySidString) |
353 | { |
354 | CONTRACTL |
355 | { |
356 | NOTHROW; |
357 | GC_NOTRIGGER; |
358 | MODE_ANY; |
359 | CANNOT_TAKE_LOCK; |
360 | } |
361 | CONTRACTL_END; |
362 | |
363 | HRESULT hr; |
364 | _ASSERTE(pwszIntegritySidString != NULL); |
365 | |
366 | NewArrayHolder<BYTE> pbLabel; |
367 | |
368 | // This grabs the mandatory label SID of the current process. We will write this |
369 | // SID into the security descriptor, to ensure that triggers of lower integrity |
370 | // levels may NOT access the object... with one exception. See code:#HighGoodEnough |
371 | hr = SecurityUtil::GetMandatoryLabelFromProcess(GetCurrentProcess(), &pbLabel); |
372 | if (FAILED(hr)) |
373 | { |
374 | return hr; |
375 | } |
376 | |
377 | TOKEN_MANDATORY_LABEL * ptml = (TOKEN_MANDATORY_LABEL *) pbLabel.GetValue(); |
378 | |
379 | // #HighGoodEnough: |
380 | // The mandatory label SID we write into the security descriptor is the same as that |
381 | // of the current process, with one exception. If the current process's integrity |
382 | // level > high (e.g., ASP.NET running at "system" integrity level), then write |
383 | // "high" into the security descriptor instead of the current process's actual |
384 | // integrity level. This allows a high integrity trigger to access the object. This |
385 | // implements the policy that a high integrity level is "good enough" to profile any |
386 | // process, even if the target process is at an even higher integrity level than |
387 | // "high". Why have this policy: |
388 | // * A high integrity process represents an elevated admin, which morally equates |
389 | // to a principal that should have complete control over the machine. This |
390 | // includes debugging or profiling any process. |
391 | // * According to a security expert dev on Windows, integrity level is not a |
392 | // "security feature". It's mainly useful as defense-in-depth or to protect |
393 | // IE users and admins from themselves in most cases. |
394 | // * It's impossible to spawn a system integrity trigger process outside of |
395 | // session 0 services. So profiling ASP.NET would be crazy hard without this |
396 | // policy. |
397 | DWORD * pdwIntegrityLevel = SecurityUtil::GetIntegrityLevelFromMandatorySID(ptml->Label.Sid); |
398 | if (*pdwIntegrityLevel > SECURITY_MANDATORY_HIGH_RID) |
399 | { |
400 | *pdwIntegrityLevel = SECURITY_MANDATORY_HIGH_RID; |
401 | } |
402 | |
403 | if (!ConvertSidToStringSid(ptml->Label.Sid, pwszIntegritySidString)) |
404 | { |
405 | return HRESULT_FROM_GetLastError(); |
406 | } |
407 | |
408 | return S_OK; |
409 | } |
410 | |
411 | |
412 | // ---------------------------------------------------------------------------- |
413 | // ProfilingAPIAttachDetach::GetSecurityDescriptor |
414 | // |
415 | // Description: |
416 | // Generates a security descriptor based on an ACL containing (1) an ACE that allows |
417 | // the current user read / write and (2) an ACE that allows admins read / write. |
418 | // Resulting security descriptor is returned in an [out] param, and is also cached for |
419 | // future use. |
420 | // |
421 | // Arguments: |
422 | // * ppsd - [out] Generated (or cached) security descriptor |
423 | // |
424 | // Return Value: |
425 | // HRESULT indicating success or failure. |
426 | // |
427 | |
428 | // static |
429 | HRESULT ProfilingAPIAttachDetach::GetSecurityDescriptor(PSECURITY_DESCRIPTOR * ppsd) |
430 | { |
431 | CONTRACTL |
432 | { |
433 | THROWS; |
434 | GC_TRIGGERS; |
435 | MODE_PREEMPTIVE; |
436 | CAN_TAKE_LOCK; |
437 | } |
438 | CONTRACTL_END; |
439 | |
440 | _ASSERTE(ppsd != NULL); |
441 | |
442 | if (s_pSecurityDescriptor != NULL) |
443 | { |
444 | *ppsd = s_pSecurityDescriptor; |
445 | return S_OK; |
446 | } |
447 | |
448 | // Get the user SID for the DACL |
449 | |
450 | PSID psidUser = NULL; |
451 | HRESULT hr = ProfilingAPIUtility::GetCurrentProcessUserSid(&psidUser); |
452 | if (FAILED(hr)) |
453 | { |
454 | return hr; |
455 | } |
456 | |
457 | WinAllocatedBlockHolder pvCurrentUserSidString; |
458 | |
459 | if (!ConvertSidToStringSid(psidUser, (LPWSTR *)(LPVOID *) &pvCurrentUserSidString)) |
460 | { |
461 | return HRESULT_FROM_GetLastError(); |
462 | } |
463 | |
464 | // Get the integrity / mandatory SID for the SACL, if Vista+ |
465 | |
466 | LPCWSTR pwszIntegritySid = NULL; |
467 | WinAllocatedBlockHolder pvIntegritySidString; |
468 | |
469 | hr = GetIntegritySidString((LPWSTR *) (LPVOID *) &pvIntegritySidString); |
470 | if (FAILED(hr)) |
471 | { |
472 | return hr; |
473 | } |
474 | pwszIntegritySid = (LPCWSTR) pvIntegritySidString.GetValue(); |
475 | |
476 | ULONG cbsd; |
477 | StackSString sddlSecurityDescriptor; |
478 | WinAllocatedBlockHolder pvSecurityDescriptor; |
479 | |
480 | // The following API (ConvertStringSecurityDescriptorToSecurityDescriptorW) takes a |
481 | // string representation of a security descriptor (using the SDDL language), and |
482 | // returns back the security descriptor object to be used when defining the globally |
483 | // named event or pipe object. For a description of this language, go to the help on |
484 | // the API, and click on "string-format security descriptor": |
485 | // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthz/security/security_descriptor_string_format.asp |
486 | // or look through sddl.h. |
487 | |
488 | // Cheat sheet for the subset of the format that we're using: |
489 | // |
490 | // Security Descriptor string: |
491 | // D:dacl_flags(string_ace1)(string_ace2)... (string_acen) |
492 | // Security SACL string: |
493 | // S:sacl_flags(string_ace1)(string_ace2)... (string_acen) |
494 | // Each string_ace: |
495 | // ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid |
496 | // |
497 | // The following portions of the security descriptor string are NOT used: |
498 | // O:owner_sid (b/c we want current user to be the owner) |
499 | // G:group_sid (b/c not setting the primary group of the object) |
500 | |
501 | // This reusable chunk defines the "(string_ace)" portion of the DACL. Given |
502 | // a SID, this makes an ACE for the SID with GENERIC_READ | GENERIC_WRITE access |
503 | #define ACE_STRING(AccountSidString) \ |
504 | \ |
505 | SDDL_ACE_BEGIN \ |
506 | \ |
507 | /* ace_type: "A;" An "allow" DACL (not "deny") */ \ |
508 | SDDL_ACCESS_ALLOWED SDDL_SEPERATOR \ |
509 | \ |
510 | /* (skipping ace_flags, so that no child auto-inherits from this object) */ \ |
511 | SDDL_SEPERATOR \ |
512 | \ |
513 | /* rights: "GRGW": GENERIC_READ | GENERIC_WRITE access allowed */ \ |
514 | SDDL_GENERIC_READ SDDL_GENERIC_WRITE SDDL_SEPERATOR \ |
515 | \ |
516 | /* (skipping object_guid) */ \ |
517 | SDDL_SEPERATOR \ |
518 | \ |
519 | /* (skipping inherit_object_guid) */ \ |
520 | SDDL_SEPERATOR \ |
521 | \ |
522 | /* account_sid (filled in by macro user) */ \ |
523 | AccountSidString \ |
524 | \ |
525 | SDDL_ACE_END |
526 | |
527 | |
528 | // First, construct the DACL |
529 | |
530 | sddlSecurityDescriptor.Printf( |
531 | // "D:" This is a DACL |
532 | SDDL_DACL SDDL_DELIMINATOR |
533 | |
534 | // dacl_flags: |
535 | |
536 | // "P" This is protected (i.e., don't allow security descriptor to be modified |
537 | // by inheritable ACEs) |
538 | SDDL_PROTECTED |
539 | |
540 | // (string_ace1) |
541 | // account_sid: "BA" built-in local administrators group |
542 | ACE_STRING(SDDL_BUILTIN_ADMINISTRATORS) |
543 | |
544 | // (string_ace2) |
545 | // account_sid: to be filled in with the current process token's primary SID |
546 | ACE_STRING(W("%s" )), |
547 | |
548 | // current process token's primary SID |
549 | (LPCWSTR) (LPVOID) pvCurrentUserSidString); |
550 | |
551 | // Next, add the SACL (Vista+ only) |
552 | |
553 | if (pwszIntegritySid != NULL) |
554 | { |
555 | sddlSecurityDescriptor.AppendPrintf( |
556 | // "S:" This is a SACL -- for the integrity level of the current process |
557 | SDDL_SACL SDDL_DELIMINATOR |
558 | |
559 | // The SACL ACE begins here |
560 | SDDL_ACE_BEGIN |
561 | |
562 | // ace_type: "ML;" A Mandatory Label ACE (i.e., integrity level) |
563 | SDDL_MANDATORY_LABEL SDDL_SEPERATOR |
564 | |
565 | // (skipping ace_flags, so that no child auto-inherits from this object) |
566 | SDDL_SEPERATOR |
567 | |
568 | // rights: "NWNR;" If the trigger's integrity level is lower than the |
569 | // integrity level we're writing into this security descriptor, then that |
570 | // trigger may not read or write to this object. |
571 | SDDL_NO_WRITE_UP SDDL_NO_READ_UP SDDL_SEPERATOR |
572 | |
573 | // (skipping object_guid) |
574 | SDDL_SEPERATOR |
575 | |
576 | // (skipping inherit_object_guid) |
577 | SDDL_SEPERATOR |
578 | |
579 | // To be filled in with the current process's mandatory label SID (which |
580 | // describes the current process's integrity level, capped at "high integrity") |
581 | W("%s" ) |
582 | |
583 | SDDL_ACE_END, |
584 | |
585 | // current process's mandatory label SID |
586 | pwszIntegritySid); |
587 | } |
588 | |
589 | if (!ConvertStringSecurityDescriptorToSecurityDescriptorW( |
590 | sddlSecurityDescriptor.GetUnicode(), |
591 | SDDL_REVISION_1, |
592 | (PSECURITY_DESCRIPTOR *) (LPVOID *) &pvSecurityDescriptor, |
593 | &cbsd)) |
594 | { |
595 | return HRESULT_FROM_GetLastError(); |
596 | } |
597 | |
598 | if (FastInterlockCompareExchangePointer( |
599 | &s_pSecurityDescriptor, |
600 | (PSECURITY_DESCRIPTOR) pvSecurityDescriptor, |
601 | NULL) == NULL) |
602 | { |
603 | // Ownership transferred to s_pSecurityDescriptor, so don't free it here |
604 | pvSecurityDescriptor.SuppressRelease(); |
605 | } |
606 | |
607 | _ASSERTE(s_pSecurityDescriptor != NULL); |
608 | *ppsd = s_pSecurityDescriptor; |
609 | return S_OK; |
610 | } |
611 | |
612 | |
613 | // ---------------------------------------------------------------------------- |
614 | // ProfilingAPIAttachDetach::Initialize |
615 | // |
616 | // Description: |
617 | // Perform startup (one-time-only) initialization for attach / detach infrastructure. |
618 | // This includes the Global Attach Event, but does NOT include the Global Attach Pipe |
619 | // (which is created only on demand). This is lazily called the first time the |
620 | // finalizer asks for the attach event. |
621 | // |
622 | // Return Value: |
623 | // S_OK: Attach / detach infrastructure initialized ok |
624 | // S_FALSE: Attach / detach infrastructure not initialized, but for an acceptable reason |
625 | // (e.g., executing memory- or sync- hosted) |
626 | // else: error HRESULT indicating an unacceptable failure that prevented attach / |
627 | // detach infrastructure from initializing (e.g., security problem, OOM, etc.) |
628 | // |
629 | // Assumptions: |
630 | // * By the time this is called: |
631 | // * Configuration must have been read from the registry |
632 | // * If there is a host, it has already initialized its state, including its |
633 | // intent to memory-host or sync-host. |
634 | // * Finalizer thread is initializing and is first asking for the attach event. |
635 | // |
636 | |
637 | // static |
638 | HRESULT ProfilingAPIAttachDetach::Initialize() |
639 | { |
640 | CONTRACTL |
641 | { |
642 | THROWS; |
643 | GC_TRIGGERS; |
644 | MODE_PREEMPTIVE; |
645 | CAN_TAKE_LOCK; |
646 | } |
647 | CONTRACTL_END; |
648 | |
649 | // This one assert verifies two things: |
650 | // * 1. Configuration has been read from the registry, AND |
651 | // * 2. If there is a host, it has already initialized its state. |
652 | // #2 is implied by this assert, because the host initializes its state before |
653 | // EEStartup is even called: Host directly calls CorHost2::SetHostControl to |
654 | // initialize itself, announce whether the CLR will be memory hosted, sync hosted, |
655 | // etc., and then host calls CorHost2::Start, which calls EEStartup, which |
656 | // initializes configuration information. So if configuration information is |
657 | // available, the host must have already initialized itself. |
658 | // |
659 | // The reason we care is that, for profiling API attach to be enabled during this |
660 | // run, we need to have the finalizer thread wait on multiple sync objects. And |
661 | // waiting on multiple objects is disallowed if we're memory / sync-hosted. So we |
662 | // need to know now whether waiting on multiple objects is allowed, so we know |
663 | // whether we can initialize the Attach support objects. |
664 | _ASSERTE(g_pConfig != NULL); |
665 | |
666 | // Even if we fail to create the event, this BOOL indicates we at least |
667 | // tried to. |
668 | _ASSERTE(!s_fInitializeCalled); |
669 | s_fInitializeCalled = TRUE; |
670 | |
671 | INDEBUG(VerifyMessageStructureLayout()); |
672 | |
673 | InitializeAttachThreadingMode(); |
674 | |
675 | if (s_attachThreadingMode == kOnDemand) |
676 | { |
677 | return InitializeForOnDemandMode(); |
678 | } |
679 | |
680 | _ASSERTE(s_attachThreadingMode == kAlwaysOn); |
681 | return InitializeForAlwaysOnMode(); |
682 | } |
683 | |
684 | #ifdef _DEBUG |
685 | |
686 | // ---------------------------------------------------------------------------- |
687 | // ProfilingAPIAttachDetach::VerifyMessageStructureLayout |
688 | // |
689 | // Description: |
690 | // Debug-only function that asserts if there appear to be changes to structures that |
691 | // are not allowed to change (for backward-compatibility reasons). In particular: |
692 | // * BaseRequestMessage must not change |
693 | // |
694 | |
695 | // static |
696 | void ProfilingAPIAttachDetach::VerifyMessageStructureLayout() |
697 | { |
698 | LIMITED_METHOD_CONTRACT; |
699 | |
700 | // If any of these asserts fire, then GetVersionRequestMessage is changing its binary |
701 | // layout in an incompatible way. Bad! |
702 | _ASSERTE(sizeof(GetVersionRequestMessage) == 8); |
703 | _ASSERTE(offsetof(GetVersionRequestMessage, m_cbMessage) == 0); |
704 | _ASSERTE(offsetof(GetVersionRequestMessage, m_requestMessageType) == 4); |
705 | |
706 | // If any of these asserts fire, then GetVersionResponseMessage is changing its binary |
707 | // layout in an incompatible way. Bad! |
708 | _ASSERTE(sizeof(GetVersionResponseMessage) == 12); |
709 | _ASSERTE(offsetof(GetVersionResponseMessage, m_hr) == 0); |
710 | _ASSERTE(offsetof(GetVersionResponseMessage, m_profileeVersion) == 4); |
711 | _ASSERTE(offsetof(GetVersionResponseMessage, m_minimumAllowableTriggerVersion) == 8); |
712 | } |
713 | |
714 | #endif //_DEBUG |
715 | |
716 | // ---------------------------------------------------------------------------- |
717 | // ProfilingAPIAttachDetach::InitializeAttachThreadingMode |
718 | // |
719 | // Description: |
720 | // Looks at environment and GC mode to determine whether the AttachThread should |
721 | // always be around, or created only on demand. See |
722 | // code:ProfilingAPIAttachDetach::AttachThreadingMode. |
723 | // |
724 | |
725 | // static |
726 | void ProfilingAPIAttachDetach::InitializeAttachThreadingMode() |
727 | { |
728 | CONTRACTL |
729 | { |
730 | THROWS; |
731 | GC_TRIGGERS; |
732 | MODE_PREEMPTIVE; |
733 | CAN_TAKE_LOCK; |
734 | } |
735 | CONTRACTL_END; |
736 | |
737 | _ASSERTE(s_attachThreadingMode == kUninitialized); |
738 | |
739 | // Environment variable trumps all, so check it first |
740 | DWORD dwAlwaysOn = g_pConfig->GetConfigDWORD_DontUse_( |
741 | CLRConfig::EXTERNAL_AttachThreadAlwaysOn, |
742 | GCHeapUtilities::IsServerHeap() ? 1 : 0); // Default depends on GC server mode |
743 | |
744 | if (dwAlwaysOn == 0) |
745 | { |
746 | s_attachThreadingMode = kOnDemand; |
747 | } |
748 | else |
749 | { |
750 | s_attachThreadingMode = kAlwaysOn; |
751 | } |
752 | } |
753 | |
754 | |
755 | // ---------------------------------------------------------------------------- |
756 | // ProfilingAPIAttachDetach::InitializeForAlwaysOnMode |
757 | // |
758 | // Description: |
759 | // Performs initialization specific to running in Always On mode. Specifically, this |
760 | // means creating the AttachThread. The attach event is not created in this case. |
761 | // |
762 | // Return Value: |
763 | // HRESULT indicating success or failure. |
764 | // |
765 | |
766 | // static |
767 | HRESULT ProfilingAPIAttachDetach::InitializeForAlwaysOnMode() |
768 | { |
769 | CONTRACTL |
770 | { |
771 | THROWS; |
772 | GC_TRIGGERS; |
773 | MODE_PREEMPTIVE; |
774 | CAN_TAKE_LOCK; |
775 | } |
776 | CONTRACTL_END; |
777 | |
778 | _ASSERTE(s_attachThreadingMode == kAlwaysOn); |
779 | |
780 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Attach AlwaysOn mode invoked; creating new AttachThread.\n" )); |
781 | |
782 | CreateAttachThread(); |
783 | |
784 | return S_OK; |
785 | } |
786 | |
787 | // ---------------------------------------------------------------------------- |
788 | // ProfilingAPIAttachDetach::InitializeForOnDemandMode |
789 | // |
790 | // Description: |
791 | // Performs initialization specific to running in On Demand mode. Specifically, this |
792 | // means creating the attach event. (The AttachThread will only be created when this |
793 | // event is signaled by a trigger process.) |
794 | // |
795 | // Return Value: |
796 | // HRESULT indicating success or failure. |
797 | // |
798 | |
799 | // static |
800 | HRESULT ProfilingAPIAttachDetach::InitializeForOnDemandMode() |
801 | { |
802 | CONTRACTL |
803 | { |
804 | THROWS; |
805 | GC_TRIGGERS; |
806 | MODE_PREEMPTIVE; |
807 | CAN_TAKE_LOCK; |
808 | } |
809 | CONTRACTL_END; |
810 | |
811 | _ASSERTE(s_attachThreadingMode == kOnDemand); |
812 | |
813 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Attach OnDemand mode invoked; creating attach event.\n" )); |
814 | |
815 | // The only part of attach that gets initialized before a profiler has |
816 | // actually requested to attach is the single global event that gets |
817 | // signaled from out-of-process. |
818 | |
819 | StackSString attachEventName; |
820 | HRESULT hr; |
821 | hr = GetAttachEventName(::GetCurrentProcess(), &attachEventName); |
822 | if (FAILED(hr)) |
823 | { |
824 | return hr; |
825 | } |
826 | |
827 | // Deliberately NOT using CLREvent, as it does not have support for a global name. |
828 | // It's ok not to use CLREvent, as we're assured above that we're not sync-hosted, |
829 | // which means CLREvent would just use raw Windows events anyway. |
830 | |
831 | SECURITY_ATTRIBUTES *psa = NULL; |
832 | |
833 | SECURITY_ATTRIBUTES sa; |
834 | |
835 | // Only assign security attributes for non-app container scenario |
836 | // We are assuming the default (blocking everything for app container scenario is good enough |
837 | if (!IsAppContainerProcess(::GetCurrentProcess())) |
838 | { |
839 | hr = InitSecurityAttributes(&sa, sizeof(sa)); |
840 | if (FAILED(hr)) |
841 | { |
842 | return hr; |
843 | } |
844 | |
845 | psa = &sa; |
846 | } |
847 | |
848 | _ASSERTE(s_hAttachEvent == NULL); |
849 | s_hAttachEvent = WszCreateEvent( |
850 | psa, // security attributes |
851 | FALSE, // bManualReset = FALSE: autoreset after waiting thread is unblocked |
852 | FALSE, // initial state = FALSE, i.e., unsignaled |
853 | attachEventName.GetUnicode() // Global name seen out-of-proc |
854 | ); |
855 | if (s_hAttachEvent == NULL) |
856 | { |
857 | return HRESULT_FROM_GetLastError(); |
858 | } |
859 | |
860 | return S_OK; |
861 | } |
862 | |
863 | // ---------------------------------------------------------------------------- |
864 | // ProfilingAPIAttachDetach::GetAttachEvent |
865 | // |
866 | // Description: |
867 | // Used by finalizer thread to get the profiling API attach event. First time this is |
868 | // called, the event and other supporting objects will be created. |
869 | // |
870 | // Return Value: |
871 | // The attach event or NULL if attach event creation failed during startup. In either |
872 | // case, do NOT call CloseHandle on the returned event handle. |
873 | // |
874 | // Assumptions: |
875 | // * ProfilingAPIUtility::InitializeProfiling should already have been called before |
876 | // this is called. That ensures that, if a profiler was configured to load on |
877 | // startup, then that load has already occurred by now. |
878 | // * The event's HANDLE refcount is managed solely by ProfilingAPIAttachDetach. So do |
879 | // not call CloseHandle() on the HANDLE returned. |
880 | // |
881 | // Notes: |
882 | // * If the attach event was not created on startup, then this will return NULL. |
883 | // Possible reasons why this can occur: |
884 | // * The current process is the NGEN service, OR |
885 | // * The process is sync- or memory- hosted, OR |
886 | // * Attach is running in "always on" mode, meaning we always have an AttachThread |
887 | // with a pipe, so there's no need for an event. |
888 | // |
889 | |
890 | // static |
891 | HANDLE ProfilingAPIAttachDetach::GetAttachEvent() |
892 | { |
893 | CONTRACTL |
894 | { |
895 | THROWS; |
896 | GC_TRIGGERS; |
897 | MODE_PREEMPTIVE; |
898 | CAN_TAKE_LOCK; |
899 | } |
900 | CONTRACTL_END; |
901 | |
902 | if (IsCompilationProcess()) |
903 | { |
904 | // No profiler attach on NGEN! |
905 | return NULL; |
906 | } |
907 | |
908 | if (!s_fInitializeCalled) |
909 | { |
910 | // If a profiler was supposed to load on startup, it's already happened |
911 | // now. So it's safe to set up the attach support objects, and allow |
912 | // an attaching profiler to make an attempt (which can now gracefully fail |
913 | // if a startup profiler has loaded). |
914 | |
915 | HRESULT hr = Initialize(); |
916 | if (FAILED(hr)) |
917 | { |
918 | LOG(( |
919 | LF_CORPROF, |
920 | LL_ERROR, |
921 | "**PROF: ProfilingAPIAttachDetach::Initialize failed, so this process will not " |
922 | "be able to attach a profiler. hr=0x%x.\n" , |
923 | hr)); |
924 | ProfilingAPIUtility::LogProfError(IDS_E_PROF_ATTACH_INIT, hr); |
925 | |
926 | return NULL; |
927 | } |
928 | } |
929 | |
930 | if (s_attachThreadingMode == kAlwaysOn) |
931 | { |
932 | // In always-on mode, we always have an AttachThread listening on the pipe, so |
933 | // there's no need for an event. |
934 | _ASSERTE(s_hAttachEvent == NULL); |
935 | } |
936 | |
937 | return s_hAttachEvent; |
938 | } |
939 | |
940 | |
941 | // ---------------------------------------------------------------------------- |
942 | // ProfilingAPIAttachDetach::ProcessSignaledAttachEvent |
943 | // |
944 | // Description: |
945 | // Called by finalizer thread when the finalizer thread detects that the globally |
946 | // named Profiler Attach Event is signaled. This simply spins up the AttachThread |
947 | // (starting in ProfilingAPIAttachThreadStart) and returns. |
948 | // |
949 | |
950 | // static |
951 | void ProfilingAPIAttachDetach::ProcessSignaledAttachEvent() |
952 | { |
953 | // This function is practically a leaf (though not quite), and is called from the |
954 | // finalizer thread at various points, so keeping the contract strict to allow for |
955 | // maximum flexibility on when this may called. |
956 | CONTRACTL |
957 | { |
958 | NOTHROW; |
959 | GC_NOTRIGGER; |
960 | MODE_ANY; |
961 | CANNOT_TAKE_LOCK; |
962 | } |
963 | CONTRACTL_END; |
964 | |
965 | LOG((LF_CORPROF, LL_INFO10, "**PROF: Attach event signaled; creating new AttachThread.\n" )); |
966 | |
967 | CreateAttachThread(); |
968 | } |
969 | |
970 | typedef BOOL |
971 | (WINAPI *PFN_GetAppContainerNamedObjectPath)( |
972 | HANDLE Token, |
973 | PSID AppContainerSid, |
974 | ULONG ObjectPathLength, |
975 | WCHAR * ObjectPath, |
976 | PULONG ReturnLength |
977 | ); |
978 | |
979 | static Volatile<PFN_GetAppContainerNamedObjectPath> g_pfnGetAppContainerNamedObjectPath = NULL; |
980 | |
981 | // ---------------------------------------------------------------------------- |
982 | // GetAppContainerNamedObjectPath |
983 | // |
984 | // Description: |
985 | // Retrieve named object path for the specified app container process |
986 | // The name looks something like the following: |
987 | // LowBoxNamedObjects\<AppContainer_SID> |
988 | // AppContainer_SID is the SID for the app container, for example: S-1-15-2-3-4-5-6-7-8 |
989 | // |
990 | // Arguments: |
991 | // * hProcess - handle of the app container proces |
992 | // * wszObjectPath - [out] Buffer to fill in |
993 | // * dwObjectPathSizeInChar - Size of buffer |
994 | // |
995 | HRESULT ProfilingAPIAttachDetach::GetAppContainerNamedObjectPath(HANDLE hProcess, __out_ecount(dwObjectPathSizeInChar) WCHAR * wszObjectPath, DWORD dwObjectPathSizeInChar) |
996 | { |
997 | LIMITED_METHOD_CONTRACT; |
998 | |
999 | _ASSERTE(wszObjectPath != NULL); |
1000 | |
1001 | HandleHolder hToken; |
1002 | |
1003 | if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) |
1004 | { |
1005 | return HRESULT_FROM_GetLastError(); |
1006 | } |
1007 | |
1008 | if (g_pfnGetAppContainerNamedObjectPath.Load() == NULL) |
1009 | { |
1010 | HMODULE hMod = WszGetModuleHandle(W("kernel32.dll" )); |
1011 | if (hMod == NULL) |
1012 | { |
1013 | // This should never happen but I'm checking it anyway |
1014 | return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); |
1015 | } |
1016 | |
1017 | PFN_GetAppContainerNamedObjectPath pfnGetAppContainerNamedObjectPath = (PFN_GetAppContainerNamedObjectPath) |
1018 | ::GetProcAddress( |
1019 | hMod, |
1020 | "GetAppContainerNamedObjectPath" ); |
1021 | |
1022 | if (!pfnGetAppContainerNamedObjectPath) |
1023 | { |
1024 | |
1025 | return HRESULT_FROM_GetLastError(); |
1026 | } |
1027 | |
1028 | // We should always get the same address back from GetProcAddress so there is no concern for race condition |
1029 | g_pfnGetAppContainerNamedObjectPath = pfnGetAppContainerNamedObjectPath; |
1030 | } |
1031 | |
1032 | DWORD dwBufferLength; |
1033 | if (!g_pfnGetAppContainerNamedObjectPath( |
1034 | hToken, // Process token |
1035 | NULL, // AppContainer package SID optional. |
1036 | dwObjectPathSizeInChar, // Object path length |
1037 | wszObjectPath, // Object path |
1038 | &dwBufferLength // return length |
1039 | )) |
1040 | { |
1041 | return HRESULT_FROM_GetLastError(); |
1042 | } |
1043 | |
1044 | return S_OK; |
1045 | } |
1046 | |
1047 | |
1048 | // @TODO: Update this once Windows header file is updated to Win8 |
1049 | #ifndef TokenIsAppContainer |
1050 | #define TokenIsAppContainer ((TOKEN_INFORMATION_CLASS) 29) |
1051 | #endif |
1052 | |
1053 | // ---------------------------------------------------------------------------- |
1054 | // ProfilingAPIAttachDetach::IsAppContainerProcess |
1055 | // |
1056 | // Description: |
1057 | // Return whether the specified process is a app container process |
1058 | // |
1059 | |
1060 | // static |
1061 | BOOL ProfilingAPIAttachDetach::IsAppContainerProcess(HANDLE hProcess) |
1062 | { |
1063 | CONTRACTL |
1064 | { |
1065 | NOTHROW; |
1066 | GC_NOTRIGGER; |
1067 | MODE_ANY; |
1068 | CANNOT_TAKE_LOCK; |
1069 | } |
1070 | CONTRACTL_END; |
1071 | |
1072 | HandleHolder hToken; |
1073 | |
1074 | if(!::OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) |
1075 | { |
1076 | return FALSE; |
1077 | } |
1078 | |
1079 | BOOL fIsAppContainerProcess; |
1080 | DWORD dwReturnLength; |
1081 | if (!::GetTokenInformation( |
1082 | hToken, |
1083 | TokenIsAppContainer, |
1084 | &fIsAppContainerProcess, |
1085 | sizeof(BOOL), |
1086 | &dwReturnLength) || |
1087 | dwReturnLength != sizeof(BOOL)) |
1088 | { |
1089 | return FALSE; |
1090 | } |
1091 | else |
1092 | { |
1093 | return fIsAppContainerProcess; |
1094 | } |
1095 | } |
1096 | |
1097 | //--------------------------------------------------------------------------------------- |
1098 | // |
1099 | // Called by other points in the runtime (e.g., finalizer thread) to create a new thread |
1100 | // to fill the role of the AttachThread. |
1101 | // |
1102 | |
1103 | // static |
1104 | void ProfilingAPIAttachDetach::CreateAttachThread() |
1105 | { |
1106 | // This function is practically a leaf (though not quite), and is called from the |
1107 | // finalizer thread at various points, so keeping the contract strict to allow for |
1108 | // maximum flexibility on when this may called. |
1109 | CONTRACTL |
1110 | { |
1111 | NOTHROW; |
1112 | GC_NOTRIGGER; |
1113 | MODE_ANY; |
1114 | CANNOT_TAKE_LOCK; |
1115 | } |
1116 | CONTRACTL_END; |
1117 | |
1118 | HandleHolder hAttachThread; |
1119 | |
1120 | // The AttachThread is intentionally not an EE Thread-object thread |
1121 | hAttachThread = ::CreateThread( |
1122 | NULL, // lpThreadAttributes; don't want child processes inheriting this handle |
1123 | 0, // dwStackSize (0 = use default) |
1124 | ProfilingAPIAttachThreadStart, |
1125 | NULL, // lpParameter (none to pass) |
1126 | 0, // dwCreationFlags (0 = use default flags, start thread immediately) |
1127 | NULL // lpThreadId (don't need therad ID) |
1128 | ); |
1129 | if (hAttachThread == NULL) |
1130 | { |
1131 | LOG(( |
1132 | LF_CORPROF, |
1133 | LL_ERROR, |
1134 | "**PROF: Failed to create AttachThread. GetLastError=%d.\n" , |
1135 | GetLastError())); |
1136 | |
1137 | // No other error-specific code really makes much sense here. An error here is |
1138 | // probably due to serious OOM issues which would also probably prevent logging |
1139 | // an event. A trigger process will report that it waited for the pipe to be |
1140 | // created, and timed out during the wait. That should be enough for the user. |
1141 | } |
1142 | } |
1143 | |
1144 | // ---------------------------------------------------------------------------- |
1145 | // CLRProfilingImpl::AttachProfiler |
1146 | // |
1147 | // Description: |
1148 | // A wrapper COM function to invoke AttachProfiler with parameters from |
1149 | // profiling trigger along with a runtime version string |
1150 | // |
1151 | HRESULT CLRProfilingImpl::AttachProfiler(DWORD dwProfileeProcessID, |
1152 | DWORD dwMillisecondsMax, |
1153 | const CLSID *pClsidProfiler, |
1154 | LPCWSTR wszProfilerPath, |
1155 | void *pvClientData, |
1156 | UINT cbClientData) |
1157 | { |
1158 | CONTRACTL |
1159 | { |
1160 | NOTHROW; |
1161 | GC_TRIGGERS; |
1162 | MODE_PREEMPTIVE; |
1163 | CAN_TAKE_LOCK; |
1164 | SO_NOT_MAINLINE; |
1165 | } |
1166 | CONTRACTL_END; |
1167 | |
1168 | WCHAR wszRuntimeVersion[MAX_PATH_FNAME]; |
1169 | DWORD dwSize = _countof(wszRuntimeVersion); |
1170 | HRESULT hr = GetCORVersionInternal(wszRuntimeVersion, dwSize, &dwSize); |
1171 | if (FAILED(hr)) |
1172 | return hr; |
1173 | |
1174 | return ::AttachProfiler(dwProfileeProcessID, |
1175 | dwMillisecondsMax, |
1176 | pClsidProfiler, |
1177 | wszProfilerPath, |
1178 | pvClientData, |
1179 | cbClientData, |
1180 | wszRuntimeVersion); |
1181 | } |
1182 | |
1183 | |
1184 | // Contract for public APIs. These must be NOTHROW. |
1185 | EXTERN_C const IID IID_ICLRProfiling; |
1186 | |
1187 | HRESULT |
1188 | CreateCLRProfiling( |
1189 | __out void ** ppCLRProfilingInstance) |
1190 | { |
1191 | CONTRACTL |
1192 | { |
1193 | NOTHROW; |
1194 | } |
1195 | CONTRACTL_END; |
1196 | |
1197 | HRESULT hrIgnore = S_OK; // ignored HResult |
1198 | HRESULT hr = S_OK; |
1199 | HMODULE hMod = NULL; |
1200 | IUnknown * pCordb = NULL; |
1201 | |
1202 | LOG((LF_CORDB, LL_EVERYTHING, "Calling CreateCLRProfiling" )); |
1203 | |
1204 | NewHolder<CLRProfilingImpl> pProfilingImpl = new (nothrow) CLRProfilingImpl(); |
1205 | if (pProfilingImpl == NULL) |
1206 | return E_OUTOFMEMORY; |
1207 | |
1208 | hr = pProfilingImpl->QueryInterface(IID_ICLRProfiling, ppCLRProfilingInstance); |
1209 | if (SUCCEEDED(hr)) |
1210 | { |
1211 | pProfilingImpl.SuppressRelease(); |
1212 | return S_OK; |
1213 | } |
1214 | return E_FAIL; |
1215 | } |
1216 | |
1217 | #endif // FEATURE_PROFAPI_ATTACH_DETACH |
1218 | |