c++cclangobjective-c-blocksboehm-gc

C blocks extension (libBlocksRuntime) - use custom memory allocator (Boehm GC) for Block_copy()


I am writing a C program that uses Apple's Blocks extension to provide lexical closures. I am also using the Boehm garbage collector. What I would like is for Block_copy() to use GC_MALLOC when allocating blocks on the heap, so that they are garbage collected.

#include <stdio.h>
#include <Block.h>
#include <GC.h>
int main()
{
  int i = 42;
  void(^test)(void) = Block_copy(^{printf("Hello %d\n", i);});
  test();
}

I compiled libBlocksRuntime (https://github.com/mackyle/blocksruntime) from source using -DREDIRECT_MALLOC=GC_MALLOC -DREDIRECT_REALLOC=GC_REALLOC -DIGNORE_FREE in order to have Boehm override malloc() and realloc() calls.

I then compiled the above c program with -fblocks -lBlocksRuntime -l:libgc.so -fsanitize=address but it showed that memory was leaked and thus Boehm's allocator was unused by Block_copy().

Hello 42
==5885==WARNING: invalid path to external symbolizer!
==5885==WARNING: Failed to use and restart external symbolizer!

=================================================================
==5885==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 36 byte(s) in 1 object(s) allocated from:
    #0 0x4961ed  (/home/finn/test+0x4961ed)
    #1 0x7ff40c7e0c04  (/lib/x86_64-linux-gnu/libBlocksRuntime.so.0+0xc04)
    #2 0x7ff40c436cc9  (/lib/x86_64-linux-gnu/libc.so.6+0x26cc9)

SUMMARY: AddressSanitizer: 36 byte(s) leaked in 1 allocation(s).

How can I force libBlocksRuntime to use Boehm's memory allocator?

EDIT: I've tried to solve this by using malloc hooks, and then with LD_PRELOAD, but neither of these seem to cooperate with libBlocksRuntime (or blocks in general for that matter).


Solution

  • Ok, I've finally worked it out. There's probably a better way of doing this, but the only documentation for this I could find was the source code. Firstly, you need to #include the Block_private.h header as well as Block.h. This allows access to the _Block_use_GC() function. The version of libBlocksRuntime in the Debian repositories is unsuitable for this, since _Block_use_GC() was not compiled into its libBlocksRuntime.so. You then need to define these 5 functions:

    BLOCK_EXPORT void* blk_alloc(const unsigned long size, __attribute__((unused)) const _Bool _, __attribute__((unused)) const _Bool __) { return GC_MALLOC(size); }
    BLOCK_EXPORT void blk_setHasRefcount(__attribute__((unused)) const void* _, __attribute__((unused)) const _Bool __) {}
    BLOCK_EXPORT void blk_gc_assign_strong(void* src, void** dst) { *dst = src; }
    BLOCK_EXPORT void blk_gc_assign_weak(const void* src, void* dst) { *(void**)dst = (void*)src; }
    BLOCK_EXPORT void blk_gc_memmove(void* dst, void* src, unsigned long size) { memmove(dst, src, size); }
    

    These are the functions that libBlocksRuntime is going to use to access the garbage collector. The important one in this case is blk_alloc(), which just calls GC_MALLOC(). Other garbage collectors may use the other functions however, and you can probably leverage them for better performance. Now we have to tell libBlocksRuntime to use these functions like so:

      _Block_use_GC(blk_alloc, blk_setHasRefcount, blk_gc_assign_strong, blk_gc_assign_weak, blk_gc_memmove);
    

    This informs libBlocksRuntime that we are using a garbage collector, and this is how to interface with it.