libffi (x86) のクロージャがbuggyな件

boost-php (謎) を書いていて、ZEND_FUNCTION_ENTRY の handler にクロージャを指定できるといろいろ便利だということが分かったので libffi に手を出してみた。が、、、テストコードが謎の SEGV を出して動かない。渋々

(gdb) disassemble closure->tramp closure->tramp+sizeof(closure->tramp)

などとして薄暗くてだだっ広い早朝のヒープの中で床面とともに鈍い光を放つトランポリンを眺めて小一時間、ようやく相対ジャンプしていることに気づいた。こういう過ちは Z80 でアセンブリを学んだ奴に多いからと自分に言い訳するものの…これではバイナリアン検定に落ちることは必至だろう。

#define FFI_INIT_TRAMPOLINE(TRAMP,FUN,CTX) \
({ unsigned char *__tramp = (unsigned char*)(TRAMP); \
   unsigned int  __fun = (unsigned int)(FUN); \
   unsigned int  __ctx = (unsigned int)(CTX); \
   unsigned int  __dis = __fun - (__ctx + 10);	\
   *(unsigned char*) &__tramp[0] = 0xb8; \
   *(unsigned int*)  &__tramp[1] = __ctx; /* movl __ctx, %eax */ \
   *(unsigned char *)  &__tramp[5] = 0xe9; \
   *(unsigned int*)  &__tramp[6] = __dis; /* jmp __fun  */ \
 })

これが分かれば後は簡単、さくっとパッチを作る。

Index: src/x86/ffi.c
===================================================================
RCS file: /cvs/libffi/libffi/src/x86/ffi.c,v
retrieving revision 1.14
diff -u -r1.14 ffi.c
--- src/x86/ffi.c	26 Feb 2008 17:40:51 -0000	1.14
+++ src/x86/ffi.c	22 Mar 2008 20:27:47 -0000
@@ -349,14 +349,14 @@
     {
       FFI_INIT_TRAMPOLINE (&closure->tramp[0],
                            &ffi_closure_SYSV,
-                           (void*)closure);
+                           (void*)codeloc);
     }
 #ifdef X86_WIN32
   else if (cif->abi == FFI_STDCALL)
     {
       FFI_INIT_TRAMPOLINE_STDCALL (&closure->tramp[0],
                                    &ffi_closure_STDCALL,
-                                   (void*)closure, cif->bytes);
+                                   (void*)codeloc, cif->bytes);
     }
 #endif
   else

これでやっと次のようなサンプルコードも動くようになった。

#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ffi.h>

typedef void (*my_closure_fn_t)(ffi_cif *, void *, void **, void *);

typedef struct _my_closure_t {
    ffi_closure clos;
    ffi_cif cif;
    void *codeloc;
    ffi_type *arg_types[];
} my_closure_t;

static my_closure_t*
my_closure_new(const ffi_cif *cif, my_closure_fn_t fun, void *userdata)
{
    void *codeloc;
    ffi_type **arg_types = NULL;
    my_closure_t *retval = NULL;

    const size_t structure_size =
        sizeof(my_closure_t) + sizeof(ffi_type) * cif->nargs;

    retval = ffi_closure_alloc(structure_size, &codeloc);
    if (!retval)
        return NULL;
    memcpy((ffi_type**)(retval + 1), cif->arg_types,
            sizeof(ffi_type) * cif->nargs);
    retval->codeloc = codeloc;
    retval->cif = *cif;
    retval->cif.arg_types = (ffi_type**)(retval + 1);

    if (FFI_OK != ffi_prep_closure_loc(&retval->clos, &retval->cif,
            fun, userdata, codeloc)) {
        ffi_closure_free(retval);
        return NULL;
    }

    return retval;
}

static void
my_closure_free(my_closure_t *clos)
{
    ffi_closure_free(clos);
}

static void adder_fun(ffi_cif *cif, void *ret, void **args, void *user_data)
{
    *(int*)ret = *((int**)args)[0] + *((int**)args)[1] + (int)user_data;
}


int main(int argc, char **argv)
{
    typedef int (*adder_fn_t)(int, int);
    ffi_cif cif;

    if (FFI_OK != ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_sint,
            (ffi_type*[]) { &ffi_type_sint, &ffi_type_sint })) {
        return 1;
    }

    my_closure_t *closures[5] = {
        my_closure_new(&cif, adder_fun, (void*)1),
        my_closure_new(&cif, adder_fun, (void*)2),
        my_closure_new(&cif, adder_fun, (void*)3),
        my_closure_new(&cif, adder_fun, (void*)4),
        my_closure_new(&cif, adder_fun, (void*)5)
    };

    for (size_t i = 0; i < 5; ++i)
        printf("%d\n", (*(adder_fn_t)(closures[i]->codeloc))(i, i));

    for (size_t i = 0; i < 5; ++i) {
        if (closures[i])
            my_closure_free(closures[i]);
    }

    return 0;
}

出力結果:

1
4
7
10
13


ffi の closure の API はもうちょっと頭を使ってほしい気もする。
使いづらいので自分でラッパーを書くのがいいと思う。