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// DbgShim.cpp
6//
7// This contains the APIs for creating a telesto managed-debugging session. These APIs serve to locate an
8// mscordbi.dll for a given telesto dll and then instantiate the ICorDebug object.
9//
10//*****************************************************************************
11
12#include <winwrap.h>
13#include <utilcode.h>
14#include <log.h>
15#include <tlhelp32.h>
16#include <cor.h>
17#include <sstring.h>
18#ifndef FEATURE_PAL
19#include <securityutil.h>
20#endif
21
22#include <ex.h>
23#include <cordebug.h> // for Version nunmbers
24#include <pedecoder.h>
25#include <getproductversionnumber.h>
26#include <dbgenginemetrics.h>
27#include <arrayholder.h>
28
29#ifndef FEATURE_PAL
30#define PSAPI_VERSION 2
31#include <psapi.h>
32#endif
33
34#include "dbgshim.h"
35
36/*
37
38// Here's a High-level overview of the API usage
39
40From the debugger:
41A debugger calls GetStartupNotificationEvent(pid of debuggee) to get an event, which is signalled when
42that process loads a Telesto. The debugger thus waits on that event, and when it's signalled, it can call
43EnumerateCLRs / CloseCLREnumeration to get an array of Telestos in the target process (including the one
44that was just loaded). It can then call CreateVersionStringFromModule, CreateDebuggingInterfaceFromVersion
45to attach to any or all Telestos of interest.
46
47From the debuggee:
48When a new Telesto spins up, it checks for the startup event (created via GetStartupNotificationEvent), and
49if it exists, it will:
50- signal it
51- wait on the "Continue" event, thus giving a debugger a chance to attach to the telesto
52
53Notes:
54- There is no CreateProcess (Launch) case. All Launching is really an "Early-attach case".
55
56*/
57
58#ifdef FEATURE_PAL
59#define INITIALIZE_SHIM { if (PAL_InitializeDLL() != 0) return E_FAIL; }
60#else
61#define INITIALIZE_SHIM
62#endif
63
64// Contract for public APIs. These must be NOTHROW.
65#define PUBLIC_CONTRACT \
66 INITIALIZE_SHIM \
67 CONTRACTL \
68 { \
69 NOTHROW; \
70 } \
71 CONTRACTL_END;
72
73//-----------------------------------------------------------------------------
74// Public API.
75//
76// CreateProcessForLaunch - a stripped down version of the Windows CreateProcess
77// that can be supported cross-platform.
78//
79//-----------------------------------------------------------------------------
80HRESULT
81CreateProcessForLaunch(
82 __in LPWSTR lpCommandLine,
83 __in BOOL bSuspendProcess,
84 __in LPVOID lpEnvironment,
85 __in LPCWSTR lpCurrentDirectory,
86 __out PDWORD pProcessId,
87 __out HANDLE *pResumeHandle)
88{
89 PUBLIC_CONTRACT;
90
91 PROCESS_INFORMATION processInfo;
92 STARTUPINFOW startupInfo;
93 DWORD dwCreationFlags = 0;
94
95 ZeroMemory(&processInfo, sizeof(processInfo));
96 ZeroMemory(&startupInfo, sizeof(startupInfo));
97
98 startupInfo.cb = sizeof(startupInfo);
99
100 if (bSuspendProcess)
101 {
102 dwCreationFlags = CREATE_SUSPENDED;
103 }
104
105 BOOL result = CreateProcessW(
106 NULL,
107 lpCommandLine,
108 NULL,
109 NULL,
110 FALSE,
111 dwCreationFlags,
112 lpEnvironment,
113 lpCurrentDirectory,
114 &startupInfo,
115 &processInfo);
116
117 if (!result) {
118 *pProcessId = 0;
119 *pResumeHandle = NULL;
120 return HRESULT_FROM_WIN32(GetLastError());
121 }
122
123 if (processInfo.hProcess != NULL)
124 {
125 CloseHandle(processInfo.hProcess);
126 }
127
128 *pProcessId = processInfo.dwProcessId;
129 *pResumeHandle = processInfo.hThread;
130
131 return S_OK;
132}
133
134//-----------------------------------------------------------------------------
135// Public API.
136//
137// ResumeProcess - to be used with the CreateProcessForLaunch resume handle
138//
139//-----------------------------------------------------------------------------
140HRESULT
141ResumeProcess(
142 __in HANDLE hResumeHandle)
143{
144 PUBLIC_CONTRACT;
145 if (ResumeThread(hResumeHandle) == (DWORD)-1)
146 {
147 return HRESULT_FROM_WIN32(GetLastError());
148 }
149 return S_OK;
150}
151
152//-----------------------------------------------------------------------------
153// Public API.
154//
155// CloseResumeHandle - to be used with the CreateProcessForLaunch resume handle
156//
157//-----------------------------------------------------------------------------
158HRESULT
159CloseResumeHandle(
160 __in HANDLE hResumeHandle)
161{
162 PUBLIC_CONTRACT;
163 if (!CloseHandle(hResumeHandle))
164 {
165 return HRESULT_FROM_WIN32(GetLastError());
166 }
167 return S_OK;
168}
169
170#ifdef FEATURE_PAL
171
172static
173void
174RuntimeStartupHandler(
175 char *pszModulePath,
176 HMODULE hModule,
177 PVOID parameter);
178
179#else // FEATURE_PAL
180
181static
182DWORD
183StartupHelperThread(
184 LPVOID p);
185
186static
187HRESULT
188GetContinueStartupEvent(
189 DWORD debuggeePID,
190 LPCWSTR szTelestoFullPath,
191 __out HANDLE *phContinueStartupEvent);
192
193#endif // FEATURE_PAL
194
195// Functions that we'll look for in the loaded Mscordbi module.
196typedef HRESULT (STDAPICALLTYPE *FPCoreCLRCreateCordbObject)(
197 int iDebuggerVersion,
198 DWORD pid,
199 HMODULE hmodTargetCLR,
200 IUnknown **ppCordb);
201
202typedef HRESULT (STDAPICALLTYPE *FPCoreCLRCreateCordbObjectEx)(
203 int iDebuggerVersion,
204 DWORD pid,
205 LPCWSTR lpApplicationGroupId,
206 HMODULE hmodTargetCLR,
207 IUnknown **ppCordb);
208
209HRESULT CreateCoreDbgWithSandboxSupport(
210 HMODULE hCLRModule, HMODULE hDBIModule, DWORD processId, LPCWSTR lpApplicationGroupId, int iDebuggerVersion, IUnknown **ppCordb)
211{
212 FPCoreCLRCreateCordbObjectEx fpCreate =
213 (FPCoreCLRCreateCordbObjectEx)GetProcAddress(hDBIModule, "CoreCLRCreateCordbObjectEx");
214 if (fpCreate == NULL)
215 {
216 return CORDBG_E_INCOMPATIBLE_PROTOCOL;
217 }
218
219 return fpCreate(iDebuggerVersion, processId, lpApplicationGroupId, hCLRModule, ppCordb);
220}
221
222HRESULT CreateCoreDbgWithoutSandboxSupport(
223 HMODULE hCLRModule, HMODULE hDBIModule, DWORD processId, int iDebuggerVersion, IUnknown **ppCordb)
224{
225 FPCoreCLRCreateCordbObject fpCreate =
226 (FPCoreCLRCreateCordbObject)GetProcAddress(hDBIModule, "CoreCLRCreateCordbObject");
227 if (fpCreate == NULL)
228 {
229 return CORDBG_E_INCOMPATIBLE_PROTOCOL;
230 }
231
232 return fpCreate(iDebuggerVersion, processId, hCLRModule, ppCordb);
233}
234
235HRESULT CreateCoreDbg(
236 HMODULE hCLRModule, HMODULE hDBIModule, DWORD processId, LPCWSTR lpApplicationGroupId, int iDebuggerVersion, IUnknown **ppCordb)
237{
238 HRESULT hr = S_OK;
239
240 if (lpApplicationGroupId != NULL)
241 {
242 hr = CreateCoreDbgWithSandboxSupport(hCLRModule, hDBIModule, processId, lpApplicationGroupId, iDebuggerVersion, ppCordb);
243 }
244 else
245 {
246 hr = CreateCoreDbgWithoutSandboxSupport(hCLRModule, hDBIModule, processId, iDebuggerVersion, ppCordb);
247 }
248
249 return hr;
250}
251
252//
253// Helper class for RegisterForRuntimeStartup
254//
255class RuntimeStartupHelper
256{
257 LONG m_ref;
258 DWORD m_processId;
259 PSTARTUP_CALLBACK m_callback;
260 PVOID m_parameter;
261#ifdef FEATURE_PAL
262 PVOID m_unregisterToken;
263 LPWSTR m_applicationGroupId;
264#else
265 bool m_canceled;
266 HANDLE m_startupEvent;
267 DWORD m_threadId;
268 HANDLE m_threadHandle;
269#endif // FEATURE_PAL
270
271public:
272 RuntimeStartupHelper(DWORD dwProcessId, PSTARTUP_CALLBACK pfnCallback, PVOID parameter) :
273 m_ref(1),
274 m_processId(dwProcessId),
275 m_callback(pfnCallback),
276 m_parameter(parameter),
277#ifdef FEATURE_PAL
278 m_unregisterToken(NULL),
279 m_applicationGroupId(NULL)
280#else
281 m_canceled(false),
282 m_startupEvent(NULL),
283 m_threadId(0),
284 m_threadHandle(NULL)
285#endif // FEATURE_PAL
286 {
287 }
288
289 ~RuntimeStartupHelper()
290 {
291#ifdef FEATURE_PAL
292 if (m_applicationGroupId != NULL)
293 {
294 delete m_applicationGroupId;
295 }
296#else // FEATURE_PAL
297 if (m_startupEvent != NULL)
298 {
299 CloseHandle(m_startupEvent);
300 }
301 if (m_threadHandle != NULL)
302 {
303 CloseHandle(m_threadHandle);
304 }
305#endif // FEATURE_PAL
306 }
307
308 LONG AddRef()
309 {
310 LONG ref = InterlockedIncrement(&m_ref);
311 return ref;
312 }
313
314 LONG Release()
315 {
316 LONG ref = InterlockedDecrement(&m_ref);
317 if (ref == 0)
318 {
319 delete this;
320 }
321 return ref;
322 }
323
324#ifdef FEATURE_PAL
325
326 HRESULT Register(LPCWSTR lpApplicationGroupId)
327 {
328 if (lpApplicationGroupId != NULL)
329 {
330 int size = wcslen(lpApplicationGroupId) + 1;
331 m_applicationGroupId = new (nothrow) WCHAR[size];
332 if (m_applicationGroupId == NULL)
333 {
334 return E_OUTOFMEMORY;
335 }
336 wcscpy_s(m_applicationGroupId, size, lpApplicationGroupId);
337 }
338
339 DWORD pe = PAL_RegisterForRuntimeStartup(m_processId, m_applicationGroupId, RuntimeStartupHandler, this, &m_unregisterToken);
340 if (pe != NO_ERROR)
341 {
342 return HRESULT_FROM_WIN32(pe);
343 }
344 return S_OK;
345 }
346
347 void Unregister()
348 {
349 PAL_UnregisterForRuntimeStartup(m_unregisterToken);
350 }
351
352 void InvokeStartupCallback(char *pszModulePath, HMODULE hModule)
353 {
354 IUnknown *pCordb = NULL;
355 HMODULE hMod = NULL;
356 HRESULT hr = S_OK;
357
358 // If either of these are NULL, there was an error from the PAL
359 // callback. GetLastError returns the error code from the PAL.
360 if (pszModulePath == NULL || hModule == NULL)
361 {
362 hr = HRESULT_FROM_WIN32(GetLastError());
363 goto exit;
364 }
365
366 PAL_CPP_TRY
367 {
368 char dbiPath[MAX_LONGPATH];
369
370 char *pszLast = strrchr(pszModulePath, DIRECTORY_SEPARATOR_CHAR_A);
371 if (pszLast == NULL)
372 {
373 _ASSERT(!"InvokeStartupCallback: can find separator in coreclr path\n");
374 hr = E_INVALIDARG;
375 goto exit;
376 }
377
378 strncpy_s(dbiPath, _countof(dbiPath), pszModulePath, pszLast - pszModulePath);
379 strcat_s(dbiPath, _countof(dbiPath), DIRECTORY_SEPARATOR_STR_A MAKEDLLNAME_A("mscordbi"));
380
381 hMod = LoadLibraryA(dbiPath);
382 if (hMod == NULL)
383 {
384 hr = CORDBG_E_DEBUG_COMPONENT_MISSING;
385 goto exit;
386 }
387
388 HRESULT hr = CreateCoreDbg(hModule, hMod, m_processId, m_applicationGroupId, CorDebugVersion_2_0, &pCordb);
389 _ASSERTE((pCordb == NULL) == FAILED(hr));
390 if (FAILED(hr))
391 {
392 goto exit;
393 }
394
395 m_callback(pCordb, m_parameter, S_OK);
396 }
397 PAL_CPP_CATCH_ALL
398 {
399 hr = E_FAIL;
400 goto exit;
401 }
402 PAL_CPP_ENDTRY
403
404 exit:
405 if (FAILED(hr))
406 {
407 _ASSERTE(pCordb == NULL);
408
409 if (hMod != NULL)
410 {
411 FreeLibrary(hMod);
412 }
413
414 // Invoke the callback on error
415 m_callback(NULL, m_parameter, hr);
416 }
417 }
418
419#else // FEATURE_PAL
420
421 HRESULT Register(LPCWSTR lpApplicationGroupId)
422 {
423 HRESULT hr = GetStartupNotificationEvent(m_processId, &m_startupEvent);
424 if (FAILED(hr))
425 {
426 goto exit;
427 }
428
429 // Add a reference for the thread handler
430 AddRef();
431
432 m_threadHandle = CreateThread(
433 NULL,
434 0,
435 ::StartupHelperThread,
436 this,
437 0,
438 &m_threadId);
439
440 if (m_threadHandle == NULL)
441 {
442 hr = E_OUTOFMEMORY;
443 Release();
444 goto exit;
445 }
446
447 exit:
448 return hr;
449 }
450
451 bool AreAllHandlesValid(HANDLE *handleArray, DWORD arrayLength)
452 {
453 for (int i = 0; i < (int)arrayLength; i++)
454 {
455 HANDLE h = handleArray[i];
456 if (h == INVALID_HANDLE_VALUE)
457 {
458 return false;
459 }
460 }
461 return true;
462 }
463
464 HRESULT InternalEnumerateCLRs(HANDLE** ppHandleArray, _In_reads_(*pdwArrayLength) LPWSTR** ppStringArray, DWORD* pdwArrayLength)
465 {
466 int numTries = 0;
467 HRESULT hr;
468
469 while (numTries < 25)
470 {
471 hr = EnumerateCLRs(m_processId, ppHandleArray, ppStringArray, pdwArrayLength);
472
473 // EnumerateCLRs uses the OS API CreateToolhelp32Snapshot which can return ERROR_BAD_LENGTH or
474 // ERROR_PARTIAL_COPY. If we get either of those, we try wait 1/10th of a second try again (that
475 // is the recommendation of the OS API owners).
476 if ((hr != HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) && (hr != HRESULT_FROM_WIN32(ERROR_BAD_LENGTH)))
477 {
478 // Just return any other error or if no handles were found (which means the coreclr module wasn't found yet).
479 if (FAILED(hr) || *ppHandleArray == NULL || *pdwArrayLength <= 0)
480 {
481 return hr;
482 }
483 // If EnumerateCLRs succeeded but any of the handles are INVALID_HANDLE_VALUE, then sleep and retry
484 // also. This fixes a race condition where dbgshim catches the coreclr module just being loaded but
485 // before g_hContinueStartupEvent has been initialized.
486 if (AreAllHandlesValid(*ppHandleArray, *pdwArrayLength))
487 {
488 return hr;
489 }
490 // Clean up memory allocated in EnumerateCLRs since this path it succeeded
491 CloseCLREnumeration(*ppHandleArray, *ppStringArray, *pdwArrayLength);
492
493 *ppHandleArray = NULL;
494 *ppStringArray = NULL;
495 *pdwArrayLength = 0;
496 }
497
498 // Sleep and retry enumerating the runtimes
499 Sleep(100);
500 numTries++;
501
502 if (m_canceled)
503 {
504 break;
505 }
506 }
507
508 // Indicate a timeout
509 hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
510
511 return hr;
512 }
513
514 void WakeRuntimes(HANDLE *handleArray, DWORD arrayLength)
515 {
516 if (handleArray != NULL)
517 {
518 for (int i = 0; i < (int)arrayLength; i++)
519 {
520 HANDLE h = handleArray[i];
521 if (h != NULL && h != INVALID_HANDLE_VALUE)
522 {
523 SetEvent(h);
524 }
525 }
526 }
527 }
528
529 void Unregister()
530 {
531 m_canceled = true;
532
533 HANDLE *handleArray = NULL;
534 LPWSTR *stringArray = NULL;
535 DWORD arrayLength = 0;
536
537 // Wake up runtime(s)
538 HRESULT hr = EnumerateCLRs(m_processId, &handleArray, &stringArray, &arrayLength);
539 if (SUCCEEDED(hr))
540 {
541 WakeRuntimes(handleArray, arrayLength);
542 CloseCLREnumeration(handleArray, stringArray, arrayLength);
543 }
544
545 // Wake up worker thread
546 SetEvent(m_startupEvent);
547
548 // Don't need to wake up and wait for the worker thread if called on it
549 if (m_threadId != GetCurrentThreadId())
550 {
551 // Wait for work thread to exit
552 WaitForSingleObject(m_threadHandle, INFINITE);
553 }
554 }
555
556 HRESULT InvokeStartupCallback(bool *pCoreClrExists)
557 {
558 HANDLE *handleArray = NULL;
559 LPWSTR *stringArray = NULL;
560 DWORD arrayLength = 0;
561 HRESULT hr = S_OK;
562
563 PAL_CPP_TRY
564 {
565 IUnknown *pCordb = NULL;
566 WCHAR verStr[MAX_LONGPATH];
567 DWORD verLen;
568
569 *pCoreClrExists = FALSE;
570
571 hr = InternalEnumerateCLRs(&handleArray, &stringArray, &arrayLength);
572 if (FAILED(hr))
573 {
574 goto exit;
575 }
576
577 for (int i = 0; i < (int)arrayLength; i++)
578 {
579 *pCoreClrExists = TRUE;
580
581 hr = CreateVersionStringFromModule(m_processId, stringArray[i], verStr, _countof(verStr), &verLen);
582 if (FAILED(hr))
583 {
584 goto exit;
585 }
586
587 hr = CreateDebuggingInterfaceFromVersion(verStr, &pCordb);
588 if (FAILED(hr))
589 {
590 goto exit;
591 }
592
593 m_callback(pCordb, m_parameter, S_OK);
594
595 // Currently only the first coreclr module in a process is supported
596 break;
597 }
598 }
599 PAL_CPP_CATCH_ALL
600 {
601 hr = E_FAIL;
602 goto exit;
603 }
604 PAL_CPP_ENDTRY
605
606 exit:
607 if (*pCoreClrExists)
608 {
609 // Wake up all the runtimes
610 WakeRuntimes(handleArray, arrayLength);
611 }
612 CloseCLREnumeration(handleArray, stringArray, arrayLength);
613
614 return hr;
615 }
616
617 void StartupHelperThread()
618 {
619 bool coreclrExists = false;
620
621 HRESULT hr = InvokeStartupCallback(&coreclrExists);
622 // The retry logic in InternalEnumerateCLRs failed if ERROR_TIMEOUT was returned.
623 if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_TIMEOUT)))
624 {
625 if (!coreclrExists && !m_canceled)
626 {
627 // Wait until the coreclr runtime (debuggee) starts up
628 if (WaitForSingleObject(m_startupEvent, INFINITE) == WAIT_OBJECT_0)
629 {
630 if (!m_canceled)
631 {
632 hr = InvokeStartupCallback(&coreclrExists);
633 if (SUCCEEDED(hr))
634 {
635 // We should always find a coreclr module so fail if we don't
636 if (!coreclrExists)
637 {
638 hr = E_FAIL;
639 }
640 }
641 }
642 }
643 else
644 {
645 hr = HRESULT_FROM_WIN32(GetLastError());
646 }
647 }
648 }
649
650 if (FAILED(hr) && !m_canceled)
651 {
652 m_callback(NULL, m_parameter, hr);
653 }
654 }
655
656#endif // FEATURE_PAL
657};
658
659#ifdef FEATURE_PAL
660
661static
662void
663RuntimeStartupHandler(char *pszModulePath, HMODULE hModule, PVOID parameter)
664{
665 RuntimeStartupHelper *helper = (RuntimeStartupHelper *)parameter;
666 helper->InvokeStartupCallback(pszModulePath, hModule);
667}
668
669#else // FEATURE_PAL
670
671static
672DWORD
673StartupHelperThread(LPVOID p)
674{
675 RuntimeStartupHelper *helper = (RuntimeStartupHelper *)p;
676 helper->StartupHelperThread();
677 helper->Release();
678 return 0;
679}
680
681#endif // FEATURE_PAL
682
683//-----------------------------------------------------------------------------
684// Public API.
685//
686// RegisterForRuntimeStartup -- Refer to RegisterForRuntimeStartupEx.
687// This method calls RegisterForRuntimeStartupEx with null application group ID value
688//
689// dwProcessId -- process id of the target process
690// pfnCallback -- invoked when coreclr runtime starts
691// parameter -- data to pass to callback
692// ppUnregisterToken -- pointer to put the UnregisterForRuntimeStartup token.
693//
694//-----------------------------------------------------------------------------
695HRESULT
696RegisterForRuntimeStartup(
697 __in DWORD dwProcessId,
698 __in PSTARTUP_CALLBACK pfnCallback,
699 __in PVOID parameter,
700 __out PVOID *ppUnregisterToken)
701{
702 return RegisterForRuntimeStartupEx(dwProcessId, NULL, pfnCallback, parameter, ppUnregisterToken);
703}
704//-----------------------------------------------------------------------------
705// Public API.
706//
707// RegisterForRuntimeStartupEx -- executes the callback when the coreclr runtime
708// starts in the specified process. The callback is passed the proper ICorDebug
709// instance for the version of the runtime or an error if something fails. This
710// API works for launch and attach (and even the attach scenario if the runtime
711// hasn't been loaded yet) equally on both xplat and Windows. The callback is
712// always called on a separate thread. This API returns immediately.
713//
714// The callback is invoked when the coreclr runtime module is loaded during early
715// initialization. The runtime is blocked during initialization until the callback
716// returns.
717//
718// If the runtime is already loaded in the process (as in the normal attach case),
719// the callback is executed and the runtime is not blocked.
720//
721// The callback is always invoked on a separate thread and this API returns immediately.
722//
723// Only the first coreclr module instance found in the target process is currently
724// supported.
725//
726// dwProcessId -- process id of the target process
727// lpApplicationGroupId - A string representing the application group ID of a sandboxed
728// process running in Mac. Pass NULL if the process is not
729// running in a sandbox and other platforms.
730// pfnCallback -- invoked when coreclr runtime starts
731// parameter -- data to pass to callback
732// ppUnregisterToken -- pointer to put the UnregisterForRuntimeStartup token.
733//
734//-----------------------------------------------------------------------------
735HRESULT
736RegisterForRuntimeStartupEx(
737 __in DWORD dwProcessId,
738 __in LPCWSTR lpApplicationGroupId,
739 __in PSTARTUP_CALLBACK pfnCallback,
740 __in PVOID parameter,
741 __out PVOID *ppUnregisterToken)
742{
743 PUBLIC_CONTRACT;
744
745 if (pfnCallback == NULL || ppUnregisterToken == NULL)
746 {
747 return E_INVALIDARG;
748 }
749
750 HRESULT hr = S_OK;
751
752 RuntimeStartupHelper *helper = new (nothrow) RuntimeStartupHelper(dwProcessId, pfnCallback, parameter);
753 if (helper == NULL)
754 {
755 hr = E_OUTOFMEMORY;
756 }
757 else
758 {
759 hr = helper->Register(lpApplicationGroupId);
760 if (FAILED(hr))
761 {
762 helper->Release();
763 helper = NULL;
764 }
765 }
766
767 *ppUnregisterToken = helper;
768 return hr;
769}
770
771//-----------------------------------------------------------------------------
772// Public API.
773//
774// UnregisterForRuntimeStartup -- stops/cancels runtime startup notification. Needs
775// to be called during the debugger's shutdown to cleanup the internal data.
776//
777// This API can be called in the startup callback. Otherwise, it will block until
778// the callback thread finishes and no more callbacks will be initiated after this
779// API returns.
780//
781// pUnregisterToken -- unregister token from RegisterForRuntimeStartup or NULL.
782//-----------------------------------------------------------------------------
783HRESULT
784UnregisterForRuntimeStartup(
785 __in PVOID pUnregisterToken)
786{
787 PUBLIC_CONTRACT;
788
789 if (pUnregisterToken != NULL)
790 {
791 RuntimeStartupHelper *helper = (RuntimeStartupHelper *)pUnregisterToken;
792 helper->Unregister();
793 helper->Release();
794 }
795
796 return S_OK;
797}
798
799//-----------------------------------------------------------------------------
800// Public API.
801//
802// GetStartupNotificationEvent -- creates a global, named event that is PID-
803// qualified (i.e. process global) that is used to notify the debugger of
804// any CLR instance startup in the process.
805//
806// debuggeePID -- process ID of the target process
807// phStartupEvent -- out param for the returned event handle
808//
809//-----------------------------------------------------------------------------
810#define StartupNotifyEventNamePrefix W("TelestoStartupEvent_")
811#define SessionIdPrefix W("Session\\")
812
813// NULL terminator is included in sizeof(StartupNotifyEventNamePrefix)
814const int cchEventNameBufferSize = (sizeof(StartupNotifyEventNamePrefix) + sizeof(SessionIdPrefix)) / sizeof(WCHAR)
815 + 8 // + hex process id DWORD
816 + 10 // + decimal session id DWORD
817 + 1; // '\' after session id
818
819HRESULT
820GetStartupNotificationEvent(
821 __in DWORD debuggeePID,
822 __out HANDLE* phStartupEvent)
823{
824 PUBLIC_CONTRACT;
825
826 if (phStartupEvent == NULL)
827 return E_INVALIDARG;
828
829#ifndef FEATURE_PAL
830 HRESULT hr;
831 DWORD currentSessionId = 0, debuggeeSessionId = 0;
832 if (!ProcessIdToSessionId(GetCurrentProcessId(), &currentSessionId))
833 {
834 return HRESULT_FROM_WIN32(GetLastError());
835 }
836
837 if (!ProcessIdToSessionId(debuggeePID, &debuggeeSessionId))
838 {
839 return HRESULT_FROM_WIN32(GetLastError());
840 }
841
842 // Here we could just add "Global\" to the event name and this would solve cross-session debugging scenario, but that would require event name change
843 // in CoreCLR, and break backward compatibility. Instead if we see that debugee is in a different session, we explicitly create startup event
844 // in that session (by adding "Session\#\"). We could do it even for our own session, but that's vaguely documented behavior and we'd
845 // like to use it as little as possible.
846 WCHAR szEventName[cchEventNameBufferSize];
847 if (currentSessionId == debuggeeSessionId)
848 {
849 swprintf_s(szEventName, cchEventNameBufferSize, StartupNotifyEventNamePrefix W("%08x"), debuggeePID);
850 }
851 else
852 {
853 swprintf_s(szEventName, cchEventNameBufferSize, SessionIdPrefix W("%u\\") StartupNotifyEventNamePrefix W("%08x"), debuggeeSessionId, debuggeePID);
854 }
855
856 // Determine an appropriate ACL and SECURITY_ATTRIBUTES to apply to this event. We use the same logic
857 // here as the debugger uses for other events (like the setup-sync-event). Specifically, this does
858 // the work to ensure a debuggee running as another user, or with a low integrity level can signal
859 // this event.
860 PACL pACL = NULL;
861 SECURITY_ATTRIBUTES * pSA = NULL;
862 IfFailRet(SecurityUtil::GetACLOfPid(debuggeePID, &pACL));
863 SecurityUtil secUtil(pACL);
864
865 HandleHolder hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, debuggeePID);
866 if (hProcess == NULL)
867 {
868 return HRESULT_FROM_WIN32(GetLastError());
869 }
870
871 IfFailRet(secUtil.Init(hProcess));
872 IfFailRet(secUtil.GetSA(&pSA));
873
874 HANDLE startupEvent = WszCreateEvent(pSA,
875 FALSE, // false -> auto-reset
876 FALSE, // false -> initially non-signaled
877 szEventName);
878 DWORD dwStatus = GetLastError();
879 if (NULL == startupEvent)
880 {
881 // if the event already exists, try to open it, otherwise we fail.
882
883 if (ERROR_ALREADY_EXISTS != dwStatus)
884 return E_FAIL;
885
886 startupEvent = WszOpenEvent(SYNCHRONIZE, FALSE, szEventName);
887
888 if (NULL == startupEvent)
889 return E_FAIL;
890 }
891
892 *phStartupEvent = startupEvent;
893 return S_OK;
894#else
895 *phStartupEvent = NULL;
896 return E_NOTIMPL;
897#endif // FEATURE_PAL
898}
899// Refer to clr\src\mscoree\mscorwks_ntdef.src.
900const WORD kOrdinalForMetrics = 2;
901
902//-----------------------------------------------------------------------------
903// The CLR_ENGINE_METRICS is a static struct in coreclr.dll. It's exported by coreclr.dll at ordinal 2 in
904// the export address table. This function returns the CLR_ENGINE_METRICS and the RVA to the continue
905// startup event for a coreclr.dll specified by its full path.
906//
907// Arguments:
908// szTelestoFullPath - (in) full path of telesto
909// pEngineMetricsOut - (out) filled in based on metrics from target telesto.
910// pdwRVAContinueStartupEvent - (out; optional) return the RVA to the continue startup event
911//
912// Returns:
913// Throwss on error.
914//
915// Notes:
916// When VS pops up the attach dialog box, it is actually enumerating all the processes on the machine
917// (if the appropiate checkbox is checked) and checking each process to see if a DLL named "coreclr.dll"
918// is loaded. If there is one, we will go down this code path, but there is no guarantee that the
919// coreclr.dll is ours. A malicious user can be running a process with a bogus coreclr.dll loaded.
920// That's why we need to be extra careful reading coreclr.dll in this function.
921//-----------------------------------------------------------------------------
922static
923void
924GetTargetCLRMetrics(
925 LPCWSTR szTelestoFullPath,
926 CLR_ENGINE_METRICS *pEngineMetricsOut,
927 DWORD *pdwRVAContinueStartupEvent = NULL)
928{
929 CONTRACTL
930 {
931 THROWS;
932 }
933 CONTRACTL_END;
934
935 CONSISTENCY_CHECK(szTelestoFullPath != NULL);
936 CONSISTENCY_CHECK(pEngineMetricsOut != NULL);
937
938#ifndef FEATURE_PAL
939 HRESULT hr = S_OK;
940
941 HandleHolder hCoreClrFile = WszCreateFile(szTelestoFullPath,
942 GENERIC_READ,
943 FILE_SHARE_READ,
944 NULL, // default security descriptor
945 OPEN_EXISTING,
946 FILE_ATTRIBUTE_NORMAL,
947 NULL);
948 if (hCoreClrFile == INVALID_HANDLE_VALUE)
949 {
950 ThrowLastError();
951 }
952
953 DWORD cbFileHigh = 0;
954 DWORD cbFileLow = GetFileSize(hCoreClrFile, &cbFileHigh);
955 if (cbFileLow == INVALID_FILE_SIZE)
956 {
957 ThrowLastError();
958 }
959
960 // A maximum size of 100 MB should be more than enough for coreclr.dll.
961 if ((cbFileHigh != 0) || (cbFileLow > 0x6400000) || (cbFileLow == 0))
962 {
963 ThrowHR(E_FAIL);
964 }
965
966 HandleHolder hCoreClrMap = WszCreateFileMapping(hCoreClrFile, NULL, PAGE_READONLY, cbFileHigh, cbFileLow, NULL);
967 if (hCoreClrMap == NULL)
968 {
969 ThrowLastError();
970 }
971
972 MapViewHolder hCoreClrMapView = MapViewOfFile(hCoreClrMap, FILE_MAP_READ, 0, 0, 0);
973 if (hCoreClrMapView == NULL)
974 {
975 ThrowLastError();
976 }
977
978 // At this point we have read the file into the process, but be careful because it is flat, i.e. not mapped.
979 // We need to translate RVAs into file offsets, but fortunately PEDecoder can do all of that for us.
980 PEDecoder pedecoder(hCoreClrMapView, (COUNT_T)cbFileLow);
981
982 // Check the NT headers.
983 if (!pedecoder.CheckNTFormat())
984 {
985 ThrowHR(E_FAIL);
986 }
987
988 // At this point we can safely read anything in the NT headers.
989
990 if (!pedecoder.HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT) ||
991 !pedecoder.CheckDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT))
992 {
993 ThrowHR(E_FAIL);
994 }
995 IMAGE_DATA_DIRECTORY * pExportDirectoryEntry = pedecoder.GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_EXPORT);
996
997 // At this point we can safely read the IMAGE_DATA_DIRECTORY of the export directory.
998
999 if (!pedecoder.CheckDirectory(pExportDirectoryEntry))
1000 {
1001 ThrowHR(E_FAIL);
1002 }
1003 IMAGE_EXPORT_DIRECTORY * pExportDir =
1004 reinterpret_cast<IMAGE_EXPORT_DIRECTORY *>(pedecoder.GetDirectoryData(pExportDirectoryEntry));
1005
1006 // At this point we have checked that everything in the export directory is readable.
1007
1008 // Check to make sure the ordinal we have fits in the table in the export directory.
1009 // The "base" here is like the starting index of the arrays in the export directory.
1010 if ((pExportDir->Base > kOrdinalForMetrics) ||
1011 (pExportDir->NumberOfFunctions < (kOrdinalForMetrics - pExportDir->Base)))
1012 {
1013 ThrowHR(E_FAIL);
1014 }
1015 DWORD dwRealIndex = kOrdinalForMetrics - pExportDir->Base;
1016
1017 // Check that we can read the RVA at the element (specified by the ordinal) in the export address table.
1018 // Then read the RVA to the CLR_ENGINE_METRICS.
1019 if (!pedecoder.CheckRva(pExportDir->AddressOfFunctions, (dwRealIndex + 1) * sizeof(DWORD)))
1020 {
1021 ThrowHR(E_FAIL);
1022 }
1023 DWORD rvaMetrics = *reinterpret_cast<DWORD *>(
1024 pedecoder.GetRvaData(pExportDir->AddressOfFunctions + dwRealIndex * sizeof(DWORD)));
1025
1026 // Make sure we can safely read the CLR_ENGINE_METRICS at the RVA we have retrieved.
1027 if (!pedecoder.CheckRva(rvaMetrics, sizeof(*pEngineMetricsOut)))
1028 {
1029 ThrowHR(E_FAIL);
1030 }
1031
1032 // Finally, copy the CLR_ENGINE_METRICS into the output buffer.
1033 CLR_ENGINE_METRICS * pMetricsInFile = reinterpret_cast<CLR_ENGINE_METRICS *>(pedecoder.GetRvaData(rvaMetrics));
1034 *pEngineMetricsOut = *pMetricsInFile;
1035
1036 // At this point, we have retrieved the CLR_ENGINE_METRICS from the target process and
1037 // stored it in output buffer.
1038 if (pEngineMetricsOut->cbSize != sizeof(*pEngineMetricsOut))
1039 {
1040 ThrowHR(E_INVALIDARG);
1041 }
1042
1043 if (pdwRVAContinueStartupEvent != NULL)
1044 {
1045 // Note that the pointer stored in the CLR_ENGINE_METRICS is assuming that the DLL is loaded at its
1046 // preferred base address. We need to translate that to an RVA.
1047 if (((SIZE_T)pEngineMetricsOut->phContinueStartupEvent < (SIZE_T)pedecoder.GetPreferredBase()) ||
1048 ((SIZE_T)pEngineMetricsOut->phContinueStartupEvent >
1049 ((SIZE_T)pedecoder.GetPreferredBase() + pedecoder.GetVirtualSize())))
1050 {
1051 ThrowHR(E_FAIL);
1052 }
1053
1054 DWORD rvaContinueStartupEvent =
1055 (DWORD)((SIZE_T)pEngineMetricsOut->phContinueStartupEvent - (SIZE_T)pedecoder.GetPreferredBase());
1056
1057 // We can't use CheckRva() here because for unmapped files it actually checks the RVA against the file
1058 // size as well. We have already checked the RVA above. Now just check that the entire HANDLE
1059 // falls in the loaded image.
1060 if ((rvaContinueStartupEvent + sizeof(HANDLE)) > pedecoder.GetVirtualSize())
1061 {
1062 ThrowHR(E_FAIL);
1063 }
1064
1065 *pdwRVAContinueStartupEvent = rvaContinueStartupEvent;
1066 }
1067
1068 // Holder will call FreeLibrary()
1069#else
1070 //TODO: So far on POSIX systems we only support one version of debugging interface
1071 // in future we might want to detect it the same way we do it on Windows.
1072 pEngineMetricsOut->cbSize = sizeof(*pEngineMetricsOut);
1073 pEngineMetricsOut->dwDbiVersion = CorDebugLatestVersion;
1074 pEngineMetricsOut->phContinueStartupEvent = NULL;
1075
1076 if (pdwRVAContinueStartupEvent != NULL)
1077 {
1078 *pdwRVAContinueStartupEvent = NULL;
1079 }
1080#endif // FEATURE_PAL
1081}
1082
1083// Returns true iff the module represents CoreClr.
1084static
1085bool
1086IsCoreClr(
1087 const WCHAR* pModulePath)
1088{
1089 _ASSERTE(pModulePath != NULL);
1090
1091 //strip off everything up to and including the last slash in the path to get name
1092 const WCHAR* pModuleName = pModulePath;
1093 while(wcschr(pModuleName, DIRECTORY_SEPARATOR_CHAR_W) != NULL)
1094 {
1095 pModuleName = wcschr(pModuleName, DIRECTORY_SEPARATOR_CHAR_W);
1096 pModuleName++; // pass the slash
1097 }
1098
1099 // MAIN_CLR_MODULE_NAME_W gets changed for desktop builds, so we directly code against the CoreClr name.
1100 return _wcsicmp(pModuleName, MAKEDLLNAME_W(W("coreclr"))) == 0;
1101}
1102
1103// Returns true iff the module sent is named CoreClr.dll and has the metrics expected in it's PE header.
1104static
1105bool
1106IsCoreClrWithGoodHeader(
1107 HANDLE hProcess,
1108 HMODULE hModule)
1109{
1110 HRESULT hr = S_OK;
1111
1112 WCHAR modulePath[MAX_LONGPATH];
1113 modulePath[0] = W('\0');
1114 if(0 == GetModuleFileNameEx(hProcess, hModule, modulePath, MAX_LONGPATH))
1115 {
1116 return false;
1117 }
1118 else
1119 {
1120 modulePath[MAX_LONGPATH-1] = 0; // on older OS'es this doesn't get null terminated automatically on truncation
1121 }
1122
1123 if (IsCoreClr(modulePath))
1124 {
1125 // We don't care about the particular error returned, only that
1126 // what we tried wasn't a 'real' coreclr.dll.
1127 EX_TRY
1128 {
1129 CLR_ENGINE_METRICS metricsStruct;
1130 GetTargetCLRMetrics(modulePath, &metricsStruct); // throws
1131
1132 // If we got this far, then we think it's a good one.
1133 }
1134 EX_CATCH_HRESULT(hr);
1135 return (hr == S_OK);
1136 }
1137
1138 return false;
1139}
1140
1141static
1142HRESULT
1143EnumProcessModulesInternal(
1144 HANDLE hProcess,
1145 DWORD *pCountModules,
1146 HMODULE** ppModules)
1147{
1148 *pCountModules = 0;
1149 *ppModules = nullptr;
1150
1151 // Start with 1024 modules
1152 DWORD cbNeeded = sizeof(HMODULE) * 1024;
1153
1154 ArrayHolder<HMODULE> modules = new (nothrow) HMODULE[cbNeeded / sizeof(HMODULE)];
1155 if (modules == nullptr)
1156 {
1157 return HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
1158 }
1159
1160 if(!EnumProcessModules(hProcess, modules, cbNeeded, &cbNeeded))
1161 {
1162 return HRESULT_FROM_WIN32(GetLastError());
1163 }
1164
1165 // If 1024 isn't enough, try the modules array size returned (cbNeeded)
1166 if (cbNeeded > (sizeof(HMODULE) * 1024))
1167 {
1168 modules = new (nothrow) HMODULE[cbNeeded / sizeof(HMODULE)];
1169 if (modules == nullptr)
1170 {
1171 return HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
1172 }
1173
1174 DWORD cbNeeded2;
1175 if(!EnumProcessModules(hProcess, modules, cbNeeded, &cbNeeded2))
1176 {
1177 return HRESULT_FROM_WIN32(GetLastError());
1178 }
1179
1180 // The only way cbNeeded2 could change on the second call is if number of
1181 // modules loaded by the process changed in the small window between the
1182 // above EnumProcessModules calls. If this actually happens, then give
1183 // up on trying to get the whole module list and risk missing the coreclr
1184 // module.
1185 cbNeeded = min(cbNeeded, cbNeeded2);
1186 }
1187
1188 *pCountModules = cbNeeded / sizeof(HMODULE);
1189 *ppModules = modules.Detach();
1190 return S_OK;
1191}
1192
1193//-----------------------------------------------------------------------------
1194// Public API.
1195//
1196// EnumerateCLRs -- returns an array of full paths to each coreclr.dll in the
1197// target process. Also returns a corresponding array of continue events
1198// that *MUST* be signaled by the caller in order to allow the CLRs in the
1199// target process to proceed.
1200//
1201// debuggeePID -- process ID of the target process
1202// ppHandleArrayOut -- out parameter in which an array of handles is returned.
1203// the length of this array is returned by the pdwArrayLengthOut out param
1204// ppStringArrayOut -- out parameter in which an array of full paths to each
1205// coreclr.dll in the process is returned. The length of this array is the
1206// same as the handle array and is returned by the pdwArrayLengthOut param
1207// pdwArrayLengthOut -- out param in which the length of the two returned arrays
1208// are returned.
1209//
1210// Notes:
1211// Callers use code:CloseCLREnumeration to free the returned arrays.
1212//-----------------------------------------------------------------------------
1213HRESULT
1214EnumerateCLRs(
1215 DWORD debuggeePID,
1216 __out HANDLE** ppHandleArrayOut,
1217 __out LPWSTR** ppStringArrayOut,
1218 __out DWORD* pdwArrayLengthOut)
1219{
1220 PUBLIC_CONTRACT;
1221
1222 // All out params must be non-NULL.
1223 if ((ppHandleArrayOut == NULL) || (ppStringArrayOut == NULL) || (pdwArrayLengthOut == NULL))
1224 return E_INVALIDARG;
1225
1226 HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID);
1227 if (NULL == hProcess)
1228 return E_FAIL;
1229
1230 // The modules in the array returned don't need to be closed
1231 DWORD countModules;
1232 ArrayHolder<HMODULE> modules = nullptr;
1233 HRESULT hr = EnumProcessModulesInternal(hProcess, &countModules, &modules);
1234 if (FAILED(hr))
1235 {
1236 return hr;
1237 }
1238
1239 //
1240 // count the number of coreclr.dll entries
1241 //
1242 DWORD count = 0;
1243 for(DWORD i = 0; i < countModules; i++)
1244 {
1245 if (IsCoreClrWithGoodHeader(hProcess, modules[i]))
1246 {
1247 count++;
1248 }
1249 }
1250
1251 // If we didn't find anything, no point in continuing.
1252 if (count == 0)
1253 {
1254 *ppHandleArrayOut = NULL;
1255 *ppStringArrayOut = NULL;
1256 *pdwArrayLengthOut = 0;
1257
1258 return S_OK;
1259 }
1260
1261 size_t cbEventArrayData = sizeof(HANDLE) * count; // event array data
1262 size_t cbStringArrayData = sizeof(LPWSTR) * count; // string array data
1263 size_t cbStringData = sizeof(WCHAR) * count * MAX_LONGPATH; // strings data
1264 size_t cbBuffer = cbEventArrayData + cbStringArrayData + cbStringData;
1265
1266 BYTE* pOutBuffer = new (nothrow) BYTE[cbBuffer];
1267 if (NULL == pOutBuffer)
1268 return E_OUTOFMEMORY;
1269
1270 ZeroMemory(pOutBuffer, cbBuffer);
1271
1272 HANDLE* pEventArray = (HANDLE*) &pOutBuffer[0];
1273 LPWSTR* pStringArray = (LPWSTR*) &pOutBuffer[cbEventArrayData];
1274 WCHAR* pStringData = (WCHAR*) &pOutBuffer[cbEventArrayData + cbStringArrayData];
1275 DWORD idx = 0;
1276
1277 // There's no guarantee that another coreclr hasn't loaded already anyhow,
1278 // so if we get the corner case that the second time through we enumerate
1279 // more coreclrs, just ignore the extras.
1280 // This mismatch could happen when
1281 // a) take module shapshot
1282 // b) underlying file is opened for exclusive access/deleted/moved/ACL'd etc so we can't open it
1283 // c) count is determined
1284 // d) file is closed/copied/moved/ACL'd etc so we can find/open it again
1285 // e) this loop runs
1286 // Thus the loop checks idx < count
1287
1288 for(DWORD i = 0; i < countModules && idx < count; i++)
1289 {
1290 if (IsCoreClrWithGoodHeader(hProcess, modules[i]))
1291 {
1292 // fill in path
1293 pStringArray[idx] = &pStringData[idx * MAX_LONGPATH];
1294 GetModuleFileNameEx(hProcess, modules[i], pStringArray[idx], MAX_LONGPATH);
1295
1296#ifndef FEATURE_PAL
1297 // fill in event handle -- if GetContinueStartupEvent fails, it will still return
1298 // INVALID_HANDLE_VALUE in hContinueStartupEvent, which is what we want. we don't
1299 // want to bail out of the enumeration altogether if we can't get an event from
1300 // one telesto.
1301
1302 HANDLE hContinueStartupEvent = INVALID_HANDLE_VALUE;
1303 HRESULT hr = GetContinueStartupEvent(debuggeePID, pStringArray[idx], &hContinueStartupEvent);
1304 pEventArray[idx] = hContinueStartupEvent;
1305#else
1306 pEventArray[idx] = NULL;
1307#endif // FEATURE_PAL
1308
1309 idx++;
1310 }
1311 }
1312
1313 // Patch things up so CloseCLREnumeration() can still have it's
1314 // pointer arithmatic checks succeed, and the user doesn't see a 'dead' entry.
1315 // Specifically, it's expected that pEventArray and pStringArray point to the
1316 // same contiguous chunk of memory so that pStringArray == pEventArray[*pdwArrayLengthOut].
1317 // This is expected to be a very rare case.
1318 if (idx < count)
1319 {
1320 // Move the string pointers back.
1321 LPWSTR* pSATemp = (LPWSTR*)&pOutBuffer[sizeof(HANDLE)*idx];
1322 for (DWORD i = 0; i < idx; i++)
1323 {
1324 pSATemp[i] = pStringArray[i];
1325 }
1326
1327 // Fix up string array pointer.
1328 pStringArray = (LPWSTR*)&pOutBuffer[sizeof(HANDLE)*idx];
1329
1330 // Strings themselves don't need moved.
1331 }
1332
1333 *ppHandleArrayOut = pEventArray;
1334 *ppStringArrayOut = pStringArray;
1335 *pdwArrayLengthOut = idx;
1336
1337 return S_OK;
1338}
1339
1340//-----------------------------------------------------------------------------
1341// Public API.
1342//
1343// CloseCLREnumeration -- used to free resources allocated by EnumerateCLRs
1344//
1345// pHandleArray -- handle array originally returned by EnumerateCLRs
1346// pStringArray -- string array originally returned by EnumerateCLRs
1347// dwArrayLength -- array length originally returned by EnumerateCLRs
1348//
1349//-----------------------------------------------------------------------------
1350HRESULT
1351CloseCLREnumeration(
1352 __in HANDLE* pHandleArray,
1353 __in LPWSTR* pStringArray,
1354 __in DWORD dwArrayLength)
1355{
1356 PUBLIC_CONTRACT;
1357
1358 if ((pHandleArray + dwArrayLength) != (HANDLE*)pStringArray)
1359 return E_INVALIDARG;
1360
1361 // It's possible that EnumerateCLRs found nothing to enumerate, in which case
1362 // pointers and count are zeroed. If a debugger calls this function in that
1363 // case, let's not try to delete [] on NULL.
1364 if (pHandleArray == NULL)
1365 return S_OK;
1366
1367#ifndef FEATURE_PAL
1368 for (DWORD i = 0; i < dwArrayLength; i++)
1369 {
1370 HANDLE hTemp = pHandleArray[i];
1371 if ( (NULL != hTemp)
1372 && (INVALID_HANDLE_VALUE != hTemp))
1373 {
1374 CloseHandle(hTemp);
1375 }
1376 }
1377#endif // FEATURE_PAL
1378
1379 delete[] pHandleArray;
1380 return S_OK;
1381}
1382
1383//-----------------------------------------------------------------------------
1384// Get the base address of a module from the remote process.
1385//
1386// Returns:
1387// - On success, base address (in remote process) of mscoree,
1388// - NULL if the module is not loaded.
1389// - else Throws. *ppBaseAddress = NULL
1390//-----------------------------------------------------------------------------
1391static
1392BYTE*
1393GetRemoteModuleBaseAddress(
1394 DWORD dwPID,
1395 LPCWSTR szFullModulePath)
1396{
1397 CONTRACTL
1398 {
1399 THROWS;
1400 }
1401 CONTRACTL_END;
1402
1403 HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
1404 if (NULL == hProcess)
1405 {
1406 ThrowHR(E_FAIL);
1407 }
1408
1409 // The modules in the array returned don't need to be closed
1410 DWORD countModules;
1411 ArrayHolder<HMODULE> modules = nullptr;
1412 HRESULT hr = EnumProcessModulesInternal(hProcess, &countModules, &modules);
1413 if (FAILED(hr))
1414 {
1415 ThrowHR(hr);
1416 }
1417
1418 for(DWORD i = 0; i < countModules; i++)
1419 {
1420 WCHAR modulePath[MAX_LONGPATH];
1421 if(0 == GetModuleFileNameEx(hProcess, modules[i], modulePath, MAX_LONGPATH))
1422 {
1423 continue;
1424 }
1425 else
1426 {
1427 modulePath[MAX_LONGPATH-1] = 0; // on older OS'es this doesn't get null terminated automatically
1428 if (_wcsicmp(modulePath, szFullModulePath) == 0)
1429 {
1430 return (BYTE*) modules[i];
1431 }
1432 }
1433 }
1434
1435 // Successfully enumerated modules but couldn't find the requested one.
1436 return NULL;
1437}
1438
1439// DBI version: max 8 hex chars
1440// SEMICOLON: 1
1441// PID: max 8 hex chars
1442// SEMICOLON: 1
1443// HMODULE: max 16 hex chars (64-bit)
1444// SEMICOLON: 1
1445// PROTOCOL STRING: (variable length)
1446const int c_iMaxVersionStringLen = 8 + 1 + 8 + 1 + 16; // 64-bit hmodule
1447const int c_iMinVersionStringLen = 8 + 1 + 8 + 1 + 8; // 32-bit hmodule
1448const int c_idxFirstSemi = 8;
1449const int c_idxSecondSemi = 17;
1450const WCHAR *c_versionStrFormat = W("%08x;%08x;%p");
1451
1452//-----------------------------------------------------------------------------
1453// Public API.
1454// Given a path to a coreclr.dll, get the Version string.
1455//
1456// Arguments:
1457// pidDebuggee - OS process ID of debuggee.
1458// szModuleName - a full or relative path to a valid coreclr.dll in the debuggee.
1459// pBuffer - the buffer to fill the version string into
1460// if pdwLength != NULL, we set *pdwLength to the length of the version string on
1461// output (including the null terminator).
1462// cchBuffer - length of pBuffer on input in characters
1463//
1464// Returns:
1465// S_OK - on success.
1466// E_INVALIDARG -
1467// HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) if the buffer is too small.
1468// COR_E_FILENOTFOUND - module is not found in a given debugee process
1469//
1470// Notes:
1471// The null-terminated version string including null, is
1472// copied to pVersion on output. Thus *pdwLength == wcslen(pBuffer)+1.
1473// The version string is an opaque string that can only be passed back to other
1474// DbgShim APIs.
1475//-----------------------------------------------------------------------------
1476HRESULT
1477CreateVersionStringFromModule(
1478 __in DWORD pidDebuggee,
1479 __in LPCWSTR szModuleName,
1480 __out_ecount_part(cchBuffer, *pdwLength) LPWSTR pBuffer,
1481 __in DWORD cchBuffer,
1482 __out DWORD* pdwLength)
1483{
1484 PUBLIC_CONTRACT;
1485
1486 if (szModuleName == NULL)
1487 {
1488 return E_INVALIDARG;
1489 }
1490
1491 // it is ok for both to be null (to query the required buffer size) or both to be non-null.
1492 if ((pBuffer == NULL) != (cchBuffer == 0))
1493 {
1494 return E_INVALIDARG;
1495 }
1496
1497 SIZE_T nLengthWithNull = c_iMaxVersionStringLen + 1;
1498 _ASSERTE(nLengthWithNull > 0);
1499
1500 if (pdwLength != NULL)
1501 {
1502 *pdwLength = (DWORD) nLengthWithNull;
1503 }
1504
1505 if (nLengthWithNull > cchBuffer)
1506 {
1507 return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
1508 }
1509 else if (pBuffer != NULL)
1510 {
1511
1512 HRESULT hr = S_OK;
1513 EX_TRY
1514 {
1515 CorDebugInterfaceVersion dbiVersion = CorDebugInvalidVersion;
1516 BYTE* hmodTargetCLR = NULL;
1517 CLR_ENGINE_METRICS metricsStruct;
1518
1519 GetTargetCLRMetrics(szModuleName, &metricsStruct); // throws
1520 dbiVersion = (CorDebugInterfaceVersion) metricsStruct.dwDbiVersion;
1521
1522 hmodTargetCLR = GetRemoteModuleBaseAddress(pidDebuggee, szModuleName); // throws
1523 if (hmodTargetCLR == NULL)
1524 {
1525 hr = COR_E_FILENOTFOUND;
1526 }
1527 else
1528 {
1529 swprintf_s(pBuffer, cchBuffer, c_versionStrFormat, dbiVersion, pidDebuggee, hmodTargetCLR);
1530 }
1531 }
1532 EX_CATCH_HRESULT(hr);
1533 return hr;
1534 }
1535
1536 return S_OK;
1537}
1538
1539//-----------------------------------------------------------------------------
1540// Parse a version string into useful data.
1541//
1542// Arguments:
1543// szDebuggeeVersion - (in) null terminated version string
1544// piDebuggerVersion - (out) interface number that the debugger expects to use.
1545// pdwPidDebuggee - (out) OS process ID of debuggee
1546// phmodTargetCLR - (out) module handle of CoreClr within the debuggee.
1547//
1548// Returns:
1549// S_OK on success. Else failures.
1550//
1551// Notes:
1552// The version string is coming from the target CoreClr and in the case of a corrupted target, could be
1553// an arbitrary string. It should be treated as untrusted public input.
1554//-----------------------------------------------------------------------------
1555static
1556HRESULT
1557ParseVersionString(
1558 LPCWSTR szDebuggeeVersion,
1559 CorDebugInterfaceVersion *piDebuggerVersion,
1560 DWORD *pdwPidDebuggee,
1561 HMODULE *phmodTargetCLR)
1562{
1563 if ((piDebuggerVersion == NULL) ||
1564 (pdwPidDebuggee == NULL) ||
1565 (phmodTargetCLR == NULL) ||
1566 (wcslen(szDebuggeeVersion) < c_iMinVersionStringLen) ||
1567 (W(';') != szDebuggeeVersion[c_idxFirstSemi]) ||
1568 (W(';') != szDebuggeeVersion[c_idxSecondSemi]))
1569 {
1570 return E_INVALIDARG;
1571 }
1572
1573 int numFieldsAssigned = swscanf_s(szDebuggeeVersion, c_versionStrFormat, piDebuggerVersion, pdwPidDebuggee, phmodTargetCLR);
1574 if (numFieldsAssigned != 3)
1575 {
1576 return E_FAIL;
1577 }
1578
1579 return S_OK;
1580}
1581
1582//-----------------------------------------------------------------------------
1583// Appends "\mscordbi.dll" to the path. This converts a directory name into the full path to mscordbi.dll.
1584//
1585// Arguments:
1586// szFullDbiPath - (in/out): on input, the directory containing dbi. On output, the full path to dbi.dll.
1587//-----------------------------------------------------------------------------
1588static
1589void
1590AppendDbiDllName(SString & szFullDbiPath)
1591{
1592 const WCHAR * pDbiDllName = DIRECTORY_SEPARATOR_STR_W MAKEDLLNAME_W(W("mscordbi"));
1593 szFullDbiPath.Append(pDbiDllName);
1594}
1595
1596//-----------------------------------------------------------------------------
1597// Return a path to the dbi next to the runtime, if present.
1598//
1599// Arguments:
1600// pidDebuggee - OS process ID of debuggee
1601// hmodTargetCLR - handle to CoreClr within debuggee process
1602// szFullDbiPath - (out) the full path of Mscordbi.dll next to the debuggee's CoreClr.dll.
1603//
1604// Notes:
1605// This just calculates a filename and does not determine if the file actually exists.
1606//-----------------------------------------------------------------------------
1607static
1608void
1609GetDbiFilenameNextToRuntime(
1610 DWORD pidDebuggee,
1611 HMODULE hmodTargetCLR,
1612 SString & szFullDbiPath,
1613 SString & szFullCoreClrPath)
1614{
1615 szFullDbiPath.Clear();
1616
1617 //
1618 // Step 1: (pid, hmodule) --> full path
1619 //
1620 HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pidDebuggee);
1621 WCHAR modulePath[MAX_LONGPATH];
1622 if(0 == GetModuleFileNameEx(hProcess, hmodTargetCLR, modulePath, MAX_LONGPATH))
1623 {
1624 ThrowHR(E_FAIL);
1625 }
1626
1627 //
1628 // Step 2: 'Coreclr.dll' --> 'mscordbi.dll'
1629 //
1630 WCHAR * pCoreClrPath = modulePath;
1631 WCHAR * pLast = wcsrchr(pCoreClrPath, DIRECTORY_SEPARATOR_CHAR_W);
1632 if (pLast == NULL)
1633 {
1634 ThrowHR(E_FAIL);
1635 }
1636
1637 // c:\abc\coreclr.dll
1638 // 01234567890
1639 // c:\abc\mscordbi.dll
1640
1641 // Copy everything up to but not including the last '\', thus excluding '\coreclr.dll'
1642 // Then append '\mscordbi.dll' to get a full path to dbi.
1643 COUNT_T len = (COUNT_T) (pLast - pCoreClrPath); // length not including final '\'
1644 szFullDbiPath.Set(pCoreClrPath, len);
1645
1646 AppendDbiDllName(szFullDbiPath);
1647
1648 szFullCoreClrPath.Set(pCoreClrPath, (COUNT_T)wcslen(pCoreClrPath));
1649}
1650
1651
1652//---------------------------------------------------------------------------------------
1653//
1654// The current policy is that the DBI DLL must live right next to the coreclr DLL. We check the product
1655// version number of both of them to make sure they match.
1656//
1657// Arguments:
1658// szFullDbiPath - full path to mscordbi.dll
1659// szFullCoreClrPath - full path to coreclr.dll
1660//
1661// Return Value:
1662// true if the versions match
1663//
1664static
1665bool
1666CheckDbiAndRuntimeVersion(
1667 SString & szFullDbiPath,
1668 SString & szFullCoreClrPath)
1669{
1670#ifndef FEATURE_PAL
1671 DWORD dwDbiVersionMS = 0;
1672 DWORD dwDbiVersionLS = 0;
1673 DWORD dwCoreClrVersionMS = 0;
1674 DWORD dwCoreClrVersionLS = 0;
1675
1676 // The version numbers follow the convention used by VS_FIXEDFILEINFO.
1677 GetProductVersionNumber(szFullDbiPath, &dwDbiVersionMS, &dwDbiVersionLS);
1678 GetProductVersionNumber(szFullCoreClrPath, &dwCoreClrVersionMS, &dwCoreClrVersionLS);
1679
1680 if ((dwDbiVersionMS == dwCoreClrVersionMS) &&
1681 (dwDbiVersionLS == dwCoreClrVersionLS))
1682 {
1683 return true;
1684 }
1685 else
1686 {
1687 return false;
1688 }
1689#else
1690 return true;
1691#endif // FEATURE_PAL
1692}
1693
1694//-----------------------------------------------------------------------------
1695// Public API.
1696// Given a version string, create the matching mscordbi.dll for it.
1697// Create a managed debugging interface for the specified version.
1698//
1699// Parameters:
1700// iDebuggerVersion - the version of interface the debugger (eg, Cordbg) expects.
1701// szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll
1702// ppCordb - the outparameter used to return the debugging interface object.
1703//
1704// Return:
1705// S_OK on success. *ppCordb will be non-null.
1706// CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if
1707// the right debug pack is not installed.
1708// else Error. (*ppCordb will be null)
1709//-----------------------------------------------------------------------------
1710HRESULT
1711CreateDebuggingInterfaceFromVersionEx(
1712 __in int iDebuggerVersion,
1713 __in LPCWSTR szDebuggeeVersion,
1714 __out IUnknown ** ppCordb)
1715{
1716 return CreateDebuggingInterfaceFromVersion2(iDebuggerVersion, szDebuggeeVersion, NULL, ppCordb);
1717}
1718
1719//-----------------------------------------------------------------------------
1720// Public API.
1721// Given a version string, create the matching mscordbi.dll for it.
1722// Create a managed debugging interface for the specified version.
1723//
1724// Parameters:
1725// iDebuggerVersion - the version of interface the debugger (eg, Cordbg) expects.
1726// szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll
1727// lpApplicationGroupId - A string representing the application group ID of a sandboxed
1728// process running in Mac. Pass NULL if the process is not
1729// running in a sandbox and other platforms.
1730// ppCordb - the outparameter used to return the debugging interface object.
1731//
1732// Return:
1733// S_OK on success. *ppCordb will be non-null.
1734// CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if
1735// the right debug pack is not installed.
1736// else Error. (*ppCordb will be null)
1737//-----------------------------------------------------------------------------
1738HRESULT
1739CreateDebuggingInterfaceFromVersion2(
1740 __in int iDebuggerVersion,
1741 __in LPCWSTR szDebuggeeVersion,
1742 __in LPCWSTR szApplicationGroupId,
1743 __out IUnknown ** ppCordb)
1744{
1745 PUBLIC_CONTRACT;
1746
1747 HRESULT hrIgnore = S_OK; // ignored HResult
1748 HRESULT hr = S_OK;
1749 HMODULE hMod = NULL;
1750 IUnknown * pCordb = NULL;
1751 FPCoreCLRCreateCordbObject fpCreate2 = NULL;
1752
1753 LOG((LF_CORDB, LL_EVERYTHING, "Calling CreateDebuggerInterfaceFromVersion, ver=%S\n", szDebuggeeVersion));
1754
1755 if ((szDebuggeeVersion == NULL) || (ppCordb == NULL))
1756 {
1757 hr = E_INVALIDARG;
1758 goto Exit;
1759 }
1760
1761 //
1762 // Step 1: Parse version information into internal data structures
1763 //
1764
1765 CorDebugInterfaceVersion iTargetVersion; // the CorDebugInterfaceVersion (CorDebugVersion_2_0)
1766 DWORD pidDebuggee; // OS process ID of the debuggee
1767 HMODULE hmodTargetCLR; // module of Telesto in target (the clrInstanceId)
1768
1769 hr = ParseVersionString(szDebuggeeVersion, &iTargetVersion, &pidDebuggee, &hmodTargetCLR);
1770 if (FAILED(hr))
1771 goto Exit;
1772
1773 //
1774 // Step 2: Find the proper dbi module (mscordbi) and load it.
1775 //
1776
1777 // Check for dbi next to target CLR.
1778 // This will be very common for internal developer setups, but not common in end-user setups.
1779 EX_TRY
1780 {
1781 SString szFullDbiPath;
1782 SString szFullCoreClrPath;
1783
1784 GetDbiFilenameNextToRuntime(pidDebuggee, hmodTargetCLR, szFullDbiPath, szFullCoreClrPath);
1785
1786 if (!CheckDbiAndRuntimeVersion(szFullDbiPath, szFullCoreClrPath))
1787 {
1788 hr = CORDBG_E_INCOMPATIBLE_PROTOCOL;
1789 goto Exit;
1790 }
1791
1792 // We calculated where dbi would be, but haven't yet verified if it's there.
1793 // Try to load it. We're using this to check for file existence.
1794
1795 // Issue:951525: coreclr mscordbi load fails on downlevel OS since LoadLibraryEx can't find
1796 // dependent forwarder DLLs. Force LoadLibrary to look for dependencies in szFullDbiPath plus the default
1797 // search paths.
1798#ifndef FEATURE_PAL
1799 hMod = WszLoadLibraryEx(szFullDbiPath, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
1800#else
1801 hMod = LoadLibraryExW(szFullDbiPath, NULL, 0);
1802#endif
1803 }
1804 EX_CATCH_HRESULT(hrIgnore); // failure leaves hMod null
1805
1806 // Couldn't find Dbi, likely because the right debug pack is not installed. Failure.
1807 if (NULL == hMod)
1808 {
1809 // Check for the following two HRESULTs and return them specifically. These are returned by
1810 // CreateToolhelp32Snapshot() and could be transient errors. The debugger may choose to retry.
1811 if ((hrIgnore == HRESULT_FROM_WIN32(ERROR_PARTIAL_COPY)) || (hrIgnore == HRESULT_FROM_WIN32(ERROR_BAD_LENGTH)))
1812 {
1813 hr = hrIgnore;
1814 }
1815 else
1816 {
1817 hr = CORDBG_E_DEBUG_COMPONENT_MISSING;
1818 }
1819 goto Exit;
1820 }
1821
1822 //
1823 // Step 3: Now that module is loaded, instantiate an ICorDebug.
1824 //
1825 hr = CreateCoreDbg(hmodTargetCLR, hMod, pidDebuggee, szApplicationGroupId, iDebuggerVersion, &pCordb);
1826 _ASSERTE((pCordb == NULL) == FAILED(hr));
1827
1828Exit:
1829 if (FAILED(hr))
1830 {
1831 if (pCordb != NULL)
1832 {
1833 pCordb->Release();
1834 pCordb = NULL;
1835 }
1836
1837 if (hMod != NULL)
1838 {
1839 _ASSERTE(pCordb == NULL);
1840 FreeLibrary(hMod);
1841 }
1842 }
1843
1844 // Set our outparam.
1845 *ppCordb = pCordb;
1846
1847 // On success case, mscordbi.dll is leaked.
1848 // - We never give the caller back the module handle, so our caller can't do FreeLibrary().
1849 // - ICorDebug can't unload itself.
1850
1851 return hr;
1852}
1853
1854//-----------------------------------------------------------------------------
1855// Public API.
1856// Superceded by CreateDebuggingInterfaceFromVersionEx in SLv4.
1857// Given a version string, create the matching mscordbi.dll for it.
1858// Create a managed debugging interface for the specified version.
1859//
1860// Parameters:
1861// szDebuggeeVersion - the version of the debuggee. This will map to a version of mscordbi.dll
1862// ppCordb - the outparameter used to return the debugging interface object.
1863//
1864// Return:
1865// S_OK on success. *ppCordb will be non-null.
1866// CORDBG_E_INCOMPATIBLE_PROTOCOL - if the proper DBI is not available. This can be a very common error if
1867// the right debug pack is not installed.
1868// else Error. (*ppCordb will be null)
1869//-----------------------------------------------------------------------------
1870HRESULT
1871CreateDebuggingInterfaceFromVersion(
1872 __in LPCWSTR szDebuggeeVersion,
1873 __out IUnknown ** ppCordb
1874)
1875{
1876 PUBLIC_CONTRACT;
1877
1878 return CreateDebuggingInterfaceFromVersionEx(CorDebugVersion_2_0, szDebuggeeVersion, ppCordb);
1879}
1880
1881#ifndef FEATURE_PAL
1882
1883//------------------------------------------------------------------------------
1884// Manually retrieves the "continue startup" event from the correct CLR instance
1885// in the target process.
1886//
1887// Arguments:
1888// debuggeePID - (in) OS Process ID of debuggee
1889// szTelestoFullPath - (in) full path to telesto within the process.
1890// phContinueStartupEvent - (out)
1891//
1892// Returns:
1893// S_OK on success.
1894//------------------------------------------------------------------------------
1895HRESULT
1896GetContinueStartupEvent(
1897 DWORD debuggeePID,
1898 LPCWSTR szTelestoFullPath,
1899 __out HANDLE* phContinueStartupEvent)
1900{
1901 if ((phContinueStartupEvent == NULL) || (szTelestoFullPath == NULL))
1902 return E_INVALIDARG;
1903
1904 HRESULT hr = S_OK;
1905 EX_TRY
1906 {
1907 *phContinueStartupEvent = INVALID_HANDLE_VALUE;
1908
1909 DWORD dwCoreClrContinueEventOffset = 0;
1910 CLR_ENGINE_METRICS metricsStruct;
1911
1912 GetTargetCLRMetrics(szTelestoFullPath, &metricsStruct, &dwCoreClrContinueEventOffset); // throws
1913
1914
1915 BYTE* pbBaseAddress = GetRemoteModuleBaseAddress(debuggeePID, szTelestoFullPath); // throws
1916
1917
1918 HandleHolder hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, debuggeePID);
1919 if (NULL == hProcess)
1920 ThrowHR(E_FAIL);
1921
1922 HANDLE continueEvent = NULL;
1923
1924 SIZE_T nBytesRead;
1925 if (!ReadProcessMemory(hProcess, pbBaseAddress + dwCoreClrContinueEventOffset, &continueEvent,
1926 sizeof(continueEvent), &nBytesRead))
1927 {
1928 ThrowHR(E_FAIL);
1929 }
1930
1931 if (NULL != continueEvent && INVALID_HANDLE_VALUE != continueEvent)
1932 {
1933 if (!DuplicateHandle(hProcess, continueEvent, GetCurrentProcess(), &continueEvent,
1934 EVENT_MODIFY_STATE, FALSE, 0))
1935 {
1936 ThrowHR(E_FAIL);
1937 }
1938 }
1939
1940 *phContinueStartupEvent = continueEvent;
1941 }
1942 EX_CATCH_HRESULT(hr)
1943 return hr;
1944}
1945
1946#endif // !FEATURE_PAL
1947
1948#if defined(FEATURE_CORESYSTEM)
1949#include "debugshim.h"
1950#endif
1951
1952//-----------------------------------------------------------------------------
1953// Public API.
1954//
1955// Parameters:
1956// clsid
1957// riid
1958// ppInterface
1959//
1960// Return:
1961// S_OK on success.
1962//-----------------------------------------------------------------------------
1963HRESULT
1964CLRCreateInstance(
1965 REFCLSID clsid,
1966 REFIID riid,
1967 LPVOID *ppInterface)
1968{
1969 PUBLIC_CONTRACT;
1970
1971#if defined(FEATURE_CORESYSTEM)
1972
1973 if (ppInterface == NULL)
1974 return E_POINTER;
1975
1976 if (clsid != CLSID_CLRDebugging || riid != IID_ICLRDebugging)
1977 return E_NOINTERFACE;
1978
1979#if defined(FEATURE_CORESYSTEM)
1980 GUID skuId = CLR_ID_ONECORE_CLR;
1981#else
1982 GUID skuId = CLR_ID_CORECLR;
1983#endif
1984
1985 CLRDebuggingImpl *pDebuggingImpl = new (nothrow) CLRDebuggingImpl(skuId);
1986 if (NULL == pDebuggingImpl)
1987 return E_OUTOFMEMORY;
1988
1989 return pDebuggingImpl->QueryInterface(riid, ppInterface);
1990#else
1991 return E_NOTIMPL;
1992#endif
1993}
1994