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// ProfAttachClient.cpp
6//
7
8//
9// Implementation of the AttachProfiler() API, used by CLRProfilingImpl::AttachProfiler.
10//
11// CLRProfilingImpl::AttachProfiler (in ndp\clr\src\DLLS\shim\shimapi.cpp) just thunks down
12// to mscorwks!AttachProfiler (below), which calls other functions in this file, all of
13// which are in mscorwks.dll. The AttachProfiler() API is consumed by trigger processes
14// in order to force the runtime of a target process to load a profiler. The prime
15// portion of this implementation lives in ProfilingAPIAttachClient, which handles
16// opening a client connection to the pipe created by the target profilee, and sending
17// requests across that pipe to force the target profilee (which acts as the pipe server)
18// to attach a profiler.
19
20//
21// Since these functions are executed by the trigger process, they intentionally seek the
22// event and pipe objects by names based on the PID of the target app to profile (which
23// is NOT the PID of the current process, as the current process is just the trigger
24// process). This implies, for example, that the variable
25// ProfilingAPIAttachDetach::s_hAttachEvent is of no use to the current process, as
26// s_hAttachEvent is only applicable to the target profilee app's process.
27//
28// Most of the contracts in this file follow the lead of default contracts throughout the
29// CLR (triggers, throws, etc.). Since AttachProfiler() is called by native code either
30// on a native thread created by the trigger process, or via a P/Invoke, these functions
31// will all run on threads in MODE_PREEMPTIVE.
32// * MODE_PREEMPTIVE also allows for GetThread() == NULL, which will be the case for
33// a native-only thread calling AttachProfiler()
34//
35
36// ======================================================================================
37
38#include "common.h"
39
40#ifdef FEATURE_PROFAPI_ATTACH_DETACH
41#include "tlhelp32.h" // For CreateToolhelp32Snapshot, etc. in MightProcessExist()
42#include "profilinghelper.h"
43#include "profattach.h"
44#include "profattach.inl"
45#include "profattachclient.h"
46
47// CLRProfilingImpl::AttachProfiler calls this, which itself is just a simple wrapper around
48// code:ProfilingAPIAttachClient::AttachProfiler. See public documentation for a
49// description of the parameters, return value, etc.
50extern "C" HRESULT STDMETHODCALLTYPE AttachProfiler(
51 DWORD dwProfileeProcessID,
52 DWORD dwMillisecondsMax,
53 const CLSID * pClsidProfiler,
54 LPCWSTR wszProfilerPath,
55 void * pvClientData,
56 UINT cbClientData,
57 LPCWSTR wszRuntimeVersion)
58{
59 CONTRACTL
60 {
61 NOTHROW;
62 GC_TRIGGERS;
63 MODE_PREEMPTIVE;
64 CAN_TAKE_LOCK;
65
66 // This is the entrypoint into the EE by a trigger process. As such, this
67 // is profiling-specific and not considered mainline EE code.
68 SO_NOT_MAINLINE;
69 }
70 CONTRACTL_END;
71
72 HRESULT hr = E_UNEXPECTED;
73
74 EX_TRY
75 {
76 ProfilingAPIAttachClient attachClient;
77 hr = attachClient.AttachProfiler(
78 dwProfileeProcessID,
79 dwMillisecondsMax,
80 pClsidProfiler,
81 wszProfilerPath,
82 pvClientData,
83 cbClientData,
84 wszRuntimeVersion);
85 }
86 EX_CATCH
87 {
88 hr = GET_EXCEPTION()->GetHR();
89 _ASSERTE(!"Unhandled exception executing AttachProfiler API");
90 }
91 EX_END_CATCH(RethrowTerminalExceptions);
92
93 // For ease-of-use by profilers, normalize similar HRESULTs down.
94 if ((hr == HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE)) ||
95 (hr == HRESULT_FROM_WIN32(ERROR_PIPE_NOT_CONNECTED)) ||
96 (hr == HRESULT_FROM_WIN32(ERROR_BAD_PIPE)))
97 {
98 hr = CORPROF_E_IPC_FAILED;
99 }
100
101 return hr;
102}
103
104
105// ----------------------------------------------------------------------------
106// AdjustRemainingMs
107//
108// Description:
109// Simple helper to do timeout arithmetic. Timeout arithmetic is based on
110// CLRGetTickCount64, which returns an unsigned 64-bit int representing the number of
111// milliseconds transpired since the machine has been up. Since a machine is unlikely
112// to be up for > 500 million years, wraparound issues may be ignored.
113//
114// Caller repeatedly calls this function (usually once before a lenghty operation
115// with a timeout) to check on its remaining time allotment and get alerted when time
116// runs out.
117//
118// Arguments:
119// * ui64StartTimeMs - [in] When did caller begin, in tick counts (ms)?
120// * dwMillisecondsMax - [in] How much time does caller have, total?
121// * pdwMillisecondsRemaining - [out] Remaining ms caller has before exceeding its
122// timeout.
123//
124// Return Value:
125// HRESULT_FROM_WIN32(ERROR_TIMEOUT) if caller is out of time; else S_OK
126//
127
128static HRESULT AdjustRemainingMs(
129 ULONGLONG ui64StartTimeMs,
130 DWORD dwMillisecondsMax,
131 DWORD * pdwMillisecondsRemaining)
132{
133 CONTRACTL
134 {
135 THROWS;
136 GC_TRIGGERS;
137 MODE_PREEMPTIVE;
138 CAN_TAKE_LOCK;
139 }
140 CONTRACTL_END;
141
142 _ASSERTE(pdwMillisecondsRemaining != NULL);
143
144 ULONGLONG ui64NowMs = CLRGetTickCount64();
145
146 if (ui64NowMs - ui64StartTimeMs > dwMillisecondsMax)
147 {
148 // Out of time!
149 return HRESULT_FROM_WIN32(ERROR_TIMEOUT);
150 }
151
152 // How much of dwMillisecondsMax remain to be used?
153 *pdwMillisecondsRemaining = dwMillisecondsMax - static_cast<DWORD>(ui64NowMs - ui64StartTimeMs);
154 return S_OK;
155}
156
157
158// ----------------------------------------------------------------------------
159// ProfilingAPIAttachClient::AttachProfiler
160//
161// Description:
162// Main worker for AttachProfiler API. Trigger process calls mscoree!AttachProfiler
163// which just defers to this function to do all the work.
164//
165// ** See public API docs for description of params / return value. **
166//
167// Note that, in the trigger process, the dwMillisecondsMax timeouts are cumulative:
168// the caller specifies a single timeout value for the entire AttachProfiler API call.
169// So we must constantly adjust the timeouts we use so they're based on the time
170// remaining from the original dwMillisecondsMax specified by the AttachProfiler API
171// client.
172//
173
174HRESULT ProfilingAPIAttachClient::AttachProfiler(
175 DWORD dwProfileeProcessID,
176 DWORD dwMillisecondsMax,
177 const CLSID * pClsidProfiler,
178 LPCWSTR wszProfilerPath,
179 void * pvClientData,
180 UINT cbClientData,
181 LPCWSTR wszRuntimeVersion)
182{
183 CONTRACTL
184 {
185 THROWS;
186 GC_TRIGGERS;
187 MODE_PREEMPTIVE;
188 CAN_TAKE_LOCK;
189 }
190 CONTRACTL_END;
191
192 InitializeLogging();
193
194 HRESULT hr;
195
196 // Is cbClientData just crazy-sick-overflow big?
197 if (cbClientData >= 0xFFFFffffUL - sizeof(AttachRequestMessage))
198 {
199 return E_OUTOFMEMORY;
200 }
201
202 if ((pvClientData == NULL) && (cbClientData != 0))
203 {
204 return E_INVALIDARG;
205 }
206
207 if (pClsidProfiler == NULL)
208 {
209 return E_INVALIDARG;
210 }
211
212 if ((wszProfilerPath != NULL) && (wcslen(wszProfilerPath) >= MAX_LONGPATH))
213 {
214 return E_INVALIDARG;
215 }
216
217 // See if we can early-out due to the profilee process ID not existing.
218 // MightProcessExist() only returns FALSE if it has positively verified the process
219 // ID didn't exist when MightProcessExist() was called. So it might incorrectly
220 // return TRUE (if it hit an error trying to determine whether the process exists).
221 // But that's ok, as we'll catch a nonexistent process later on when we try to fiddle
222 // with its event & pipe. MightProcessExist() is used strictly as an optional
223 // optimization to early-out before waiting for the event to appear.
224 if (!MightProcessExist(dwProfileeProcessID))
225 {
226 return CORPROF_E_PROFILEE_PROCESS_NOT_FOUND;
227 }
228
229 // Adjust time out value according to env var COMPlus_ProfAPI_AttachProfilerTimeoutInMs
230 // The default is 10 seconds as we want to avoid client (trigger process) time out too early
231 // due to wait operation for concurrent GC in the server (profilee side)
232 DWORD dwMillisecondsMinFromEnv = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ProfAPI_AttachProfilerMinTimeoutInMs);
233
234 if (dwMillisecondsMax < dwMillisecondsMinFromEnv)
235 dwMillisecondsMax = dwMillisecondsMinFromEnv;
236
237#ifdef _DEBUG
238 {
239 WCHAR wszClsidProfiler[40];
240 if (!StringFromGUID2(*pClsidProfiler, wszClsidProfiler, _countof(wszClsidProfiler)))
241 {
242 wcscpy_s(&wszClsidProfiler[0], _countof(wszClsidProfiler), W("(error)"));
243 }
244 LOG((
245 LF_CORPROF,
246 LL_INFO10,
247 "**PROF TRIGGER: mscorwks!AttachProfiler invoked with Trigger Process ID: '%d', "
248 "Target Profilee Process ID: '%d', dwMillisecondsMax: '%d', pClsidProfiler: '%S',"
249 "wszProfilerPath: '%S'\n",
250 GetProcessId(GetCurrentProcess()),
251 dwProfileeProcessID,
252 dwMillisecondsMax,
253 wszClsidProfiler,
254 wszProfilerPath == NULL ? W("") : wszProfilerPath));
255 }
256#endif // _DEBUG
257
258 // See code:AdjustRemainingMs
259 ULONGLONG ui64StartTimeMs = CLRGetTickCount64();
260 DWORD dwMillisecondsRemaining = dwMillisecondsMax;
261
262 HandleHolder hProfileeProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwProfileeProcessID);
263 if (!hProfileeProcess)
264 {
265 LOG((
266 LF_CORPROF,
267 LL_ERROR,
268 "**PROF TRIGGER: OpenProcess failed. LastError=0x%x.\n",
269 ::GetLastError()));
270 return HRESULT_FROM_GetLastError();
271 }
272
273 StackSString attachPipeName;
274 ProfilingAPIAttachDetach::GetAttachPipeNameForPidAndVersion(hProfileeProcess, wszRuntimeVersion, &attachPipeName);
275
276 // Try to open pipe with 0ms timeout in case the pipe is still around from
277 // a previous attach request
278 hr = OpenPipeClient(attachPipeName.GetUnicode(), 0);
279 if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
280 {
281 // Pipe doesn't exist, so signal attach event and retry. Note that any other
282 // failure from the above OpenPipeClient call will NOT cause us to signal
283 // the attach event, as signaling the attach event can only help with making
284 // sure the pipe gets created, and nothing else.
285 StackSString attachEventName;
286 ProfilingAPIAttachDetach::GetAttachEventNameForPidAndVersion(hProfileeProcess, wszRuntimeVersion, &attachEventName);
287 hr = SignalAttachEvent(attachEventName.GetUnicode());
288 if (FAILED(hr))
289 {
290 LOG((
291 LF_CORPROF,
292 LL_ERROR,
293 "**PROF TRIGGER: Unable to signal the global attach event. hr=0x%x.\n",
294 hr));
295
296 // It's reasonable for SignalAttachEvent to err out if the event
297 // simply doesn't exist. This happens on server apps that just circumvent
298 // using an event. They just create the AttachThread and attach pipe on
299 // startup, and are always listening on the pipe. So if event signaling
300 // failed due to nonexistent event, keep on going and try connecting to the
301 // pipe again. But if event signaling failed for any other reason, that's
302 // unexpected so give up.
303 if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
304 {
305 return hr;
306 }
307 }
308
309 hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
310 if (FAILED(hr))
311 {
312 return hr;
313 }
314
315 hr = OpenPipeClient(attachPipeName.GetUnicode(), dwMillisecondsRemaining);
316 }
317
318 // hr now holds the result of either the original OpenPipeClient call (if it
319 // failed for a reason other than ERROR_FILE_NOT_FOUND) or the 2nd
320 // OpenPipeClient call (if the first call yielded ERROR_FILE_NOT_FOUND and we
321 // signaled the event and retried).
322 if (FAILED(hr))
323 {
324 LOG((
325 LF_CORPROF,
326 LL_ERROR,
327 "**PROF TRIGGER: Unable to open a client connection to the pipe. hr=0x%x.\n",
328 hr));
329 return hr;
330 }
331
332 // At this point the pipe is definitely open
333 _ASSERTE(IsValidHandle(m_hPipeClient));
334
335 hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
336 if (FAILED(hr))
337 {
338 return hr;
339 }
340
341 // Send the GetVersion message and verify we're talking the same language
342 hr = VerifyVersionIsCompatible(dwMillisecondsRemaining);
343 if (FAILED(hr))
344 {
345 return hr;
346 }
347
348 hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
349 if (FAILED(hr))
350 {
351 return hr;
352 }
353
354 // Send the attach message!
355 HRESULT hrAttach;
356 hr = SendAttachRequest(
357 dwMillisecondsRemaining,
358 pClsidProfiler,
359 wszProfilerPath,
360 pvClientData,
361 cbClientData,
362 &hrAttach);
363 if (FAILED(hr))
364 {
365 return hr;
366 }
367
368 LOG((
369 LF_CORPROF,
370 LL_INFO10,
371 "**PROF TRIGGER: AttachProfiler succeeded sending attach request. Trigger Process ID: '%d', "
372 "Target Profilee Process ID: '%d', Attach HRESULT: '0x%x'\n",
373 GetProcessId(GetCurrentProcess()),
374 dwProfileeProcessID,
375 hrAttach));
376
377 return hrAttach;
378}
379
380// ----------------------------------------------------------------------------
381// ProfilingAPIAttachClient::MightProcessExist
382//
383// Description:
384// Returns BOOL indicating whether a process with the specified process ID might exist
385// on the local computer.
386//
387// Arguments:
388// * dwProcessID - Process ID to look up
389//
390// Return Value:
391// nonzero if process might possibly exist; FALSE if not
392//
393// Notes:
394// * Since processes come and go while this function executes, this should only be
395// used on a process ID that is supposed to exist both before and after this
396// function returns. A return of FALSE reliably tells you that supposition is
397// wrong. A return of TRUE, however, only means the process ID existed when this
398// function did its search. It's still possible the process has exited by the time
399// this function returns.
400// * If this function is unsure of a process's existence (e.g., if it encounters an
401// error while trying to find out), it errs on the side of optimism and returns
402// TRUE.
403//
404
405BOOL ProfilingAPIAttachClient::MightProcessExist(DWORD dwProcessID)
406{
407 CONTRACTL
408 {
409 NOTHROW;
410 GC_NOTRIGGER;
411 FORBID_FAULT;
412 MODE_ANY;
413 CANNOT_TAKE_LOCK;
414 }
415 CONTRACTL_END;
416
417 // There are a few ways to check whether a process exists. Some dismissed
418 // alternatives:
419 //
420 // * OpenProcess() with a "limited" access right.
421 // * Even relatively limited access rights such as SYNCHRONIZE and
422 // PROCESS_QUERY_INFORMATION often fail with ERROR_ACCESS_DENIED, even if
423 // the caller is running as administrator.
424 //
425 // * EnumProcesses() + search through returned PIDs
426 // * EnumProcesses() requires psychic powers to know how big to allocate the
427 // array of PIDs to receive (EnumProcesses() won't give you a hint if
428 // you're wrong).
429 //
430 // Method of choice is CreateToolhelp32Snapshot, which gives an enumerator to iterate
431 // through all processes.
432
433 // Take a snapshot of all processes in the system.
434 HandleHolder hProcessSnap = CreateToolhelp32Snapshot(
435 TH32CS_SNAPPROCESS,
436 0 // Unused when snap type is TH32CS_SNAPPROCESS
437 );
438 if (hProcessSnap == INVALID_HANDLE_VALUE)
439 {
440 // Dunno if process exists. Err on the side of optimism
441 return TRUE;
442 }
443
444 // Set the size of the structure before using it.
445 PROCESSENTRY32 entry;
446 ZeroMemory(&entry, sizeof(entry));
447 entry.dwSize = sizeof(PROCESSENTRY32);
448
449 // Start enumeration with Process32First. It will set dwSize to tell us how many
450 // members of PROCESSENTRY32 we can trust. We only need th32ProcessID
451 if (!Process32First(hProcessSnap, &entry) ||
452 (offsetof(PROCESSENTRY32, th32ProcessID) + sizeof(entry.th32ProcessID) > entry.dwSize))
453 {
454 // Can't tell if process exists, so assume it might
455 return TRUE;
456 }
457
458 do
459 {
460 if (entry.th32ProcessID == dwProcessID)
461 {
462 // Definitely exists
463 return TRUE;
464 }
465 } while (Process32Next(hProcessSnap, &entry));
466
467 // Process32Next() failed. Return FALSE only if we exhausted our search
468 return (GetLastError() != ERROR_NO_MORE_FILES);
469}
470
471
472
473// ----------------------------------------------------------------------------
474// ProfilingAPIAttachClient::OpenPipeClient
475//
476// Description:
477// Attempts to create a client connection to the remote server pipe
478//
479// Arguments:
480// * wszPipeName - Name of pipe to connect to.
481// * dwMillisecondsMax - Total ms to spend trying to connect to the pipe.
482//
483// Return Value:
484// HRESULT indicating success / failure
485//
486
487HRESULT ProfilingAPIAttachClient::OpenPipeClient(
488 LPCWSTR wszPipeName,
489 DWORD dwMillisecondsMax)
490{
491 CONTRACTL
492 {
493 THROWS;
494 GC_TRIGGERS;
495 MODE_PREEMPTIVE;
496 CAN_TAKE_LOCK;
497 }
498 CONTRACTL_END;
499
500 const DWORD kSleepMsUntilRetryCreateFile = 100;
501 HRESULT hr;
502 DWORD dwErr;
503
504 // See code:AdjustRemainingMs
505 ULONGLONG ui64StartTimeMs = CLRGetTickCount64();
506 DWORD dwMillisecondsRemaining = dwMillisecondsMax;
507
508 HandleHolder hPipeClient;
509
510 // We need to wait until the pipe is both CREATED (i.e., target profilee app has
511 // created the server end of the pipe) and AVAILABLE (i.e., no other trigger has opened
512 // the client end to the pipe). There is no Win32 API to wait until the pipe is
513 // CREATED, so we must make our own retry loop that calls CreateFileW. Once the pipe
514 // is known to be CREATED, we can use WaitNamedPipe to wait until the pipe is
515 // AVAILABLE. (Note: It would have been nice if we could use WaitNamedPipe to wait
516 // until the pipe is both CREATED and AVAILABLE. But WaitNamedPipe just returns an
517 // error immediately if the pipe is not yet CREATED, regardless of the timeout value
518 // specified.)
519 while (TRUE)
520 {
521 // This CreateFile call doesn't create the pipe. The pipe must be created by the
522 // target profilee. This CreateFile call attempts to open a client connection to
523 // the pipe. If CreateFile succeeds, that implies the pipe had already been
524 // successfully CREATED by the target profilee, and is AVAILABLE, and we now have
525 // a client connection to the pipe ready to go.
526 hPipeClient = CreateFileW(
527 wszPipeName,
528 GENERIC_READ | GENERIC_WRITE,
529 0, // dwShareMode (i.e., no sharing)
530 NULL, // lpSecurityAttributes (i.e., handle not inheritable and
531 // only current user may access this handle)
532 OPEN_EXISTING, // Only open (don't create) the pipe
533 FILE_FLAG_OVERLAPPED, // Using overlapped I/O allows async ops w/ timeout
534 NULL); // hTemplateFile
535
536 if (hPipeClient != INVALID_HANDLE_VALUE)
537 {
538 // CreateFile succeeded! Pipe is CREATED (by target profilee)
539 // and AVAILABLE and we're connected
540 break;
541 }
542
543 // Opening the pipe failed. Why?
544 dwErr = GetLastError();
545 switch(dwErr)
546 {
547 default:
548 // Any error other than the ones specifically brought out below isn't
549 // retry-able (e.g., security failure)
550 return HRESULT_FROM_WIN32(dwErr);
551
552 case ERROR_FILE_NOT_FOUND:
553 // Pipe not CREATED yet. Can we retry?
554 if (dwMillisecondsRemaining <= kSleepMsUntilRetryCreateFile)
555 {
556 // No time left, gotta bail!
557 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
558 }
559
560 // Sleep and retry
561 // (bAlertable=FALSE: don't wake up due to overlapped I/O)
562 ClrSleepEx(kSleepMsUntilRetryCreateFile, FALSE);
563 dwMillisecondsRemaining -= kSleepMsUntilRetryCreateFile;
564 break;
565
566 case ERROR_PIPE_BUSY:
567 // Pipe CREATED, but it's not AVAILABLE. Wait until it's AVAILABLE
568
569 LOG((
570 LF_CORPROF,
571 LL_INFO10,
572 "**PROF TRIGGER: Found pipe, but pipe is busy. Waiting until pipe is available.\n"));
573
574 hr = AdjustRemainingMs(ui64StartTimeMs, dwMillisecondsMax, &dwMillisecondsRemaining);
575 if (FAILED(hr))
576 {
577 return HRESULT_FROM_WIN32(ERROR_PIPE_BUSY);
578 }
579
580 if (!WaitNamedPipeW(wszPipeName, dwMillisecondsRemaining))
581 {
582 // If we timeout here, convert the error into something more useful
583 dwErr = GetLastError();
584 if ((dwErr == ERROR_TIMEOUT) || (dwErr == ERROR_SEM_TIMEOUT))
585 {
586 return HRESULT_FROM_WIN32(ERROR_PIPE_BUSY);
587 }
588
589 // Failed for a reason other timeout. Send that reason back to caller
590 LOG((
591 LF_CORPROF,
592 LL_ERROR,
593 "**PROF TRIGGER: WaitNamedPipe failed for a reason other timeout. hr=0x%x.\n",
594 HRESULT_FROM_WIN32(dwErr)));
595 return HRESULT_FROM_WIN32(dwErr);
596 }
597
598 // Pipe should be ready to open now, so retry. Note that it's still
599 // possible that another client sneaks in and connects before we get a
600 // chance to. If that happens, CreateFile will fail again, and we'll end up
601 // here waiting again (until we timeout).
602 break;
603 }
604 }
605
606 // Only way to exit loop above is if pipe is CREATED and AVAILABLE.
607 _ASSERTE(IsValidHandle(hPipeClient));
608
609 // We now have a valid handle on the pipe, which means we're connected
610 // to the pipe, and no one else is
611
612 // change to message-read mode.
613 DWORD dwMode = PIPE_READMODE_MESSAGE;
614 if (!SetNamedPipeHandleState(
615 hPipeClient, // pipe handle
616 &dwMode, // new pipe mode (PIPE_READMODE_MESSAGE)
617 NULL, // lpMaxCollectionCount, must be NULL when client & server on same box
618 NULL)) // lpCollectDataTimeout, must be NULL when client & server on same box
619 {
620 hr = HRESULT_FROM_GetLastError();
621 LOG((
622 LF_CORPROF,
623 LL_ERROR,
624 "**PROF TRIGGER: SetNamedPipeHandleState failed. hr=0x%x.\n",
625 hr));
626 return hr;
627 }
628
629 // Pipe's client handle is now ready for use by this class
630 m_hPipeClient = (HANDLE) hPipeClient;
631
632 // Ownership transferred to this class, so this function shouldn't call CloseHandle()
633 hPipeClient.SuppressRelease();
634
635 return S_OK;
636}
637
638
639// ----------------------------------------------------------------------------
640// ProfilingAPIAttachClient::SignalAttachEvent
641//
642// Description:
643// Trigger process calls this (indirectly via AttachProfiler()) to find, open, and
644// signal the Globally Named Attach Event.
645//
646// Arguments:
647// * wszEventName - Name of event to signal
648//
649// Return Value:
650// HRESULT indicating success or failure.
651//
652
653HRESULT ProfilingAPIAttachClient::SignalAttachEvent(LPCWSTR wszEventName)
654{
655 CONTRACTL
656 {
657 THROWS;
658 GC_TRIGGERS;
659 MODE_PREEMPTIVE;
660 CAN_TAKE_LOCK;
661 }
662 CONTRACTL_END;
663
664 HandleHolder hAttachEvent;
665
666 hAttachEvent = OpenEventW(
667 EVENT_MODIFY_STATE, // dwDesiredAccess
668 FALSE, // bInheritHandle
669 wszEventName);
670 if (hAttachEvent == NULL)
671 {
672 return HRESULT_FROM_GetLastError();
673 }
674
675 // Dealing directly with Windows event objects, not CLR event cookies, so
676 // using Win32 API directly. Note that none of this code executes on Unix,
677 // so the CLR wrapper is of no use to us anyway.
678#pragma push_macro("SetEvent")
679#undef SetEvent
680 if (!SetEvent(hAttachEvent))
681#pragma pop_macro("SetEvent")
682 {
683 return HRESULT_FROM_GetLastError();
684 }
685
686 return S_OK;
687}
688
689
690// ----------------------------------------------------------------------------
691// ProfilingAPIAttachClient::VerifyVersionIsCompatible
692//
693// Description:
694// Sends a GetVersion request message across the pipe to the target profilee, reads
695// the response, and determines if the response allows for compatible communication.
696//
697// Arguments:
698// * dwMillisecondsMax - How much time do we have left to wait for the response?
699//
700// Return Value:
701// HRESULT indicating success or failure. If pipe communication succeeds, but we
702// determine that the response doesn't allow for compatible communication, return
703// CORPROF_E_PROFILEE_INCOMPATIBLE_WITH_TRIGGER.
704//
705// Assumptions:
706// * Client connection should be established before calling this function (or a
707// callee will assert).
708//
709
710HRESULT ProfilingAPIAttachClient::VerifyVersionIsCompatible(
711 DWORD dwMillisecondsMax)
712{
713 STANDARD_VM_CONTRACT;
714 HRESULT hr;
715 DWORD cbReceived;
716 GetVersionRequestMessage requestMsg;
717 GetVersionResponseMessage responseMsg;
718
719 hr = SendAndReceive(
720 dwMillisecondsMax,
721 reinterpret_cast<LPVOID>(&requestMsg),
722 static_cast<DWORD>(sizeof(requestMsg)),
723 reinterpret_cast<LPVOID>(&responseMsg),
724 static_cast<DWORD>(sizeof(responseMsg)),
725 &cbReceived);
726 if (FAILED(hr))
727 {
728 return hr;
729 }
730
731 // Did profilee successfully carry out the GetVersion request?
732 if (FAILED(responseMsg.m_hr))
733 {
734 return responseMsg.m_hr;
735 }
736
737 // We should have valid version info for the target profilee. Now do the
738 // comparisons to determine if we're compatible.
739 if (
740 // Am I too old (i.e., profilee requires a newer trigger)?
741 (ProfilingAPIAttachDetach::kCurrentProcessVersion <
742 responseMsg.m_minimumAllowableTriggerVersion) ||
743
744 // Is the profilee too old (i.e., this trigger requires a newer profilee)?
745 (responseMsg.m_profileeVersion <
746 ProfilingAPIAttachDetach::kMinimumAllowableProfileeVersion))
747 {
748 return CORPROF_E_PROFILEE_INCOMPATIBLE_WITH_TRIGGER;
749 }
750
751 return S_OK;
752}
753
754
755// ----------------------------------------------------------------------------
756// ProfilingAPIAttachClient::SendAttachRequest
757//
758// Description:
759// Sends an Attach request message across the pipe to the target profilee, and returns
760// the response.
761//
762// Arguments:
763// * dwMillisecondsMax - [in] How much time is left to wait for response?
764// * pClsidProfiler - [in] CLSID of profiler to attach
765// * pvClientData - [in] Client data to pass to profiler's InitializeForAttach
766// callback
767// * cbClientData - [in] Size of client data
768// * phrAttach - [out] Response HRESULT sent back by target profilee
769//
770// Return Value:
771// HRESULT indicating success / failure with sending request & receiving response. If
772// S_OK is returned, consult phrAttach to determine success / failure of the actual
773// attach operation.
774//
775// Assumptions:
776// * Client connection should be established before calling this function (or a callee
777// will assert).
778//
779
780HRESULT ProfilingAPIAttachClient::SendAttachRequest(
781 DWORD dwMillisecondsMax,
782 const CLSID * pClsidProfiler,
783 LPCWSTR wszProfilerPath,
784 void * pvClientData,
785 UINT cbClientData,
786 HRESULT * phrAttach)
787{
788 CONTRACTL
789 {
790 THROWS;
791 GC_TRIGGERS;
792 MODE_PREEMPTIVE;
793 CAN_TAKE_LOCK;
794 }
795 CONTRACTL_END;
796
797 _ASSERTE(phrAttach != NULL);
798
799 // These were already verified early on
800 _ASSERTE(cbClientData < 0xFFFFffffUL - sizeof(AttachRequestMessageV2));
801 _ASSERTE((pvClientData != NULL) || (cbClientData == 0));
802
803 // Allocate enough space for the message, including the variable-length client data.
804 DWORD cbMessage = sizeof(AttachRequestMessageV2) + cbClientData;
805 _ASSERTE(cbMessage >= sizeof(AttachRequestMessageV2));
806 NewHolder<BYTE> pbMessageStart(new (nothrow) BYTE[cbMessage]);
807 if (pbMessageStart == NULL)
808 {
809 return E_OUTOFMEMORY;
810 }
811
812 // Initialize the message. First the client data at the tail end...
813 memcpy(pbMessageStart + sizeof(AttachRequestMessageV2), pvClientData, cbClientData);
814
815 // ...then the message struct fields (use constructor in-place)
816 new ((void *) pbMessageStart) AttachRequestMessageV2(
817 cbMessage,
818 ProfilingAPIAttachDetach::kCurrentProcessVersion, // Version of the trigger process
819 pClsidProfiler,
820 wszProfilerPath,
821 sizeof(AttachRequestMessageV2), // dwClientDataStartOffset
822 cbClientData,
823 dwMillisecondsMax
824 );
825
826 HRESULT hr;
827 DWORD cbReceived;
828 AttachResponseMessage attachResponseMessage(E_UNEXPECTED);
829
830 hr = SendAndReceive(
831 dwMillisecondsMax,
832 (LPVOID) pbMessageStart,
833 cbMessage,
834 reinterpret_cast<LPVOID>(&attachResponseMessage),
835 static_cast<DWORD>(sizeof(attachResponseMessage)),
836 &cbReceived);
837 if (FAILED(hr))
838 {
839 return hr;
840 }
841
842 // Successfully got a response from target. The response contained the HRESULT
843 // indicating whether the attach was successful, so return that HRESULT in the [out]
844 // param.
845 *phrAttach = attachResponseMessage.m_hr;
846 return S_OK;
847}
848
849
850// ----------------------------------------------------------------------------
851// ProfilingAPIAttachClient::SendAndReceive
852//
853// Description:
854// Used in trigger process to send a request and receive the response.
855//
856// Arguments:
857// * dwMillisecondsMax - [in] Timeout for entire send/receive operation
858// * pvInBuffer - [in] Buffer contaning the request message
859// * cbInBuffer - [in] Number of bytes in the request message
860// * pvOutBuffer - [in/out] Buffer to write the response into
861// * cbOutBuffer - [in] Size of the response buffer
862// * pcbReceived - [out] Number of bytes actually written into response buffer
863//
864// Return Value:
865// HRESULT indicating success or failure
866//
867// Notes:
868// * The [out] parameters may be written to even if this function fails. But their
869// contents should be ignored by the caller in this case.
870//
871
872HRESULT ProfilingAPIAttachClient::SendAndReceive(
873 DWORD dwMillisecondsMax,
874 LPVOID pvInBuffer,
875 DWORD cbInBuffer,
876 LPVOID pvOutBuffer,
877 DWORD cbOutBuffer,
878 DWORD * pcbReceived)
879{
880 CONTRACTL
881 {
882 THROWS;
883 GC_TRIGGERS;
884 MODE_PREEMPTIVE;
885 CAN_TAKE_LOCK;
886 }
887 CONTRACTL_END;
888
889 _ASSERTE(IsValidHandle(m_hPipeClient));
890 _ASSERTE(pvInBuffer != NULL);
891 _ASSERTE(pvOutBuffer != NULL);
892 _ASSERTE(pcbReceived != NULL);
893
894 HRESULT hr;
895 DWORD dwErr;
896 ProfilingAPIAttachDetach::OverlappedResultHolder overlapped;
897 hr = overlapped.Initialize();
898 if (FAILED(hr))
899 {
900 return hr;
901 }
902
903 if (TransactNamedPipe(
904 m_hPipeClient,
905 pvInBuffer,
906 cbInBuffer,
907 pvOutBuffer,
908 cbOutBuffer,
909 pcbReceived,
910 overlapped))
911 {
912 // Hot dog! Send and receive succeeded immediately! All done.
913 return S_OK;
914 }
915
916 dwErr = GetLastError();
917 if (dwErr != ERROR_IO_PENDING)
918 {
919 // An unexpected error. Caller has to deal with it
920 hr = HRESULT_FROM_WIN32(dwErr);
921 LOG((
922 LF_CORPROF,
923 LL_ERROR,
924 "**PROF TRIGGER: TransactNamedPipe failed. hr=0x%x.\n",
925 hr));
926 return hr;
927 }
928
929 // Typical case=ERROR_IO_PENDING: TransactNamedPipe has begun the transaction, and
930 // it's still in progress. Wait until it's done (or timeout expires).
931 hr = overlapped.Wait(
932 dwMillisecondsMax,
933 m_hPipeClient,
934 pcbReceived);
935 if (FAILED(hr))
936 {
937 LOG((
938 LF_CORPROF,
939 LL_ERROR,
940 "**PROF TRIGGER: Waiting for overlapped result for TransactNamedPipe failed. hr=0x%x.\n",
941 hr));
942 return hr;
943 }
944
945 return S_OK;
946}
947
948#endif // FEATURE_PROFAPI_ATTACH_DETACH
949