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 | |
50 | class ICLRBBSweepCallback |
51 | { |
52 | public: |
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 | |
66 | class BBSweep |
67 | { |
68 | public: |
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 | |
179 | private: |
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 |
299 | cleanup: |
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 | } |
408 | private: |
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 | |