Эта заметка пишется для тех, кто когда-нибудь будет гуглить название 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ы. Потестируем нашу программу.



На первый взгляд всё ок: несуществующий 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().
Поделиться с друзьями
-->

Комментарии (25)


  1. digitallez
    25.08.2016 12:54
    +6

    Краткое изложение: «Ты жив пока о тебе помнят».


    1. pda0
      25.08.2016 14:00
      +3

      О мёртвых или хорошо (64 бит) или ничего. :)


    1. EvgenijDv
      25.08.2016 16:45
      +2

      «Ты не ты, когда зомби»


  1. MichaelBorisov
    25.08.2016 14:13
    +1

    Любопытно. Кстати, насчет «оживления умерших» — может быть, это возможно с помощью «CreateRemoteThread»?


    1. pda0
      25.08.2016 15:38

      Вряд ли. Согласно MSDN к моменту установки ExitCode и признака «мёртв» останавливаются все потоки, закрываются все хэдлы, выгружается исполняемый код. Значит сам процесс переводится в состояние «остановлен», а в этом состоянии CreateRemoteThread не работает. А поскольку своих потоков (а может и адресного пространства) у процесса нет, то запускать уже нечего.


    1. sebres
      25.08.2016 19:43

      Нет, оживлять некого, в зомби находятся только незакрытые, на момент завершения, handle. Ну и соответственно все reference (включая id и псевдо-закрытый handle процесса).


      Кстати тот еще гиморой с этим, если это например listening socket, который резервируется за системой (и соединение на другом конце считается открытым), пока система не схлопнет, если ничего не предпринять (например переодически слать NOP пакет через такое соединение).
      Такие сокеты называют псевдо-открытые или псевдо-закрытые (half-open (dropped),
      half-closed)…
      Даже если процесс перезапустится, то "netstat /abn" покажет 2-а listener на этом порту (один за системой, второй за новым процессом).


      Кстати это происходит только если создавать дочерний процесс с флагом bInheritHandle=true (т.е. все дочерние handle наследуются родителем). Подробнее про процесс мучений можно почитать, например, тут Re: Fix windows issue with multiple workers, где я прививал nginx multi-processing под виндовс...


  1. hdfan2
    25.08.2016 14:17

    Даже страшно спросить, как именно вы на это наткнулись. Для чего вообще может понадобиться вызов такой функции (кроме системных мониторов типа ProcessExplorer)?


    1. DistortNeo
      25.08.2016 14:44
      +1

      Для чего вообще может понадобиться вызов такой функции (кроме системных мониторов типа ProcessExplorer)?

      Например, для инъекции кода, когда хотим подгрузить свою библиотечку в чужой процесс.


  1. HonoraryBoT
    25.08.2016 15:11

    Между GetExitCodeProcess() и IsWow64Process() всё равно будет рейс.


    1. onto
      25.08.2016 15:28
      +1

      Даже в случае если GetExitCodeProcess() вызывать после IsWow64Process()?


      1. HonoraryBoT
        25.08.2016 15:40

        Ну да. Не поверите правильному IsWow. Если только вам мертвые совсем не нужны…


    1. tangro
      25.08.2016 17:20

      Ну, если нам нужны данные по живому процессу, то:

      GetExitCodeProcess()
      IsWow64Process()
      GetExitCodeProcess()

      Если оба вызова GetExitCodeProcess() вернули STILL_ACTIVE — то результату IsWow64Process() можно верить.


      1. HonoraryBoT
        25.08.2016 18:26

        Так — да, я буквально текст понял просто.


        1. dmitryredkin
          26.08.2016 00:16
          +1

          С чего бы можно верить?
          сразу после первого GetExit грохаем процесс, а перед вторым стартуем другой процесс с таким же PID.


          1. HonoraryBoT
            26.08.2016 00:31

            Не поведаете, как зареюзать PID процесса?


          1. HonoraryBoT
            26.08.2016 00:33

            Алсо, работа идет с открытым хэндлом процесса, а не с его ID


  1. aquamakc
    25.08.2016 16:45

    Когда-то давно пытался с помощью этой функции определять разрядность винды, на которой запускается программа Долго не мог понять почему не всегда правильно определяет, пока не дочитал до конца описание и очень огорчился. Настолько, что переехал с плюсов на шарп для десктоп приожений.


    1. dkv
      26.08.2016 03:36

      В шарпе можно не дочитывать до конца описание WinAPI функций по причине пониженных требований к стабильности софта, написанного на шарпе или стараетесь не вылезать за пределы возможностей самого фреймворка? Никакого сарказма, реально пытаюсь пытаюсь понять мотивацию.


      1. aquamakc
        26.08.2016 09:12

        Стояла простая задачка — при запуске программы определять разрядность ОСи, в зависимости от этого делать то или иное действие. Я тогда был совсем джуном, можно было самому выбирать язык и среду, решил делать на QT, нативного способа не нашёл, обратился к WinApi и этой функции. Импортировал, проверил, реализовал функционал и интерфейс. При эксплуатации начались жалобы, что неверно определяет разрядность. Внимательно прочитал MSDN. Дошёл до места:

        A pointer to a value that is set to TRUE if the process is running under WOW64. If the process is running under 32-bit Windows, the value is set to FALSE. If the process is a 64-bit application running under 64-bit Windows, the value is also set to FALSE.

        Огорчился. Начал искать способ определения разрядности ВООБЩЕ. Нашёл Environment.Is64BitOperatingSystem в шарпе. Переписал на нём программку. Ну и пошло-поехало.


        1. tangro
          26.08.2016 10:39

          Так а что не так с этим местом в MSDN? Если Ваша программа скомпилирована 64-битной и она сейчас работает — значит ОС определённо 64-битная. Если Ваша программа скомпилирована 32-битной, то смотрим на результат: если он FALSE — то мы под 32-битной ОС, если он TRUE — то под 64-битной. Плюс отдельно случай когда мы на очень старой винде, где IsWoW64Process() вообще нет в kernel32.dll — эта винда определённо 32-битная.

          Описання в статье проблема не может возникнуть при работе с хендлом своего приложения — мы не можем быть «зомби» если наш код ещё выполняется.


          1. aquamakc
            26.08.2016 10:42

            Я же говорю — было дело очень давно и от функции ожидался простой ответ «да 64» или «Нет 32». Программа была настолько простой, что мне на тот момент оказалось проще написать на шарпе, чем понять смысл работы этой функции.


  1. jedai
    25.08.2016 16:45

    Функция GetExitCodeProcess так же имеет некоторые ограничения, описанные в секции Important. Так что если процесс уже вот-вот станет «зомби», а поток еще не завершен, то подход GetExitCodeProcess() + IsWow64Process() тоже может не сработать.


  1. IGR2014
    25.08.2016 16:46
    +1

    Заметка интересная и хорошо что вы её написали. Это полезно.
    Но простите, вроде логично что сначала стоит проверить есть ли в системе такой процесс и жив ли он.
    А уже потом пытаться понять — x64 он или нет.


    1. tangro
      25.08.2016 17:22

      Мне вначале подумалось, что IsWow64Process() делает эту проверку внутри и быстрый тест (с невалидным PID) это предположение вроде бы подтвердил. Ошибка обнаружилась лишь значительно позже, при попытке взаимодействовать с «зомби». Теперь, конечно, проверяю.


      1. IGR2014
        25.08.2016 21:30

        Подозреваю что там просто проверка на существование процесса, а вот на то что «пациент скорее мёртв чем жив» или «пациент скорее жив чем мёртв» никто не смотрит.