Неожиданное поведение WinAPI-функции IsWow64Process()

в 8:41, , рубрики: c++, windows, Блог компании Инфопульс Украина, Программирование, разработка под windows, системное программирование

Эта заметка пишется для тех, кто когда-нибудь будет гуглить название WinAPI-функции IsWow64Process() в попытках понять, почему же она иногда работает не так, как это описано в MSDN. Вполне возможно, что это буду я сам через год-другой. Но, возможно, пригодиться и кому-то ещё.

Итак, о чём же идёт речь? Операционная система Windows, как известно, бывает 32-битной или 64-битной. На 32-битной Windows можно запустить только 32-битные приложения — а значит вопрос «это 32-битное приложение или 64-битное?» там попросту не имеет смысла, ответ известен заранее. Жизнь на 64-битном варианте Windows немного веселее — здесь можно запускать как 64-битные приложения (они считаются нативными), так и 32-битные, которые не являются родными для ОС, и выполняются они в специальной подсистеме WoW64 (Windows-on-Windows 64-bit). Подсистема эта включает в себя средства запуска 32-битного кода, отдельные ветки реестра и системные папки для работы 32-битных приложений в 64-битной среде.

Иногда бывает важно знать, является ли некоторый процесс, работающий в 64-битной Windows, действительно нативным 64-битным процессом, или WoW64-процессом (то есть 32-битным приложением, работающим в WoW64-подсистеме). Для этих целей Microsoft предлагает использовать функцию IsWow64Process(). Описание в MSDN достаточно детально, есть пара предупреждений на счёт способа её вызова, но в общём-то всё тривиально. Пример кода даже есть. Беда только в том, что в некоторых случаях эта функция врёт и определяет архитектуру процесса неверно.

Давайте напишем тестовое приложение, которое будет спрашивать у пользователя PID процесса и определять его архитектуру. За основу возьмём код из MSDN. Добавим в него обработку ошибок — т.е. на входе у нас PID процесса, а на выходе один из трёх вариантов — «определить не удалось», «это 64-битный процесс», «это WoW64-процесс».

#include <windows.h>
#include <iostream>


bool IsWow64(DWORD pid, BOOL &isWow64)
{
	HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
	if (hProcess == NULL)
		return false;

	typedef BOOL(WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
	LPFN_ISWOW64PROCESS fnIsWow64Process;
	fnIsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process");

	bool res = fnIsWow64Process != NULL && fnIsWow64Process(hProcess, &isWow64);
	CloseHandle(hProcess);
	return res;
}

int main(void)
{
	for (;;)
	{
		std::cout << "Please enter PID: ";

		DWORD pid;
		std::cin >> pid;

		BOOL isWow64 = false;
		BOOL resultKnown = IsWow64(pid, isWow64);

		if (resultKnown == false)
			std::cout << "Process type is unknown";
		else
			std::cout << "Process type is " << (isWow64 ? "x86 (wow64)" : "x64");

		std::cout << std::endl << std::endl;
	}
	
	return 0;
}

Давайте запустим нашу программу, а рядом с ней откроем диспетчер задач (или Process Hacker, который я люблю больше) чтобы видеть архитектуру процессов и их PIDы. Потестируем нашу программу.
Неожиданное поведение WinAPI-функции IsWow64Process() - 1

На первый взгляд всё ок: несуществующий PID не определился, 32-битное и 64-битные приложения были определены верно.

Идём дальше. Запускаем Chrome. При запуске он стартует некоторое количество дочерних процессов, отвечающих за рендеринг, обработку контента страниц и т.д. Давайте попробуем определить битность одного из таких процессов:
Неожиданное поведение WinAPI-функции IsWow64Process() - 2

Всё ок, это 32-битное приложение.

А теперь делаем вот такой финт ушами: набираем в нашем тестовом приложении тот же PID, убиваем дочерний процесс Chrome с этим PID в Process Hacker, быстро возвращаемся в наше тестовое приложение и жмём Enter. И видим прекрасную картину:
Неожиданное поведение WinAPI-функции IsWow64Process() - 3

Убитый только что процесс не определяется как «не найденный» (вроде того PID 999999 из примера выше). Она также не определяется как 32-битный (каковым он был при жизни). Он определяется чётко и ясно как существующий в системе 64-битный процесс. Но как ?! Почему?

А вот почему.

Когда мы убиваем некоторый процесс — он завершает свою работу не сразу. Его потоки останавливаются, занятая им память освобождается, но уйдёт ли процесс полностью — зависит не от него, а от того, есть ли у какого-нибудь другого процесса открытые дескрипторы (HANDLE) на этот процесс. Ну, знаете, возможно кто-то хотел с ним как-нибудь взаимодействовать. Это может быть, например, антивирус, вирус, системная утилита вроде Process Hacker, родительский процесс и т.д… Если у кого-нибудь из них остался висеть открытый дескриптор на процесс — он перейдёт в состояние «зомби» и будет находиться в нём, пока что-то будет продолжать держать его в этом бренном мире. В этом состоянии он уже не выполняет код ни в одном потоке, но всё ещё существует как сущность в операционной системе — например, он занимает свой «прижизненный» PID и ни один процесс не может получить такой же PID, пока «зомби» не умрёт полностью. Здесь вы уже можете догадаться, почему я предложил пример с дочерним процессом Chrome — родительский процесс Chrome держит дескриптор дочернего процесса, а это прямой ему путь в «зомби»-процессы.

Вернёмся к нашей проблеме — почему же функция IsWow64Process() определяет архитектуру этого процесса неверно? А здесь всё очень просто — при переходе процесса в состояние «зомби» подсистема WoW64 в нём останавливается и выгружается. Она уже не нужна (нет никаких вариантов снова вернуть «зомби» к жизни) — так зачем занимать ресурсы? В итоге, поинтересовавшись архитектурой некоторого процесса не вовремя, мы можем получить неверный результат.

Кстати, Chrome — качественный продукт, он быстро определяет факт смерти своего дочернего процесса, отпускает его дескриптор (что даёт «зомби» шанс упокоиться с миром) и пересоздаёт процесс данного типа. В итоге, вызвав ту же функцию для того же PID через несколько секунд вы вообще увидите вот такую картину:
Неожиданное поведение WinAPI-функции IsWow64Process() - 4

Как с этим бороться?

Да очень просто — кроме вызова IsWow64Process() вам необходим ещё и вызов функции GetExitCodeProcess(), которая для ещё живых (не «зомби») процессов всегда будет возвращать STILL_ACTIVE. По этому признаку можно понять «зомби» перед вами или нет и стоит ли верить результату IsWow64Process(). Здесь, конечно, возникает вопрос что же делать, когда мы поймёт, что это «зомби», а значит его архитектура нам неизвестна по-определению. Единственным ответом на это может быть вопрос, а что же вы вообще собираетесь делать с «зомби», с которым поделать уже ничего умного нельзя. В абсолютном большинстве случаев перед вами будет стоять обратная задача — найти «живой» процесс, а для получения информации по нему комбинация GetExitCodeProcess() + IsWow64Process() отлично сработает.

Вот и всё, что я хотел рассказать о функции IsWow64Process().

Автор: Инфопульс Украина

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js