EternalWindows
ETW / リアルタイムモード

プロバイダからのイベントをトレースする方法には、 前節で述べたログファイルモードの他に、 リアルタイムモードがあります。 この方法の場合、イベントがログファイルに書き込まれるのではなく、 アプリケーションに通知されることになっているため、 イベントが書き込まれた瞬間を特定できるようになります。 ログファイルにせよ、リアルタイムにせよ、 書き込まれたイベントを取得するためにはOpenTraceを呼び出します。

TRACEHANDLE OpenTrace(
  PEVENT_TRACE_LOGFILE Logfile
);

Logfileは、EVENT_TRACE_LOGFILE構造体のアドレスを指定します。 この構造体は、次のように定義されています。

typedef struct _EVENT_TRACE_LOGFILE {
  LPTSTR                       LogFileName;
  LPTSTR                       LoggerName;
  LONGLONG                     CurrentTime;
  ULONG                        BuffersRead;
  union {
    ULONG LogFileMode;
    ULONG ProcessTraceMode;
  } ;
  EVENT_TRACE                  CurrentEvent;
  TRACE_LOGFILE_HEADER         LogfileHeader;
  PEVENT_TRACE_BUFFER_CALLBACK BufferCallback;
  ULONG                        BufferSize;
  ULONG                        Filled;
  ULONG                        EventsLost;
  union {
    PEVENT_CALLBACK        EventCallback;
    PEVENT_RECORD_CALLBACK EventRecordCallback;
  } ;
  ULONG                        IsKernelTrace;
  PVOID                        Context;
} EVENT_TRACE_LOGFILE, *PEVENT_TRACE_LOGFILE;

LogFileNameは、イベントが格納されたトレースファイルの名前を指定します。 リアルタイムモードの場合はNULLで問題ありません。 LoggerNameは、イベントをトレースするセッションの名前を指定します。 ログファイルモードの場合はNULLで問題ありません。 ProcessTraceModeは、モードに関する定数を指定します。 PROCESS_TRACE_MODE_REAL_TIMEを指定した場合は、リアルタイムモードになります。 CurrentEventとLogfileHeaderは、関数によって初期化されます。 BufferCallbackは、バッファがコンシューマに送られたタイミングを受け取るコールバック関数を指定します。 不要な場合はNULLを指定できます。 BufferSizeとFilledは、関数によって初期化されます。 EventsLostは、使用されません。 EventCallbackとEventRecordCallbackは、イベントを受け取るコールバック関数を指定します。 古いプロバイダはTraceEventを使用してイベントを書き込みますが、 このイベントを受け取りたい場合はEventCallbackの方を初期化します。 一方、新しいプロバイダはEventWriteを呼び出してイベントを書き込みますが、 このイベントを受け取りたい場合はEventRecordCallbackの方を初期化します。 この場合は、ProcessTraceModeにPROCESS_TRACE_MODE_EVENT_RECORDに指定する必要があります。 IsKernelTraceは、カーネルイベントをトレースする際にはTRUEが格納されるとされていますが、 実際に確認してみたところ0が格納されていました。 Contextは、EventRecordCallbackに渡したいユーザー定義のデータを指定します。 不要な場合はNULLを指定できます。

アプリケーションは、イベントを処理したくなった段階でProcessTraceを呼び出します。 この関数を呼び出すと、EVENT_TRACE_LOGFILE構造体に指定したコールバック関数が呼ばれます。

ULONG ProcessTrace(
  PTRACEHANDLE HandleArray,
  ULONG HandleCount,
  LPFILETIME StartTime,
  LPFILETIME EndTime
);

HandleArrayは、OpenTraceで取得したハンドルを指定します。 ログファイルモードの場合は複数のハンドルを指定することもできますが、 リアルタイムモードの場合は1つだけになります。 HandleCountは、HandleArrayに指定した要素数を指定します。 StartTimeとEndTimeは、NULLで問題ありません。

