summaryrefslogtreecommitdiff
path: root/src/inc/bbsweep.h
blob: d5526d8e5c3c215322910173912a1b040893bf89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/*****************************************************************************\
*                                                                             *
* BBSweep.h -    Classes for sweeping profile data to disk                    *
*                                                                             *
*               Version 1.0                                                   *
*******************************************************************************
*                                                                             *
*  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY      *
*  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE        *
*  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR      *
*  PURPOSE.                                                                   *
*                                                                             *
\*****************************************************************************/

#ifndef _BBSWEEP_H_
#define _BBSWEEP_H_

#ifndef FEATURE_PAL
#include <aclapi.h>
#endif // !FEATURE_PAL

// The CLR headers don't allow us to use methods like SetEvent directly (instead
// we need to use the host APIs).  However, this file is included both in the CLR
// and in the BBSweep tool, and the host API is not available in the tool.  Moreover,
// BBSweep is not designed to work in an environment where the host controls
// synchronization.  For this reason, we work around the problem by undefining
// these APIs (the CLR redefines them so that they will not be used).
#pragma push_macro("SetEvent")
#pragma push_macro("ResetEvent")
#pragma push_macro("ReleaseSemaphore")
#pragma push_macro("LocalFree")
#undef SetEvent
#undef ResetEvent
#undef ReleaseSemaphore
#undef LocalFree

// MAX_COUNT is the maximal number of runtime processes that can run at a given time
#define MAX_COUNT 20

#define INVALID_PID  -1

/* CLRBBSweepCallback is implemented by the CLR which passes it as an argument to WatchForSweepEvents.
 * It is used by BBSweep to tell the CLR to write the profile data to disk at the right time.
 */

class ICLRBBSweepCallback
{
public:
    virtual HRESULT WriteProfileData() = NULL;  // tells the runtime to write the profile data to disk
};

/* BBSweep is used by both the CLR and the BBSweep utility.
 * BBSweep: calls the PerformSweep method which returns after all the CLR processes
 *          have written their profile data to disk. 
 * CLR:     starts up a sweeper thread which calls WatchForSweepEvents and waits until the
 *          sweeper program is invoked.  At that point, all the CLR processes will synchronize
 *          and write their profile data to disk one at a time.  The sweeper threads will then
 *          wait for the next sweep event.  The CLR also calls ShutdownBBSweepThread at
 *          shutdown which returns when the BBSweep thread has terminated.
 */

class BBSweep
{
public:
    BBSweep() 
    {
        // The BBSweep constructor could be called even the the object is not used, so
        // don't do any work here.
        bInitialized = false;
        bTerminate   = false;
        hSweepMutex          = NULL;
        hProfDataWriterMutex = NULL;
        hSweepEvent          = NULL;
        hTerminationEvent    = NULL;
        hProfWriterSemaphore = NULL;
        hBBSweepThread       = NULL;
    }

    ~BBSweep()
    {
        // When the destructor is called, everything should be cleaned up already.
    }

    // Called by the sweeper utility to tell all the CLR threads to write their profile
    // data to disk.
    // THIS FUNCTIONALITY IS ALSO DUPLICATED IN TOOLBOX\MPGO\BBSWEEP.CS
    // IF YOU CHANGE THIS CODE, YOU MUST ALSO CHANGE THAT TO MATCH!
    bool PerformSweep(DWORD processID = INVALID_PID)
    {
        bool success = true;

        if (!Initialize(processID, FALSE)) return false;

        ::WaitForSingleObject(hSweepMutex, INFINITE);
        {
            success = success && ::SetEvent(hSweepEvent);
            {
                for (int i=0; i<MAX_COUNT; i++) 
                {
                    ::WaitForSingleObject(hProfWriterSemaphore, INFINITE);
                }

                ::ReleaseSemaphore(hProfWriterSemaphore, MAX_COUNT, NULL);            

            }
            success = success && ::ResetEvent(hSweepEvent);
        }
        ::ReleaseMutex(hSweepMutex);

        return success;
    }

