@DWZ

Повреждение стека вокруг переменной — как побороть?

Есть Windows 7 SP1 Pro 64 bit RUS. Под ним в Visual C++ 2017 скомпилирована 32-разрядная программа командной строки. EXE вызывает из DLL некую функцию.

EXE
int main(int argc, char *argv[])

// куча всяких printf();

#ifdef _WIN32
	char version[2500];

	if (getWindowsVersion(version))
		printf("Operating System: %s\n", version);
#endif

#if defined(__USE_LARGEFILE) && defined(__USE_LARGEFILE64)
	printf("\nLarge file available: %d offset\n", __USE_FILE_OFFSET64);
#endif
}


DLL
bool getWindowsVersion(char* version)
{
    int index = 0;
    OSVERSIONINFOEX osvi = {};

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if (!GetVersionEx((OSVERSIONINFO*)&osvi))
        return false;

    if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT)
        return false;
// Windows Vista / Windows 7
    if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion <= 1)
    {
        if (osvi.wProductType == VER_NT_WORKSTATION)
        {
            if (osvi.dwMinorVersion == 1)
                index += sprintf(version + index, "Microsoft Windows 7");
            else
                index += sprintf(version + index, "Microsoft Windows Vista");

            uint32_t productType = 0;

            HMODULE hKernel = GetModuleHandle("KERNEL32.DLL");

            if (hKernel)
            {
                typedef bool (__stdcall *funcGetProductInfo)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t*);
                funcGetProductInfo pGetProductInfo = (funcGetProductInfo)GetProcAddress(hKernel, "GetProductInfo");

                if (pGetProductInfo)
                    pGetProductInfo(6, 0, 0, 0, &productType);

                switch (productType)
                {
                case PRODUCT_STARTER:
                {
                    index += sprintf(version + index, " Starter");
                    break;
                }
                case PRODUCT_HOME_BASIC_N:
                {
                    index += sprintf(version + index, " Home Basic N");
                    break;
                }
                case PRODUCT_HOME_BASIC:
                {
                    index += sprintf(version + index, " Home Basic");
                    break;
                }
                case PRODUCT_HOME_PREMIUM:
                {
                    index += sprintf(version + index, " Home Premium");
                    break;
                }
                case PRODUCT_BUSINESS_N:
                {
                    index += sprintf(version + index, " Business N");
                    break;
                }
                case PRODUCT_BUSINESS:
                {
                    index += sprintf(version + index, " Business");
                    break;
                }
                case PRODUCT_ENTERPRISE:
                {
                    index += sprintf(version + index, " Enterprise");
                    break;
                }
                case PRODUCT_ULTIMATE:
                {
                    index += sprintf(version + index, " Ultimate");
                    break;
                }
                default:
                    break;
                }
            }
        }
        else if (osvi.wProductType == VER_NT_SERVER)
        {
            if (osvi.dwMinorVersion == 1)
                index += sprintf(version + index, "Microsoft Windows Server 2008 R2");
            else
                index += sprintf(version + index, "Microsoft Windows Server 2008");

            if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
                index += sprintf(version + index, " Datacenter Edition");
            else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
                index += sprintf(version + index, " Enterprise Edition");
            else if (osvi.wSuiteMask == VER_SUITE_BLADE)
                index += sprintf(version + index, " Web Edition");
            else
                index += sprintf(version + index, " Standard Edition");
        }
    }
// Windows Server 2003
    else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
    {
        index += sprintf(version + index, "Microsoft Windows Server 2003");

        if (GetSystemMetrics(SM_SERVERR2))
            index += sprintf(version + index, " R2");

        if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
            index += sprintf(version + index, " Datacenter Edition");
        else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
            index += sprintf(version + index, " Enterprise Edition");
        else if (osvi.wSuiteMask == VER_SUITE_BLADE)
            index += sprintf(version + index, " Web Edition");
        else
            index += sprintf(version + index, " Standard Edition");
    }
// Windows XP
    else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
    {
        index += sprintf(version + index, "Microsoft Windows XP");

        if (GetSystemMetrics(SM_MEDIACENTER))
            index += sprintf(version + index, " Media Center Edition");
        else if (GetSystemMetrics(SM_STARTER))
            index += sprintf(version + index, " Starter Edition");
        else if (GetSystemMetrics(SM_TABLETPC))
            index += sprintf(version + index, " Tablet PC Edition");
        else if (osvi.wSuiteMask & VER_SUITE_PERSONAL)
            index += sprintf(version + index, " Home Edition");
        else
            index += sprintf(version + index, " Professional");
    }
// Windows 2000
    else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
    {
        index += sprintf(version + index, "Microsoft Windows 2000");

        if (osvi.wProductType == VER_NT_WORKSTATION)
        {
            index += sprintf(version + index, " Professional");
        }
        else if (osvi.wProductType == VER_NT_SERVER)
        {
            if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
                index += sprintf(version + index, " Datacenter Server");
            else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
                index += sprintf(version + index, " Advanced Server");
            else
                index += sprintf(version + index, " Server");
        }
    }
// Windows NT 4
    else if (osvi.dwMajorVersion == 4)
    {
        index += sprintf(version + index, "Microsoft Windows NT 4");

        if (osvi.wProductType == VER_NT_WORKSTATION)
        {
            index += sprintf(version + index, " Workstation");
        }
        else if (osvi.wProductType == VER_NT_SERVER)
        {
            if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
                index += sprintf(version + index, " Server, Enterprise Edition");
            else
                index += sprintf(version + index, " Server");
        }
    }
    else
    {
        index += sprintf(version + index, "Microsoft Windows");
    }