イベントをトレースするためには、トレース対象のプロバイダを決定しなければなりませんが、 これはなかなか難しいことです。 理由は、どのプロバイダがどのようなイベントを書き込むかが不明確だからです。 そこで今回は、カーネルイベントをトレースするようにしたいと思います。 これならば、どのようなイベントが書き込まれるかがMSDNに記述されているため、 トレースしたデータがどのような意味を持つのかが理解できます。 カーネルイベントは、Windows 2000の頃でもDisk IOやプロセスの作成、ページフォルトなどの十分な種類がありましたが、 Vistaになってからはファイルやレジストリキーの作成などもトレースできため、 何らかのデータを監視するアプリケーションにとっては非常に有用といえます。 ETWによって監視処理を行っているアプリケーションとしては、 SysinternalsのツールであるDiskmonが有名でしょう。 実際にDiskmonを起動して「信頼性とパフォーマンス モニタ」を開いてみると、 NT Kernel Loggerというカーネルセッションが存在することを確認できます。

カーネルイベントをトレースするというのは、前節のような特定のプロバイダのイベントをトレースする事とは違いますから、 EnableTraceExでプロバイダをセッションに追加するようなことは不要です。 その代わりとして、StartTraceに指定していたEVENT_TRACE_PROPERTIES構造体のEnableFlagsに、 トレースしたいイベントを表す定数を指定することになります。 この定数の中には、たとえばEVENT_TRACE_FLAG_PROCESSというものがあり、 その際の情報はProcess_TypeGroup1として定義されています。 試しに、この定義をMSDNから確認してみましょう。

[EventType{1, 2, 3, 4, 39}, EventTypeName{"Start", "End", "DCStart", "DCEnd", "Defunct"}]class Process_TypeGroup1 : Process
{
  uint32 UniqueProcessKey;
  uint32 ProcessId;
  uint32 ParentId;
  uint32 SessionId;
  sint32 ExitStatus;
  uint32 DirectoryTableBase;
  object UserSID;
  string ImageFileName;
  string CommandLine;
};

classの中に定義されているデータはプロパティと呼ばれますが、 これらの値がEVENT_TRACE_FLAG_PROCESSを指定することによって取得できることになります。 プロパティの中身は基本的に名前通りであり、 たとえばImageFileNameという文字列で識別されるプロパティには、プロセスのファイル名が格納されています。 他に重要なのは、上部に定義されているEventTypeとEventTypeNameです。 EventTypeNameのDCStartは、既に起動されたプロセスを示すイベントであることを意味するため、 これから起動されるプロセスを監視したいアプリケーションにとっては、このイベントは除外しなければなりません。 EventTypeNameの順番はEventTypeの順番と関連しており、 DCStartは3という数値で識別できますから、この3という数値を除外すればよいでしょう。 そのような要領で除外を行い、起動だけ検出した結果を次に示します。

Propertyより上の部分は今の段階で理解しておく必要はありませんが、Opcodeに関しては重要です。 先に述べたEventTypeの値はこのOpcodeに格納されているため、この値は調べておく必要があります。 Propertyより下の部分の意味は先に述べた通りであり、たとえば上図ではtaskmgr.exeが起動されたことが分かります。 プロセスの起動が行われる度に、リストボックスが更新されることになります。

今回のプログラムは、プロセスに関するカーネルイベントをトレースします。 リアルタイムモードとして動作しているため、プロセスの作成や終了が行われるとプログラムに通知が送られます。

#include <windows.h>
#include <evntcons.h>
#include <tdh.h>

#pragma comment(lib, "tdh.lib")

HWND  g_hwndListBox;
DWORD g_dwPointerSize;