    // Called by the CLR sweeper thread to wait until a sweep event, at which point
    // it calls back into the CLR via the clrCallback interface to write the profile
    // data to disk.
    bool WatchForSweepEvents(ICLRBBSweepCallback *clrCallback)
    {
        if (!Initialize()) return false;

        bool success = true;

        while (!bTerminate) 
        {
            ::WaitForSingleObject(hSweepMutex, INFINITE);
            {
                ::WaitForSingleObject(hProfWriterSemaphore, INFINITE);
            }
            ::ReleaseMutex(hSweepMutex);

            HANDLE hEvents[2];
            hEvents[0] = hSweepEvent;
            hEvents[1] = hTerminationEvent;
            ::WaitForMultipleObjectsEx(2, hEvents, false, INFINITE, FALSE);

            ::WaitForSingleObject(hProfDataWriterMutex, INFINITE);
            {
                if (!bTerminate && FAILED(clrCallback->WriteProfileData()))
                    success = false;
            }
            ::ReleaseMutex(hProfDataWriterMutex);

            ::ReleaseSemaphore(hProfWriterSemaphore, 1, NULL);
        }

        return success;
    }

    void SetBBSweepThreadHandle(HANDLE threadHandle)
    {
        hBBSweepThread = threadHandle;
    }

    void ShutdownBBSweepThread()
    {
        // Set the termination event and wait for the BBSweep thread to terminate on its own.
        // Note that this is called by the shutdown thread (and never called by the BBSweep thread).
        if (hBBSweepThread && bInitialized) 
        {
            bTerminate = true;
            ::SetEvent(hTerminationEvent);
            ::WaitForSingleObject(hBBSweepThread, INFINITE);
            Cleanup();
        }
    }

    void Cleanup()
    {
        if (hSweepMutex)          { ::CloseHandle(hSweepMutex);           hSweepMutex =          NULL;}
        if (hProfDataWriterMutex) { ::CloseHandle(hProfDataWriterMutex);  hProfDataWriterMutex = NULL;}
        if (hSweepEvent)          { ::CloseHandle(hSweepEvent);           hSweepEvent =          NULL;}
        if (hTerminationEvent)    { ::CloseHandle(hTerminationEvent);     hTerminationEvent =    NULL;}
        if (hProfWriterSemaphore)  { ::CloseHandle(hProfWriterSemaphore); hProfWriterSemaphore = NULL;}
    }

private:

    // THIS FUNCTIONALITY IS ALSO DUPLICATED IN TOOLBOX\MPGO\BBSWEEP.CS
    // IF YOU CHANGE THIS CODE, YOU MUST ALSO CHANGE THAT TO MATCH!
    bool Initialize(DWORD processID = INVALID_PID, BOOL fromRuntime = TRUE)
    {
        if (!bInitialized) 
        {
            SECURITY_ATTRIBUTES * pSecurityAttributes = NULL;

#ifndef FEATURE_CORESYSTEM // @CORESYSTEMTODO
            PSECURITY_DESCRIPTOR pSD = NULL;
            PSID pAdminSid = NULL;
            HANDLE hToken = NULL;
            PACL pACL = NULL;
            LPVOID buffer = NULL;

            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
                goto cleanup;

            // don't set pSecurityAttributes for Metro processes
            if(!IsAppContainerProcess(hToken))
            {
                SECURITY_ATTRIBUTES securityAttributes;
                PSID pUserSid = NULL;
                SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
                DWORD retLength;

#ifdef _PREFAST_
#pragma warning(push)
#pragma warning(disable:6211) // PREfast warning: Leaking memory 'pSD' due to an exception. 
#endif /*_PREFAST_ */
                pSD = (PSECURITY_DESCRIPTOR) new char[SECURITY_DESCRIPTOR_MIN_LENGTH];
                if (!pSD)
                    goto cleanup;

                if (GetTokenInformation(hToken, TokenOwner, NULL, 0, &retLength))
                    goto cleanup;

                buffer = (LPVOID) new char[retLength];
                if (!buffer)
                    goto cleanup;
#ifdef _PREFAST_
#pragma warning(pop)
#endif /*_PREFAST_*/

                // Get the SID for the current user
                if (!GetTokenInformation(hToken, TokenOwner, (LPVOID) buffer, retLength, &retLength))
                    goto cleanup;

                pUserSid = ((TOKEN_OWNER *) buffer)->Owner;

                // Get the SID for the admin group
                // Create a SID for the BUILTIN\Administrators group.
                if(! AllocateAndInitializeSid(&SIDAuthNT, 2,
                    SECURITY_BUILTIN_DOMAIN_RID,
                    DOMAIN_ALIAS_RID_ADMINS,
                    0, 0, 0, 0, 0, 0,
                    &pAdminSid))
                    goto cleanup;

                EXPLICIT_ACCESS ea[2];
                ZeroMemory(ea, 2 * sizeof(EXPLICIT_ACCESS));

                // Initialize an EXPLICIT_ACCESS structure for an ACE.
                // The ACE will allow the current user full access
                ea[0].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL; // KEY_ALL_ACCESS;
                ea[0].grfAccessMode = SET_ACCESS;
                ea[0].grfInheritance= NO_INHERITANCE;
                ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
                ea[0].Trustee.TrusteeType = TRUSTEE_IS_USER;
                ea[0].Trustee.ptstrName  = (LPTSTR) pUserSid;

                // Initialize an EXPLICIT_ACCESS structure for an ACE.
                // The ACE will allow admins full access
                ea[1].grfAccessPermissions = STANDARD_RIGHTS_ALL | SPECIFIC_RIGHTS_ALL; //KEY_ALL_ACCESS;
                ea[1].grfAccessMode = SET_ACCESS;
                ea[1].grfInheritance= NO_INHERITANCE;
                ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
                ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
                ea[1].Trustee.ptstrName  = (LPTSTR) pAdminSid;

                if (SetEntriesInAcl(2, ea, NULL, &pACL) != ERROR_SUCCESS) 
                    goto cleanup;

                if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
                    goto cleanup;

                if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE))
                    goto cleanup;

                memset((void *) &securityAttributes, 0, sizeof(SECURITY_ATTRIBUTES));
                securityAttributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
                securityAttributes.lpSecurityDescriptor = pSD;
                securityAttributes.bInheritHandle       = FALSE;

                pSecurityAttributes = &securityAttributes;
            }
#endif // !FEATURE_CORESYSTEM

