Zend: How to correctly destruct a custom object in PHP 7?
Asked Answered
S

2

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;
Startle answered 29/12, 2015 at 19:24 Comment(11)
How about: $dict = null; unset($dict); ?Assai
Isn't the issue simply that you used (bdict_object *)object instead of php_bdict_object_fetch_object?Pyongyang
I'd also suggest to do the whole bdict_object_handlers initialization in MINIT (you are currently assigning two members in create_object)Pyongyang
@Assai This can be executed normally without any issues, but why?Startle
@Pyongyang 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()).Startle
@Pyongyang I added some new significant debug information, please kindly check it, thanks!Startle
@FrederickZhang You don't need getThis(), simply php_bdict_object_fetch_object(object)`.Pyongyang
@Pyongyang 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.Startle
You mean, that my suggestion actually works?Assai
@Assai 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.Startle
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....Assai
Z
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));
Zareba answered 18/5, 2018 at 11:8 Comment(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 github.com/Frederick888/php-bencode/blob/master/binit.h#L9Startle
W
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.

Westonwestover answered 30/10, 2017 at 8:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.