С самого начала: с 1 марта 2017 года эта ошибка подтверждена Microsoft. Прочитайте комментарии в конце.
Краткое описание:
У меня случайные сбои в больших приложениях, использующих MFC, ATL. Во всех таких случаях после использования подклассов ATL для окна при простых действиях с окном (перемещение, изменение размера, установка фокуса, рисование и т. Д.) Происходит сбой при случайном адресе выполнения.
Сначала это выглядело как дикий указатель или повреждение кучи, но я сузил весь сценарий до очень простого приложения, использующего чистый ATL и только Windows API.
Требования / мои использованные сценарии:
Что делает приложение:
Он просто создает рамочное окно и пытается создать множество статических окон с помощью Windows API.
После создания статического окна это окно разделяется на подклассы с помощью метода ATL CWindowImpl :: SubclassWindow.
После операции подкласса отправляется простое оконное сообщение.
Что просходит:
Не при каждом запуске, но очень часто происходит сбой приложения при отправке сообщения в подклассовое окно.
В окне 257 (или другом кратном 256 + 1) подкласс каким-то образом завершается неудачей. Созданный блок ATL недействителен. Кажется, что сохраненный адрес выполнения новой функции подкласса неверен.
Отправка любого сообщения в окно вызывает сбой.
CallCack всегда одинаков. Последний видимый и известный адрес в стеке вызовов находится в atlthunk.dll
atlthunk.dll!AtlThunk_Call(unsigned int,unsigned int,unsigned int,long) Unknown
atlthunk.dll!AtlThunk_0x00(struct HWND__ *,unsigned int,unsigned int,long) Unknown
user32.dll!__InternalCallWinProc@20() Unknown
user32.dll!UserCallWinProcCheckWow() Unknown
user32.dll!SendMessageWorker() Unknown
user32.dll!SendMessageW() Unknown
CrashAtlThunk.exe!WindowCheck() Line 52 C++
Выданное исключение в отладчике показано как:
Exception thrown at 0x0BF67000 in CrashAtlThunk.exe:
0xC0000005: Access violation executing location 0x0BF67000.
или другой образец
Exception thrown at 0x2D75E06D in CrashAtlThunk.exe:
0xC0000005: Access violation executing location 0x2D75E06D.
Что я знаю о atlthunk.dll:
Atlthunk.dll, кажется, является лишь частью 64-битной ОС. Я нашел это на системах Win 8.1 и Win 10.
Если доступен файл atlthunk.dll (все машины с Windows 10), эта DLL заботится о thunking. Если DLL отсутствует, thunking выполняется стандартным способом: выделяет блок в куче, помечает его как исполняемый, добавляет некоторую загрузку и оператор jump.
Если DLL присутствует. Он содержит 256 предопределенных слотов для подклассов. Если сделано 256 подклассов, DLL повторно загружает себя в память и использует следующие 256 доступных слотов в DLL.
Насколько я вижу, atlthunk.dll принадлежит Windows 10 и не подлежит обмену или распространению.
Вещи проверены:
Воспроизводимость:
Проблема как-то воспроизводима. Он не падает все время, он падает случайно. У меня есть машина, где код вылетает при каждом третьем выполнении.
Я могу воспроизвести его на двух настольных станциях с i7-4770 и i7-6700.
На другие машины это никак не влияет (работает всегда на ноутбуке i3-3217 или на рабочем столе с i7-870)
О образце:
Для простоты я использую обработчик SEH, чтобы поймать ошибку. Если вы отлаживаете приложение, отладчик покажет упомянутый выше стек вызовов.
Программу можно запустить с целым числом в командной строке. В этом случае программа запускается снова с уменьшенным на 1. счетчиком. Так что, если вы запустите CrashAtlThunk 100, она запустит приложение 100 раз. В случае ошибки обработчик SEH обнаружит ошибку и отобразит текст «Crash» в окне сообщения. Если приложение работает без ошибок, приложение отображает «Успешно» в окне сообщения.
Если приложение запускается без параметра, оно выполняется только один раз.
Вопросы:
Заметки:
2017-01-20 Открыта поддержка в Microsoft.
Код
// CrashAtlThunk.cpp : Defines the entry point for the application.
//
// Windows Header Files:
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit
#include <atlbase.h>
#include <atlstr.h>
#include <atlwin.h>// Global Variables:
HINSTANCE hInst; // current instance
const int NUM_WINDOWS = 1000;
//------------------------------------------------------
// The problematic code
// After the 256th subclass the application randomly crashes.
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
virtual BOOL ProcessWindowMessage(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam, _In_ LPARAM lParam, _Inout_ LRESULT& lResult, _In_ DWORD dwMsgMapID) override
{
return FALSE;
}
};
void WindowCheck()
{
HWND ahwnd[NUM_WINDOWS];
CMyWindow subclass[_countof(ahwnd)];
HWND hwndFrame;
ATLVERIFY(hwndFrame = ::CreateWindow(_T("Static"), _T("Frame"), SS_SIMPLE, 0, 0, 10, 10, NULL, NULL, hInst, NULL));
for (int i = 0; i<_countof(ahwnd); ++i)
{
ATLVERIFY(ahwnd[i] = ::CreateWindow(_T("Static"), _T("DummyWindow"), SS_SIMPLE|WS_CHILD, 0, 0, 10, 10, hwndFrame, NULL, hInst, NULL));
if (ahwnd[i])
{
subclass[i].SubclassWindow(ahwnd[i]);
ATLVERIFY(SendMessage(ahwnd[i], WM_GETTEXTLENGTH, 0, 0)!=0);
}
}
for (int i = 0; i<_countof(ahwnd); ++i)
{
if (ahwnd[i])
::DestroyWindow(ahwnd[i]);
}
::DestroyWindow(hwndFrame);
}
//------------------------------------------------------
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
hInst = hInstance;
int iCount = _tcstol(lpCmdLine, nullptr, 10);
__try
{
WindowCheck();
if (iCount==0)
{
::MessageBox(NULL, _T("Succeeded"), _T("CrashAtlThunk"), MB_OK|MB_ICONINFORMATION);
}
else
{
TCHAR szFileName[_MAX_PATH];
TCHAR szCount[16];
_itot_s(--iCount, szCount, 10);
::GetModuleFileName(NULL, szFileName, _countof(szFileName));
::ShellExecute(NULL, _T("open"), szFileName, szCount, nullptr, SW_SHOW);
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
::MessageBox(NULL, _T("Crash"), _T("CrashAtlThunk"), MB_OK|MB_ICONWARNING);
return FALSE;
}
return 0;
}
Комментарий после ответа от Евгения (24 февраля 2017 г.):
Я не хочу менять свой первоначальный вопрос, но я хочу добавить дополнительную информацию о том, как получить это в 100% Repro.
1, изменить основную функцию на
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
// Get the load address of ATLTHUNK.DLL
// HMODULE hMod = LoadLibrary(_T("atlThunk.dll"));
// Now allocate a page at the prefered start address
void* pMem = VirtualAlloc(reinterpret_cast<void*>(0x0f370000), 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
DWORD dwLastError = ::GetLastError();
hInst = hInstance;
WindowCheck();
return 0;
}
Раскомментируйте вызов LoadLibrary. Компиляция.
Запустите программу один раз и остановитесь в отладчике. Обратите внимание на адрес, куда была загружена библиотека (hMod).
Остановите программу. Теперь прокомментируйте вызов библиотеки снова и измените VirtualAlloc
вызов адреса предыдущего значения hMod, это предпочтительный адрес загрузки в этом окне сеанса.
Перекомпилируйте и запустите. CRASH!
Спасибо Евгению.
До сих пор. Microsoft все еще расследует это. У них есть дампы и весь код. Но у меня нет окончательного ответа. Факт в том, что у нас есть фатальная ошибка в некоторых 64-битных ОС Windows.
В настоящее время я сделал следующие изменения, чтобы обойти это
Откройте atlstdthunk.h VS-2015.
Полностью раскомментируйте блок #ifdef, который определяет USE_ATL_THUNK2. Строки кода от 25 до 27.
Перекомпилируйте вашу программу.
Это позволяет использовать старый механизм громкоговорителей, хорошо известный по VC-2010, VC-2013 … и это работает без сбоев для меня. Пока не задействованы другие уже скомпилированные библиотеки, которые могут каким-либо образом создавать подклассы или использовать 256 окон через ATL.
Комментарий (1 марта 2017 г.):
На самом деле это говорит. Пока не будет стабильного патча, я никогда не смогу снова использовать обычный ATL thunking, потому что я никогда не буду знать, какие версии Window в мире будут использовать мою программу. Потому что Windows 8 и Windows 8.1 и Windows 10 до RS2 пострадают от этой ошибки.
Заключительный комментарий (9 марта 2017 г.):
Мой совет всем программистам: замените файл atlstdthunk.h в вашей версии Visual Studio VS-2015, VS-2017 (см. Выше). Я не понимаю Microsoft. Эта ошибка является серьезной проблемой в ATL thunking. Это может поразить каждого программиста, который использует большее количество окон и / или подклассов.
Мы знаем только об исправлении в Windows 10 RS2. Так что все старые ОС затронуты! Поэтому я рекомендую отключить использование atlthunk.dll, закомментировав определение, указанное выше.
Это ошибка внутри atlthunk.dll. Когда он загружается сам второй раз и дальше это происходит вручную через вызов MapViewOfFile. В этом случае не каждый адрес относительно базы модуля должным образом изменяется (когда DLL, загруженная LoadLibarary / LoadLibraryEx, вызывает системный загрузчик, делает это автоматически). Тогда если первый время DLL был загружен на предпочтительный базовый адрес все работает нормально, так как неизмененные адреса указывают на похожий код или данные. Но если нет, вы получите сбой, когда 257-е подклассовое окно обрабатывает сообщения.
Начиная с Vista у нас есть функция «рандомизации макета адресного пространства», это объясняет, почему ваш код зависает случайно. Чтобы каждый раз происходил сбой, вам необходимо обнаружить базовый адрес atlthunk.dll в вашей ОС (он отличается в разных версиях ОС) и выполнить резервирование адресного пространства одной страницы памяти по этому адресу с помощью вызова VirtualAlloc до первого подкласса. Чтобы найти базовый адрес, вы можете использовать dumpbin /headers atlthunk.dll
Команда или анализировать PE заголовки вручную.
Мой тест показывает, что в Windows 10 build 14393.693 версия x32 подвержена уязвимости, а x64 — нет. На сервере 2012R2 с последними обновлениями затронуты обе версии (x32 и x64).
Кстати, код atlthunk.dll содержит примерно в 10 раз больше инструкций ЦП на вызов thunk, чем предыдущая реализация. Это может быть не очень значительным, но это замедляет обработку сообщений.
Других решений пока нет …