おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

intercept拡張モジュールをいじってみる (その2)

ということで、随分前の前回に続き、更新が止まっているintercept拡張モジュールをいじっていきます。

前回は、intercept拡張のコードのコードを読み解き、pre-intercept、post-interceptハンドラ関数に関数名を渡すようにするように実装しました。
今回は、pre-interceptハンドラ関数に、ターゲットの関数の引数を渡します。

引数を受け取る関数を作る

これは、以前にも変な動きを追っかけたfunc_get_args()の実装を参考にします。
intercept拡張は、あんまりよくない実装ですが、zend_executeと、zend_execute_internalの2つのフックそれぞれ両方に同じようなロジックが書いてある形になっているので、両方から呼び出すために、関数として作ります。現在の引数を詰めた配列のzvalを返却するもので、こんな感じ。

zval *intercept_get_active_function_arguments()
{
    void **p;
    int arg_count, i;
    zval *ret_arr;

    // 引数の数を取得
    p = EG(argument_stack).top_element - 2;
    arg_count = (int)(zend_uintptr_t) *p; 

    // 戻り値ポインタを配列として初期化
    MAKE_STD_ZVAL(ret_arr);
    array_init(ret_arr);

    // 配列に詰めていく
    for (i = 0; i < arg_count; i++) {
        zval *element;

        ALLOC_ZVAL(element);
        *element = **((zval **) (p - arg_count + i));
        zval_copy_ctor(element);
        INIT_PZVAL(element);
        add_next_index_zval(ret_arr, element);
    }   
    return ret_arr;
}
ループ部分の補足

ループの部分のマクロとか謎なので、メモ。超あやしい。

/* fast cache for zval's */
#define ALLOC_ZVAL(z)   \
    ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST)

#define ZEND_FAST_ALLOC(p, type, fc_type)   \
    (p) = (type *) emalloc(sizeof(type))

ということで、
ALLOC_ZVAL(element) は、

element = (zval *) emalloc(sizeof(zval));

となります。単にアロケートしてるだけ。ZEND_FAST_ALLOCってのがいつも謎なのですが、なにかの最適化とか高速化とかで使われるのかなぁ。
確保された領域に対して、*element = **( (zval **) (p - arg_count + i) );と、引数のzvalの実体をコピーします。

そして、zval_copy_ctor()は、zval->valueの複製を行います。
例えば、文字列を格納していたら、zvalue->value.str.val のポインタの指す文字列を複製して、アロケートし直したり、そういう感じ。

最後に、INIT_PZVAL(element);で、

#define INIT_PZVAL(z)       \
    (z)->refcount = 1;      \
    (z)->is_ref = 0;

と、リファレンスカウンタと、参照フラグをリセットします。これで、分離された値ができあがり。

ちなみに、Extension書いてるとよく使うMAKE_STD_ZVALは、

#define MAKE_STD_ZVAL(zv)                \
    ALLOC_ZVAL(zv); \
    INIT_PZVAL(zv);

という実体になっています。

参照渡し

上の補足のとおり、func_get_argsのコードがベースになっていることもあって、値は参照渡しではなく、完全に分離した状態になっています。
どうせなら、pre-interceptハンドラで引数も差し替えられるとおもしろいのですが、いろいろ面倒なことがおきそうなので、あとでじっくりやります。

組み込み

修正後の intercept.c,v 1.5 との差分は以下。

--- intercept.c.orig    2007-07-30 00:17:27.000000000 +0900
+++ intercept.c 2007-09-09 23:41:42.000000000 +0900
@@ -369,20 +369,48 @@
 }
 #endif
 
+zval *intercept_get_active_function_arguments()
+{
+       void **p;
+       int arg_count, i;
+       zval *ret_arr;
+
+       p = EG(argument_stack).top_element - 2;
+       arg_count = (int)(zend_uintptr_t) *p;
+
+       MAKE_STD_ZVAL(ret_arr);
+       array_init(ret_arr);
+
+       for (i = 0; i < arg_count; i++) {
+               zval *element;
+
+               ALLOC_ZVAL(element);
+               *element = **((zval **) (p - arg_count + i));
+               zval_copy_ctor(element);
+               INIT_PZVAL(element);
+               add_next_index_zval(ret_arr, element);
+       }
+       return ret_arr;
+}
 
 ZEND_API void intercept_execute(zend_op_array *op_array TSRMLS_DC)
 {
        char *fname = NULL;
        int cb_res = NULL;
        zval *retval = NULL;
-       zval *args[1];
+       zval *args[2];
        zval **func_name;
-       zval **object;
 
        fname = intercept_get_active_function_name(op_array TSRMLS_CC);
 
        if( zend_hash_find(Z_ARRVAL_P(IntG(pre_intercept_handlers)), fname, strlen(fname) + 1, (void **) &func_name) != FAILURE ) {
+
                MAKE_STD_ZVAL(args[0]);
+               ZVAL_STRING(args[0], fname, 1);
+               args[1] = intercept_get_active_function_arguments();
                MAKE_STD_ZVAL(retval);
 
                // MARKER
@@ -394,22 +422,25 @@
                cb_res = call_user_function(EG(function_table),
                                                                        NULL,
                                                                        *func_name,
-                                                                       retval, 0, args TSRMLS_CC);
+                                                                       retval, 2, args TSRMLS_CC);
                zval_dtor(retval);
                zval_dtor(args[0]);
+               zval_dtor(args[1]);
                efree(retval);
                efree(args[0]);
+               efree(args[1]);
        }
 
        intercept_old_execute(op_array TSRMLS_CC);
 
        if( zend_hash_find(Z_ARRVAL_P(IntG(post_intercept_handlers)), fname, strlen(fname) + 1, (void **) &func_name) != FAILURE ) {
                MAKE_STD_ZVAL(args[0]);
+               ZVAL_STRING(args[0], fname, 1);
                MAKE_STD_ZVAL(retval);
                cb_res = call_user_function(EG(function_table),
                                                                        NULL,
                                                                        *func_name,
-                                                                       retval, 0, args TSRMLS_CC);
+                                                                       retval, 1, args TSRMLS_CC);
                zval_dtor(retval);
                zval_dtor(args[0]);
                efree(retval);