            WCHAR objectName[MAX_LONGPATH] = {0};
            WCHAR objectNamePrefix[MAX_LONGPATH] = {0};
            GetObjectNamePrefix(processID, fromRuntime, objectNamePrefix);
            // if there is a non-empty name prefix, append a '\'
            if (objectNamePrefix[0] != '\0')
                wcscat_s(objectNamePrefix, ARRAYSIZE(objectNamePrefix), W("\\"));
            swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hSweepMutex"), objectNamePrefix);
            hSweepMutex          = ::WszCreateMutex(pSecurityAttributes, false,       objectName);
            swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hProfDataWriterMutex"), objectNamePrefix);
            hProfDataWriterMutex = ::WszCreateMutex(pSecurityAttributes, false,       objectName);
            swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hSweepEvent"), objectNamePrefix);
            hSweepEvent          = ::WszCreateEvent(pSecurityAttributes, true, false, objectName);

            // Note that hTerminateEvent is not a named event.  That is because it is not
            // shared amongst the CLR processes (each process terminates at a different time)
            hTerminationEvent    = ::WszCreateEvent(pSecurityAttributes, true, false, NULL);
            swprintf_s(objectName, MAX_LONGPATH, W("%sBBSweep_hProfWriterSemaphore"), objectNamePrefix);
            hProfWriterSemaphore = ::WszCreateSemaphore(pSecurityAttributes, MAX_COUNT, MAX_COUNT, objectName);

#ifndef FEATURE_CORESYSTEM // @CORESYSTEMTODO
cleanup:
            if (pSD) delete [] ((char *) pSD);
            if (pAdminSid) FreeSid(pAdminSid);
            if (hToken) CloseHandle(hToken);
            if (pACL) LocalFree(pACL);
            if (buffer) delete [] ((char *) buffer);
#endif
        }

        bInitialized = hSweepMutex          && 
            hProfDataWriterMutex &&
            hSweepEvent          &&
            hTerminationEvent    &&
            hProfWriterSemaphore;

        if (!bInitialized) Cleanup();
        return bInitialized;
    }

#ifndef FEATURE_PAL
    BOOL IsAppContainerProcess(HANDLE hToken)
    {
#ifndef TokenIsAppContainer
#define TokenIsAppContainer ((TOKEN_INFORMATION_CLASS) 29)
#endif
        BOOL fIsAppContainerProcess;
        DWORD dwReturnLength;
        if (!GetTokenInformation(hToken, TokenIsAppContainer, &fIsAppContainerProcess, sizeof(BOOL), &dwReturnLength) ||
            dwReturnLength != sizeof(BOOL))
        {
            fIsAppContainerProcess = FALSE;
        }
        return fIsAppContainerProcess;
    }
