php-extsample icon indicating copy to clipboard operation
php-extsample copied to clipboard

Pass array by reference and modify inside function

Open gorazdr opened this issue 11 years ago • 58 comments
trafficstars

I have something like this: PHP_FUNCTION(modify) { smart_str ss = { 0 }; zval *val; long opts = 0; zval *strings ; HashTable sht; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zla", &val, &opts, &strings) == FAILURE) return; sht = Z_ARRVAL_P(strings) ; cmodify(&ss, val, opts, &sht TSRML_CC) ; RETVAL_STRINGL(ss.c, ss.len, 1); smart_str_free(&ss); }

cmodify expects sht to be HashTable but I would like strings(array) could replace sht

Thanks in advance.

gorazdr avatar Nov 22 '13 14:11 gorazdr

I am not sure I fully understand that the question. Do you want to modify the elements of "strings" array inside cmodify function?

mkoppanen avatar Nov 22 '13 15:11 mkoppanen

Yes that is exactly what I want. Basicly a would like to rewrite php-amf3 (AMF serializator) extension so that it keeps proper stringReferences. I tried writing to strings inside modify function with add_assoc_* and that worked OK but when I try writing to sht with zend_hash_add_* I get internal error

gorazdr avatar Nov 24 '13 08:11 gorazdr

This is original unmodified function PHP_FUNCTION(amf3_encode) { smart_str ss = { 0 }; zval *val; long opts = 0; HashTable sht, oht, tht; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &val, &opts) == FAILURE) return; zend_hash_init(&sht, 0, NULL, NULL, 0); zend_hash_init(&oht, 0, NULL, NULL, 0); zend_hash_init(&tht, 0, NULL, NULL, 0); encodeValue(&ss, val, opts, &sht, &oht, &tht TSRMLS_CC); zend_hash_destroy(&sht); zend_hash_destroy(&oht); zend_hash_destroy(&tht); RETVAL_STRINGL(ss.c, ss.len, 1); smart_str_free(&ss); } I would like sht, oht and tht to be passed in by reference

gorazdr avatar Nov 24 '13 09:11 gorazdr

Essentially what you need to do is to mark the argument as a reference in arg info, see first parameter in: http://lxr.php.net/xref/PHP_5_5/Zend/zend_API.h#101

Also, note that Z_ARRVAL_P gives a pointer to the HashTable so if the cmodify expects HashTable * you need to pass "sht" rather than "&sht".

mkoppanen avatar Nov 24 '13 09:11 mkoppanen

So change to: ZEND_BEGIN_ARG_INFO_EX(arginfo_amf3_encode, 0, 0, 1) ZEND_ARG_INFO(0, value) ZEND_ARG_INFO(1, sht) ZEND_ARG_INFO(1, oht) ZEND_ARG_INFO(1, tht) ZEND_ARG_INFO(0, options) ZEND_END_ARG_INFO()

and

PHP_FUNCTION(amf3_encode) { smart_str ss = { 0 }; zval *val, *sht, *oht, *tht; long opts = 0;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zaaa|l", &val, &sht, &oht, &tht, &opts) == FAILURE) return;

encodeValue(&ss, val, opts, HASH_OF(sht), HASH_OF(oht), HASH_OF(tht) TSRMLS_CC);

RETVAL_STRINGL(ss.c, ss.len, 1);
smart_str_free(&ss);

}

Is that correct?

gorazdr avatar Nov 24 '13 09:11 gorazdr

The last argument to ZEND_BEGIN_ARG_INFO_EX should be 4 as there are four mandatory arguments for the function. Other than that it looks ok.

mkoppanen avatar Nov 24 '13 10:11 mkoppanen

Changed ZEND_BEGIN_ARG_INFO_EX to 4 and it compiled without problems. When I test it I get in kern.log php5-fpm[6403]: segfault at 10 ip 00000000006db764 sp 00007fff75b5cd90 error 4 in php5-fpm[400000+7fd000]

php version: php 5.5.3

gorazdr avatar Nov 24 '13 10:11 gorazdr

if I comment out //encodeValue(&ss, val, opts, HASH_OF(sht), HASH_OF(oht), HASH_OF(tht) TSRMLS_CC); There is no segfault but of course it does nothing

