Неожиданное поведение WinAPI-функции IsWow64Process()
Итак, о чём же идёт речь? Операционная система 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
#include
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ы. Потестируем нашу программу.
На первый взгляд всё ок: несуществующий PID не определился, 32-битное и 64-битные приложения были определены верно.
Идём дальше. Запускаем Chrome. При запуске он стартует некоторое количество дочерних процессов, отвечающих за рендеринг, обработку контента страниц и т.д. Давайте попробуем определить битность одного из таких процессов:
Всё ок, это 32-битное приложение.
А теперь делаем вот такой финт ушами: набираем в нашем тестовом приложении тот же PID, убиваем дочерний процесс Chrome с этим PID в Process Hacker, быстро возвращаемся в наше тестовое приложение и жмём Enter. И видим прекрасную картину:
Убитый только что процесс не определяется как «не найденный» (вроде того PID 999999 из примера выше). Она также не определяется как 32-битный (каковым он был при жизни). Он определяется чётко и ясно как существующий в системе 64-битный процесс. Но как?! Почему?
А вот почему.
Когда мы убиваем некоторый процесс — он завершает свою работу не сразу. Его потоки останавливаются, занятая им память освобождается, но уйдёт ли процесс полностью — зависит не от него, а от того, есть ли у какого-нибудь другого процесса открытые дескрипторы (HANDLE) на этот процесс. Ну, знаете, возможно кто-то хотел с ним как-нибудь взаимодействовать. Это может быть, например, антивирус, вирус, системная утилита вроде Process Hacker, родительский процесс и т.д… Если у кого-нибудь из них остался висеть открытый дескриптор на процесс — он перейдёт в состояние «зомби» и будет находиться в нём, пока что-то будет продолжать держать его в этом бренном мире. В этом состоянии он уже не выполняет код ни в одном потоке, но всё ещё существует как сущность в операционной системе — например, он занимает свой «прижизненный» PID и ни один процесс не может получить такой же PID, пока «зомби» не умрёт полностью. Здесь вы уже можете догадаться, почему я предложил пример с дочерним процессом Chrome — родительский процесс Chrome держит дескриптор дочернего процесса, а это прямой ему путь в «зомби»-процессы.
Вернёмся к нашей проблеме — почему же функция IsWow64Process () определяет архитектуру этого процесса неверно? А здесь всё очень просто — при переходе процесса в состояние «зомби» подсистема WoW64 в нём останавливается и выгружается. Она уже не нужна (нет никаких вариантов снова вернуть «зомби» к жизни) — так зачем занимать ресурсы? В итоге, поинтересовавшись архитектурой некоторого процесса не вовремя, мы можем получить неверный результат.
Кстати, Chrome — качественный продукт, он быстро определяет факт смерти своего дочернего процесса, отпускает его дескриптор (что даёт «зомби» шанс упокоиться с миром) и пересоздаёт процесс данного типа. В итоге, вызвав ту же функцию для того же PID через несколько секунд вы вообще увидите вот такую картину:
Как с этим бороться?
Да очень просто — кроме вызова IsWow64Process () вам необходим ещё и вызов функции GetExitCodeProcess (), которая для ещё живых (не «зомби») процессов всегда будет возвращать STILL_ACTIVE. По этому признаку можно понять «зомби» перед вами или нет и стоит ли верить результату IsWow64Process (). Здесь, конечно, возникает вопрос что же делать, когда мы поймёт, что это «зомби», а значит его архитектура нам неизвестна по-определению. Единственным ответом на это может быть вопрос, а что же вы вообще собираетесь делать с «зомби», с которым поделать уже ничего умного нельзя. В абсолютном большинстве случаев перед вами будет стоять обратная задача — найти «живой» процесс, а для получения информации по нему комбинация GetExitCodeProcess () + IsWow64Process () отлично сработает.
Вот и всё, что я хотел рассказать о функции IsWow64Process ().