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 have its own ImageBase
, sections like .text
or .data
are handled in the same way as 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.
Now I know that ETW when doing the trace by this command-line:
xperf -on latency -stackwalk profile -buffersize 1024 -minbuffers 300 -start tracea1 -on Microsoft-Windows-Win32k:::'stack'
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
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 my plugins these ImageLoad
events are not generated, because they're not technically DLLs (that is, not loaded by the same functions as DLLs, although their function is the same). That is why tools like xperfview
don't know about their existence in the process space.
What I'd like to do, is to write my own EventWrites 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 info like ImageBase
, ProcessId
, ImageSize
, etc.
To do this, I understand that I need to register the event MSNT_SystemTrace
provider, which is the owner of ImageLoad
events, 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>
and emit the event.
The problem is that I'm getting ERROR_ACCESS_DENIED
when trying to register another MSNT_SystemTrace
, which is logical, since this provider already exists.
But that forces me to ask the question, is the thing that I'm trying to do even supported by ETW?
I think I've found a solution.
While I don't know how to emit an event through an existing provider in realtime, 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
. You need these guids:
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}
and relogger.h
file from Windows 8 SDK (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 LIB file to supplement it. I'm sure you will manage to resolve this though!
To use it, simply create an instance by:
ITraceRelogger *relog = NULL;
hres = CoCreateInstance(CLSID_TraceRelogger, 0, CLSCTX_INPROC_SERVER, IID_ITraceRelogger2, (LPVOID *)& relog);
add you input.etl
and output.etl
files:
#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, you need to register a callback, which will handle event modification. Event callback's implementation example is placed at the end of this answer. Here's the code on 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 register any callbacks.
Here's 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. You can use MSDN for ITraceRelogger
to check for the available methods which will allow you to change desired event properties. The method that I was interested in was SetProviderId()
.
Also, have in mind that MSDN states, that ITraceRelogger
is available in Windows 7. That's not what I experienced -- I can't instantiate this class in Windows 7, so MSDN can have wrong information about this subject.