// Service pack and full version info
    if (strlen(osvi.szCSDVersion) > 0)
    {
        index += sprintf(version + index, " %s", osvi.szCSDVersion);
    }

    index += sprintf(version + index, " (%d.%d.%d", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);

// 64-bit Windows
#ifdef _WIN64
    index += sprintf(version + index, "; 64-bit");
#else
    bool isWow64 = false;
    HMODULE hKernel = GetModuleHandle("kernel32.dll");

    if (hKernel)
    {
        typedef bool (__stdcall *funcIsWow64Process)(void*, bool*);

        funcIsWow64Process pIsWow64Process = (funcIsWow64Process)GetProcAddress(hKernel, "IsWow64Process");

        if (pIsWow64Process)
        {
            pIsWow64Process(GetCurrentProcess(), &isWow64);
        }
    }

   if (isWow64)
        index += sprintf(version + index, "; 64-bit");
    else
        index += sprintf(version + index, "; 32-bit");
#endif

   index += sprintf(version + index, ")");

    return true;
}


При запуске получаем ошибку Run-Time Check Failure #2 - Stack around the variable 'isWow64 was corrupted'

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

Если закомментировать вызов getWindowsVersion() или несколько sprintf, то получаем исключение Вызвано исключение по адресу 0x7740E136 (ntdll.dll) в avidemux_jobs.exe: 0xC0000005: нарушение прав доступа при чтении по адресу 0x00000044.

Увеличивал размер буфера до 2500 - не помогает, заменял sprintf на sprintf_s - не помогает.
Что делать, где копать?
  • Вопрос задан
  • 241 просмотр
Решения вопроса 2
@MarkusD Куратор тега C++
все время мелю чепуху :)
Соглашение __stdcall обязывает пользователя функции передать параметры функции на стеке по значению, но освобождает его чистить стек после вызова. Стек от параметров чистит сама вызванная функция.

В результате подмены сигнатуры с BOOL (__stdcall *)(HANDLE, PBOOL) на bool (__stdcall *)(void*, bool*) компилятор думает об одном размере стека, а код функции - о других.

Поэтому сигнатуры и соглашение о вызове импортируемой функции всегда должны совпадать с объявлением этой функции в импортируемой библиотеке! Вообще всегда! Даже при условии следующего текста.

Однако, конкретно тут проблема у нас не в несовпадении типов, потому что.
__stdcall свой результат передает через регистр. регистр используется целочисленный или вещественный. Для целочисленного регистра используется правило продвижения типа. Это означает, что функция, записав значение типа BOOL (размер 4Б) ничего не испортит пользовательскому коду, который прочитает из регистра все 4Б с учетом правила продвижения.

Реальная проблема кроется в том, что указатель на однобайтовое целое (bool*) передается в использование как указатель на четырехбайтовое целое (BOOL* или PBOOL). Вызываемая функция ведь имеет сигнатуру BOOL (__stdcall *)(HANDLE, PBOOL) и со вторым параметром работает как с 4Б целым по указателю.
Именно это и приводит к порче стека и тебе, автор, сильно повезло что ты запускаешься в отладке, где каждое значение на стеке обрамлено заборчиком, за сохранностью которого всегда приглядывает специальный сервисный код между обращениями к подпрограммам.

Именно такой заборчик рядом с isWow64 и был поломан в результате вызова IsWow64Process с параметром неподходящей длины. Измени тип isWow64 на BOOL и все станет нормально, даже хендл "kernel32.dll" потом сможешь нормально освободить.
Ответ написан
gbg
@gbg Куратор тега C++
Любые ответы на любые вопросы
Используйте вендовые типы и вендовый прототип для функции, которую тащите из DLL.
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);
BOOL isWow64 = FALSE;
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
Adamos
@Adamos
index += sprintf(...)
sprintf возвращает отрицательное число при ошибке.
Ответ написан
@res2001
Developer, ex-admin
В getWindowsVersion передавайте размер буфера.
Замените spintf на snprintf и контролируйте переполнение буфера. Да, при ошибке snprintf то же может вернуть отрицательное значение.
Если snprintf вызвать с первыми двумя параметрами равными 0, она посчитает необходимый размер буфера для вашего шаблона и параметров и вернет его ничего реально не скопировав. Можно использовать эту возможность.
Ответ написан
@Andy_U
Ну, если добавить отсутствующие include, и исправить одну строчку в начале функции (вызывающую синтаксическую ошибку!)OSVERSIONINFOEX osvi = {}; то все работает...Хоть debug, хоть release, хоть x86, хоть amd65. См. кусок с исправлениями (если объединить все в один файл).

Использовался CLion и MS BuildTools. 2019 года. Версия Windows такая же.

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <windows.h>

bool getWindowsVersion(char* version);

int main(int argc, char* argv[])
{
// куча всяких printf();

#ifdef _WIN32
    char version[2500];

    if (getWindowsVersion(version))
        printf("Operating System: %s\n", version);
#endif

#if defined(__USE_LARGEFILE) && defined(__USE_LARGEFILE64)
    printf("\nLarge file available: %d offset\n", __USE_FILE_OFFSET64);
#endif
}

bool getWindowsVersion(char* version)
{
    int index = 0;
    OSVERSIONINFOEX osvi;
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы