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

Тема в разделе "Другие языки программирования", создана пользователем Dragokas, 4 сен 2016.

  1. Dragokas
    Онлайн

    Dragokas Very kind Developer Команда форума Супер-Модератор Разработчик Клуб переводчиков

    Сообщения:
    4.493
    Симпатии:
    4.309
    Автор: tangro (habrahabr)

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

    Код (C++):


    #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ы. Потестируем нашу программу.

    [​IMG]

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

    Идём дальше. Запускаем Chrome. При запуске он стартует некоторое количество дочерних процессов, отвечающих за рендеринг, обработку контента страниц и т.д. Давайте попробуем определить битность одного из таких процессов:

    [​IMG]

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

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

    [​IMG]

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

    А вот почему.

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

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

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

    [​IMG]

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

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

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

Поделиться этой страницей