EternalWindows
ETW / イベントの解析

ETWを使用した開発において、独自のプロバイダからのイベントをトレースするまでの作業は1つの目安といえるでしょう。 この段階まで来れば、後はトレースしたイベントを解析するだけになりますから、今回はこの解析作業を行います。 書き込まれるイベントが予め分かっていれば解析もしやすくなるため、 前節のプロバイダが書き込んだイベントを例に話を進めていきます。 次に、今回のプログラムの実行結果を示します。

イベントの情報は、リストボックスの項目として追加するようになっています。 最初の方に表示されている項目はEVENT_DESCRIPTOR構造体の中身を参照しており、 これはプロバイダで定義したEVENT_DESCRIPTOR構造体と矛盾がないはずです。 Property以下には、イベントに含まれるデータが追加されます。 これらは、プロバイダのEventWriteに指定した値と矛盾がないはずです。

イベントを解析するためには、具体的にどのような処理を行えばよいのでしょうか。 リアルタイムモードについて説明した際にも述べましたが、 トレースしたイベントを処理するにはOpenTraceとProcessTraceを呼び出します。 OpenTraceはProcessTraceで指定したコールバック関数を呼び出すことになっており、 そこではイベントの情報を格納したEVENT_RECORD構造体が渡されます。 しかし、この構造体はデータをPVOID型で表現するようになっており、 これではデータがどのようなものであるかを確認することができません。 よって、データを詳細に返すTdhGetEventInformationを呼び出すことになります。

ULONG __stdcall TdhGetEventInformation(
  PEVENT_RECORD pEvent,
  ULONG TdhContextCount,
  PTDH_CONTEXT pTdhContext,
  PTRACE_EVENT_INFO pBuffer,
  ULONG *pBufferSize
);

pEventは、EVENT_RECORD構造体のアドレスを指定します。 TdhContextCountは、TdhContextCountの要素数を指定します。 pTdhContextは、新しいプロバイダを対象としている場合はNULLで問題ありません。 pBufferは、TRACE_EVENT_INFO構造体を受け取るバッファを指定します。 *pBufferSizeは、バッファのサイズを格納した変数のアドレスを指定します。 サイズが足りない場合は、必要なサイズが格納されます。

プロバイダがEventWriteで書き込んだプロパティを取得するには、TdhGetPropertyを呼び出します。

ULONG __stdcall TdhGetProperty(
  EVENT_RECORD pEvent,
  ULONG TdhContextCount,
  PTDH_CONTEXT pTdhContext,
  ULONG PropertyDataCount,
  PPROPERTY_DATA_DESCRIPTOR pPropertyData,
  ULONG BufferSize,
  PBYTE pBuffer
);

pEventは、EVENT_RECORD構造体のアドレスを指定します。 TdhContextCountは、TdhContextCountの要素数を指定します。 pTdhContextは、新しいプロバイダを対象としている場合はNULLで問題ありません。 PropertyDataCountは、pPropertyDataの要素数を指定します。 pPropertyDataは、取得したいプロパティの情報を格納したPROPERTY_DATA_DESCRIPTOR構造体の配列を指定します。 BufferSizeは、pBufferのサイズを指定します。 pBufferは、プロパティを受け取るバッファを指定します。

データの中には、map属性によって数値と文字列を関連付けているものも存在します。 このような場合は、TdhGetEventMapInformationで関連する文字列を取得できます。

ULONG __stdcall TdhGetEventMapInformation(
  PEVENT_RECORD pEvent,
  LPWSTR pMapName,
  PEVENT_MAP_INFO pBuffer,
  ULONG *pBufferSize
);

pEventは、EVENT_RECORD構造体のアドレスを指定します。 pMapNameは、map属性の名前を指定します。 pBufferは、EVENT_MAP_INFO構造体を受け取るバッファを指定します。 *pBufferSizeは、バッファのサイズを格納した変数のアドレスを指定します。 サイズが足りない場合は、必要なサイズが格納されます。

今回のプログラムは、カレントディレクトリに存在するetlファイルからイベントを取得し、 その内容をリストボックスに追加します。

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

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

HWND g_hwndListBox = NULL;
DWORD g_dwPointerSize = 0;

