How do I log messages / break execution for alloc, retain, release and dealloc of CFType objects?

1.3k views Asked by At

I want to be able to log messages (and preferably break to the debugger) each time a specific CFType object (for my current purposes, a CGPDFDocument) is allocated, retained, released or deallocated.

Because there isn't a Create...() method for CGPDFDocument which takes a CFAllocatorRef, I'm trying to change the default allocator temporarily like this:

void MyPDFDocumentCreate()
{
    // ...

    CFAllocatorRef defaultAllocator = CFAllocatorGetDefault();
    CFAllocatorSetDefault(MyLogAllocator());

    CGPDFDocumentRef documentRef = CGPDFDocumentCreateWithProvider(provider);

    CFAllocatorSetDefault(defaultAllocator);

    // ...
}

where MyLogAllocator() is defined as follows:

static void *(*DefaultAllocate)(CFIndex size, CFOptionFlags hint, void *info);
static const void *(*DefaultRetain)(const void *info);
static void (*DefaultRelease)(const void *info);

void *LogAllocate(CFIndex size, CFOptionFlags hint, void *info)
{
    fprintf(stderr, "LogAllocate %p", info);
    if (DefaultAllocate)
        return DefaultAllocate(size, hint, info);
    else
        return NULL;
}

const void *LogRetain(const void *info)
{
    fprintf(stderr, "LogRetain");
    if (DefaultRetain)
        return DefaultRetain(info);
    else
        return info;
}

void LogRelease(const void *info)
{
    fprintf(stderr, "LogRelease");
    if (DefaultRelease)
        DefaultRelease(info);
}

static CFAllocatorRef MyLogAllocator()
{
    static CFAllocatorRef theLogAllocator = NULL;

    if (!theLogAllocator)
    {
        CFAllocatorContext context;
        CFAllocatorRef defaultAllocator = CFAllocatorGetDefault();
        CFAllocatorGetContext(defaultAllocator, &context);

        DefaultAllocate = context.allocate;
        DefaultRetain = context.retain;
        DefaultRelease = context.release;

        context.allocate = LogAllocate;
        context.retain = LogRetain;
        context.release = LogRelease;

        theLogAllocator = CFAllocatorCreate(kCFAllocatorUseContext, &context);
    }

    return theLogAllocator;
}

However, it seems that the default allocator (kCFAllocatorSystemDefault as far as I can tell) has NULL for context.retain and context.release, so I don't have any original implementations to call. That may be why, when I try the code above, I get the following stack trace:

#0  0x357ded12 in CFRetain ()
#1  0x357dcb68 in _CFRuntimeCreateInstance ()
#2  0x303fe35e in CGTypeCreateInstanceWithAllocator ()
#3  0x303fe34c in CGTypeCreateInstance ()
#4  0x304b32f4 in CGPDFDocumentCreateWithProvider ()
#5  0x000293f4 in MyPDFDocumentCreate ([...]) at [...]

XCode doesn't actually tell me why it's halting, but if I try to continue I get:

(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x357ded12 in CFRetain ()
(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x357ded12 in CFRetain ()
(gdb) 

However many times I continue, I get the same SIGTRAP. I don't know how to interpret it; the only breakpoint I have set up is a symbolic one on objc_exception_throw.

One thing to note is that LogRetain() and LogAllocate() are each called successfully once (in that order) from CFAllocatorCreate():

#0  LogRetain (info=0x1a8000) at [...]
#1  0x358086f2 in CFAllocatorCreate ()
#2  0x00028d58 in MyLogAllocator () at [...] 
#3  0x000293e0 in MyPDFDocumentCreate ([...]) at [...]

#0  LogAllocate (size=104, hint=0, info=0x1a8000) at [...] 
#1  0x3580882e in CFAllocatorCreate ()
#2  0x00028d58 in MyLogAllocator () at [...]
#3  0x000293e0 in MyPDFDocumentCreate ([...]) at [...]

And then LogAllocate() is again successful from CFAllocatorAllocate():

#0  LogAllocate (size=64, hint=1024, info=0x1a8000) at [...] 
#1  0x357dcc06 in CFAllocatorAllocate ()
#2  0x357dcb04 in _CFRuntimeCreateInstance ()
#3  0x303fe35e in CGTypeCreateInstanceWithAllocator ()
#4  0x303fe34c in CGTypeCreateInstance ()
#5  0x304b32f4 in CGPDFDocumentCreateWithProvider ()
#6  0x000293f4 in MyPDFDocumentCreate ([...]) at [...]

before the _CFRuntimeCreateInstance() at #2 calls the problematic CFRetain() detailed above.

Could somebody please help me to understand what's going on here (especially how the default allocator handles retain and release, and why I'm getting the SIGTRAP); how to fix it; and whether there's a better way to do what I'm trying to do?

(I figured I might be able to work out how to use DTrace to probe CFRetain() and CFRelease(), filtered by the CFTypeID for CGPDFDocument, but I wouldn't know what to probe for deallocation (allocation is not so important to track as I know it's done within CGPDFDocumentCreateWithProvider()). Also, I'd prefer to be able to break to the debugger on retain / release / deallocate, which I don't think is possible using DTrace.)

UPDATE: Having now read the source code for CFRelease I realise I misunderstood the purpose of context.retain and context.release -- they are for retaining and releasing context.info. So the entire approach described above is a non-starter. However, perhaps a DTrace/Instruments wizard may still be able to work some magic?!

1

There are 1 answers

1
Rob Napier On BEST ANSWER

It's a very interesting problem. Since you've gone as deep as investigating filtering DTrace, and you're diving into the CFRelease source, then you can look at using gdb breakpoint conditions to choose when to break. To determine if a deallocation is going to occur, just use CFGetRetainCount().

That said, I'm guessing you're pulling your hair out tracking down some over-release crash, right? Things to look at that are probably more useful than reverse-engineering CFRelease():

  • CFZombie
  • Instruments' Allocations instrument provides full stacks of when objects were retained and released, allocated and destroyed. Turn on the option "Record reference counts."