DWORD WINAPI ThreadProc(LPVOID lpParamater);
void WINAPI EventRecordCallback(PEVENT_RECORD EventRecord);
void ShowPropertyInfo(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, PEVENT_PROPERTY_INFO pPropertyInfo, LPWSTR lpszStructName);
void ShowEventInfo(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo);
void ShowNameString(LPBYTE lp, ULONG uOffset, LPWSTR lpszTitle);
BOOL ConvertSidToName(PSID pSid, LPWSTR lpszName, DWORD dwSizeName);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static PEVENT_TRACE_PROPERTIES pProperties = NULL;
	static HANDLE                  hThread = NULL;
	static TRACEHANDLE             hSession = 0;

	switch (uMsg) {

	case WM_CREATE: {
		DWORD dwBufferSize;
		ULONG uResult;

		dwBufferSize = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(KERNEL_LOGGER_NAME);
		pProperties = (PEVENT_TRACE_PROPERTIES)HeapAlloc(GetProcessHeap(), 0, dwBufferSize);

		ZeroMemory(pProperties, dwBufferSize); 
		pProperties->Wnode.BufferSize    = dwBufferSize;
		pProperties->Wnode.Guid          = SystemTraceControlGuid;
		pProperties->Wnode.ClientContext = 1;
		pProperties->Wnode.Flags         = WNODE_FLAG_TRACED_GUID;
		pProperties->EnableFlags         = EVENT_TRACE_FLAG_PROCESS;
		pProperties->MaximumFileSize     = 1;
		pProperties->LogFileMode         = EVENT_TRACE_REAL_TIME_MODE;
		pProperties->LoggerNameOffset    = sizeof(EVENT_TRACE_PROPERTIES);

		uResult = StartTrace(&hSession, KERNEL_LOGGER_NAME, pProperties);
		if (uResult != ERROR_SUCCESS) {
			if (uResult == ERROR_ACCESS_DENIED)
				MessageBox(NULL, TEXT("アクセスが拒否されました。"), NULL, MB_ICONWARNING);
			else if (uResult == ERROR_ALREADY_EXISTS)
				MessageBox(NULL, TEXT("セッションは既に存在します。"), NULL, MB_ICONWARNING);
			else
				MessageBox(NULL, TEXT("StartTraceの呼び出しに失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}
	
		g_hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, NULL);
		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(g_hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (hSession != 0) {
			ControlTrace(hSession, KERNEL_LOGGER_NAME, pProperties, EVENT_TRACE_CONTROL_STOP);
			WaitForSingleObject(hThread, 5000);
			CloseHandle(hThread);
			HeapFree(GetProcessHeap(), 0, pProperties);
		}
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	TRACEHANDLE         hTrace;
	ULONG               uResult;
	EVENT_TRACE_LOGFILE logfile;

	ZeroMemory(&logfile, sizeof(EVENT_TRACE_LOGFILE));
	logfile.LoggerName          = KERNEL_LOGGER_NAME;
	logfile.ProcessTraceMode    = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
	logfile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;

	hTrace = OpenTrace(&logfile);
	if (hTrace == (TRACEHANDLE)INVALID_HANDLE_VALUE)
		return 0;

	uResult = ProcessTrace(&hTrace, 1, NULL, NULL);
	if (uResult != ERROR_SUCCESS) {
		TCHAR szBuf[256];
		wsprintf(szBuf, TEXT("%d"), uResult);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		CloseTrace(hTrace);
		return 0;
	}

	CloseTrace(hTrace);

	return 0;
}

void WINAPI EventRecordCallback(PEVENT_RECORD EventRecord)
{
	ULONG             uBufferSize = 0;
	TDHSTATUS         status;
	PTRACE_EVENT_INFO pInfo;

	status = TdhGetEventInformation(EventRecord, 0, NULL, 0, &uBufferSize);
	if (status != ERROR_INSUFFICIENT_BUFFER)
		return;

	pInfo = (PTRACE_EVENT_INFO)HeapAlloc(GetProcessHeap(), 0, uBufferSize);
	TdhGetEventInformation(EventRecord, 0, NULL, pInfo, &uBufferSize);

	if ((EventRecord->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER) == EVENT_HEADER_FLAG_32_BIT_HEADER)
		g_dwPointerSize = 4;
	else
		g_dwPointerSize = 8;
	
	ShowEventInfo(EventRecord, pInfo);

	HeapFree(GetProcessHeap(), 0, pInfo);
}

void ShowEventInfo(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo)
{
	ULONG i;
	TCHAR szBuf[256];
	
	if (pInfo->EventDescriptor.Opcode != 1)
		return;

	wsprintf(szBuf, TEXT("Id: %u"), pInfo->EventDescriptor.Id);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	wsprintf(szBuf, TEXT("Version: %u"), pInfo->EventDescriptor.Version);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	wsprintf(szBuf, TEXT("Channel: %u"), pInfo->EventDescriptor.Channel);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	wsprintf(szBuf, TEXT("Level: %u"), pInfo->EventDescriptor.Level);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	wsprintf(szBuf, TEXT("Opcode: %u"), pInfo->EventDescriptor.Opcode);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	wsprintf(szBuf, TEXT("Task: %u"), pInfo->EventDescriptor.Task);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	wsprintf(szBuf, TEXT("Keyword: %lu"), pInfo->EventDescriptor.Keyword);
	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

	ShowNameString((LPBYTE)pInfo + pInfo->ProviderNameOffset, pInfo->ProviderNameOffset, L"ProviderName");
	ShowNameString((LPBYTE)pInfo + pInfo->LevelNameOffset, pInfo->LevelNameOffset, L"LevelName");
	ShowNameString((LPBYTE)pInfo + pInfo->ChannelNameOffset, pInfo->ChannelNameOffset, L"ChannelName");
	ShowNameString((LPBYTE)pInfo + pInfo->KeywordsNameOffset, pInfo->KeywordsNameOffset, L"KeywordsName");
	ShowNameString((LPBYTE)pInfo + pInfo->TaskNameOffset, pInfo->TaskNameOffset, L"TaskName");
	ShowNameString((LPBYTE)pInfo + pInfo->OpcodeNameOffset, pInfo->OpcodeNameOffset, L"OpcodeName");
	ShowNameString((LPBYTE)pInfo + pInfo->ActivityIDNameOffset, pInfo->ActivityIDNameOffset, L"ActivityIDName");
	ShowNameString((LPBYTE)pInfo + pInfo->RelatedActivityIDNameOffset, pInfo->RelatedActivityIDNameOffset, L"RelatedActivityIDName");

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("----------Property----------"));
	for (i = 0; i < pInfo->TopLevelPropertyCount; i++)
		ShowPropertyInfo(pEvent, pInfo, &pInfo->EventPropertyInfoArray[i], NULL);

	SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
}

void ShowPropertyInfo(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo, PEVENT_PROPERTY_INFO pPropertyInfo, LPWSTR lpszStructName)
{
	WCHAR                    szBuf[1024];
	PROPERTY_DATA_DESCRIPTOR propertyData[2];
	LPWSTR                   lpszName;
	LPBYTE                   lpProperty;
	ULONG                    i, uPropertyCount;
	ULONG                    uBufferSize;
	
	lpszName = (LPWSTR)((LPBYTE)pInfo + pPropertyInfo->NameOffset);

	if (pPropertyInfo->Flags & PropertyStruct) {
		PEVENT_PROPERTY_INFO p;
		for (i = 0; i < pPropertyInfo->structType.NumOfStructMembers; i++) {
			p = &pInfo->EventPropertyInfoArray[i + pPropertyInfo->structType.StructStartIndex];
			ShowPropertyInfo(pEvent, pInfo, p, lpszName);
		}
		return;
	}

	if (lpszStructName == NULL) {
		propertyData[0].ArrayIndex = ULONG_MAX;
		propertyData[0].PropertyName = (ULONGLONG)lpszName;
		uPropertyCount = 1;
	}
	else {
		propertyData[0].ArrayIndex = 0;
		propertyData[0].PropertyName = (ULONGLONG)lpszStructName;
		propertyData[1].ArrayIndex = ULONG_MAX;
		propertyData[1].PropertyName = (ULONGLONG)lpszName;
		uPropertyCount = 2;
	}

	uBufferSize = 0;
	TdhGetPropertySize(pEvent, 0, NULL, uPropertyCount, propertyData, &uBufferSize);
	lpProperty = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, uBufferSize);
	TdhGetProperty(pEvent, 0, NULL, uPropertyCount, propertyData, uBufferSize, lpProperty);

	if (pPropertyInfo->nonStructType.MapNameOffset == 0) {
		WCHAR szContainStructName[256];

		if (lpszStructName != NULL) {
			wsprintfW(szContainStructName, L"%s.%s", lpszStructName, lpszName);
			lpszName = szContainStructName;
		}
		
		switch (pPropertyInfo->nonStructType.InType) {
		case TDH_INTYPE_UNICODESTRING:
			wsprintfW(szBuf, L"%s : %s", lpszName, (LPWSTR)lpProperty);
			break;
		case TDH_INTYPE_ANSISTRING: {
			LPSTR  lpszA = (LPSTR)lpProperty;
			LPWSTR lpszW;
			DWORD  dwSize;

			dwSize = (lstrlenA(lpszA) + 1) * sizeof(WCHAR);
			lpszW = (LPWSTR)HeapAlloc(GetProcessHeap(), 0, dwSize);
			MultiByteToWideChar(CP_ACP, 0, lpszA, -1, lpszW, dwSize);

			wsprintfW(szBuf, L"%s : %s", lpszName, lpszW);
			HeapFree(GetProcessHeap(), 0, lpszW);

			break;
		}
		case TDH_INTYPE_INT16:
			wsprintfW(szBuf, L"%s : %d", lpszName, *(PSHORT)lpProperty);
			break;
		case TDH_INTYPE_UINT16:
			wsprintfW(szBuf, L"%s : %d", lpszName, *(PUSHORT)lpProperty);
			break;
		case TDH_INTYPE_INT32:
			wsprintfW(szBuf, L"%s : %d", lpszName, *(PLONG)lpProperty);
			break;
		case TDH_INTYPE_UINT32:
			wsprintfW(szBuf, L"%s : %d", lpszName, *(PULONG)lpProperty);
			break;
		case TDH_INTYPE_UINT64:
			wsprintfW(szBuf, L"%s : %d", lpszName, *(PULONGLONG)lpProperty);
			break;
		case TDH_INTYPE_GUID: {
			WCHAR szGuid[256];
			StringFromGUID2(*(LPGUID)lpProperty, szGuid, 256);
			wsprintfW(szBuf, L"%s : %s", lpszName, szGuid);
			break;
		}
		case TDH_INTYPE_POINTER:
			if (g_dwPointerSize == 4)
				wsprintfW(szBuf, L"%s : %#x", lpszName, *(PULONG)lpProperty);
			else
				wsprintfW(szBuf, L"%s : %#x", lpszName, *(PULONGLONG)lpProperty);
			break;
		case TDH_INTYPE_SID: {
			WCHAR szAccountName[256];
			PSID  pSid = (PSID)lpProperty;
			ConvertSidToName(pSid, szAccountName, 256);
			wsprintfW(szBuf, L"%s : %s", lpszName, szAccountName);
			break;
		}
		case TDH_INTYPE_WBEMSID: {
			WCHAR szAccountName[256];
			PSID  pSid = (PSID)(lpProperty + g_dwPointerSize * 2);
			ConvertSidToName(pSid, szAccountName, 256);
			wsprintfW(szBuf, L"%s : %s", lpszName, szAccountName);
			break;
		}
		default:
			wsprintfW(szBuf, L"%s : type %d", lpszName, pPropertyInfo->nonStructType.InType);
			break;
		}
	}
	else {
		PEVENT_MAP_INFO pMapInfo;
		LPWSTR          lpszMapName;
		TDHSTATUS       status;
		ULONG           uMapValue = *(PULONG)lpProperty;
		BOOL            bFound = FALSE;
		
		uBufferSize = 0;
		lpszMapName = (LPWSTR)((LPBYTE)pInfo + pPropertyInfo->nonStructType.MapNameOffset);
		status = TdhGetEventMapInformation(pEvent, lpszMapName, NULL, &uBufferSize);
		if (status != ERROR_INSUFFICIENT_BUFFER) {
			HeapFree(GetProcessHeap(), 0, lpProperty);
			return;
		}
		pMapInfo = (PEVENT_MAP_INFO)HeapAlloc(GetProcessHeap(), 0, uBufferSize);
		TdhGetEventMapInformation(pEvent, lpszMapName, pMapInfo, &uBufferSize);

		if (pMapInfo->Flag & EVENTMAP_INFO_FLAG_MANIFEST_VALUEMAP) {
			LPWSTR lpsz;
			
			for (i = 0; i < pMapInfo->EntryCount; i++) {
				if (pMapInfo->MapEntryArray[i].Value == uMapValue) {
					lpsz = (LPWSTR)((LPBYTE)pMapInfo + pMapInfo->MapEntryArray[i].OutputOffset);
					wsprintfW(szBuf, L"%s : %s(%d)", lpszName, lpsz, uMapValue);
					bFound = TRUE;
					break;
				}
			}
		}
		
		if (!bFound)
			wsprintfW(szBuf, L"%s : %d", lpszName, uMapValue);

		HeapFree(GetProcessHeap(), 0, pMapInfo);
	}

	SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	
	HeapFree(GetProcessHeap(), 0, lpProperty);
}

void ShowNameString(LPBYTE lp, ULONG uOffset, LPWSTR lpszTitle)
{
	LPWSTR lpszName = (LPWSTR)lp;
	WCHAR  szBuf[256];

	if (uOffset != 0) {
		wsprintfW(szBuf, L"%s: %s", lpszTitle, lpszName);
		SendMessageW(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	}
}

BOOL ConvertSidToName(PSID pSid, LPWSTR lpszName, DWORD dwSizeName)
{
	WCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	SID_NAME_USE sidName;

	return LookupAccountSidW(NULL, pSid, lpszName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);
}

イベントをトレースするには、前節で述べたようにStartTraceを呼び出すことになります。 ただし、今回はリアルタイムモードとして実行したいため、EVENT_TRACE_PROPERTIES.LogFileModeにEVENT_TRACE_REAL_TIME_MODEを指定しています。 また、カーネルイベントをトレースする場合はEVENT_TRACE_PROPERTIES.EnableFlagsを初期化し、 StartTraceの第2引数にKERNEL_LOGGER_NAMEを指定します。 CreateThreadでスレッドを作成しているのは、リアルタイムモードのProcessTraceではコードの進行がブロックされるからです。 このようなブロックがメインスレッドで行われてはUI操作に対応できないため、別スレッドで行うようにしています。 ProcessTraceは、ControlTraceにEVENT_TRACE_CONTROL_STOPを指定することで制御を返しますが、 それまでには少々の時間がかかるようです。 このため、メインスレッドではWaitForSingleObjectを呼び出すことで、 スレッドがProcessTraceから制御を返し、ThreadProcから制御を返すまで待機するようにしています。

ThreadProcの実装は次のようになっています。

DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	TRACEHANDLE         hTrace;
	ULONG               uResult;
	EVENT_TRACE_LOGFILE logfile;

	ZeroMemory(&logfile, sizeof(EVENT_TRACE_LOGFILE));
	logfile.LoggerName          = KERNEL_LOGGER_NAME;
	logfile.ProcessTraceMode    = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
	logfile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;

	hTrace = OpenTrace(&logfile);
	if (hTrace == (TRACEHANDLE)INVALID_HANDLE_VALUE)
		return 0;
	
	uResult = ProcessTrace(&hTrace, 1, NULL, NULL);
	if (uResult != ERROR_SUCCESS) {
		CloseTrace(hTrace);
		return 0;
	}

	CloseTrace(hTrace);

	return 0;
}

セッションに書き込まれたイベントを取得するには、OpenTraceを呼び出してトレースハンドルを取得しなければなりません。 今回はリアルタイムモードであるため、LoggerNameにセッションの名前を指定し、 ProcessTraceModeにPROCESS_TRACE_MODE_REAL_TIMEを指定します。 また、PROCESS_TRACE_MODE_EVENT_RECORDを指定している場合は、 EventRecordCallbackにコールバック関数のアドレスを指定します。 ProcessTraceを呼び出したらこのコールバック関数が、 イベントの数だけ呼ばれることになります。

void WINAPI EventRecordCallback(PEVENT_RECORD EventRecord)
{
	ULONG             uBufferSize = 0;
	TDHSTATUS         status;
	PTRACE_EVENT_INFO pInfo;

	status = TdhGetEventInformation(EventRecord, 0, NULL, 0, &uBufferSize);
	if (status != ERROR_INSUFFICIENT_BUFFER)
		return;

	pInfo = (PTRACE_EVENT_INFO)HeapAlloc(GetProcessHeap(), 0, uBufferSize);
	TdhGetEventInformation(EventRecord, 0, NULL, pInfo, &uBufferSize);

	if ((EventRecord->EventHeader.Flags & EVENT_HEADER_FLAG_32_BIT_HEADER) == EVENT_HEADER_FLAG_32_BIT_HEADER)
		g_dwPointerSize = 4;
	else
		g_dwPointerSize = 8;
	
	ShowEventInfo(EventRecord, pInfo);

	HeapFree(GetProcessHeap(), 0, pInfo);
}

引数として渡されるPEVENT_RECORDには、イベントを書き込んだプロバイダのIDやプロセスIDなどが格納されていますが、 肝心のイベントの内容については直接取得することができません。 よって、イベントの情報を取得するためにTdhGetEventInformationを呼び出すことになります。 1回目の呼び出しでは必要なサイズを取得することに専念し、 メモリを確保してから2回目の呼び出しで情報を取得します。 この情報はPTRACE_EVENT_INFOで識別することができますが、 この具体的な使い方については、インストルメンテーション マニフェストの知識がないと難しいため、 後の節で詳しく取り上げます。 ちなみに、フィルタリングの処理については、ShowEventInfoの次のコードです。

if (pInfo->EventDescriptor.Opcode != 1)
	return;

既に述べたように、プロセスイベントにおいては、Opcodeを確認することによって行われた動作を把握できます。 1の場合はプロセスが起動されたことを意味しますから、これ以外の場合は処理を続行しないようにすると、 リストボックスに表示される情報は起動されたプロセスのみになります。 逆に2を指定した場合は、終了されたプロセスの情報だけが表示されます。

BufferCallbackについて

リアルタイムモードにおけるProcessTraceは、イベントを受け取るためにコードの進行をブロックするようになっています。 この状態から制御を返させるにはControlTraceを呼び出す方法がありますが、 それ以外にもBufferCallbackを使用した方法があります。

ZeroMemory(&logfile, sizeof(EVENT_TRACE_LOGFILE));
logfile.LoggerName          = g_szSessionName;
logfile.ProcessTraceMode    = PROCESS_TRACE_MODE_REAL_TIME | PROCESS_TRACE_MODE_EVENT_RECORD;
logfile.BufferCallback      = BufferCallback;
logfile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;

上記のように、BufferCallbackメンバにコールバック関数のアドレスを指定します。 この関数がいつ呼ばれるかについては、ETWの仕組みを今一度考える必要があるでしょう。 StartTraceで作成したセッションには内部にバッファが存在し、 プロバイダが書き込んだイベントはこのバッファに格納されます。 バッファは中身が一杯になった時点でコンシューマに送られ、 そのバッファに格納されているイベントの数だけEventRecordCallbackが呼ばれます。 そしてこれが終了し、バッファが不要になった際にBufferCallbackが呼ばれます。

ULONG WINAPI BufferCallback(PEVENT_TRACE_LOGFILE Buffer)
{
	return TRUE;
}

BufferCallbackでTRUEを返した場合は、引き続きバッファを受け取るということであり、 ProcessTraceは依然として制御を返しません。 しかし、FALSEを返せば制御を返すようにになります。



戻る