BOOL EventAnalyze();
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);
BOOL TraceStart();
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;
	BOOL       bTraceStart = TRUE;
	
	if (bTraceStart) {
		TraceStart();
		return 0;
	}

	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)
{
	switch (uMsg) {

	case WM_CREATE:
		g_hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		EventAnalyze();
		return 0;

	case WM_SIZE:
		MoveWindow(g_hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

BOOL EventAnalyze()
{
	ULONG               uResult;
	TRACEHANDLE         hTrace;
	EVENT_TRACE_LOGFILE logFile;

	ZeroMemory(&logFile, sizeof(EVENT_TRACE_LOGFILE));
	logFile.LogFileName         = TEXT("sample.etl");
	logFile.EventRecordCallback = (PEVENT_RECORD_CALLBACK)EventRecordCallback;
	logFile.ProcessTraceMode    = PROCESS_TRACE_MODE_EVENT_RECORD;

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

	CloseTrace(hTrace);

	return TRUE;
}

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 (pInfo->DecodingSource != DecodingSourceXMLFile) {
		HeapFree(GetProcessHeap(), 0, pInfo);
		return;
	}

	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];

	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);
}

BOOL TraceStart()
{
	TCHAR                   szSessionName[] = TEXT("My Event Trace Session LogFile");
	TCHAR                   szLogFilePath[] = TEXT("sample.etl");
	TRACEHANDLE             hSession;
	PEVENT_TRACE_PROPERTIES pProperties;
	DWORD                   dwBufferSize;
	ULONG                   uResult;
	const GUID              guidSession = {0xefa6b42b, 0xe1f0, 0x410d, {0x8a, 0xe9, 0xb5, 0x43, 0x29, 0xaa, 0x33, 0x1c}};
	const GUID              guidProvider = {0xa0b5676f, 0x0de9, 0x424a, {0xb8, 0x17, 0x83, 0x3e, 0x60, 0x5d, 0x57, 0x55}};

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

	ZeroMemory(pProperties, dwBufferSize);
	pProperties->Wnode.BufferSize    = dwBufferSize;
	pProperties->Wnode.Guid          = guidSession;
	pProperties->Wnode.ClientContext = 1;
	pProperties->Wnode.Flags         = WNODE_FLAG_TRACED_GUID;
	pProperties->MaximumFileSize     = 1;
	pProperties->LogFileMode         = EVENT_TRACE_FILE_MODE_SEQUENTIAL;
	pProperties->LoggerNameOffset    = sizeof(EVENT_TRACE_PROPERTIES);
	pProperties->LogFileNameOffset   = sizeof(EVENT_TRACE_PROPERTIES) + sizeof(szSessionName);
	lstrcpy((LPTSTR)((LPBYTE)pProperties + pProperties->LogFileNameOffset), szLogFilePath);

	uResult = StartTrace(&hSession, szSessionName, 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);
		HeapFree(GetProcessHeap(), 0, pProperties);
		return FALSE;
	}
	
	EnableTraceEx((LPCGUID)&guidProvider, NULL, hSession, 1, TRACE_LEVEL_INFORMATION, 0, 0, 0, NULL);
	
	MessageBox(NULL, TEXT("ボタンを押すとセッションを終了します。"), TEXT("OK"), MB_OK);

	ControlTrace(hSession, szSessionName, pProperties, EVENT_TRACE_CONTROL_STOP);
	HeapFree(GetProcessHeap(), 0, pProperties);
	
	return TRUE;
}

WinMainで宣言されているbTraceStartという変数がTRUEである場合は、セッションを作成することでイベントのトレースを開始します。 メッセージボックスが表示されたら、前節のプロバイダを実行してイベントを書き込み、 両方のアプリケーションを終了させます。 この状態で既に、プロバイダのイベントを格納したetlファイルが作成されているはずですから、 今度はbTraceStartをFALSEに設定してプログラムを実行します。 これにより、イベントの内容がリストボックスに追加されます。

EventAnalyzeという自作関数では、OpenTraceとProcessTraceを呼び出しています。 これにより、イベントの数だけEventRecordCallbackが呼ばれるようになるため、 そこでイベントを処理します。

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 (pInfo->DecodingSource != DecodingSourceXMLFile) {
		HeapFree(GetProcessHeap(), 0, pInfo);
		return;
	}

	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);
}

