PHP 的 HOOK 实现原理

570 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

PHP HOOK OPCode 是通过

int zend_set_user_opcode_handler(zend_uchar opcode, opcode_handler_t handler)

接口替换了 PHP 内置的 OPCode 的 handler (函数指针),那 PHP 是如何执行到我们替换后的 handler 的呢?

一、zend_user_opcodes 和 zend_user_opcode_handlers

以 PHP 5.1 版本为例,zend_set_user_opcode_handler() 接口的实现如下:

ZEND_API int zend_set_user_opcode_handler(zend_uchar opcode, opcode_handler_t handler)
{
    if (opcode != ZEND_USER_OPCODE) {
        zend_user_opcodes[opcode] = ZEND_USER_OPCODE;
        zend_user_opcode_handlers[opcode] = handler;
        return SUCCESS;
    }
    return FAILURE;
}

这里面用到了两个静态数组 zend_user_opcodes 和 zend_user_opcode_handlers,这两个数组的声明和初始化是由 zend_vm_gen.php 脚本根据 zend_vm_def.h 生成的,具体生成过程可参考

PHP内核生成zend_vm_opcodes.h - 知乎 (zhihu.com)

生成的 zend_cm_execute.h 文件里有这两个数组的声明和初始化,如下:

static opcode_handler_t zend_user_opcode_handlers[256] = {(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL,(opcode_handler_t)NULL};
​
static zend_uchar zend_user_opcodes[256] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255};

两个数组的下标代表 opcode 的值,当我们调用 zend_set_user_opcode_handler 时,除了会改变该 opcode 对应的 zend_user_opcode_handlers 的值,对应 opcode 的 zend_user_opcodes 的数组值会全部变成150(ZEND_USER_OPCODE 对应的值)。

二、zend_opcode_handlers

由 zend_vm_gen.php 生成的 zend_vm_execute.h 文件里还有个 zend_opcode_handlers 数组,这个数组的初始化如下:

void zend_init_opcodes_handlers()
{
  static const opcode_handler_t labels[] = {
    ZEND_NOP_SPEC_HANDLER,
    ZEND_NOP_SPEC_HANDLER,
    ...
    ZEND_ADD_SPEC_CONST_CONST_HANDLER,
    ZEND_ADD_SPEC_CONST_TMP_HANDLER,
    ZEND_ADD_SPEC_CONST_VAR_HANDLER,
    ZEND_NULL_HANDLER,
    ZEND_ADD_SPEC_CONST_CV_HANDLER,
    ZEND_ADD_SPEC_TMP_CONST_HANDLER,
    ZEND_ADD_SPEC_TMP_TMP_HANDLER,
    ZEND_ADD_SPEC_TMP_VAR_HANDLER,
    ZEND_NULL_HANDLER,
    ZEND_ADD_SPEC_TMP_CV_HANDLER,
    ZEND_ADD_SPEC_VAR_CONST_HANDLER,
    ZEND_ADD_SPEC_VAR_TMP_HANDLER,
    ...
};
    zend_opcode_handlers = (opcode_handler_t*)labels;
}

zend_opcode_handlers 包含有 3776 个 handlers,这个数字时怎么来的呢?

PHP 的 handler 是每条 opcode 对应的 C 语言编写的处理逻辑,我们可以在每个 opcode 对应一个 handler,在一个 handler 里对两个操作数的所有类型进行处理(PHP 5.0 的处理方式),也可以根据两个操作数的类型,直接定义不同的 handler 函数,而 PHP 5.0 以上的版本采取的就是后面的方式,以 PHP 5.1 为例,每个操作数有 5 种类型,两个操作数两两组合,就有 25 种可能,所以每个 opcode 对应 25 个handler,每个handler的命令方式就变成了{OPCode_name}SPEC{OP1_TYPE}_{OP2_TYPE}, 一共 151 个 opcode,对应 151 * 25 = 3775 个 handler,最后数组加了一个空 handler,所以一共 3776 个 handlers。

三、zend_opcode_handlers 和 zend_user_opcode_handlers 映射关系

zend_opcode_handlers 里一已经包含了所有 opcode 的 handler,但是怎么找到 opcode 对应的 handler 呢?

这部分代码如下:

static opcode_handler_t zend_vm_get_opcode_handler(zend_uchar opcode, zend_op* op)
{
        static const int zend_vm_decode[] = {
            _UNUSED_CODE, /* 0              */
            _CONST_CODE,  /* 1 = IS_CONST   */
            _TMP_CODE,    /* 2 = IS_TMP_VAR */
            _UNUSED_CODE, /* 3              */
            _VAR_CODE,    /* 4 = IS_VAR     */
            _UNUSED_CODE, /* 5              */
            _UNUSED_CODE, /* 6              */
            _UNUSED_CODE, /* 7              */
            _UNUSED_CODE, /* 8 = IS_UNUSED  */
            _UNUSED_CODE, /* 9              */
            _UNUSED_CODE, /* 10             */
            _UNUSED_CODE, /* 11             */
            _UNUSED_CODE, /* 12             */
            _UNUSED_CODE, /* 13             */
            _UNUSED_CODE, /* 14             */
            _UNUSED_CODE, /* 15             */
            _CV_CODE      /* 16 = IS_CV     */
        };
        return zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];
}
​
ZEND_API void zend_vm_set_opcode_handler(zend_op* op)
{
    op->handler = zend_vm_get_opcode_handler(zend_user_opcodes[op->opcode], op);
}
​

加入我们调用 zend_set_user_opcode_handler(ZEND_ADD, handler),对 ZEND_ADD(对应的值为 1),则 zend_user_opcodes[1] = 150,这个 opcode 的 handler 会通过 zend_vm_get_opcode_handler(zend_user_opcodes[op->opcode], op); 设置,此时相当于调用 zend_vm_get_opcode_handler(150, op);

而zend_vm_get_opcode_handler里的zend_vm_decode数组代表操作数的类型,只有五种类型生效,对应的值也是0~ 4,最后通过opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]

这个下标即可找到 opcode 对应的 handler, 假如两个操作数类型都为 const,可计算出下标值为 150 * 25 + 0 * 5 + 0 = 3750,对应的 handler 为 ZEND_USER_OPCODE_SPEC_HANDLER,其实​​ ZEND_USER_OPCODE 这个 opcode 的25 个handler 都是 ZEND_USER_OPCODE_SPEC_HANDLER,具体实现为:

static int ZEND_USER_OPCODE_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{   
    int ret = zend_user_opcode_handlers[EX(opline)->opcode](ZEND_OPCODE_HANDLER_ARGS_PASSTHRU_INTERNAL);
​
    switch (ret) {
        case ZEND_USER_OPCODE_CONTINUE:
            ZEND_VM_CONTINUE();
        case ZEND_USER_OPCODE_RETURN:
            ZEND_VM_RETURN();
        case ZEND_USER_OPCODE_DISPATCH:
            ZEND_VM_DISPATCH(EX(opline)->opcode, EX(opline));
        default:
            ZEND_VM_DISPATCH(ret & 0xff, EX(opline));
    }
}

可以看到里面执行了该 opcode 对应的 handler,也就是我们用 zend_set_user_handler 设置的 handler。