summaryrefslogtreecommitdiff
path: root/src/vm/diagnosticsprotocol.h
blob: 4669813acced93bb031051b58cfd567746b693a4 (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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
// 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.

#ifndef __DIAGNOSTICS_PROTOCOL_H__
#define __DIAGNOSTICS_PROTOCOL_H__

#ifdef FEATURE_PERFTRACING

#include "clr_std/type_traits"
#include "new.hpp"
#include "diagnosticsipc.h"
#include "corerror.h"

#define DOTNET_IPC_V1_MAGIC "DOTNET_IPC_V1"

template <typename T>
bool TryParse(uint8_t *&bufferCursor, uint32_t &bufferLen, T &result)
{
    static_assert(
        std::is_integral<T>::value || std::is_same<T, float>::value ||
        std::is_same<T, double>::value || std::is_same<T, CLSID>::value,
        "Can only be instantiated with integral and floating point types.");

    if (bufferLen < sizeof(T))
        return false;
    memcpy(&result, bufferCursor, sizeof(T));
    bufferCursor += sizeof(T);
    bufferLen -= sizeof(T);
    return true;
}

template <typename T>
bool TryParseString(uint8_t *&bufferCursor, uint32_t &bufferLen, const T *&result)
{
    static_assert(
        std::is_same<T, char>::value || std::is_same<T, wchar_t>::value,
        "Can only be instantiated with char and wchar_t types.");

    uint32_t stringLen = 0;
    if (!TryParse(bufferCursor, bufferLen, stringLen))
        return false;
    if (stringLen == 0)
    {
        result = nullptr;
        return true;
    }
    if (stringLen > (bufferLen / sizeof(T)))
        return false;
    if ((reinterpret_cast<const T *>(bufferCursor))[stringLen - 1] != 0)
        return false;
    result = reinterpret_cast<const T *>(bufferCursor);

    const uint32_t TotalStringLength = stringLen * sizeof(T);
    bufferCursor += TotalStringLength;
    bufferLen -= TotalStringLength;
    return true;
}

namespace DiagnosticsIpc
{
    enum class IpcMagicVersion : uint8_t
    {
        DOTNET_IPC_V1 = 0x01,
        // FUTURE
    };

    enum class DiagnosticServerCommandSet : uint8_t
    {
        // reserved   = 0x00,
        Dump          = 0x01,
        EventPipe     = 0x02,
        Profiler      = 0x03,

        Server        = 0xFF,
    };

    enum class DiagnosticServerCommandId : uint8_t
    {
        OK    = 0x00,
        Error = 0xFF,
    };

    struct MagicVersion
    {
        uint8_t Magic[14];
    };

    // The header to be associated with every command and response
    // to/from the diagnostics server
    struct IpcHeader
    {
        union
        {
            MagicVersion _magic;
            uint8_t  Magic[14];  // Magic Version number; a 0 terminated char array
        };
        uint16_t Size;       // The size of the incoming packet, size = header + payload size
        uint8_t  CommandSet; // The scope of the Command.
        uint8_t  CommandId;  // The command being sent
        uint16_t Reserved;   // reserved for future use
    };

    const MagicVersion DotnetIpcMagic_V1 = { "DOTNET_IPC_V1" };

    const IpcHeader GenericSuccessHeader =
    {
        { DotnetIpcMagic_V1 },
        (uint16_t)sizeof(IpcHeader),
        (uint8_t)DiagnosticServerCommandSet::Server,
        (uint8_t)DiagnosticServerCommandId::OK,
        (uint16_t)0x0000
    };

    const IpcHeader GenericErrorHeader =
    {
        { DotnetIpcMagic_V1 },
        (uint16_t)sizeof(IpcHeader),
        (uint8_t)DiagnosticServerCommandSet::Server,
        (uint8_t)DiagnosticServerCommandId::Error,
        (uint16_t)0x0000
    };

    // The Following structs are template, meta-programming to enable
    // users of the IpcMessage class to get free serialization for fixed-size structures.
    // They check that the template parameter has a member (or static) function that
    // has a specified signature and returns true or false based on that check.
    //
    // std::enable_if (and enable_if_t) act as a compile time flag to enable or
    // disable a template specialization based on a boolean value.
    //
    // The Has* structs can be used as the boolean check in std::enable_if to
    // enable a specific overload of a function based on whether the template parameter
    // has that member function.
    //
    // These "switches" can be used in a variety of ways, but are used in the function
    // template parameters below, e.g.,
    //
    // template <typename T,
    //           typename = enable_if_t<HasTryParse<T>::value, const T*> = nullptr>
    // const T* FnName(...)
    //
    // For more details on this pattern, look up "Substitution Failure Is Not An Error" or SFINAE

    // template meta-programming to check for bool(Flatten)(void*) member function
    template <typename T>
    struct HasFlatten
    {
        template <typename U, U u> struct Has;
        template <typename U> static std::true_type test(Has<bool (U::*)(void*), &U::Flatten>*);
        template <typename U> static std::false_type test(...);
        static constexpr bool value = decltype(test<T>(nullptr))::value;
    };

    // template meta-programming to check for uint16_t(GetSize)() member function
    template <typename T>
    struct HasGetSize
    {
        template <typename U, U u> struct Has;
        template <typename U> static std::true_type test(Has<uint16_t(U::*)(), &U::GetSize>*);
        template <typename U> static std::false_type test(...);
        static constexpr bool value = decltype(test<T>(nullptr))::value;
    };

    // template meta-programming to check for a const T*(TryParse)(BYTE*,uint16_t&) static function
    template <typename T>
    struct HasTryParse
    {
        template <typename U, U u> struct Has;
        template <typename U> static std::true_type test(Has<const U* (*)(BYTE*, uint16_t&), &U::TryParse>*);
        template <typename U> static std::false_type test(...);
        static constexpr bool value = decltype(test<T>(nullptr))::value;
    };

    // Encodes the messages sent and received by the Diagnostics Server.
    //
    // Payloads that are fixed-size structs don't require any custom functionality.
    //
    // Payloads that are NOT fixed-size simply need to implement the following methods:
    //  * uint16_t GetSize()                                     -> should return the flattened size of the payload
    //  * bool Flatten(BYTE *lpBuffer)                           -> Should serialize and write the payload to the provided buffer
    //  * const T *TryParse(BYTE *lpBuffer, uint16_t& bufferLen) -> should decode payload or return nullptr
    class IpcMessage
    {
    public:

        // empty constructor for default values.  Use Initialize.
        IpcMessage()
            : m_pData(nullptr), m_Header(), m_Size(0)
        {
            LIMITED_METHOD_CONTRACT;
        };

        // Initialize an outgoing IpcMessage with a header and payload
        template <typename T>
        bool Initialize(IpcHeader header, T& payload)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            m_Header = header;

            return FlattenImpl<T>(payload);
        };

        // Initialize an outgoing IpcMessage with a header and payload
        template <typename T>
        bool Initialize(IpcHeader header, T&& payload)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            m_Header = header;

            return FlattenImpl<T>(payload);
        };

        // Initialize an outgoing IpcMessage for an error
        bool Initialize(HRESULT error)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            return Initialize(GenericErrorHeader, error);
        }

        // Initialize an incoming IpcMessage from a stream by parsing
        // the header and payload.
        //
        // If either fail, this returns false, true otherwise
        bool Initialize(::IpcStream* pStream)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            return TryParse(pStream);
        }

        ~IpcMessage()
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_ANY;
            }
            CONTRACTL_END;

            delete[] m_pData;
        };

        // Given a buffer, attempt to parse out a given payload type
        // If a payload type is fixed-size, this will simply return
        // a pointer to the buffer of data reinterpreted as a const pointer.
        // Otherwise, your payload type should implement the following static method:
        // > const T *TryParse(BYTE *lpBuffer)
        // which this will call if it exists.
        //
        // user is expected to check for a nullptr in the error case for non fixed-size payloads
        // user owns the memory returned and is expected to free it when finished
        template <typename T>
        const T* TryParsePayload()
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            ASSERT(IsFlattened());
            return TryParsePayloadImpl<T>();
        };

        const IpcHeader& GetHeader() const
        {
            LIMITED_METHOD_CONTRACT;

            return m_Header;
        };

        bool Send(IpcStream* pStream)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_ANY;
                PRECONDITION(pStream != nullptr);
            }
            CONTRACTL_END;

            ASSERT(IsFlattened());
            uint32_t nBytesWritten;
            bool success = pStream->Write(m_pData, m_Size, nBytesWritten);

            return nBytesWritten == m_Size && success;
        };

        // Send an Error message across the pipe.
        // Will return false on failure of any step (init or send).
        // Regardless of success of this function, the spec
        // dictates that the connection be closed on error,
        // so the user is expected to delete the IpcStream
        // after handling error cases.
        static bool SendErrorMessage(IpcStream* pStream, HRESULT error)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
                PRECONDITION(pStream != nullptr);
            }
            CONTRACTL_END;

            IpcMessage errorMessage;
            bool success = errorMessage.Initialize((int32_t)error);
            if (success)
                success = errorMessage.Send(pStream);
            return success;
        };
    private:
        // Pointer to flattened buffer filled with:
        // incoming message: payload (could be empty which would be nullptr)
        // outgoing message: header + payload
        BYTE* m_pData;
        // header associated with this message
        struct IpcHeader m_Header;
        // The total size of the message (header + payload)
        uint16_t m_Size;

        bool IsFlattened() const
        {
            LIMITED_METHOD_CONTRACT;

            return m_pData != NULL;
        };

        // Attempt to populate header and payload from a buffer.
        // Payload is left opaque as a flattened buffer in m_pData
        bool TryParse(::IpcStream* pStream)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
                PRECONDITION(pStream != nullptr);
            }
            CONTRACTL_END;

            // Read out header first
            uint32_t nBytesRead;
            bool success = pStream->Read(&m_Header, sizeof(IpcHeader), nBytesRead);
            if (!success || nBytesRead < sizeof(IpcHeader))
            {
                return false;
            }

            if (m_Header.Size < sizeof(IpcHeader))
            {
                return false;
            }

            m_Size = m_Header.Size;

            // Then read out payload to buffer
            uint16_t payloadSize = m_Header.Size - sizeof(IpcHeader);
            if (payloadSize != 0)
            {
                BYTE* temp_buffer = new (nothrow) BYTE[payloadSize];
                if (temp_buffer == nullptr)
                {
                    // OOM
                    return false;
                }

                success = pStream->Read(temp_buffer, payloadSize, nBytesRead);
                if (!success || nBytesRead < payloadSize)
                {
                    delete[] temp_buffer;
                    return false;
                }
                m_pData = temp_buffer;
            }

            return true;
        };

        // Create a buffer of the correct size filled with
        // header + payload. Correctly handles flattening of
        // trivial structures, but uses a bool(Flatten)(void*)
        // and uint16_t(GetSize)() when available.

        // Handles the case where the payload structure exposes Flatten
        // and GetSize methods
        template <typename U,
                  typename std::enable_if<HasFlatten<U>::value&& HasGetSize<U>::value, int>::type = 0>
        bool FlattenImpl(U& payload)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            if (IsFlattened())
                return true;

            S_UINT16 temp_size = S_UINT16(0);
            temp_size += sizeof(struct IpcHeader) + payload.GetSize();
            ASSERT(!temp_size.IsOverflow());

            m_Size = temp_size.Value();

            BYTE* temp_buffer = new (nothrow) BYTE[m_Size];
            if (temp_buffer == nullptr)
            {
                // OOM
                return false;
            }

            BYTE* temp_buffer_cursor = temp_buffer;

            m_Header.Size = m_Size;

            memcpy(temp_buffer_cursor, &m_Header, sizeof(struct IpcHeader));
            temp_buffer_cursor += sizeof(struct IpcHeader);

            payload.Flatten(temp_buffer_cursor);

            ASSERT(m_pData == nullptr);
            m_pData = temp_buffer;

            return true;
        };

        // handles the case where we were handed a struct with no Flatten or GetSize method
        template <typename U,
                  typename std::enable_if<!HasFlatten<U>::value && !HasGetSize<U>::value, int>::type = 0>
        bool FlattenImpl(U& payload)
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_PREEMPTIVE;
            }
            CONTRACTL_END;

            if (IsFlattened())
                return true;

            S_UINT16 temp_size = S_UINT16(0);
            temp_size += sizeof(struct IpcHeader) + sizeof(payload);
            ASSERT(!temp_size.IsOverflow());

            m_Size = temp_size.Value();

            BYTE* temp_buffer = new (nothrow) BYTE[m_Size];
            if (temp_buffer == nullptr)
            {
                // OOM
                return false;
            }

            BYTE* temp_buffer_cursor = temp_buffer;

            m_Header.Size = m_Size;

            memcpy(temp_buffer_cursor, &m_Header, sizeof(struct IpcHeader));
            temp_buffer_cursor += sizeof(struct IpcHeader);

            memcpy(temp_buffer_cursor, &payload, sizeof(payload));

            ASSERT(m_pData == nullptr);
            m_pData = temp_buffer;

            return true;
        };

        template <typename U,
                  typename std::enable_if<HasTryParse<U>::value, int>::type = 0>
        const U* TryParsePayloadImpl()
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_ANY;
            }
            CONTRACTL_END;

            uint16_t payloadSize = m_Size - (uint16_t)sizeof(IpcHeader);
            const U* payload = U::TryParse(m_pData, payloadSize);
            m_pData = nullptr; // user is expected to clean up buffer when finished with it
            return payload;
        };

        template <typename U,
                  typename std::enable_if<!HasTryParse<U>::value, int>::type = 0>
        const U* TryParsePayloadImpl()
        {
            CONTRACTL
            {
                NOTHROW;
                GC_TRIGGERS;
                MODE_ANY;
            }
            CONTRACTL_END;

            const U* payload = reinterpret_cast<const U*>(m_pData);
            m_pData = nullptr; // user is expected to clean up buffer when finished with it
            return payload;
        };
    };
};

#endif // FEATURE_PERFTRACING

#endif // __DIAGNOSTICS_PROTOCOL_H__