深入理解PHP-zend最终调用函数分析
原文出处:http://jackywdx.cn/2009/05/notes_of_zend_kernel
在追踪zend 内核的时候发现最终调用的函数都在zend_vm_execute.h文件里面定义了,也就是说基本每一个PHP的语句,比如定义一个函数、echo一个表达式等任何一个语句最后的实现都是在zend_vm_execute.h里面定义了,
在zend.c文件zend_startup()函数里面调用了zend_init_opcodes_handlers(),初始化了labels数组,这个数组的类型是opcode_handler_t,这个类型在zend_compile.h文件里面定义了:
typedef int
(*opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS);
//在zend_vm_execute.h引用zend_opcode_handlers,
//zend_opcode_handlers = (opcode_handler_t*)labels;
extern
ZEND_API opcode_handler_t
*zend_opcode_handlers; ;
opcode_handler_t是函数指针,指定一个用来处理每一个opcode的函数的入口地址,这些地址都保存在labels数组里面了。
static const opcode_handler_t labels[] =
{
ZEND_NOP_SPEC_HANDLER,
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,
。。。。。。。
} ;
这个结构体包含了近3776个成员,这些成员都是函数名称,每次执行一个opcode的时候都要到这个labels里面找对应的handler处理函数。
在zend_vm_execute.h文件最后的有两个函数:
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 */
};
//这句很关键,就是返回处理当前opcode的handler,
//zend_opcode_handlers等于上面定义的lables
return zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];
}
//设置zend_op里面的handler,指定处理的函数
ZEND_API void zend_vm_set_opcode_handler(zend_op* op)
{
op->handler = zend_vm_get_opcode_handler(zend_user_opcodes[op->opcode], op);
} ;
如果想知道每个opcode是交给哪个handler处理,可以在
return zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];
这条语句前面打印出索引值:
int index;
index = opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type];
printf("zend_opcode_handlers_index:%d\n", index); ;
然后再重新 make && make install
编写一个test.php文件:
<?php
$a = "test";
?> ;
再运行/usr/local/php5.2.9/bin/php test.php
输出的结果如下:
zend_opcode_handlers_index:970
zend_opcode_handlers_index:1553
zend_opcode_handlers_index:3743
简单的一个赋值语句,最后执行了三个函数,这三个数字代表在labels里面的偏移量,找到labels里面第970个值:
ZEND_ASSIGN_SPEC_CV_CONST_HANDLER
再找到这个函数:
static int ZEND_ASSIGN_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zval *value = &opline->op2.u.constant;
zend_assign_to_variable(&opline->result, &opline->op1,
&opline->op2, value, (0?IS_TMP_VAR:IS_CONST), EX(Ts) TSRMLS_CC);
/* zend_assign_to_variable() always takes care of op2, never free it! */
ZEND_VM_NEXT_OPCODE();
} ;
这个函数主要是对变量进行赋值,把值存进opline->result里面。后面1553和3743偏移值分别是
ZEND_RETURN_SPEC_CONST_HANDLER
ZEND_HANDLE_EXCEPTION_SPEC_HANDLER
分别是用来处理函数返回和异常,每个PHP文件执行到最后都会自动执行这两个函数。
另外想要快速查找每个偏移量对应的是哪个函数,可以先把 static const opcode_handler_t labels[]里面的3776个成员保存在一个文件opcode_handler.txt,然后写个函数来智能处理:
#include<stdio.h>
#include<stdlib.h>
int main(int c, char *v[]){
//在labels的偏移量
int offset = atoi(v[1]);
FILE *fp;
fp = fopen("/home/dexin/op_code_handler.txt","r");
char handler[200];
int i = 0;
while(!feof(fp)){
fgets(handler,1024,fp);
if(i != offset){
i++;
}else{
printf("opcode handler:%s\n", handler);
break;
}
}
return 1;
} ;
编译:gcc -g -o gethandler gethandler.c
然后把gethandler 放入/bin/gethandler就可以了,比如我想查询偏移量为970的handler:
gethandler 970
结果:opcode handler: ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,
再去找到这个函数的实现就好了。
写完之后发现用shell写更加快:
#gethandler_awk
#!/bin/bash
awk 'NR == offset {print "opcode handler:"$0} offset=$1 /home/dexin/op_code_handler.txt
chmod+x gethandler_awk
放入/bin/gethandler_awk
gethandler_awk 970
同样可以得出结果
opcode handler: ZEND_ASSIGN_SPEC_CV_CONST_HANDLER,