替换PHP底层函数实现

在PHP中,有很多内置的函数,这些函数包括绝大部分的功能,还有一些函数,是PHP的扩展提供的,只有安装了扩展并开启,函数才可以被使用,这些函数的实现,都是用C来实现的,因此有着最好的效率。

在某些情况下,可能要对这些用C实现的函数进行一些额外的更改,比如说,对其进行一个伪实现,或者,在调用之前检查是否满足特定的情况,也就是说,对这些函数进行Hook。
比较暴力的办法,就是找到需要伪实现或者Hook的函数的源代码,直接把对应的源代码修改后重新编译,这样虽然能解决问题,但是相对的,非常麻烦,也不好移植。

下面就是另一个办法,我们可以自己新建一个扩展,然后,在扩展中找到对应的函数实现,然后直接把这个函数实现替换成自己的。

首先,需要知道的是,在PHP中的所有的函数,都会在执行期间存放在一个大的HashTable中,这个HashTable就是function_table,在PHP的扩展中,可以通过宏CG(function_table)去获取这个HashTable,所有的操作,都是围绕这个HashTable做的文章。

还要明确的一点,就是PHP扩展中的函数参数都是一样的,在PHP中提供了INTERNAL_FUNCTION_PARAMETERS这个宏来表示整个函数的参数列表。
同样,在PHP的扩展中,要想定义一个上层PHP代码能调用的函数,就必须使用
PHP_FUNCTION这个宏将函数名称包裹起来,就像:

PHP_FUNCTION(my_php_info)
{
}

背景介绍完了,下面就是真正的实现了:

首先,我们先定义一个函数指针类型php_func,这个指针类型指向一个PHP扩展函数:

typedef void (*php_func)(INTERNAL_FUNCTION_PARAMETERS);

接下来是实现一个php_override_func函数,这个函数的作用,就是从之前所说的function_table中找到需要被替换的函数,并且用自定义的函数替换掉原有的实现,同时,还可以根据需要,把原有的实现保留下来,用作它用。

// PHP 5版本
static void php_override_func(const char *name, size_t len, php_func handler, php_func *stash TSRMLS_DC)
{
    zend_function *func;
    if (zend_hash_find(CG(function_table), name, len, (void **)&func) == SUCCESS) {
        if (stash) {
            *stash = func->internal_function.handler;
        }
        func->internal_function.handler = handler;
    }
}

// PHP 7版本
static void php_override_func(const char *name, size_t len, php_func handler, php_func *stash)
{
    zend_function *func;
    if ((func = zend_hash_str_find_ptr(CG(function_table), name, strlen(name))) != NULL) {
        if (stash) {
            *stash = func->internal_function.handler;
        }
        func->internal_function.handler = handler;
    }
}

有了这两个,就可以实现对某个函数的替换了,举个例子,假如现在需要将PHP的ini_get()这个函数重写了,让这个函数再任何情况下都返回一个字符串“Fake ini_get”:
首先我们先把伪实现后的函数定义并编写完成:

PHP_FUNCTION(fake_ini_get)
{
    RETURN_STRING("Fake ini_get", 1);
}

然后我们在扩展的初始化函数中调用:

php_override_func("ini_get", sizeof("ini_get"), PHP_FN(fake_ini_get), NULL TSRMLS_CC);

就可以实现对ini_get()函数的伪实现了。

这样,当需要对PHP的函数进行一些修改的话,就可以直接通过自己的扩展来实现,而不用大动干戈的去修改PHP的源代码了。