引数として渡されるEVENT_RECORDではイベントを解析しにくいので、 TdhGetEventInformationを呼び出してTRACE_EVENT_INFO構造体を取得します。 このためには事前にバッファを確保しておかなければならないため、 1回目の呼び出しでは必要なサイズを取得することに専念します。 DecodingSourceメンバがDecodingSourceXMLFileでないということは、 マニフェストをベースとしたイベントではないことを意味するため、 処理しないようにしています。 ShowEventInfoという自作関数は、TRACE_EVENT_INFO構造体の中身を表示します。

void ShowEventInfo(PEVENT_RECORD pEvent, PTRACE_EVENT_INFO pInfo)
{
	ULONG i;
	TCHAR szBuf[256];

	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(""));
}

EVENT_DESCRIPTOR構造体の中身をwsprintfで整形して表示しています。 EVENT_DESCRIPTOR構造体はEVENT_RECORDからも参照できるため、 EVENT_DESCRIPTOR構造体だけを参照する場合は、 TdhGetEventInformationを呼び出す必要はないといえます。 ShowNameStringは、数値に関連する文字列を表示するために使用されています。 EVENT_DESCRIPTOR構造体のメンバが0でない場合は、 その値に関連する文字列へのオフセットが格納されていることがあり、 そうしたメンバはShowNameStringで文字列が表示されることになります。 プロバイダがEVENT_DATA_DESCRIPTORとして初期化した独自のデータは、プロパティという単位で扱われることになっており、 その数はTopLevelPropertyCountメンバに格納されています。 よって、この数だけプロパティの中身を表示することになります。 ShowPropertyInfoの中身は複雑であるため、分割して説明していきます。

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);

1つのプロパティはEVENT_PROPERTY_INFO構造体で識別でき、 NameOffsetからプロパティの名前を参照すできます。 FlagsにPropertyStructが含まれている場合は、そのプロパティが構造体であることを意味します。 この場合はstructTypeメンバを参照することになり、 structType.NumOfStructMembersからメンバの数を取得することができます。 これらのメンバもプロパティとして表示されるべきですから、 ShowPropertyInfoを呼び出すようにしています。 ちなみに、非構造体のプロパティは、structTypeではなくnonStructTypeを使用することになります。 プロパティの値を実際に取得するためには、TdhGetPropertySizeでプロパティのサイズを取得し、 そのサイズ分のバッファを確保してからTdhGetPropertyを呼び出します。 取得対象となるプロパティの情報はPROPERTY_DATA_DESCRIPTOR構造体に指定することになっており、 非構造体のプロパティの場合は最初の要素にプロパティの名前を指定します。 一方、構造体に含まれるプロパティの場合は最初の要素に構造体の名前を指定し、 次の要素に自身の名前を指定します。 プロパティを取得したら次にそれを表示します。

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;
	}
}

nonStructType.MapNameOffsetが0である場合は、プロパティにmap属性が関連していないことを意味するため、 プロパティを通常の方法で表示することになります。 lpszStructNameがNULLでない場合は、構造体のメンバであるプロパティが処理の対象になっていることを意味し、 この場合はlpszNameで識別する名前を構造体.メンバ名のように変更します。 プロパティを表示するためには型を特定しなければなりませんが、 これはnonStructType.InTypeに格納されています。 各型に応じてszBufを適切に整形するようにしますが、 ここではわずかな型しか確認していないことに注意してください。

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);
}

nonStructType.MapNameOffsetが0でない場合は、プロパティにmap属性が関連していることを意味しているため、 TdhGetEventMapInformationでその情報を取得します。 FlagにEVENTMAP_INFO_FLAG_MANIFEST_VALUEMAPが含まれる場合は、mapの種類がvaluemapであることを意味し、 この場合はpMapInfo->MapEntryArray[i].Valueとプロパティの値が一致するかを調べることができます。 これが一致する場合は、pMapInfo->MapEntryArray[i].OutputOffsetから関連する名前を取得し、それを表示します。 ちなみに、mapの種類がbitmapの場合は、プロパティの値と一致するかを調べるのではなく、 プロパティの値に含まれるかどうかを調べます。


戻る