Troubleshooting MFC state related issues

In this article we will be talking about MFC Module state and Thread state , and how do we troubleshoot issues related to Module state mismatch.

Why MFC dll does needs a module-specific state?

Well reason is MFC is a shared dll, it needs to be shared between various modules (dlls\exe) that are linked with it and uses its functionality. The module-specifc information that MFC needs to maintain includes but is not limited to resource-handle, activation-context, reference count of OLE modules, pointer to application object, window proc corresponding to module etc.

Why does it need a per-thread state?

MFC is a multithreaded dll , so it can execute on multiple threads of application at same time , it needs to maintain information about currently executing MFC modules on that thread among other things. Thread state is keyed in thread local storage. One of the important things present in thread state is pointer to module state.

What kind-of failures can one expect.

As a thread of execution leaves one MFC module and enters into another module, it is expected that module state pointer of thread state points to correct corresponding module state. If there is a mismatch then , whenever MFC tries to access a part of module state, like resource-handle ,handle to the activation context since the module state is not correct , application can run into crashes\hangs.

As an example let’s see how a wrongly set module-state can cause a wrong activation being popped from activation-context record stack. The activation context is created in AfxWinInit. It is destroyed in the AFX_MODULE_STATE destructor. An activation context handle is kept in AFX_MODULE_STATE.

The AFX_MANAGE_STATE macro activates and deactivates the activation context. You get a  0xC000000D exception with following at top of callstack.

0:000> kpn

 # ChildEBP RetAddr 

00 0012ef9c 7c942980 ntdll!RtlRaiseStatus(long Status = -1073741811)+0x26

01 0012f008 7c80a72d ntdll!RtlDeactivateActivationContext

02 0012f018 781ffa1e kernel32!DeactivateActCtx

03 0012f024 781ff893 mfc80!AFX_MAINTAIN_STATE2::~AFX_MAINTAIN_STATE2

04 0012f05c 7e418734 mfc80!AfxWndProcBase

05 0012f088 7e418816 user32!InternalCallWinProc(void)+0x28

06 0012f0f0 7e42a013 user32!UserCallWinProcCheckWow+0x150

07 0012f120 7e42a998 user32!CallWindowProcAorW+0x98

08 0012f140 0663163b user32!CallWindowProcA()

Every regular dll and exe in MFC world has a global application object. Application object contains a pointer to AFX_MODULE_STATE. Following are the steps that one can follow to check if correct module state is keyed into thread state or not.

1.      From the call stack , see which is the MFC module on top of call stack and get corresponding app object.

0:000> x my!*theApp*

066682b8          my!theApp = class CMyApp

2.      From the app object , we get the pointer to corresponding module state.

0:000> dt 066682b8         


   +0x01c m_pModuleState   : 0x066683a0 AFX_MODULE_STATE

   +0x050 m_pszAppName     : 0x07c63ac0  “dllname”

0:000> dt 0x066683a0 AFX_MODULE_STATE


   +0x010 m_lpszCurrentAppName : 0x07c63ac0  “dllname”

   +0x09c m_hActCtx        : 0x069eb3c0 Void

3.       From the callstack get the pointer to AFX_THREAD_STATE. Check what does m_ pModuleState   member points to.

0:000> dt 0x0015acf0 _AFX_THREAD_STATE


   +0x000 __VFN_table : 0x781d80f4

   +0x004 m_pModuleState   : (null)

   +0x008 m_pPrevModuleState : 0x0015b600 AFX_MODULE_STATE



 Now for a currently executing thread , MFC maintains a AFX_THREAD_STATE which is keyed into Thread Local Storage. Thread state contains a pointer to AFX_MODULESTATE(m_ pModuleState  ). This pointer should always point to the AFX_MODULE_STATE corresponding to MFC module that is currently executing ie which is at the top of call-stack right now. 

So we can see that module state has not been set correctly and it results in crash here.  The way to correct the error would be to make sure that you call AFX_MANAGE_STATE(AfxGetStaticModuleState) at all the entry points of dllname dll.



Comments (4)

  1. Bob Lambert says:

    I don't see why you're using "my!" as the module name in your 1st x command?

  2. Bob Lambert says:

    Also, in step 3 says get a pointer from the call stack, but then the next x command uses 0x0015acf0, which is not listed in the call stack displayed, or anywhere else in your article – where did this value come from?

  3. Bob Lambert says:

    my stack:

    0:023> kbn

     *** Stack trace for last set context – .thread/.cxr resets it

    # ChildEBP RetAddr  Args to Child              

    00 0d24f948 7671544c 0977d1b8 17154290 0d24f9a4 ntdll!RtlDeactivateActivationContext+0x154

    01 0d24f958 73459882 00000000 17154290 221075b3 kernel32!DeactivateActCtx+0x31

    02 0d24f964 221075b3 deb48ab2 0e38b1cc 0e38b160 mfc100u!AFX_MAINTAIN_STATE2::~AFX_MAINTAIN_STATE2+0x1d

    03 0d24f9a4 221093ce deb48aea 00000000 00000000 dd10shrd!ClsVDctNotifySink::XDgnVDctNotifySink::JITPause_+0x93 [e:work1250relnwesharedmdsink.cpp @ 892]

    04 0d24f9fc 005e68b0 0e38b1cc 00000000 0061d57c dd10shrd!ClsVDctNotifySink::XDgnVDctNotifySink::JITPause+0x7e [e:work1250relnwesharedmdsink.cpp @ 876]

    05 0d24fa08 0061d57c debe184d 059e5cec 059e5a70 natspeak!CList<IDgnSRGramCommon *,IDgnSRGramCommon *>::`scalar deleting destructor'

    06 0d24fa4c 0061d745 000000e0 000023e2 22034848 natspeak!ClsVoiceDictation::paused+0xcc [e:work1250relnwevdct2mdsink.cpp @ 1637]

    07 0d24faa0 220360b9 000000e0 000023e2 deb48816 natspeak!ClsVoiceDictation::paused+0x295 [e:work1250relnwevdct2mdsink.cpp @ 1687]