C++ 使用ATL子类化在Windows 10 64位上随机崩溃

C++ 使用ATL子类化在Windows 10 64位上随机崩溃,c++,visual-studio-2015,windows-10,atl,visual-studio-2017,C++,Visual Studio 2015,Windows 10,Atl,Visual Studio 2017,从一开始:自2017年3月1日起,这是微软确认的一个错误。阅读结尾处的评论。 简短描述: 我在使用MFC、ATL的大型应用程序中遇到随机崩溃。在所有这些情况下,当ATL子类化用于一个窗口时,通过一个窗口的简单操作(移动、调整大小、设置焦点、绘制等),我会在一个随机执行地址上崩溃 首先,它看起来像一个疯狂的指针或堆损坏,但我将整个场景缩小到一个使用纯ATL和仅使用Windows API的非常简单的应用程序 需求/我使用的场景: 该应用程序是使用VS 2015 Enterprise Update

从一开始:自2017年3月1日起,这是微软确认的一个错误。阅读结尾处的评论。

简短描述:

我在使用MFC、ATL的大型应用程序中遇到随机崩溃。在所有这些情况下,当ATL子类化用于一个窗口时,通过一个窗口的简单操作(移动、调整大小、设置焦点、绘制等),我会在一个随机执行地址上崩溃

首先,它看起来像一个疯狂的指针或堆损坏,但我将整个场景缩小到一个使用纯ATL和仅使用Windows API的非常简单的应用程序

需求/我使用的场景:

  • 该应用程序是使用VS 2015 Enterprise Update 3创建的
  • 该程序应编译为32位
  • 测试应用程序使用CRT作为共享DLL
  • 该应用程序在Windows 10 Build 14393.693 64位下运行(但我们在Windows 8.1和Windows Server 2012 R2下都有repos,均为64位)
  • atlthunk.dll的版本为10.0.14393.0
应用程序的功能:

它只是创建一个框架窗口,并尝试使用WindowsAPI创建许多静态窗口。 创建静态窗口后,将使用ATL CWindowImpl::SubclassWindow方法对该窗口进行子类化。 子类操作完成后,将发送一条简单的窗口消息

发生了什么:

并非每次运行时都如此,但在向子类窗口发送消息时,应用程序通常会崩溃。 在257窗口(或256+1的另一个倍数)上,子类以某种方式失败。创建的ATL thunk无效。新子类函数的存储执行地址似乎不正确。 向窗口发送任何消息都会导致崩溃。 调用堆栈总是相同的。调用堆栈中最后可见和已知的地址位于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关心Thunk。如果DLL不存在,则按标准方式执行thunk:在堆上分配一个块,将其标记为可执行,添加一些load和一个jump语句

如果DLL存在。它包含256个用于子类化的预定义插槽。如果完成了256个子类,DLL会再次将自身重新加载到内存中,并使用DLL中接下来的256个可用插槽

据我所知,atlthunk.dll属于Windows 10,不可交换或再发行

检查内容:

  • 防病毒系统已打开或关闭,没有更改
  • 数据执行保护并不重要。(/NXCOMPAT:NO,EXE在系统设置中定义为排除,也会崩溃)
  • 子类之后对FlushinInstructionCache的其他调用或睡眠调用没有任何效果
  • 堆完整性在这里不是问题,我用多个工具重新检查了它
  • 还有上千个(我可能已经忘记我测试的内容了)…;)
再现性:

这个问题在某种程度上是可以重现的。它不是一直崩溃,而是随机崩溃。我有一台机器,代码每执行三次就崩溃一次

我可以用i7-4770和i7-6700在两个桌面工作站上重新编程

其他机器似乎一点也不受影响(总是在笔记本电脑i3-3217上工作,或使用i7-870的台式机上工作)

关于样本:

为了简单起见,我使用SEH处理程序来捕获错误。如果调试应用程序,调试器将显示上面提到的调用堆栈。 可以在命令行上使用整数启动程序。在这种情况下,程序再次启动自身,计数递减1。因此,如果启动CrashatthUnk 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;
}
//crashatlhunk.cpp:定义应用程序的入口点。
//
//Windows头文件:
#包括
//C运行时头文件
#包括
#包括
#包括
#包括
#定义_ATL_CSTRING_EXPLICIT_构造函数//某些CSTRING构造函数将是显式的
#包括
#包括
#包括
//全局变量:
HINSTANCE hInst;//当前实例
const int NUM_WINDOWS=1000;
//------------------------------------------------------
//有问题的代码
//在第256个子类之后,应用程序随机崩溃。
CMyWindow类:公共CWindowImpl
{
公众:
虚拟BOOL ProcessWindowMessage(_In_hwndhwnd,_In_uintumsg,_In_wparamwparam,u In_lparamlparam,_Inout_LRESULT&LRESULT,u In_worddwmsgmapid)重写
{
返回FALSE;
}
};
void WindowCheck()
{
HWND ahwnd[NUM_WINDOWS];
CMyWindow亚类[_countof(ahwnd)];
HWND-HWND帧;
ATLVERIFY(hwndFrame=::CreateWindow(_T(“Static”),_T(“Frame”),SS_SIMPLE,0,0,10,10,NULL,NULL,hInst,NULL));

对于(int i=0;i这是atlthunk.dll中的错误。当它第二次加载自身时,然后通过MapViewOfFile调用手动执行此操作。在这种情况下,并非每个相对于模块基的地址都是
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;
}
// 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();
}