深入理解PHP-zend最终调用函数分析

深入理解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文件里面定义了:

[复制到剪切板]
CODE:
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数组里面了。

[复制到剪切板]
CODE:
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文件最后的有两个函数:

[复制到剪切板]
CODE:
static opcode_handler_t zend_vm_get_opcode_handler(zend_uchar opcodezend_opop)
{
        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] * zend_vm_decode[op->op2.op_type]];
}

//设置zend_op里面的handler,指定处理的函数
ZEND_API void zend_vm_set_opcode_handler(zend_opop)
{
    
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]];
这条语句前面打印出索引值:

[复制到剪切板]
CODE:
int index;
index opcode 25 zend_vm_decode[op->op1.op_type] * zend_vm_decode[op->op2.op_type];
printf("zend_opcode_handlers_index:%d\n"index); ;

然后再重新 make && make install
编写一个test.php文件:

[复制到剪切板]
CODE:
<?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
再找到这个函数:

[复制到剪切板]
CODE:
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->op2value, (0?IS_TMP_VAR:IS_CONST), EX(TsTSRMLS_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,然后写个函数来智能处理:

[复制到剪切板]
CODE:
#include<stdio.h>
#include<stdlib.h>
int main(int cchar *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(
!= 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,