gorazdr avatar Nov 24 '13 10:11 gorazdr

Definition of encodeValue

static void encodeValue(smart_str *ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht TSRMLS_DC) {

gorazdr avatar Nov 24 '13 10:11 gorazdr

What are the contents of encodeValue? Have you tried debugging with gdb?

mkoppanen avatar Nov 24 '13 11:11 mkoppanen

Full: static void encodeValue(smart_str ss, zval *val, int opts, HashTable *sht, HashTable *oht, HashTable *tht TSRMLS_DC) { switch (Z_TYPE_P(val)) { default: smart_str_appendc(ss, AMF3_UNDEFINED); break; case IS_NULL: smart_str_appendc(ss, AMF3_NULL); break; case IS_BOOL: smart_str_appendc(ss, Z_LVAL_P(val) ? AMF3_TRUE : AMF3_FALSE); break; case IS_LONG: { int i = Z_LVAL_P(val); if ((i >= AMF3_MIN_INT) && (i <= AMF3_MAX_INT)) { smart_str_appendc(ss, AMF3_INTEGER); encodeU29(ss, i); } else { smart_str_appendc(ss, AMF3_DOUBLE); encodeDouble(ss, i); } break; } case IS_DOUBLE: smart_str_appendc(ss, AMF3_DOUBLE); encodeDouble(ss, Z_DVAL_P(val)); break; case IS_STRING: smart_str_appendc(ss, AMF3_STRING); encodeStr(ss, Z_STRVAL_P(val), Z_STRLEN_P(val), sht TSRMLS_CC); break; case IS_ARRAY: if (!(opts & AMF3_FORCE_OBJECT) || (getHashLen(Z_ARRVAL_P(val)) >= 0)) { smart_str_appendc(ss, AMF3_ARRAY); encodeArray(ss, val, opts, sht, oht, tht TSRMLS_CC); break; } / fall through; encode array as object */ case IS_OBJECT: smart_str_appendc(ss, AMF3_OBJECT); encodeObject(ss, val, opts, sht, oht, tht TSRMLS_CC); break; } }

I did not use GDB.

Whole extension works without problems if not changed.

gorazdr avatar Nov 24 '13 11:11 gorazdr

One function that changes sht value static void encodeStr(smart_str ss, const char *str, int len, HashTable *ht TSRMLS_DC) { if (len > AMF3_MAX_INT) len = AMF3_MAX_INT; if (len) { / empty string is never sent by reference / int *oidx, nidx; if (zend_hash_find(ht, str, len, (void *)&oidx) == SUCCESS) { encodeU29(ss, *oidx << 1); return; } nidx = zend_hash_num_elements(ht); if (nidx <= AMF3_MAX_INT) zend_hash_add(ht, str, len, &nidx, sizeof(nidx), NULL); } encodeU29(ss, (len << 1) | 1); smart_str_appendl(ss, str, len); }

gorazdr avatar Nov 24 '13 11:11 gorazdr

GDB [New Thread 0x7ffff101e700 (LWP 11493)] [Thread 0x7ffff101e700 (LWP 11493) exited] string(12) " test"

Program received signal SIGSEGV, Segmentation fault. _zval_ptr_dtor (zval_ptr=0x7ffff7fcc330) at /build/buildd/php5-5.5.3+dfsg/Zend/zend_execute_API.c:426 426 /build/buildd/php5-5.5.3+dfsg/Zend/zend_execute_API.c: No such file or directory.

gorazdr avatar Nov 24 '13 11:11 gorazdr

It's going to be a lot easier if you compile a debug version of php:

./configure --prefix=/opt/php-debug --disable-all
make
make install

and then compile the extension against the debug version

./configure --with-php-config=/opt/php-debug/bin/php-config
make

And then run the code under GDB from the extension directory:

gdb --args /opt/php-debug/bin/php -d extension_dir=modules/ -d extension=<extnamehere>.so yourscript.php

That way you will get symbols in the backtrace and can see where the crash happens.

mkoppanen avatar Nov 24 '13 12:11 mkoppanen

now I get

Program received signal SIGSEGV, Segmentation fault. _zval_ptr_dtor (zval_ptr=0x7ffff7fcfd00) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute_API.c:426 426 i_zval_ptr_dtor(*zval_ptr ZEND_FILE_LINE_RELAY_CC);

gorazdr avatar Nov 24 '13 12:11 gorazdr

Type "bt" into the gdb console when it crashes and press enter and you will get a full backtrace.

mkoppanen avatar Nov 24 '13 12:11 mkoppanen

#0 _zval_ptr_dtor (zval_ptr=0x7ffff7fcfd00) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute_API.c:426 #1 0x000000000057a5b8 in zend_hash_destroy (ht=0x7ffff7fcec38) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_hash.c:560 #2 0x000000000056c0e2 in _zval_dtor_func (zvalue=0x7ffff7fcece8) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_variables.c:45 #3 0x000000000055da30 in _zval_dtor (zvalue=0x7ffff7fcece8) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_variables.h:35 #4 i_zval_ptr_dtor (zval_ptr=0x7ffff7fcece8) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute.h:81 #5 _zval_ptr_dtor (zval_ptr=) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute_API.c:426 #6 0x0000000000578f6b in zend_hash_apply_deleter (ht=ht@entry=0x9a3788 <executor_globals+360>, p=0x7ffff7fcec90) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_hash.c:650 #7 0x000000000057a778 in zend_hash_graceful_reverse_destroy (ht=0x9a3788 <executor_globals+360>) at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_hash.c:687 #8 0x000000000055e069 in shutdown_executor () at /home/gorazd/Prejemi/php-5.5.5/Zend/zend_execute_API.c:247 #9 0x000000000056cff6 in zend_deactivate () at /home/gorazd/Prejemi/php-5.5.5/Zend/zend.c:939 #10 0x000000000050e580 in php_request_shutdown (dummy=dummy@entry=0x0) at /home/gorazd/Prejemi/php-5.5.5/main/main.c:1808 #11 0x000000000061a070 in do_cli (argc=6, argv=0x9a4b70) at /home/gorazd/Prejemi/php-5.5.5/sapi/cli/php_cli.c:1177 #12 0x0000000000418982 in main (argc=6, argv=0x9a4b70) at /home/gorazd/Prejemi/php-5.5.5/sapi/cli/php_cli.c:1378

gorazdr avatar Nov 24 '13 12:11 gorazdr

It looks like one of the arrays gets corrupted as it crashes during shutdown. Arrays returned to PHP must contain zvals as members. It looks like the code is adding something else than zvals?

mkoppanen avatar Nov 24 '13 12:11 mkoppanen

Code adds strings into hashtabe

            int *oidx, nidx;
    if (zend_hash_find(ht, str, len, (void **)&oidx) == SUCCESS) {
        encodeU29(ss, *oidx << 1);
        return;
    }
    nidx = zend_hash_num_elements(ht);
    if (nidx <= AMF3_MAX_INT) zend_hash_add(ht, str, len, &nidx, sizeof(nidx), NULL);

gorazdr avatar Nov 24 '13 12:11 gorazdr

Those cannot be returned to PHP userland code "as is". Most likely you want to modify the code to pass zvals to encodeValue and use add_index_* and add_assoc_* functions to add the values into the arrays.

mkoppanen avatar Nov 24 '13 12:11 mkoppanen

So I need to make hash of zval loop elements of hash and copy them to empty hash run encodeValue and then do reverse and add hash to zval?

gorazdr avatar Nov 24 '13 12:11 gorazdr

In essence what needs to happen is something similar to:


static void encodeValue(smart_str ss, zval *val, int opts, zval *sht, zval *oht, zval *tht TSRMLS_DC)
{
...
  add_next_index_stringl(sht, c_str, c_str_len, 1);
...
}

PHP_FUNCTION(amf3_encode) {
  smart_str ss = { 0 };
  zval *val, *sht, *oht, *tht;
  long opts = 0;

  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zaaa|l", &val, &sht, &oht, &tht, &opts) == FAILURE)
    return;

  encodeValue(&ss, val, opts, sht, oht, tht TSRMLS_CC);

  RETVAL_STRINGL(ss.c, ss.len, 1);
  smart_str_free(&ss);
}

mkoppanen avatar Nov 24 '13 13:11 mkoppanen

OK Thanks for now. You have helped me allot.:)

