_CrtSetAllocHook - unhandled exception

1

I use Windows 10 x64 and Visual Studio 2019. Here is a simple program (x86-Debug) which should print a memory allocation size:

#include <iostream>

int MyAllocHook(int allocType, void* userData, std::size_t size, int blockType, long requestNumber,
const unsigned char* filename, int lineNumber)
{
    std::cout << "Alloc: " << size << std::endl;
    return 1;
}

int main()
{
    _CrtSetAllocHook(MyAllocHook);
    void* ptr = malloc(128);
    if (ptr)
    {
        free(ptr);
    }

    system("pause");
    return 0;
}

When I run the program I get Exception Unhandled error:

Unhandled exception at 0x7A6A2B19 (ucrtbased.dll) in testcpp.exe: 0xC00000FD: Stack overflow (parameters: 0x00000000, 0x00EB2000). occurred

What is wrong ? How to fix that ?

c++
c
visual-studio
visual-c++
asked on Stack Overflow Dec 8, 2019 by Irbis

2 Answers

3

"Stack Overflow" (ha ha) was the giveaway for me.

The I/O and string manipulation in your hook is causing its own memory allocation, so the hook gets called again, and again, and again, until it overflows the stack.

You have to have to look at blockType and explicitly exclude calls from the runtime itself, as noted here: https://docs.microsoft.com/en-us/visualstudio/debugger/allocation-hooks-and-c-run-time-memory-allocations?view=vs-2019

int MyAllocHook(int allocType, void* userData, std::size_t size,
                int blockType, long requestNumber,
                const unsigned char* filename, int lineNumber)
{
    // Do not hook calls from the C Runtime itself
    if (blockType == _CRT_BLOCK) return TRUE;

    std::cout << "Alloc: " << size << std::endl;
    return 1;
}

This way, the memory allocation calls from the I/O library itself don't cause recursive calls.

EDIT Aha, you're not just getting hung up from C runtime calls, it's also from your own "normal" string and I/O calls, which are not from the runtime. Basically you're indirectly calling malloc() and/or new from within the handler, and that's just not allowed.

This test program lets you see the crazy amount of memory being allocated:

#include <iostream>

struct allocinfo {
    int size;
    const char *filename;
    int lineno;
    int alloctype;
    int blocktype;
};

struct allocinfo allocs[256], *allocp = allocs;
volatile bool capturing = true;

static const char *printableAllocType(int t)
{
    switch (t)
    {
    case _HOOK_ALLOC: return "_HOOK_ALLOC";
    case _HOOK_REALLOC: return "_HOOK_REALLOC";
    case _HOOK_FREE: return "_HOOK_FREE";
    default: return "?";
    }
}

static const char *printableBlockType(int t)
{
    switch (t)
    {
    case _FREE_BLOCK: return "_FREE_BLOCK";
    case _NORMAL_BLOCK: return "_NORMAL_BLOCK";
    case _CRT_BLOCK: return "_CRT_BLOCK";
    case _IGNORE_BLOCK: return "_IGNORE_BLOCK";
    case _CLIENT_BLOCK: return "_CLIENT_BLOCK";
    default: return "?";
    }
}

int MyAllocHook( int allocType, void* userData, std::size_t size,
                 int blockType, long requestNumber,
                 const unsigned char* filename, int lineNumber)
{
    if (blockType == _CRT_BLOCK) return 1;

    if (capturing)
    {
        allocp->size = (int)size;
        allocp->lineno = lineNumber;
        allocp->blocktype = blockType;
        allocp->alloctype = allocType;
        allocp->filename = (const char *)filename;
        allocp++;
    }

    static bool firstTime = true;

    if (firstTime)
    {
        firstTime = false;
        std::cout << "Alloc : " << size << std::endl;
    }

    return 1;
}

int main()
{
    _CrtSetAllocHook(MyAllocHook);
    void* ptr = malloc(128);
    if (ptr)
    {
        free(ptr);
    }

    capturing = false;

    for (struct allocinfo *ap = allocs; ap < allocp; ap++)
        printf("%-15s %-15s Size %d  file %s line %d\n",
            printableAllocType(ap->alloctype),
            printableBlockType(ap->blocktype),
            ap->size, ap->filename, ap->lineno);

    return 0;
}

It records all non C runtime calls in a way that doesn't allocate any memory (which should be safe), and it reports at the end that your request generated the obvious 128 bytes you expected, plus two more 16-byte allocations from the string code in the handler:

Alloc : 128
_HOOK_ALLOC     _NORMAL_BLOCK   Size 128  file (null) line 0 <-- EXPECTED
_HOOK_ALLOC     _NORMAL_BLOCK   Size 16  file (null) line 0  <-- SURPRISE!
_HOOK_ALLOC     _NORMAL_BLOCK   Size 16  file (null) line 0  <-- SURPRISE!
_HOOK_FREE      _NORMAL_BLOCK   Size 0  file (null) line 0
_HOOK_FREE      _NORMAL_BLOCK   Size 0  file (null) line 0
_HOOK_FREE      _NORMAL_BLOCK   Size 0  file (null) line 0

The code itself is completely hacked together, but it demonstrates what's going on.

NOTE: This code is not properly reentrant as noted in another answer.

Summary: you cannot do anything in a memory hook that directly or indirectly allocates user memory even if you properly exclude the C runtime memory.

Fun fact: move the _CRT_BLOCK check after the capture of info and you'll see just how busy the C runtime is.

EDIT - I just modified my little test program and found that calls to malloc() and new are both treated as "normal" memory blocks, not runtime, so this suggests you simply cannot use them in a memory hook handler.

answered on Stack Overflow Dec 8, 2019 by Steve Friedl • edited Dec 8, 2019 by Steve Friedl
1

When you are calling for _CrtSetAllocHook, you should handle the reentrat call case in your callback function. This situation could be caused if your MyAllocHook function code is also trying to allocate memory.

A simple approach could be (took it from StackWalker):

static LONG g_lMallocCalled = 0;

int MyAllocHook(int allocType, void* userData, std::size_t size, int blockType, long requestNumber,
const unsigned char* filename, int lineNumber)
{
    // Prevent from reentrat calls
    if (InterlockedIncrement(&g_lMallocCalled) > 1) { // I was already called
        InterlockedDecrement(&g_lMallocCalled);
        return TRUE;
    }

    std::cout << "Alloc: " << size << std::endl;

    InterlockedDecrement(&g_lMallocCalled);

    return TRUE;
}
answered on Stack Overflow Dec 8, 2019 by EylM

User contributions licensed under CC BY-SA 3.0