当前位置:  开发笔记 > 编程语言 > 正文

使用ATL子类化在Windows 10 64bit上随机崩溃

如何解决《使用ATL子类化在Windows1064bit上随机崩溃》经验,为你挑选了1个好方法。

从一开始:自2017年3月1日起,这是Microsoft确认的错误.最后阅读评论.

简短的介绍:

我在使用MFC,ATL的大型应用程序中随机崩溃.在所有这些情况下,ATL子类化用于窗口的简单操作窗口(移动,调整大小,设置焦点,绘画等)后,我在随机执行地址上崩溃.

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

要求/我使用的场景:

该应用程序是使用VS 2015 Enterprise Update 3创建的.

该程序应编译为32位.

测试应用程序使用CRT作为共享DLL.

该应用程序在Windows 10 Build 14393.693 64bit下运行(但我们在Windows 8.1和Windows Server 2012 R2下都有repros,全部是64位)

atlthunk.dll的版本为10.0.14393.0

该应用程序的作用:

它只是创建一个框架窗口,并尝试使用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,不可交换或可再发行.

检查事项:

防病毒系统已启用或打开,无需更改

数据执行保护无关紧要.(/ NXCOMPAT:NO和EXE被定义为系统设置中的排除,也崩溃了)

在子类之后对FlushInstructionCache或Sleep调用的其他调用没有任何影响.

堆完整性在这里不是问题,我用多个工具重新检查它.

还有成千上万(我可能已经忘记了我测试过的东西)......;)

重现:

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

我可以使用i7-4770和i7-6700在两个桌面工作站上进行复制.

其他机器似乎根本没有受到影响(总是在笔记本电脑i3-3217或i7-870台式机上工作)

关于样本:

为简单起见,我使用SEH处理程序来捕获错误.如果您调试应用程序,调试器将显示上面提到的callstack.程序可以在命令行上使用整数启动.在这种情况下,程序再次启动,计数递减1.因此,如果启动CrashAtlThunk 100,它将启动应用程序100次.发生错误时,SEH处理程序将捕获错误并在消息框中显示文本"Crash".如果应用程序运行没有错误,应用程序会在消息框中显示"Succeeded".如果应用程序在没有参数的情况下启动,则只执行一次.

问题:

有没有人可以重复这个?

有没有人看到类似的效果?

有人知道或可以想象出这个原因吗?

有谁知道如何解决这个问题?

笔记:

2017-01-20微软支持案例开启.

代码

// CrashAtlThunk.cpp : Defines the entry point for the application.
//

// Windows Header Files:
#include 

// C RunTime Header Files
#include 
#include 
#include 
#include 

#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS      // some CString constructors will be explicit

#include 
#include 
#include 


// 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
{
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%的摄制此.

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(0x0f370000), 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    DWORD dwLastError = ::GetLastError();

    hInst = hInstance; 

    WindowCheck();

    return 0;
}

    取消注释LoadLibrary调用.编译.

    运行一次程序并在调试器中停止.请注意加载库的地址(hMod).

    停止程序.现在,再次评论库调用和修改VirtualAlloc调用先前HMOD值的地址,这是在这个窗口会话中的首选加载地址.

    重新编译并运行.崩溃!

感谢eugene.

到现在.微软仍在调查此事.他们有转储和所有代码.但我没有最终答案.事实上,我们在某些Windows 64位操作系统中存在致命错误.

我目前做了以下更改来解决这个问题

    打开VS-2015的atlstdthunk.h.

    完全取消注释定义USE_ATL_THUNK2的#ifdef块.代码行25到27.

    重新编译您的程序.

这使得从VC-2010,VC-2013中众所周知的旧的thunking机制......这对我来说是免费的.只要没有其他已编译的库可以通过ATL以任何方式子类化或使用256个窗口.

评论(2017年3月1日):

微软证实这是一个错误.它应该在Windows 10 RS2中修复.

Mircrosoft同意编辑atlstdthunk.h中的标头是解决问题的方法.

事实上,这说.只要没有稳定的补丁我就再也不能使用正常的ATL thunking,因为我永远不会知道世界上哪些Window版本会使用我的程序.因为RS2之前的Windows 8和Windows 8.1以及Windows 10会受到此错误的影响.

最后评论(2017年3月9日):

VS-2017的搭建也受到影响,VS-2015和VS-2017之间没有区别

对于这种情况,微软决定不会对旧操作系统进行修复.

Windows 8.1,Windows Server 2012 RC2或其他Windows 10版本都不会获得修补此问题的补丁.

问题很罕见,对我们公司的影响很小.我们方面的修复也很简单.有关此错误的其他报告尚不清楚.

案件已经结案.

我对所有程序员的建议:更改Visual Studio版本VS-2015,VS-2017中的atlstdthunk.h(参见上文).我不懂微软.这个bug是ATL thunking中的一个严重问题.它可能击中每个使用更多窗口和/或子类化的程序员.

我们只知道Windows 10 RS2中的修复程序.所以旧的操作系统都受到影响!所以我建议通过注释掉上面提到的定义来禁用atlthunk.dll的使用.



1> Eugene..:

这是atlthunk.dll中的错误。当它第二次加载自身时,进一步会通过MapViewOfFile调用手动发生。在这种情况下,并非相对于模块库的每个地址都已正确更改(当LoadLibarary / LoadLibraryEx加载的DLL调用系统加载程序自动执行此操作时)。然后,如果一次将DLL加载到首选的基地址上,那么一切都将正常工作,因为未更改的地址指向相似的代码或数据。但是,如果不是这样,当第257个子类窗口处理消息时,您将崩溃。

从Vista开始,我们具有“地址空间布局随机化”功能,这解释了为什么您的代码随机崩溃。每次崩溃时,您都必须在操作系统上发现atlthunk.dll基址(在不同的OS版本上有所不同),并在第一个子类之前使用VirtualAlloc调用在该地址处进行一个内存页面地址空间的保留。要查找基址,可以使用dumpbin /headers atlthunk.dll命令或手动解析PE标头。

我的测试表明,在Windows 10内部版本14393.693 x32版本上会受到影响,而x64则不会。在具有最新更新的Server 2012R2上,x32和x64版本均会受到影响。

顺便说一句,atlthunk.dll代码每个thunk调用的CPU指令大约是以前实现的10倍。它可能不是很重要,但是会减慢消息处理速度。

推荐阅读
mobiledu2402851323
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有