从一开始:自2017年3月1日起,这是Microsoft确认的错误。最后阅读评论。
简短说明:
我在使用MFC,ATL的大型应用程序中随机崩溃。在所有这些情况下,ATL子类化用于窗口的简单操作窗口(移动,调整大小,设置焦点,绘画等)后,我在随机执行地址上崩溃。
首先它看起来像一个狂野的指针或堆损坏,但我使用纯ATL和只有Windows API将完整的场景缩小到一个非常简单的应用程序。
要求/我使用的方案:
该应用程序的作用:
它只是创建一个框架窗口,并尝试使用Windows API创建许多静态窗口。 创建静态窗口后,此窗口将使用ATL CWindowImpl :: SubclassWindow方法进行子类化。 在子类操作之后,发送一个简单的窗口消息。
会发生什么:
不是每次运行,但是应用程序经常会在SendMessage上崩溃到子类窗口。 在257窗口(或256 + 1的另一个倍数)上,子类以某种方式失败。创建的ATL thunk无效。似乎新子类函数的存储执行地址不正确。 将任何消息发送到窗口会导致崩溃。 callstack总是一样的。 callstack中最后一个可见和已知的地址位于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:在堆上分配块,将其标记为可执行,添加一些加载和跳转语句。
如果DLL存在。它包含256个用于子类化的预定义槽。如果完成256个子类,则DLL会再次将其自身重新加载到内存中,并使用DLL中的下一个256个可用插槽。
据我所知,atlthunk.dll属于Windows 10,不可交换或可再发行。
检查事项:
再现性:
问题在某种程度上是可重现的。它不会一直崩溃,随机崩溃。我有一台机器,每三次执行代码崩溃。
我可以使用i7-4770和i7-6700在两个桌面电台上进行复制。
其他机器似乎根本没有受到影响(总是在笔记本电脑i3-3217或带有i7-870的台式机上工作)
关于示例:
为简单起见,我使用SEH处理程序来捕获错误。如果您调试应用程序,调试器将显示上面提到的callstack。 程序可以在命令行上使用整数启动。在这种情况下,程序再次启动,计数递减1.因此,如果启动CrashAtlThunk 100,它将启动应用程序100次。发生错误时,SEH处理程序将捕获错误并显示文本" Crash"在消息框中。如果应用程序运行没有错误,应用程序显示"成功"在消息框中。 如果应用程序在没有参数的情况下启动,则只执行一次。
问题:
备注:
2017-01-20微软支持案例开启。
代码
// 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;
}
经Eugene回答后的评论(2017年2月24日):
我不想更改原始问题,但我想添加一些其他信息,以便将其转换为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)。
停止该程序。现在再次注释Library调用并将VirtualAlloc
调用更改为先前hMod值的地址,这是此窗口会话中的首选加载地址。
重新编译并运行。 CRASH!
感谢eugene。
到目前为止。微软仍在调查此事。他们有转储和所有代码。但我没有最终答案。 事实上,我们在某些Windows 64bit操作系统中存在致命错误。
我目前进行了以下更改以解决此问题
打开VS-2015的atlstdthunk.h。
完全取消注释定义USE_ATL_THUNK2的#ifdef块。代码行25至27.
重新编译您的程序。
这使得从VC-2010,VC-2013中众所周知的旧的thunking机制......这对我来说是免费的。只要没有其他已编译的库可以以任何方式通过ATL子类化或使用256个窗口。
评论(2017年3月1日):
事实上,这说。只要没有稳定的补丁我就再也不能使用正常的ATL thunking,因为我永远不会知道世界上哪些Window版本会使用我的程序。因为RS2之前的Windows 8和Windows 8.1以及Windows 10会受到此错误的影响。
最终评论(2017年3月9日):
我对所有程序员的建议:更改Visual Studio版本VS-2015,VS-2017中的atlstdthunk.h(见上文)。 我不会&#39了解微软。这个bug是ATL thunking中的一个严重问题。它可能击中每个使用更多窗口和/或子类化的程序员。
我们只知道Windows 10 RS2中的修复程序。所以旧的操作系统都受到影响!所以我建议通过注释掉上面提到的定义来禁用atlthunk.dll。
答案 0 :(得分:4)
这是atlthunk.dll中的错误。当它自己加载第二次并且进一步时,这通过MapViewOfFile调用手动发生。在这种情况下,并非每个相对于模块库的地址都已正确更改(当LoadLibarary / LoadLibraryEx加载的DLL调用系统加载程序时会自动执行此操作)。然后,如果首选时间DLL加载到首选基地,一切正常,因为未更改的地址指向相似的代码或数据。但是,如果没有,当第257个子类窗口处理消息时,你就会崩溃。
由于Vista具有“地址空间布局随机化”功能,因此可以解释为什么您的代码会随机崩溃。每次你必须在你的操作系统上发现atlthunk.dll基地址(它在不同的操作系统版本上有所不同)时崩溃,并使用VirtualAlloc在第一个子类之前调用在此地址进行一次内存页地址空间预留 。要查找基址,可以使用dumpbin /headers atlthunk.dll
命令或手动解析PE头。
我的测试显示在Windows 10上构建14393.693 x32版本受影响但x64不受影响。在具有最新更新的Server 2012R2上,(x32和x64)版本都会受到影响。
BTW,atlthunk.dll代码的每个thunk调用的CPU指令大约是之前实现的10倍。它可能不是很重要,但它会减慢消息处理速度。答案 1 :(得分:0)
已经描述过的形式略自动:
// A minimum ATL program with more than 256 windows. In practise they would not be toplevel, but e.g. buttons.
// Thanks to https://www.codeguru.com/cpp/com-tech/atl/article.php/c3605/Using-the-ATL-Windowing-Classes.htm
// for helping with ATL.
// You need to be up to date, like have KB3030947 or KB3061512. Otherwise asserts will fail instead.
#undef _DEBUG
#include <atlbase.h>
ATL::CComModule _Module;
#include <atlwin.h>
#include <assert.h>
#include <string>
BEGIN_OBJECT_MAP(ObjectMap) END_OBJECT_MAP()
struct CMyWindow : CWindowImpl<CMyWindow>
{
BEGIN_MSG_MAP(CMyWindow) END_MSG_MAP()
};
int __cdecl wmain()
{
// Exacerbate the problem, which can happen more like if by chance.
PROCESS_INFORMATION process = { 0 };
{
// Be sure another process has atlthunk loaded.
WCHAR cmd[] = L"rundll32 atlthunk,x";
STARTUPINFOW startup = { sizeof(startup) };
BOOL success = CreateProcessW(0, cmd, 0, 0, 0, 0, 0, 0, &startup, &process);
assert(success && process.hProcess);
CloseHandle(process.hThread);
// Get atlthunk's usual address.
HANDLE file = CreateFileW((std::wstring(_wgetenv(L"SystemRoot")) + L"\\system32\\atlthunk.dll").c_str(), GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
assert(file != INVALID_HANDLE_VALUE);
HANDLE mapping = CreateFileMappingW(file, 0, PAGE_READONLY | SEC_IMAGE, 0, 0, 0);
assert(mapping);
void* view = MapViewOfFile(mapping, 0, 0, 0, 0);
assert(view);
UnmapViewOfFile(view);
VirtualAlloc(view, 1, MEM_COMMIT | MEM_RESERVE, PAGE_NOACCESS);
}
_Module.Init(0, 0);
const int N = 300;
CMyWindow wnd[N];
for (int i = 0; i < N; ++i)
{
wnd[i].Create(0, CWindow::rcDefault, L"Hello", (i < N - 1) ? 0 : (WS_OVERLAPPEDWINDOW | WS_VISIBLE));
wnd[i].DestroyWindow();
}
TerminateProcess(process.hProcess, 0);
CloseHandle(process.hProcess);
MSG msg;
while (GetMessageW(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
_Module.Term();
}