#endif // !FEATURE_PAL

    // helper to get the correct object name prefix
    void GetObjectNamePrefix(DWORD processID, BOOL fromRuntime, __inout_z WCHAR* objectNamePrefix)
    {
        // default prefix
        swprintf_s(objectNamePrefix, MAX_LONGPATH, W("Global"));
#ifndef FEATURE_PAL
        //
        // This method can be called:
        // 1. From process init code
        // 2. From bbsweepclr.exe
        //
        // When called from process init code, processID is always INVALID_PID.
        // In case it is a Win8-Metro/WP8 process, we need to add the AppContainerNamedObjectPath to prefix.
        // And if it is a non-Metro process, we will continue to use the default prefix (Global).
        // We use IsAppContainerProcess(CurrentProcessId) to make this decision.
        //
        //
        // When called from bbsweepclr, processID is valid when sweeping a Metro or WP8 process.
        // We use this valid processID to determine if the process being swept is Metro/WP8 indeed and then
        // add AppContainerNamedObjectPath to prefix. This is done by IsAppContainerProcess(processID).
        //
        // In case INVALID_PID is passed(non-Metro process), we have to use default prefix. To handle this
        // case we use IsAppContainerProcess(CurrentProcessId) and since bbsweepclr is a non-Metro process,
        // this check always returns false and we end up using the intended(default) prefix.
        //
        if(processID == INVALID_PID) {
            // we reach here when:
            // * called from process init code:
            // * called from bbsweepclr.exe and no processID has been passed as argument, that is, when sweeping a non-Metro process
            processID = GetCurrentProcessId();
        }

        HandleHolder hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, processID);
        if (hProcess  != INVALID_HANDLE_VALUE)
        {
            HandleHolder hToken = NULL;
            // if in the process init code of a Metro app or if bbsweepclr is used to sweep a Metro app,
            // construct the object name prefix using AppContainerNamedObjectPath
            if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken) && IsAppContainerProcess(hToken))
            {
                WCHAR appxNamedObjPath[MAX_LONGPATH] = { 0 };
                ULONG appxNamedObjPathBufLen = 0;

                if (fromRuntime)
                {
                    // for Metro apps, create the object in the "default" object path, i.e. do not provide any prefix
                    objectNamePrefix[0] = W('\0');
                }
                else
                {
#if defined (FEATURE_CORESYSTEM) && !defined(CROSSGEN_COMPILE) && !defined(DACCESS_COMPILE)
#define MODULE_NAME W("api-ms-win-security-appcontainer-l1-1-0.dll")
#else
#define MODULE_NAME W("kernel32.dll")
#endif
                    typedef BOOL(WINAPI *PFN_GetAppContainerNamedObjectPath)
                        (HANDLE Token, PSID AppContainerSid, ULONG ObjectPathLength, WCHAR * ObjectPath, PULONG ReturnLength);

                    PFN_GetAppContainerNamedObjectPath pfnGetAppContainerNamedObjectPath = (PFN_GetAppContainerNamedObjectPath)
                        GetProcAddress(WszGetModuleHandle(MODULE_NAME), "GetAppContainerNamedObjectPath");
                    if (pfnGetAppContainerNamedObjectPath)
                    {
                        // for bbsweepclr sweeping a Metro app, create the object specifying the AppContainer's path
                        DWORD sessionId = 0;
                        ProcessIdToSessionId(processID, &sessionId);
                        pfnGetAppContainerNamedObjectPath(hToken, NULL, sizeof (appxNamedObjPath) / sizeof (WCHAR), appxNamedObjPath, &appxNamedObjPathBufLen);
                        swprintf_s(objectNamePrefix, MAX_LONGPATH, W("Global\\Session\\%d\\%s"), sessionId, appxNamedObjPath);
                    }
                }
            }
        }
#endif // FEATURE_PAL        
    }
private:

    bool bInitialized;            // true when the BBSweep object has initialized successfully
    bool bTerminate;              // set to true when the CLR wants us to terminate
    HANDLE hSweepMutex;           // prevents processing from incrementing the semaphore after the sweep has began
    HANDLE hProfDataWriterMutex;  // guarantees that profile data will be written by one process at a time
    HANDLE hSweepEvent;           // tells the CLR processes to sweep their profile data
    HANDLE hTerminationEvent;     // set when the CLR process is ready to terminate
    HANDLE hProfWriterSemaphore;  // helps determine when all the writers are finished
    HANDLE hBBSweepThread;        // a handle to the CLR sweeper thread (that calls watch for sweep events)
};

#pragma pop_macro("LocalFree")
#pragma pop_macro("ReleaseSemaphore")
#pragma pop_macro("ResetEvent")
#pragma pop_macro("SetEvent")

#endif //_BBSWEEP_H