Emitting an event through an existing ETW provider

written on Sat 12 July 2014

Let's assume a simple scenario.

I have an application that uses native plugins. I have my own binary format for these plugins. Each plugin is loaded at runtime, using a method similar to mapping a DLL into the process' space. This means, each plugin has its own ImageBase, sections like .text or .data are handled in the same way as in conventional DLLs. The only thing that is different is the binary format of the plugin (it's not a PE file) and the loader code that maps the plugin into the process space, which is located in my custom, plugin-loading application.

We know how ETW behaves when doing the trace by using this command line:

xperf -on latency -stackwalk profile -buffersize 1024 -minbuffers 300 -start tracea1 -on Microsoft-Windows-Win32k:::'stack'

It will emit events that can be used to reconstruct the process environment during the trace capture. That is, it will emit events like add process, add thread to a process, add DLL module to a process, so that tools like xperfview or wpa can build a virtual environment of the state of processes in the system, and build information like current process tree. These events are, for example, ImageLoad events that provide information about each DLL that is being loaded before, or during the trace.

Of course, for our plugins, these ImageLoad events are not generated, because they're not technically DLLs (that is, not loaded by the same system functions as normal DLL files, although their function is the same). This is why tools like xperfview dosn't know about their existence in the process space.

What I wanted to do, is to write my own EventWrite's in my plugin loader code, and emit these ImageLoad events with necessary information, so that xperfview, and similar tools, can interpret my plugins as normal DLLs. I would fill up necessary information like ImageBase, ProcessId, ImageSize, etc.

To do this, I started to try to solve the problem by registering the event provider MSNT_SystemTrace, which is the owner of ImageLoad events, and trying to build the event with this kind of structure:

    <Data Name="ImageBase">0x7FEFDBD0000</Data>
    <Data Name="ImageSize">0x12D000</Data>
    <Data Name="ProcessId">     548</Data>
    ...
    <Data Name="Reserved0">       0</Data>
    <Data Name="DefaultBase">0x7FEFDBD0000</Data>

After event building would be complete, maybe I could send the event.

The problem I had that I was getting the ERROR_ACCESS_DENIED when trying to register another MSNT_SystemTrace provider, which was logical, since that provider already existed.

After some time, digging and reading, I've found a solution that solves my problem in a slightly different way.

While I still don't know how to emit an event through an existing provider in realtime, it seems that Windows 8 exposes an interface which allows to modify ETL trace logs, so it's possible to alter the ProviderId of an event to a different value. The interface in question is ITraceRelogger.

To be able to use it, these GUID's are needed:

EXTERN_GUID(CLSID_TraceRelogger, 0x7b40792d, 0x05ff, 0x44c4, 0x90, 0x58, 0xf4, 0x40, 0xc7, 0x1f, 0x17, 0xd4);
DEFINE_GUID(IID_ITraceRelogger, 0xF754AD43, 0x3BCC, 0x4286, 0x80, 0x09,0x9C, 0x5D, 0xA2, 0x14, 0xE8, 0x4E); // {F754AD43-3BCC-4286-8009-9C5DA214E84E}
DEFINE_GUID(IID_ITraceEventCallback, 0x3ED25501, 0x593F, 0x43E9, 0x8F, 0x38,0x3A, 0xB4, 0x6F, 0x5A, 0x4A, 0x52); // {3ED25501-593F-43E9-8F38-3AB46F5A4A52}

Together with these values, the relogger.h file from Windows 8 SDK from this path is needed:

c:\program files (x86)\windows kits\8.0\include\um\relogger.h

Original relogger.h seems to be broken somehow, because it references some external symbols, but it seems there's no library file (.lib) to supplement it. I'm sure a hacker like you can resolve it though!

To use it, it's necessary to create its instance by:

ITraceRelogger *relog = NULL;
hres = CoCreateInstance(CLSID_TraceRelogger, 0, CLSCTX_INPROC_SERVER, IID_ITraceRelogger2, (LPVOID *)& relog);

Then, input.etl and output.etl files need to be specified:

#include <windows.h>
#include <cguid.h>
#include <atlbase.h>
#include <comdef.h>
// ...
CComBSTR input = "input.etl";
CComBSTR output = "output.etl";
// ...
hres = relog->AddLogfileTraceStream(input, NULL, & trace);
// ...
hres = relog->SetOutputFilename(output);

Then, we need to register a callback, which will handle event modification(s). Event callback's implementation example is placed at the end of this post. Here is the code which demonstrates how to use it with current ITraceRelogger:

EventCallback *ec = new EventCallback();
hres = relog->RegisterCallback(ec);
// ...
hres = relog->ProcessTrace();

Warning: ProcessTrace() will return an error if you haven't registered any callbacks to the interface.

Here is an example of a working callback:

class EventCallback: public ITraceEventCallback {
private:
    DWORD ref_count;
    DWORD64 evno;

public:
    EventCallback() {
        ref_count = 0;
        evno = 0;
    }

    STDMETHODIMP QueryInterface(const IID& iid, void **obj) {
        if(iid == IID_IUnknown) {
            *obj = dynamic_cast<IUnknown *>(this);
        } else if(iid == IID_ITraceEventCallback) {
            *obj = dynamic_cast<ITraceEventCallback *>(this);
        } else {
            *obj = NULL;
            return E_NOINTERFACE;
        }

        return S_OK;
    }

    STDMETHODIMP_ (ULONG) AddRef(void) {
        return InterlockedIncrement(& ref_count);
    }

    STDMETHODIMP_ (ULONG) Release() {
        ULONG ucount = InterlockedDecrement(& ref_count);
        if(ucount == 0) {
            delete this;
        }

        return ucount;
    }

    HRESULT STDMETHODCALLTYPE OnBeginProcessTrace(ITraceEvent *HeaderEvent, ITraceRelogger *Relogger)   {
        return S_OK;
    }

    HRESULT STDMETHODCALLTYPE OnEvent(ITraceEvent *Event, ITraceRelogger *Relogger) {
        // Your main method.
        evno++;
        Relogger->Inject(Event);
    }


    HRESULT STDMETHODCALLTYPE OnFinalizeProcessTrace(ITraceRelogger *Relogger) {
        return S_OK;
    }
};

Relogger->Inject() will copy current Event to the output file. It is possible to use MSDN entry for ITraceRelogger to check for the available methods, which allow us to change the desired event properties. The method that I was interested most was named SetProviderId().

Also, the MSDN entry states that ITraceRelogger is available since Windows 7. it is not the whole truth, because you will need KB2882822 to be able to use it on Windows 7. However, on Windows 8 it should be available without installation of additional patches.

PS. Yes, this note first was "presented" on StackOverflow in the form of a question, but since I've answered my question myself, I think it's legit to include it on this blog for future reference.

This entry was tagged on #c++ and #windows