gorazdr avatar Nov 24 '13 13:11 gorazdr

The other option is to pass HashTables to encodeValue function and marshal the values separetely:

PHP_FUNCTION(amf3_encode) {
  smart_str ss = { 0 };
  zval *val, *sht, *oht, *tht;
  long opts = 0;
  HashTable sht_ht, oht_ht, tht_ht;
  if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zaaal", &val, &sht, &oht, &tht, &opts) == FAILURE) return;

  zend_hash_init(&sht_ht, 0, NULL, NULL, 0);
  zend_hash_init(&oht_ht, 0, NULL, NULL, 0);
  zend_hash_init(&tht_ht, 0, NULL, NULL, 0);
  encodeValue(&ss, val, opts, &sht_ht, &oht_ht, &tht_ht TSRMLS_CC);

  /* Loop through sht_ht HashTable here and copy values to sht using add_next_index_* */

  zend_hash_destroy(&sht_ht);
  zend_hash_destroy(&oht_ht);
  zend_hash_destroy(&tht_ht);
  RETVAL_STRINGL(ss.c, ss.len, 1);
  smart_str_free(&ss);
}

mkoppanen avatar Nov 24 '13 13:11 mkoppanen

PHP_FUNCTION(amf3_encode) { smart_str ss = { 0 }; zval *val, *sht, *oht, *tht; long opts = 0; HashTable sht_ht, oht_ht, tht_ht; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &val, &sht, &oht, &tht, &opts) == FAILURE) return;

