Any way to find out if a function call to a closed source library invokes memory allocation?

Not directly JUCE-related, but as there are a lot of programmers around here that care about real-time performance I hope to find some answers here.

I’m doing some benchmarking to find out under what circumstances OpenCL executed on CPUs FPGAs and GPUs might be used in the context of realtime and non-realtime DSP.

As most OpenCL implementations are closed source I can only suspect what certain calls are doing under the hood, however a critical part for realtime DSP is if certain calls allocate memory.

So, is there any cross-platform way to find out if a function call to an external library allocates memory? I’m especially interested in solutions for embedded Linux platforms but also in all other desktop operating systems… Didn’t find that much helpful information on the web so far, but maybe I looked for the wrong keywords :wink:

This is what i use to check for allocations in macOS:

#include <stdlib.h>
#include <unistd.h>
#include <malloc/malloc.h>
#include <signal.h>

void *(*system_malloc)(malloc_zone_t *zone, size_t size);

void *my_malloc(malloc_zone_t *zone, size_t size)
{
    kill(0, SIGABRT);

    void *ptr = system_malloc(zone, size);
    return ptr;
}

void setup_intercept()
{
    malloc_zone_t *zone = malloc_default_zone();
    system_malloc = zone->malloc;
    zone->malloc = my_malloc;
}

void reverse_intercept()
{
    malloc_zone_t *zone = malloc_default_zone();
    zone->malloc = system_malloc;
}

int main(int argc, char* argv[])
{
    setup_intercept();

    //do your library calls here

    reverse_intercept();

    return 0;
}
2 Likes

Thank you, looks like a great starting point.

I’ll try digging a bit deeper for other OS if any similar options exist and then maybe make some nice ScopedAllocationDetector class that calls a user-supplied lambda in case of allocation in the scope.

Bonus question: Am I right that any allocation happening on a different thread at the time this is active would be captured too?

Okay, I found ways on all three major OS to do what I want. There it is:

Usage is quite simple as a RAII object:

void foo()
{
    someFunctionThatWontBeDetected();
    
    {
        ntlab::ScopedAllocationDetector allocationDetector;
        
        someFunctionThatWillBeDetectedIfItAllocates();
        
        // will obviously be detected as allocation
        std::vector<int> largeVec (100000);
    }
    
    // will not be detected
    std::vector<int> anotherLargeVec (100000);
}

It prints information about the allocation to stderr but a custom callback can be supplied too if allocation happens. You can e.g. set a breakpoint in your custom callback lambda and get a call-stack this way as soon as allocation happens, abort or assert or just invoke some logging - whatever suits your use-case.

On Linux it uses the deprecated __malloc_hook API, however I didn’t find any information about a more up to date option of doing it with a non-deprecated API. If anyone has any information regarding this, I would be happy to improve this!

4 Likes

Cool … Looks great! Will take a look one of these days.

From the readme

if an allocation detector is active in a certain scope, any allocation on any thread happening at the same time will be detected

So this would only work in single threaded tests, not in a plugin? And does it catch free?

Yes, at least in the current version. However a thread-aware version would not be that difficult to build, probably I‘ll add some additional functionality in the near future.

Also catching frees is not implemented yet but should not be that hard to add

I have been using something similar for a long time but in the latest macOS version I can’t get this to work. Did anyone else try this recently?

I borrowed one of the examples in this thread to create this:

coincidently i stumbled upon the problem you were reporting too. it seems like the memory that holds alloc zones is write protected in newer versions of macOS now. so doing the following worked for me to restore functionality:

(1) don’t use malloc_default_zone() anymore. instead use malloc_get_all_zones() to get a list of used zones. zone at index 0 will be the default.
(2) use mach’s vm_protect to allow writing to the zone memory before changing functions pointers like so:

vm_protect(mach_task_self(), (uintptr_t)zone, sizeof(malloc_zone_t), 0, VM_PROT_READ | VM_PROT_WRITE);
systemMalloc = zone->malloc;
zone->malloc = mallocTrap;
1 Like