6

I'm learning PHP extension writing in order to make some old extensions work with PHP 7.

I tried to modify the sample extension from http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension/ but it kept causing segfaults when destructing the custom object. All other functions worked normally. (Car is replaced by BDict in my code.)

Here's my code:

#define Z_BDICT_OBJ_P(zv) php_bdict_object_fetch_object(Z_OBJ_P(zv))

zend_object_handlers bdict_object_handlers;

typedef struct _bdict_object {
    BDict *bdict_data;
    zend_object std;
} bdict_object;

zend_class_entry *bdict_ce;

static void bdict_free_storage(zend_object *object TSRMLS_DC)
{
    bdict_object *intern = (bdict_object *)object;

    // ***Both the following two lines will cause segfault***
    delete intern->bdict_data;
    zend_object_std_dtor(&intern->std TSRMLS_CC);
}

zend_object * bdict_object_new(zend_class_entry *ce TSRMLS_DC)
{
    bdict_object *intern = (bdict_object *)ecalloc(1,
            sizeof(bdict_object) +
            zend_object_properties_size(ce));

    zend_object_std_init(&intern->std, ce TSRMLS_CC);
    object_properties_init(&intern->std, ce);

    intern->std.handlers = &bdict_object_handlers;

    return &intern->std;
}

static inline bdict_object * php_bdict_object_fetch_object(zend_object *obj)
{
    return (bdict_object *)((char *)obj - XtOffsetOf(bdict_object, std));
}

PHP_METHOD(BDict, __construct)
{
    long maxGear;
    BDict *bdict = NULL;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &maxGear) == FAILURE) {
        RETURN_NULL();
    }

    bdict = new BDict(maxGear);
    bdict_object *intern = Z_BDICT_OBJ_P(getThis());
    intern->bdict_data = bdict;
}

PHP_MINIT_FUNCTION(bencode)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "BDict", bdict_methods);
    bdict_ce = zend_register_internal_class(&ce TSRMLS_CC);
    bdict_ce->create_object = bdict_object_new;

    memcpy(&bdict_object_handlers,
            zend_get_std_object_handlers(), sizeof(zend_object_handlers));

    bdict_object_handlers.offset = XtOffsetOf(bdict_object, std);
    bdict_object_handlers.free_obj = bdict_free_storage;

    return SUCCESS;
}

By changing the sequence of the two lines and executing $dict = new BDict(10); unset($dict);, I managed to get the error information for both of them.

/***** delete intern->bdict_data; *****/
Starting program: /opt/php-7.0.1/bin/php test.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
__GI___libc_free (mem=0xc002180800000001) at malloc.c:2933

/***** zend_object_std_dtor(&intern->std TSRMLS_CC); *****/
Starting program: /opt/php-7.0.1/bin/php test.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x0000000000a45890 in zend_object_std_dtor (object=0x7ffff4001c90) at /home/frederick/php-7.0.1/Zend/zend_objects.c:59
59                      if (EXPECTED(!(GC_FLAGS(object->properties) & IS_ARRAY_IMMUTABLE))) {

I'm new to PHP extensions and I'm really confused now. Any help would be greatly appreciated, thanks.

UPDATE

I noticed that the object actually changed after the conversion in bdict_free_storage() which was obviously wrong but I had no idea about how to fix it.

Here's the debug log. You can see that the data of intern and object in bdict_free_storage() are totally different and the address of BDict is wrong.

(gdb) b bencode.cc:20   // bdict_free_storage():            bdict_object *intern = (bdict_object *)object;
Breakpoint 1 at 0x7ffff36de608: file /home/frederick/php-7.0.1/ext/php-bencode/bencode.cc, line 20.
(gdb) b bencode.cc:54   // PHP_METHOD(BDict, __construct):  intern->bdict_data = bdict;
Breakpoint 2 at 0x7ffff36de77e: file /home/frederick/php-7.0.1/ext/php-bencode/bencode.cc, line 54.
(gdb) r
Starting program: /opt/php-7.0.1/bin/php test.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 2, zim_BDict___construct (execute_data=0x7ffff40140d0, return_value=0x7ffff40140b0) at /home/frederick/php-7.0.1/ext/php-bencode/bencode.cc:54
54          intern->bdict_data = bdict;
(gdb) n
55      }
(gdb) p intern->bdict_data
$1 = (BDict *) 0x150c530
(gdb) c
Continuing.

Breakpoint 1, bdict_free_storage (object=0x7ffff4001c88) at /home/frederick/php-7.0.1/ext/php-bencode/bencode.cc:20
20          bdict_object *intern = (bdict_object *)object;
(gdb) n
21          zend_object_std_dtor(&intern->std TSRMLS_CC);
(gdb) p *object     // type = 8 means IS_OBJECT
$2 = {gc = {refcount = 1, u = {v = {type = 8 '\b', flags = 24 '\030', gc_info = 49154}, type_info = 3221362696}}, handle = 1, ce = 0x14fe6e0,
  handlers = 0x7ffff38e0300 <bdict_object_handlers>, properties = 0x0, properties_table = {{value = {lval = 48, dval = 2.3715151000379834e-322, counted = 0x30, str = 0x30, arr = 0x30,
        obj = 0x30, res = 0x30, ref = 0x30, ast = 0x30, zv = 0x30, ptr = 0x30, ce = 0x30, func = 0x30, ww = {w1 = 48, w2 = 0}}, u1 = {v = {type = 232 '\350', type_flags = 237 '\355',
          const_flags = 109 'm', reserved = 243 '\363'}, type_info = 4084067816}, u2 = {var_flags = 32767, next = 32767, cache_slot = 32767, lineno = 32767, num_args = 32767,
        fe_pos = 32767, fe_iter_idx = 32767}}}}
