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/*****************************************************************************\
6* *
7* BBSweep.h - Classes for sweeping profile data to disk *
8* *
9* Version 1.0 *
10*******************************************************************************
11* *
12* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY *
13* KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE *
14* IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR *
15* PURPOSE. *
16* *
17\*****************************************************************************/
18
19#ifndef _BBSWEEP_H_
20#define _BBSWEEP_H_
21
22#ifndef FEATURE_PAL
23#include <aclapi.h>
24#endif // !FEATURE_PAL
25
26// The CLR headers don't allow us to use methods like SetEvent directly (instead
27// we need to use the host APIs). However, this file is included both in the CLR
28// and in the BBSweep tool, and the host API is not available in the tool. Moreover,
29// BBSweep is not designed to work in an environment where the host controls
30// synchronization. For this reason, we work around the problem by undefining
31// these APIs (the CLR redefines them so that they will not be used).
32#pragma push_macro("SetEvent")
33#pragma push_macro("ResetEvent")
34#pragma push_macro("ReleaseSemaphore")
35#pragma push_macro("LocalFree")
36#undef SetEvent
37#undef ResetEvent
38#undef ReleaseSemaphore
39#undef LocalFree
40
41// MAX_COUNT is the maximal number of runtime processes that can run at a given time
42#define MAX_COUNT 20
43
44#define INVALID_PID -1
45
46/* CLRBBSweepCallback is implemented by the CLR which passes it as an argument to WatchForSweepEvents.
47 * It is used by BBSweep to tell the CLR to write the profile data to disk at the right time.
48 */
49
50class ICLRBBSweepCallback
51{
52public:
53 virtual HRESULT WriteProfileData() = NULL; // tells the runtime to write the profile data to disk
54};
55
56/* BBSweep is used by both the CLR and the BBSweep utility.
57 * BBSweep: calls the PerformSweep method which returns after all the CLR processes
58 * have written their profile data to disk.
59 * CLR: starts up a sweeper thread which calls WatchForSweepEvents and waits until the
60 * sweeper program is invoked. At that point, all the CLR processes will synchronize
61 * and write their profile data to disk one at a time. The sweeper threads will then
62 * wait for the next sweep event. The CLR also calls ShutdownBBSweepThread at
63 * shutdown which returns when the BBSweep thread has terminated.
64 */
65
66class BBSweep
67{
68public:
69 BBSweep()
70 {
71 // The BBSweep constructor could be called even the the object is not used, so
72 // don't do any work here.
73 bInitialized = false;
74 bTerminate = false;
75 hSweepMutex = NULL;
76 hProfDataWriterMutex = NULL;
77 hSweepEvent = NULL;
78 hTerminationEvent = NULL;
79 hProfWriterSemaphore = NULL;
80 hBBSweepThread = NULL;
81 }
82
83 ~BBSweep()
84 {
85 // When the destructor is called, everything should be cleaned up already.
86 }
87
88 // Called by the sweeper utility to tell all the CLR threads to write their profile
89 // data to disk.
90 // THIS FUNCTIONALITY IS ALSO DUPLICATED IN TOOLBOX\MPGO\BBSWEEP.CS
91 // IF YOU CHANGE THIS CODE, YOU MUST ALSO CHANGE THAT TO MATCH!
92 bool PerformSweep(DWORD processID = INVALID_PID)
93 {
94 bool success = true;
95
96 if (!Initialize(processID, FALSE)) return false;
97
98 ::WaitForSingleObject(hSweepMutex, INFINITE);
99 {
100 success = success && ::SetEvent(hSweepEvent);
101 {
102 for (int i=0; i<MAX_COUNT; i++)
103 {
104 ::WaitForSingleObject(hProfWriterSemaphore, INFINITE);
105 }
106
107 ::ReleaseSemaphore(hProfWriterSemaphore, MAX_COUNT, NULL);
108
109 }
110 success = success && ::ResetEvent(hSweepEvent);
111 }
112 ::ReleaseMutex(hSweepMutex);
113
114 return success;
115 }
116
117 // Called by the CLR sweeper thread to wait until a sweep event, at which point
118 // it calls back into the CLR via the clrCallback interface to write the profile
119 // data to disk.
120 bool WatchForSweepEvents(ICLRBBSweepCallback *clrCallback)
121 {
122 if (!Initialize()) return false;
123
124 bool success = true;
125
126 while (!bTerminate)
127 {
128 ::WaitForSingleObject(hSweepMutex, INFINITE);
129 {
130 ::WaitForSingleObject(hProfWriterSemaphore, INFINITE);
131 }
132 ::ReleaseMutex(hSweepMutex);
133
134 HANDLE hEvents[2];
135 hEvents[0] = hSweepEvent;
136 hEvents[1] = hTerminationEvent;
137 ::WaitForMultipleObjectsEx(2, hEvents, false, INFINITE, FALSE);
138
139 ::WaitForSingleObject(hProfDataWriterMutex, INFINITE);
140 {
141 if (!bTerminate && FAILED(clrCallback->WriteProfileData()))
142 success = false;
143 }
144 ::ReleaseMutex(hProfDataWriterMutex);
145
146 ::ReleaseSemaphore(hProfWriterSemaphore, 1, NULL);
147 }
148
149 return success;
150 }
151
152 void SetBBSweepThreadHandle(HANDLE threadHandle)
153 {
154 hBBSweepThread = threadHandle;
155 }
156
157 void ShutdownBBSweepThread()
158 {
159 // Set the termination event and wait for the BBSweep thread to terminate on its own.
160 // Note that this is called by the shutdown thread (and never called by the BBSweep thread).
161 if (hBBSweepThread && bInitialized)
162 {
163 bTerminate = true;
164 ::SetEvent(hTerminationEvent);
165 ::WaitForSingleObject(hBBSweepThread, INFINITE);
166 Cleanup();
167 }
168 }
169
170 void Cleanup()
171 {
172 if (hSweepMutex) { ::CloseHandle(hSweepMutex); hSweepMutex = NULL;}
173 if (hProfDataWriterMutex) { ::CloseHandle(hProfDataWriterMutex); hProfDataWriterMutex = NULL;}
174 if (hSweepEvent) { ::CloseHandle(hSweepEvent); hSweepEvent = NULL;}
175 if (hTerminationEvent) { ::CloseHandle(hTerminationEvent); hTerminationEvent = NULL;}
176 if (hProfWriterSemaphore) { ::CloseHandle(hProfWriterSemaphore); hProfWriterSemaphore = NULL;}
177 }
178
179private:
180
181 // THIS FUNCTIONALITY IS ALSO DUPLICATED IN TOOLBOX\MPGO\BBSWEEP.CS
182 // IF YOU CHANGE THIS CODE, YOU MUST ALSO CHANGE THAT TO MATCH!
183 bool Initialize(DWORD processID = INVALID_PID, BOOL fromRuntime = TRUE)
184 {
185 if (!bInitialized)
186 {
187 SECURITY_ATTRIBUTES * pSecurityAttributes = NULL;
188
189#ifndef FEATURE_CORESYSTEM // @CORESYSTEMTODO
190 PSECURITY_DESCRIPTOR pSD = NULL;
191 PSID pAdminSid = NULL;
192 HANDLE hToken = NULL;
193 PACL pACL = NULL;
194 LPVOID buffer = NULL;
195
196 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
197 goto cleanup;
198
199 // don't set pSecurityAttributes for Metro processes
200 if(!IsAppContainerProcess(hToken))
201 {
202 SECURITY_ATTRIBUTES securityAttributes;
203 PSID pUserSid = NULL;
204 SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
205 DWORD retLength;
206
207#ifdef _PREFAST_
208#pragma warning(push)
209#pragma warning(disable:6211) // PREfast warning: Leaking memory 'pSD' due to an exception.
210#endif /*_PREFAST_ */
211 pSD = (PSECURITY_DESCRIPTOR) new char[SECURITY_DESCRIPTOR_MIN_LENGTH];
212 if (!pSD)
213 goto cleanup;
214
215 if (GetTokenInformation(hToken, TokenOwner, NULL, 0, &retLength))
216 goto cleanup;
217
218 buffer = (LPVOID) new char[retLength];
219 if (!buffer)
220 goto cleanup;
221#ifdef _PREFAST_
222#pragma warning(pop)
223#endif /*_PREFAST_*/
224
225 // Get the SID for the current user
226 if (!GetTokenInformation(hToken, TokenOwner, (LPVOID) buffer, retLength, &retLength))
227 goto cleanup;
228
229 pUserSid = ((TOKEN_OWNER *) buffer)->Owner;
230
231 // Get the SID for the admin group
232 // Create a SID for the BUILTIN\Administrators group.
233 if(! AllocateAndInitializeSid(&SIDAuthNT, 2,
234 SECURITY_BUILTIN_DOMAIN_RID,
235 DOMAIN_ALIAS_RID_ADMINS,
236 0, 0, 0, 0, 0, 0,
237 &pAdminSid))
238 goto cleanup;
239
240 EXPLICIT_ACCESS ea[2];
241 ZeroMemory(ea, 2 * sizeof(EXPLICIT_ACCESS));
242
243 // Initialize an EXPLICIT_ACCESS structure for an ACE.
244 // The ACE will allow the current user full access
245 ea[0].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL; // KEY_ALL_ACCESS;
246 ea[0].grfAccessMode = SET_ACCESS;
247 ea[0].grfInheritance= NO_INHERITANCE;
248 ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
249 ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
250 ea[0].Trustee.ptstrName = (LPTSTR) pUserSid;
251
252 // Initialize an EXPLICIT_ACCESS structure for an ACE.
253 // The ACE will allow admins full access
254 ea[1].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL; //KEY_ALL_ACCESS;
255 ea[1].grfAccessMode = SET_ACCESS;
256 ea[1].grfInheritance= NO_INHERITANCE;
257 ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
258 ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
259 ea[1].Trustee.ptstrName = (LPTSTR) pAdminSid;
260
261 if (SetEntriesInAcl(2, ea, NULL, &pACL) != ERROR_SUCCESS)
262 goto cleanup;
263
264 if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
265 goto cleanup;
266
267 if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE))
268 goto cleanup;
269
270 memset((void *) &securityAttributes, 0, sizeof(SECURITY_ATTRIBUTES));
271 securityAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
272 securityAttributes.lpSecurityDescriptor = pSD;
273 securityAttributes.bInheritHandle = FALSE;
274
275 pSecurityAttributes = &securityAttributes;
276 }
277#endif // !FEATURE_CORESYSTEM
278
279 WCHAR objectName[MAX_LONGPATH] = {0};
280 WCHAR objectNamePrefix[MAX_LONGPATH] = {0};
281 GetObjectNamePrefix(processID, fromRuntime, objectNamePrefix);
282 // if there is a non-empty name prefix, append a '\'
283 if (objectNamePrefix[0] != '\0')
284 wcscat_s(objectNamePrefix, ARRAYSIZE(objectNamePrefix), W("\\"));
285 swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hSweepMutex"), objectNamePrefix);
286 hSweepMutex = ::WszCreateMutex(pSecurityAttributes, false, objectName);
287 swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hProfDataWriterMutex"), objectNamePrefix);
288 hProfDataWriterMutex = ::WszCreateMutex(pSecurityAttributes, false, objectName);
289 swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hSweepEvent"), objectNamePrefix);
290 hSweepEvent = ::WszCreateEvent(pSecurityAttributes, true, false, objectName);
291
292 // Note that hTerminateEvent is not a named event. That is because it is not
293 // shared amongst the CLR processes (each process terminates at a different time)
294 hTerminationEvent = ::WszCreateEvent(pSecurityAttributes, true, false, NULL);
295 swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hProfWriterSemaphore"), objectNamePrefix);
296 hProfWriterSemaphore = ::WszCreateSemaphore(pSecurityAttributes, MAX_COUNT, MAX_COUNT, objectName);
297
298#ifndef FEATURE_CORESYSTEM // @CORESYSTEMTODO
299cleanup:
300 if (pSD) delete [] ((char *) pSD);
301 if (pAdminSid) FreeSid(pAdminSid);
302 if (hToken) CloseHandle(hToken);
303 if (pACL) LocalFree(pACL);
304 if (buffer) delete [] ((char *) buffer);
305#endif
306 }
307
308 bInitialized = hSweepMutex &&
309 hProfDataWriterMutex &&
310 hSweepEvent &&
311 hTerminationEvent &&
312 hProfWriterSemaphore;
313
314 if (!bInitialized) Cleanup();
315 return bInitialized;
316 }
317
318#ifndef FEATURE_PAL
319 BOOL IsAppContainerProcess(HANDLE hToken)
320 {
321#ifndef TokenIsAppContainer
322#define TokenIsAppContainer ((TOKEN_INFORMATION_CLASS) 29)
323#endif
324 BOOL fIsAppContainerProcess;
325 DWORD dwReturnLength;
326 if (!GetTokenInformation(hToken, TokenIsAppContainer, &fIsAppContainerProcess, sizeof(BOOL), &dwReturnLength) ||
327 dwReturnLength != sizeof(BOOL))
328 {
329 fIsAppContainerProcess = FALSE;
330 }
331 return fIsAppContainerProcess;
332 }
333#endif // !FEATURE_PAL
334
335 // helper to get the correct object name prefix
336 void GetObjectNamePrefix(DWORD processID, BOOL fromRuntime, __inout_z WCHAR* objectNamePrefix)
337 {
338 // default prefix
339 swprintf_s(objectNamePrefix, MAX_LONGPATH, W("Global"));
340#ifndef FEATURE_PAL
341 //
342 // This method can be called:
343 // 1. From process init code
344 // 2. From bbsweepclr.exe
345 //
346 // When called from process init code, processID is always INVALID_PID.
347 // In case it is a Win8-Metro/WP8 process, we need to add the AppContainerNamedObjectPath to prefix.
348 // And if it is a non-Metro process, we will continue to use the default prefix (Global).
349 // We use IsAppContainerProcess(CurrentProcessId) to make this decision.
350 //
351 //
352 // When called from bbsweepclr, processID is valid when sweeping a Metro or WP8 process.
353 // We use this valid processID to determine if the process being swept is Metro/WP8 indeed and then
354 // add AppContainerNamedObjectPath to prefix. This is done by IsAppContainerProcess(processID).
355 //
356 // In case INVALID_PID is passed(non-Metro process), we have to use default prefix. To handle this
357 // case we use IsAppContainerProcess(CurrentProcessId) and since bbsweepclr is a non-Metro process,
358 // this check always returns false and we end up using the intended(default) prefix.
359 //
360 if(processID == INVALID_PID) {
361 // we reach here when:
362 // * called from process init code:
363 // * called from bbsweepclr.exe and no processID has been passed as argument, that is, when sweeping a non-Metro process
364 processID = GetCurrentProcessId();
365 }
366
367 HandleHolder hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processID);
368 if (hProcess != INVALID_HANDLE_VALUE)
369 {
370 HandleHolder hToken = NULL;
371 // if in the process init code of a Metro app or if bbsweepclr is used to sweep a Metro app,
372 // construct the object name prefix using AppContainerNamedObjectPath
373 if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken) && IsAppContainerProcess(hToken))
374 {
375 WCHAR appxNamedObjPath[MAX_LONGPATH] = { 0 };
376 ULONG appxNamedObjPathBufLen = 0;
377
378 if (fromRuntime)
379 {
380 // for Metro apps, create the object in the "default" object path, i.e. do not provide any prefix
381 objectNamePrefix[0] = W('\0');
382 }
383 else
384 {
385#if defined (FEATURE_CORESYSTEM) && !defined(CROSSGEN_COMPILE) && !defined(DACCESS_COMPILE)
386#define MODULE_NAME W("api-ms-win-security-appcontainer-l1-1-0.dll")
387#else
388#define MODULE_NAME W("kernel32.dll")
389#endif
390 typedef BOOL(WINAPI *PFN_GetAppContainerNamedObjectPath)
391 (HANDLE Token, PSID AppContainerSid, ULONG ObjectPathLength, WCHAR * ObjectPath, PULONG ReturnLength);
392
393 PFN_GetAppContainerNamedObjectPath pfnGetAppContainerNamedObjectPath = (PFN_GetAppContainerNamedObjectPath)
394 GetProcAddress(WszGetModuleHandle(MODULE_NAME), "GetAppContainerNamedObjectPath");
395 if (pfnGetAppContainerNamedObjectPath)
396 {
397 // for bbsweepclr sweeping a Metro app, create the object specifying the AppContainer's path
398 DWORD sessionId = 0;
399 ProcessIdToSessionId(processID, &sessionId);
400 pfnGetAppContainerNamedObjectPath(hToken, NULL, sizeof (appxNamedObjPath) / sizeof (WCHAR), appxNamedObjPath, &appxNamedObjPathBufLen);
401 swprintf_s(objectNamePrefix, MAX_LONGPATH, W("Global\\Session\\%d\\%s"), sessionId, appxNamedObjPath);
402 }
403 }
404 }
405 }
406#endif // FEATURE_PAL
407 }
408private:
409
410 bool bInitialized; // true when the BBSweep object has initialized successfully
411 bool bTerminate; // set to true when the CLR wants us to terminate
412 HANDLE hSweepMutex; // prevents processing from incrementing the semaphore after the sweep has began
413 HANDLE hProfDataWriterMutex; // guarantees that profile data will be written by one process at a time
414 HANDLE hSweepEvent; // tells the CLR processes to sweep their profile data
415 HANDLE hTerminationEvent; // set when the CLR process is ready to terminate
416 HANDLE hProfWriterSemaphore; // helps determine when all the writers are finished
417 HANDLE hBBSweepThread; // a handle to the CLR sweeper thread (that calls watch for sweep events)
418};
419
420#pragma pop_macro("LocalFree")
421#pragma pop_macro("ReleaseSemaphore")
422#pragma pop_macro("ResetEvent")
423#pragma pop_macro("SetEvent")
424
425#endif //_BBSWEEP_H
426