EternalWindows
DLL / モジュールの列挙 (Tool Help)

今回は、プロセスのアドレス空間にマッピングされているモジュールを 列挙する方法を紹介します。 これを成すにはいくつかの方法があるのですが、 本節で取り上げるのはTool Help Libraryを使った方法です。 Tool Helpの魅力なところは、モジュールに限らず プロセスやスレッドも同じような感覚で列挙できることしょう。 つまり、関数の使い方に非常に統一性があるのです。 Tool Helpの第1処理は、CreateToolhelp32Snapshotの呼び出しです。

HANDLE WINAPI CreateToolhelp32Snapshot(
  DWORD dwFlags,
  DWORD th32ProcessID
);

この関数の説明にはプロセスと、プロセスが使っているヒープ、モジュール、 スレッドのスナップショットを作成するとあります。 これは恐らく、1つのプロセスがヒープ、モジュール、スレッドのリストを維持しており、 CreateToolhelp32Snapshotは、その写しを取得するということなのでしょう。 dwFlagsは、どの部分をスナップショットに含めたいかを指定します。 次の値のいずれかの定数を指定します。

定数 意味
TH32CS_INHERIT スナップショットのハンドルが継承可能であることを意味する。
TH32CS_SNAPALL TH32CS_SNAPHEAPLIST、TH32CS_SNAPMODULE、 TH32CS_SNAPPROCESS、 TH32CS_SNAPTHREAD の すべてを指定したのと同じことを意味する。
TH32CS_SNAPHEAPLIST 指定されたプロセスのヒープリストをスナップショットに含める。
TH32CS_SNAPMODULE 指定されたプロセスのモジュールリストをスナップショットに含める。
TH32CS_SNAPPROCESS プロセスリストをスナップショットに含める。
TH32CS_SNAPTHREAD スレッドリストをスナップショットに含める。

th32ProcessIDは、dwFlagsにTH32CS_SNAPHEAPLISTかTH32CS_SNAPMODULEを 指定したときに利用され、それ以外のフラグを指定したときには無視されます。 この2つのフラグは、特定のプロセスからリストを取得する意味を持つので、 そのプロセスのプロセスIDを指定する必要があります。 戻り値は、スナップショットのハンドルです。 このハンドルを用いて、スナップショットに含まれるリストを取得することになります。

今回のプログラムは、CreateToolhelp32Snapshotの第1引数に TH32CS_SNAPMODULEを指定して、モジュールのリストを取得します。 その後、Module32FirstとModule32Nextを呼び出してリストを走査します。 Tool Helpの関数を使うため、tlhelp32.hのインクルードを忘れないでください。

#include <windows.h>
#include <tlhelp32.h>

void EnumModule(HWND hwndListBox);
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 HWND hwndListBox = NULL;

	switch (uMsg) {

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

	case WM_SIZE:
		MoveWindow(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);
}

void EnumModule(HWND hwndListBox)
{
	TCHAR         szBuf[256];
	HANDLE        hSnapshot;
	MODULEENTRY32 me;
	
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE)
		return;

	me.dwSize = sizeof(MODULEENTRY32);
	Module32First(hSnapshot, &me);

	do {
		wsprintf(szBuf, TEXT("%s %x"), me.szModule, me.modBaseAddr);
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
	} while (Module32Next(hSnapshot, &me));
	
	CloseHandle(hSnapshot);
}

プログラムはリストボックスを作成し、 そこにモジュール名とモジュールがマッピングされた アドレスを格納している文字列を追加しています。 リストボックスには、そのプロセスが利用しないであろう関数を エクスポートしているDLLが含まれていることが分かります。 これは、DLL自身が特定のDLLの関数を呼び出しているため、 結果的にそのDLLもプロセスのアドレス空間にマッピングされるからです。 モジュールを列挙するのは、EnumModuleという自作関数です。 次のコードは、EnumModuleの一部です。

hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);

CreateToolhelp32Snapshotの第1引数にTH32CS_SNAPMODULEを指定した場合、 第2引数はプロセスIDを指定すべきはずでしたが、0になっています。 実は、第2引数に0を指定したときには自プロセスが対象となるため、 GetCurrentProcessIdでプロセスIDを取得するようなことはしなくてもよいのです。

me.dwSize = sizeof(MODULEENTRY32);
Module32First(hSnapshot, &me);

Module32Firstは、モジュールリストの最初のエントリを取得します。 このエントリには、1個のモジュールに関する情報が含まれており、 MODULEENTRY32構造体で表されます。 この構造体のdwSizeには、構造体のサイズを代入しておかなければなりません。 後続のエントリを取得するには、Module32Nextを呼び出します。

do {
	wsprintf(szBuf, TEXT("%s %x"), me.szModule, me.modBaseAddr);
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
} while (Module32Next(hSnapshot, &me));

Module32Nextは、エントリをバッファにコピーできたらTRUEを返し、 それ以外のときはFALSEを返します。 そのため、この関数の戻り値はループ文の判定に利用できます。


戻る