(gdb) p *intern     // type = 0 means IS_UNDEF, all other data are also different
$3 = {bdict_data = 0xc002180800000001, std = {gc = {refcount = 1, u = {v = {type = 0 '\000', flags = 0 '\000', gc_info = 0}, type_info = 0}}, handle = 22013664,
    ce = 0x7ffff38e0300 <bdict_object_handlers>, handlers = 0x0, properties = 0x30, properties_table = {{value = {lval = 140737277455848, dval = 6.9533453880162249e-310,
          counted = 0x7ffff36dede8, str = 0x7ffff36dede8, arr = 0x7ffff36dede8, obj = 0x7ffff36dede8, res = 0x7ffff36dede8, ref = 0x7ffff36dede8, ast = 0x7ffff36dede8, zv = 0x7ffff36dede8,
          ptr = 0x7ffff36dede8, ce = 0x7ffff36dede8, func = 0x7ffff36dede8, ww = {w1 = 4084067816, w2 = 32767}}, u1 = {v = {type = 0 '\000', type_flags = 131 '\203',
            const_flags = 3 '\003', reserved = 1 '\001'}, type_info = 17007360}, u2 = {var_flags = 0, next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0}}}}}

I've found some others using

static void bdict_free_storage(void *object TSRMLS_DC)

instead of

static void bdict_free_storage(zend_object *object TSRMLS_DC)

I tried it but it gave me a compile-time error.

/home/frederick/php-7.0.1/ext/php-bencode/bencode.cc: In function ‘int zm_startup_bencode(int, int)’:
/home/frederick/php-7.0.1/ext/php-bencode/bencode.cc:131:36: error: invalid conversion from ‘void (*)(void*)’ to ‘zend_object_free_obj_t {aka void (*)(_zend_object*)}’ [-fpermissive]
     bdict_object_handlers.free_obj = bdict_free_storage;
Frederick Zhang
  • 3,593
  • 4
  • 32
  • 54
  • How about: `$dict = null; unset($dict);` ? – Hackerman Dec 29 '15 at 19:41
  • Isn't the issue simply that you used `(bdict_object *)object` instead of `php_bdict_object_fetch_object`? – NikiC Dec 29 '15 at 21:24
  • 1
    I'd also suggest to do the whole `bdict_object_handlers` initialization in MINIT (you are currently assigning two members in create_object) – NikiC Dec 29 '15 at 21:42
  • @Hackerman This can be executed normally without any issues, but why? – Frederick Zhang Dec 30 '15 at 03:01
  • @NikiC I moved the initialization to MINIT but didn't help (code modified above). I also tried using `php_bdict_object_fetch_object` instead but it seems that the `getThis()` macro can only be used in the scope of `PHP_METHOD` (cannot compile, error in expansion of `getThis()`). – Frederick Zhang Dec 30 '15 at 03:07
  • @NikiC I added some new significant debug information, please kindly check it, thanks! – Frederick Zhang Dec 30 '15 at 03:09
  • 2
    @FrederickZhang You don't need `getThis()`, simply php_bdict_object_fetch_object(object)`. – NikiC Dec 30 '15 at 10:34
  • @NikiC Oh, yes, you were right. I noticed if I put `zend_object` at the top of the struct to keep the byte order can help me avoid the issue but it looks like an ugly hack. However, by calling `php_bdict_object_fetch_object` like you said, I won't need to do so. This is perfect! I should have had a look at `XtOffsetOf`. Thanks a lot. – Frederick Zhang Dec 30 '15 at 10:54
  • You mean, that my suggestion actually works? – Hackerman Dec 30 '15 at 13:26
  • @Hackerman Sorry it seems that I misunderstood you. You meant `$dict = new BDict(10); $dict = null; unset($dict);`, right? I don't think this would work. According to the debug info, this was caused by the pointer, which pointed to a wrong address, while destructing the object. Whether you use `$dict = null` or not, the object will finally be destructed. So fix the pointer issue is the only way to solve the problem. – Frederick Zhang Dec 30 '15 at 13:35
  • It is really unclear what you are asking, but it seems that this is more an C memory management problem than an actual php 7 problem.... – Hackerman Dec 30 '15 at 14:07

2 Answers2

1

The parameter in "bdict_free_storage" must be reached out through its offset

You can try instead :

bdict_object *intern = (bdict_object *) ((char *) object - XtOffsetOf(bdict_object, std));
Stef
  • 3,691
  • 6
  • 43
  • 58
  • 1
    Yea, I actually solved the issue long ago with the same solution but forgot to post it here. Thank you anyway! Btw, the new codes can be found in https://github.com/Frederick888/php-bencode/blob/master/binit.h#L9 – Frederick Zhang May 18 '18 at 13:22
0

Change "zend_object_handlers bdict_object_handlers;" to "zend_object_handlers bdict_object_handlers = std_object_handlers;" can fix this problem because you did not initialize the structure.

ZhiyangLee
  • 23
  • 3