如何记录CFType对象的分配、保留、释放和销毁的消息/中止执行?

3

我希望能够在特定的CFType对象(对于我的当前目的,是一个CGPDFDocument)被分配、保留、释放或解除分配时记录消息(最好还能中断到调试器)。

由于CGPDFDocument没有接受CFAllocatorRefCreate...()方法,因此我正在尝试暂时更改默认分配器,如下所示:

void MyPDFDocumentCreate()
{
    // ...

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

    CGPDFDocumentRef documentRef = CGPDFDocumentCreateWithProvider(provider);

    CFAllocatorSetDefault(defaultAllocator);

    // ...
}

其中MyLogAllocator()的定义如下:

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;
}

然而,似乎默认的分配器(据我所知是kCFAllocatorSystemDefault)在 context.retain 和 context.release 上都为 NULL,因此我没有任何原始实现可以调用。这可能就是为什么当我尝试上面的代码时,会得到以下堆栈跟踪:

#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并没有告诉我它为什么停止运行,但是如果我尝试继续运行,会出现以下提示:

(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) 

无论我尝试多少次,都会得到相同的SIGTRAP错误。我不知道如何解释它;我设置的唯一断点是在objc_exception_throw上的符号断点。

需要注意的一点是,在CFAllocatorCreate()中,每个LogRetain()LogAllocate()都成功调用了一次(按顺序):

#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 [...]

然后LogAllocate()再次从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 [...]

在#2处调用出现问题的CFRetain()之前,需要调用_CFRuntimeCreateInstance()
请问有人能帮我理解这里发生了什么(特别是默认分配器如何处理保留和释放以及我为什么会收到SIGTRAP)?如何修复它?是否有更好的方法来做我试图做的事情?
(我想我可能可以弄清楚如何使用DTrace来探测由CGPDFDocumentCFTypeID过滤的CFRetain()CFRelease(),但我不知道要探测什么进行释放操作(跟踪分配不那么重要,因为我知道它是在CGPDFDocumentCreateWithProvider()内完成的)。另外,我更愿意能够在保留/释放/解除分配时中断到调试器,我认为使用DTrace是不可能的。)
更新:现在已阅读了CFRelease源代码,我意识到我误解了context.retaincontext.release的目的--它们用于保留和释放context.info。因此,上述整个方法都行不通。但是,也许DTrace/Instruments巫师仍然可以施魔法?!

嗨,我在为CFAllocatorRef分配内存时有些困惑。我知道使用默认和NULL创建内存的两种方法,但这些内存对于我的应用程序来说不够。请帮助我手动创建一些内存来满足我的应用程序需求。提前感谢您的帮助! - Suresh D
1个回答

0

这是一个非常有趣的问题。既然你已经深入研究了过滤 DTrace,而且正在深入研究 CFRelease 的源代码,那么你可以考虑使用 gdb 断点条件来选择何时中断。要确定是否将发生释放,只需使用 CFGetRetainCount()

话虽如此,我猜想你正在追踪某些超出释放崩溃的问题,对吧?以下是一些比反向工程 CFRelease() 更有用的查看内容:

  • CFZombie
  • Instruments 的分配工具提供了对象何时被保留和释放、分配和销毁的完整堆栈信息。打开选项“记录引用计数”。

这是一个泄漏而不是过度释放,最终我编写了一个最小的非原子ObjC包装器并更改了代码,但下次我将使用GDB断点或Instruments。谢谢! - hatfinch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接