@@ -424,7 +455,7 @@
        char *fname = NULL;
        int cb_res = NULL;
        zval *retval = NULL;
-       zval *args[1];
+       zval *args[2];
        zval **func_name;
        zend_execute_data *execd;
 
@@ -433,16 +464,21 @@
 
        if( zend_hash_find(Z_ARRVAL_P(IntG(pre_intercept_handlers)), fname, strlen(fname) + 1, (void **) &func_name) != FAILURE ) {
                MAKE_STD_ZVAL(args[0]);
+               ZVAL_STRING(args[0], fname, 1);
+               args[1] = intercept_get_active_function_arguments();
                MAKE_STD_ZVAL(retval);
+
                cb_res = call_user_function(EG(function_table),
                                                                        NULL,
                                                                        *func_name,
-                                                                       retval, 0, args TSRMLS_CC);
+                                                                       retval, 2, args TSRMLS_CC);
 
                zval_dtor(retval);
                zval_dtor(args[0]);
+               zval_dtor(args[1]);
                efree(retval);
                efree(args[0]);
+               efree(args[1]);
        }
 
 
@@ -454,11 +490,12 @@
 
        if( zend_hash_find(Z_ARRVAL_P(IntG(post_intercept_handlers)), fname, strlen(fname) + 1, (void **) &func_name) != FAILURE ) {
                MAKE_STD_ZVAL(args[0]);
+               ZVAL_STRING(args[0], fname, 1);
                MAKE_STD_ZVAL(retval);
                cb_res = call_user_function(EG(function_table),
                                                                        NULL,
                                                                        *func_name,
-                                                                       retval, 0, args TSRMLS_CC);
+                                                                       retval, 1, args TSRMLS_CC);
 
                zval_dtor(retval);
                zval_dtor(args[0]);

今回は、前回の変更に加え、pre-interceptハンドラの呼び出し部にのみ手を入れています。call_user_functionで、ハンドラを呼び出す際の引数が2つになるので、

-       zval *args[1];
+       zval *args[2];

と引数配列の要素数を2にし、

    cb_res = call_user_function(EG(function_table),
                                NULL,
                                *func_name,
-                               retval, 0, args TSRMLS_CC);
+                               retval, 2, args TSRMLS_CC);

と前回の1から2に引数の数を増やしています。
そして、増えた args[1] には、

+   args[1] = intercept_get_active_function_arguments();

と、上に書いた関数呼び出しで帰ってくる zval* をつっこみます。
あとは、解放を忘れずに。

+   zval_dtor(args[1]);
+   efree(args[1]);

実行してみる

あいかわらず、これをどう使えばいいのか分かっていないのですが、今回の実装のテスト。

<?php
function myfunc($a, $b) {
    echo "this is my function\n";
}

function pre_myfunc($target, $args) {
    echo "--------------\n";
    echo "this is pre_myfunc\n";
    echo "target function is $target\n";
    var_dump($args);
    echo "--------------\n";
}

intercept_add('myfunc', 'pre_myfunc', PRE_INTERCEPT);
intercept_add('printf', 'pre_myfunc', PRE_INTERCEPT);

myfunc("passing arguments1", "arguments2");
printf("Hello! %s\n", "guest");
?>

を実行すると、

--------------
this is pre_myfunc
target function is myfunc
array(2) {
  [0]=>
  string(18) "passing arguments1"
  [1]=>
  string(10) "arguments2"
}
--------------
this is my function
--------------
this is pre_myfunc
target function is printf
array(2) {
  [0]=>
  string(10) "Hello! %s
"
  [1]=>
  string(5) "guest"
}
--------------
Hello! guest

ユーザ定義関数と、組み込み関数が共に、pre-intercept関数であるpre_myfuncを先に実行していて、その中でターゲットの関数名(第1引数)と、引数の配列(第2引数)が取れているのが分かります。

次回は

post-interceptハンドラ関数側での、戻り値受け取りです。
最終的には、作者に投げて、interceptに取り込んでもらう形になればいいのかなぁ。。