typedef int (ZEND_FASTCALL *opcode_handler_t) (ZEND_OPCODE_HANDLER_ARGS);
这个函数指针为op定义了执行方式,每一种opcode字段都对应一个种类的handler,比如如果$a = 1;这样的代码生成的op,操作数为const和cv,最后就能确定handler为函数ZEND_ASSIGN_SPEC_CV_CONST_HANDLER
/Zend/zend_vm_execute.h
void zend_init_opcodes_handlers(void) { static const opcode_handler_t labels[] = { .. ZEND_ASSIGN_SPEC_CV_CONST_HANDLER, .. } }
0x4: opcpde操作数znode
操作数字段是_zend_op类型中比较重要的部分了,其中op1,op2,result三个操作数定义为znode类型
\php-5.6.17\Zend\zend_compile.h
typedef struct _znode { /* used only during compilation */ /* 这个int类型的字段定义znode操作数的类型 #define IS_CONST (1<<0) //表示常量,例如$a = 123; $b = "hello";这些代码生成OP后,123和"hello"都是以常量类型操作数存在 #define IS_TMP_VAR (1<<1) //表示临时变量,临时变量一般在前面加~来表示,这是一些OP执行过程中需要用到的中间变量,例如初始化一个数组的时候,就需要一个临时变量来暂时存储数组zval,然后将数组赋值给变量 #define IS_VAR (1<<2) //一般意义上的变量,以$开发表示 #define IS_UNUSED (1<<3) // Unused variable #define IS_CV (1<<4) // Compiled variable,这种类型的操作数比较重要,此类型是在PHP后来的版本中(大概5.1)中才出现,CV的意思是compiled variable,即编译后的变量,变量都是保存在一个符号表中,这个符号表是一个哈希表,如果每次读写变量的时候都需要到哈希表中去检索,会对效率有一定的影响,因此在执行上下文环境中,会将一些编译期间生成的变量缓存起来。此类型操作数一般以!开头表示,比如变量$a=123;$b="hello"这段代码,$a和$b对应的操作数可能就是!0和!1, 0和1相当于一个索引号,通过索引号从缓存中取得相应的值 */ int op_type; /* 此字段为一个联合体,根据op_type的不同,u取不同的值 1. op_type=IS_CONST的时候,u中的constant保存的就是操作数对应的zval结构 2. 例如$a=123时,123这个操作数中,u中的constant是一个IS_LONG类型的zval,其值lval为123 */ union { znode_op op; zval constant; /* replaced by literal/zv */ zend_op_array *op_array; zend_ast *ast; } u; zend_uint EA; /* extended attributes */ } znode;
0x5: opcode编译后数组op_array
在zend_do_print函数中的第一行,我们注意到下面这行代码
zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
PHP脚本代码被编译后产生的opcode保存在op_array中,其内部存储的结构如下
\php-5.6.17\Zend\zend_compile.h