zend_hash_init(&sht_ht, 0, NULL, NULL, 0); zend_hash_init(&oht_ht, 0, NULL, NULL, 0); zend_hash_init(&tht_ht, 0, NULL, NULL, 0); encodeValue(&ss, val, opts, &sht_ht, &oht_ht, &tht_ht TSRMLS_CC);

/* Loop through sht_ht HashTable here and copy values to sht using add_next_index_* */

zend_hash_destroy(&sht_ht); zend_hash_destroy(&oht_ht); zend_hash_destroy(&tht_ht); RETVAL_STRINGL(ss.c, ss.len, 1); smart_str_free(&ss); }

I made small correction to the code is there as simple way of copying from zval to ht like: zend_hash_init(&sht_sh, 0, NULL, NULL, 0); pseudo copy to sht_ht from HASH_OF(sht) encodeValue(...)

gorazdr avatar Nov 24 '13 13:11 gorazdr

I don't really fully understand the question. Do sht, oht and tht parameters contain values when you receive them?

mkoppanen avatar Nov 24 '13 13:11 mkoppanen

Yes that is the reason I am trying to change the extension.

gorazdr avatar Nov 24 '13 13:11 gorazdr

Can you show the calling PHP code for amf3_encode? I mean how you would call it after the modifications

mkoppanen avatar Nov 24 '13 13:11 mkoppanen

I would like to use it like this: public function writeArray_n(&$data) {

    $this->_stream->writeBytes(amf3_encode($data,$this->_referenceStrings, $this->_referenceObjects, $this->_referenceDefinitions, 0)) ;
}

original use was like this: public function writeArray_n(&$data) {

    $this->_stream->writeBytes(amf3_encode($data, 0)) ;
}

but the problem is the amf3_encode has no clue that references allready exists and it makes new ones. When you decode such stream you get scrambled object properties and array associations.

The whole reason why I am trying to use extension is this: Zend_AMF uses 96 seconds to encode huge data array (memory peak before encoding 517MB) this extansion uses 7 seconds.

gorazdr avatar Nov 24 '13 13:11 gorazdr

These arrays you receive from PHP will contain zvals and inside the extension you need to marshall these into native types accordingly and back zvals if you add new values to the arrays.

So most likely you want to pass the zvals to encodeValue function. Instead of using zend_hash_add etc directly you want to use add_index_* and add_assoc_* family functions to add values back to the arrays.

mkoppanen avatar Nov 24 '13 14:11 mkoppanen