How To Read VirtualAllocations With TraceProcessor?

397 Views Asked by At

When I have enabled VirtualAlloc Tracing how can I get the VirtualAlloc Events back with TraceProcessor?

In Microsoft.Windows.EventTracing.Memory I find only

  • IHeapAllocation
  • IHeapSnapshot
  • IReferenceSetAccessedPage
  • IWorkingSetEntry
  • ...

But no mention of VirtualAlloc things.

On a related note: How hard would it be to parse .NET ETW events with this library. The TraceEvent library has very good support for .NET Events but but it is not clear to me how I should extend TraceProcessor. Are .NET Events for TraceProcessor on the roadmap?

2

There are 2 best solutions below

1
dmatson On BEST ANSWER

A couple of folks have asked now, so here's an example of getting VirtualAlloc/VirtualFree event data:

using Microsoft.Windows.EventTracing;
using Microsoft.Windows.EventTracing.Processes;
using Microsoft.Windows.EventTracing.Symbols;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

class Program
{
    static int Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.Error.WriteLine("Usage: VirtualAllocFree.exe <trace.etl>");
            return 1;
        }

        try
        {
            Run(args[0]);
        }
        catch (Exception exception)
        {
            Console.Error.WriteLine(exception);
            return exception.HResult;
        }

        return 0;
    }

    static void Run(string tracePath)
    {
        using (ITraceProcessor trace = TraceProcessor.Create(tracePath))
        {
            Guid kernelMemoryProviderId = new Guid("3d6fa8d3-fe05-11d0-9dda-00c04fd7ba7c");

            IPendingResult<IStackDataSource> pendingStackDataSource = trace.UseStacks();
            IPendingResult<IProcessDataSource> pendingProcessDataSource = trace.UseProcesses();

            List<VirtualAllocOrFreeEvent> virtualAllocOrFreeEvents = new List<VirtualAllocOrFreeEvent>();

            TraceEventCallback handleKernelMemoryEvent = (EventContext eventContext) =>
            {
                ClassicEvent classicEvent = eventContext.Event.AsClassicEvent;

                if (classicEvent.Version < 2)
                {
                    return;
                }

                int eventId = classicEvent.Id;

                const int virtualAllocEventId = 98;
                const int virtualFreeEventId = 99;

                if (eventId != virtualAllocEventId && eventId != virtualFreeEventId)
                {
                    return;
                }

                VirtualAlloc64EventData eventData;

                if (classicEvent.Is32Bit)
                {
                    if (classicEvent.Data.Length != Marshal.SizeOf<VirtualAlloc32EventData>())
                    {
                        throw new InvalidTraceDataException("Invalid virtual alloc/free event.");
                    }

                    VirtualAlloc32EventData thunk = MemoryMarshal.Read<VirtualAlloc32EventData>(classicEvent.Data);

                    eventData.Base = thunk.Base;
                    eventData.Size = thunk.Size;
                    eventData.ProcessId = thunk.ProcessId;
                    eventData.Flags = thunk.Flags;
                }
                else
                {
                    if (classicEvent.Data.Length != Marshal.SizeOf<VirtualAlloc64EventData>())
                    {
                        throw new InvalidTraceDataException("Invalid virtual alloc/free event.");
                    }

                    eventData = MemoryMarshal.Read<VirtualAlloc64EventData>(classicEvent.Data);
                }

                AddressRange addressRange = new AddressRange(new Address(eventData.Base),
                    unchecked((long)eventData.Size));
                int processId = unchecked((int)eventData.ProcessId);
                VirtualAllocFlags flags = eventData.Flags;
                TraceTimestamp timestamp = classicEvent.Timestamp;
                int threadId = classicEvent.ThreadId.Value;
                virtualAllocOrFreeEvents.Add(new VirtualAllocOrFreeEvent(addressRange, processId, flags, timestamp,
                    threadId, pendingProcessDataSource, pendingStackDataSource));
            };

            trace.Use(new Guid[] { kernelMemoryProviderId }, handleKernelMemoryEvent);
            IPendingResult<ISymbolDataSource> pendingSymbolDataSource = trace.UseSymbols();

            trace.Process();

            pendingSymbolDataSource.Result
                .LoadSymbolsForConsoleAsync(SymCachePath.Automatic, SymbolPath.Automatic).GetAwaiter().GetResult();

            Console.WriteLine($"Total virtual alloc/free events: {virtualAllocOrFreeEvents.Count}");
        }
    }

    struct VirtualAlloc64EventData
    {
        public ulong Base;
        public ulong Size;
        public uint ProcessId;
        public VirtualAllocFlags Flags;
    }

    struct VirtualAlloc32EventData
    {
#pragma warning disable CS0649
        public uint Base;
        public uint Size;
        public uint ProcessId;
        public VirtualAllocFlags Flags;
#pragma warning restore CS0649
    }

    // See:
    //   https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
    // and:
    //   https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
    [Flags]
    enum VirtualAllocFlags : uint
    {
        None = 0,
        Commit = 0x1000,
        Reserve = 0x2000,
        Decommit = 0x4000,
        Release = 0x8000,
        Reset = 0x80000,
        TopDown = 0x100000,
        WriteWatch = 0x200000,
        Physical = 0x400000,
        ResetUndo = 0x1000000,
        LargePages = 0x20000000
    }

    class VirtualAllocOrFreeEvent
    {
        readonly IPendingResult<IProcessDataSource> pendingProcessDataSource;
        readonly IPendingResult<IStackDataSource> pendingStackDataSource;
        readonly int threadId;

        public VirtualAllocOrFreeEvent(AddressRange addressRange, int processId, VirtualAllocFlags flags,
            TraceTimestamp timestamp, int threadId, IPendingResult<IProcessDataSource> pendingProcessDataSource,
            IPendingResult<IStackDataSource> pendingStackDataSource)
        {
            this.pendingProcessDataSource = pendingProcessDataSource;
            this.pendingStackDataSource = pendingStackDataSource;
            this.threadId = threadId;

            AddressRange = addressRange;
            ProcessId = processId;
            Flags = flags;
            Timestamp = timestamp;
        }

        public AddressRange AddressRange { get; }
        public int ProcessId { get; }
        public VirtualAllocFlags Flags { get; }
        public TraceTimestamp Timestamp { get; }

        public IStackSnapshot Stack => pendingStackDataSource.Result.GetStack(Timestamp, threadId);

        public IProcess Process => pendingProcessDataSource.Result.GetProcess(Timestamp, ProcessId);
    }
}
1
dmatson On

(I worked on the TraceProcessor library until recently.)

As you've noticed, I don't think we have built-in support for VirtualAlloc events today. I'll let the current team speak to priority, but we haven't had significant requests for .NET events in the past.

For VirtualAlloc (and .NET events), yes, you could parse these events on your own. See:

https://learn.microsoft.com/en-us/windows/apps/trace-processing/extensibility

for an overview. You'd need to understand the format of a VirtualAlloc event to know how to parse it, as well as the provider ID / event ID / version of these events.