CString use coupled with HeapWalk and HeapLock/HeapUnlock deadlocks in the kernel

こ雲淡風輕ζ 提交于 2019-12-08 14:16:05

问题


My goal is to lock virtual memory allocated for my process heaps (to prevent a possibility of it being swapped out to disk.)

I use the following code:

//pseudo-code, error checks are omitted for brevity

struct MEM_PAGE_TO_LOCK{
    const BYTE* pBaseAddr;          //Base address of the page
    size_t szcbBlockSz;         //Size of the block in bytes

    MEM_PAGE_TO_LOCK()
        : pBaseAddr(NULL)
        , szcbBlockSz(0)
    {
    }
};


void WorkerThread(LPVOID pVoid)
{
    //Called repeatedly from a worker thread

    HANDLE hHeaps[256] = {0};   //Assume large array for the sake of this example
    UINT nNumberHeaps = ::GetProcessHeaps(256, hHeaps);
    if(nNumberHeaps > 256)
        nNumberHeaps = 256;

    std::vector<MEM_PAGE_TO_LOCK> arrPages;

    for(UINT i = 0; i < nNumberHeaps; i++)
    {
        lockUnlockHeapAndWalkIt(hHeaps[i], arrPages);
    }

    //Now lock collected virtual memory
    for(size_t p = 0; p < arrPages.size(); p++)
    {
        ::VirtualLock((void*)arrPages[p].pBaseAddr, arrPages[p].szcbBlockSz);
    }

}

void lockUnlockHeapAndWalkIt(HANDLE hHeap, std::vector<MEM_PAGE_TO_LOCK>& arrPages)
{
    if(::HeapLock(hHeap))
    {
        __try
        {
            walkHeapAndCollectVMPages(hHeap, arrPages);
        }
        __finally
        {
            ::HeapUnlock(hHeap);
        }
    }
}

void walkHeapAndCollectVMPages(HANDLE hHeap, std::vector<MEM_PAGE_TO_LOCK>& arrPages)
{
    PROCESS_HEAP_ENTRY phe = {0};

    MEM_PAGE_TO_LOCK mptl;

    SYSTEM_INFO si = {0};
    ::GetSystemInfo(&si);

    for(;;)
    {
        //Get next heap block
        if(!::HeapWalk(hHeap, &phe))
        {
            if(::GetLastError() != ERROR_NO_MORE_ITEMS)
            {
                //Some other error
                ASSERT(NULL);
            }

            break;
        }

        //We need to skip heap regions & uncommitted areas
        //We're interested only in allocated blocks
        if((phe.wFlags & (PROCESS_HEAP_REGION | 
            PROCESS_HEAP_UNCOMMITTED_RANGE | PROCESS_HEAP_ENTRY_BUSY)) == PROCESS_HEAP_ENTRY_BUSY)
        {
            if(phe.cbData &&
                phe.lpData)
            {
                //Get address aligned at the page size boundary
                size_t nRmndr = (size_t)phe.lpData % si.dwPageSize;
                BYTE* pBegin = (BYTE*)((size_t)phe.lpData - nRmndr);

                //Get segment size, also page aligned (round it up though)
                BYTE* pLast = (BYTE*)phe.lpData + phe.cbData;
                nRmndr = (size_t)pLast % si.dwPageSize;
                if(nRmndr)
                    pLast += si.dwPageSize - nRmndr;

                size_t szcbSz = pLast - pBegin;

                //Do we have such a block already, or an adjacent one?
                std::vector<MEM_PAGE_TO_LOCK>::iterator itr = arrPages.begin();
                for(; itr != arrPages.end(); ++itr)
                {
                    const BYTE* pLPtr = itr->pBaseAddr + itr->szcbBlockSz;

                    //See if they intersect or are adjacent
                    if(pLPtr >= pBegin &&
                        itr->pBaseAddr <= pLast)
                    {
                        //Intersected with another memory block

                        //Get the larger of the two
                        if(pBegin < itr->pBaseAddr)
                            itr->pBaseAddr = pBegin;

                        itr->szcbBlockSz = pLPtr > pLast ? pLPtr - itr->pBaseAddr : pLast - itr->pBaseAddr;

                        break;
                    }
                }

                if(itr == arrPages.end())
                {
                    //Add new page
                    mptl.pBaseAddr = pBegin;
                    mptl.szcbBlockSz = szcbSz;

                    arrPages.push_back(mptl);
                }
            }
        }
    }
}

This method works, except that rarely the following happens. The app hangs up, UI and everything, and even if I try to run it with the Visual Studio debugger and then try to Break all, it shows an error message that no user-mode threads are running:

The process appears to be deadlocked (or is not running any user-mode code). All threads have been stopped.

I tried it several times. The second time when the app hung up, I used the Task Manager to create dump file, after which I loaded the .dmp file into Visual Studio & analyzed it. The debugger showed that the deadlock happened somewhere in the kernel:

and if you review the call stack:

It points to the location of the code as such:

CString str;

str.Format(L"Some formatting value=%d, %s", value, etc);

Experimenting further with it, if I remove HeapLock and HeapUnlock calls from the code above, it doesn't seem to hang anymore. But then HeapWalk may sometimes issue an unhandled exception, access violation.

So any suggestions how to resolve this?


回答1:


The problem is that you're using the C runtime's memory management, and more specifically the CRT's debug heap, while holding the operating system's heap lock.

The call stack you've posted includes _free_dbg, which always claims the CRT debug heap lock before taking any other action, so we know the thread holds the CRT debug heap lock. We can also see that the CRT was inside an operating system call made by _CrtIsValidHeapPointer when the deadlock occurred; the only such call is to HeapValidate and HEAP_NO_SERIALIZE is not specified.

So the thread whose call stack has been posted is holding the CRT debug heap lock and attempting to claim the operating system's heap lock.

The worker thread, on the other hand, holds the operating system's heap lock and makes calls that attempt to claim the CRT debug heap lock.

QED. Classic deadlock situation.

In a debug build, you will need to refrain from using any C or C++ library functions that might allocate or free memory while you are holding the corresponding operating system heap lock.

Even in a release build, you would still need to avoid any library functions that might allocate or release memory while holding a lock, which might be a problem if, for example, a hypothetical future implementation of std::vector was changed to make it thread-safe.

I recommend that you avoid the issue entirely, which is probably best done by creating a dedicated heap for your worker thread and taking all necessary memory allocations out of that heap. It would probably be best to exclude this heap from processing; the documentation for HeapWalk does not explicitly say that you should not modify the heap during enumeration, but it seems risky.



来源:https://stackoverflow.com/questions/44316994/cstring-use-coupled-with-heapwalk-and-heaplock-heapunlock-deadlocks-in-the-kerne

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!