当前位置: 首页 > 后端技术 > PHP

【PHP7源码学习】抽象语法树编译过程验证

时间:2023-03-29 15:02:03 PHP

grape阅读前请查看【PHP源码学习】2019-03-22AST遍历了解基本概念。本文针对$a=1进行gdb实战调试,验证【PHP源码学习】2019-03-22AST遍历中的一些参数。实验代码:kind:可以看到原来我们接下来要做的就是上图中的函数,那么这些函数是如何工作的呢?我们继续,首先,进入zend_delayed_compile_begin():这个函数的作用是取栈顶,函数结束后赋值给offest,那么我们看看这个offest是什么?等号左边$a的编译则进入$a的处理函数:这里记录$a处理过程中调用的函数:zend_delayed_compile_var(result=0x7fffffffa580,ast=0x7ffff5e7f060,type=1)zend_compile_simple_var(结果=0x7fffffffa580,ast=0x7ffff5e7f060,类型=1,延迟=1)zend_try_compile_cv(结果=0x7fffffffa580,ast=0x7ffff5e7f060)lookup_cv(op_array=0x7ffff5e75460,name=0x7ffff5e5e4e0)lookup_cv_cv_cv_cv函数函数函数的作用是/{整数i=0;zend_ulonghash_value=zend_string_hash_val(name);while(ilast_var){if(ZSTR_VAL(op_array->vars[i])==ZSTR_VAL(name)||(ZSTR_H(op_array->vars[i])==hash_value&&ZSTR_LEN(op_array->vars[i])==ZSTR_LEN(名称)&&memcmp(ZSTR_VAL(op_array->vars[i]),ZSTR_VAL(名称),ZSTR_LEN(名称))==0)){zend_string_release(名称);返回(int)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL,i);我++;}i=op_array->last_var;op_array->last_var++;if(op_array->last_var>CG(context).vars_size){CG(context).vars_size+=16;/*FIXME*/op_array->vars=errealloc(op_array->vars,CG(context).vars_大小*sizeof(zend_string*));}op_array->vars[i]=zend_new_interned_string(name);return(int)(zend_intptr_t)ZEND_CALL_VAR_NUM(NULL,i);}我们发现lookup_cv()函数返回的是一个int类型的地址,是sizeof(zval)的整数倍,通过它可以得到每个变量的偏移量(80+16*i),i是变量的编号,指定运行时Shift时相对于zend_execute_data在栈上的偏移量,从而方便将变量$a存入栈中。而$a也存储在zend_op_array的vars数组中,所以如果后面要用到$a,直接去zend_op_array的vars数组中找,如果存在则直接使用前面的数i,如果不存在然后按顺序分配一个数字,然后插入zend_op_array的vars数组,节省分配数字的时间。另外,在函数zend_try_compile_cv中给result赋值,然后我们打印result的值:我们发现op_type=16(IS_CV),u.op.var=80这里总结一下核心函数$a的过程lookupcv,在lookupcv中,我们将变量存储在op_array->vars中,并返回一个表示偏移量的int整数。然后将op_type和u.op.var分配给znode*resultinzend_try_compile_cv。具体编译示例如下图所示:至此,左子节点$a结束。编译等号右边的1接下来我们来处理右子树,gdb中所示:右子树的处理比较简单,其调用函数为:zend_compile_exprZVAL_COPY(z,v)关键函数为宏ZVAL_COPY,首先我们看这个宏在gdb中是到达的:然后我们继续分析这个宏的功能,老规矩,先贴源码:#defineZVAL_COPY(z,v)\do{\zval*_z1=(z);\constzval*_z2=(v);\zend_refcounted*_gc=Z_COUNTED_P(_z2);\uint32_t_t=Z_TYPE_INFO_P(_z2);\ZVAL_COPY_VALUE_EX(_z1,_z2,_gc,_t);\if((_t&(IS_TYPE_REFCOUNTED<op_type=IS_CONST,op_type赋值:转载result为最终结果:至此$a和1分别存储在两个znode中。下面开始生成指令。根据assign和op1,op2生成opline,我们进入zend_emit_op函数,该函数会生成opcode:首先,我们看一下这个函数执行的所有指令:其中set_node出现的比较频繁,我们来看看它有什么使用:#defineSET_NODE(target,src)do{\target##_type=(src)->op_type;\if((src)->op_type==IS_CONST){\target.constant=zend_add_literal(CG(active_op_array),&(src)->u.constant);\}else{\target=(src)->u.op;\}\}while(0)从代码中可以看出,对于操作数1,在编译过程中,将临时结构体znode传递给了zend_op。对于操作数2,因为它是一个常量(IS_CONST),所以会调用zend_add_literal将其插入到op_array->literals中。接下来,我们设置返回值。这时候我们会调用函数zend_make_var_result:staticinlinevoidzend_make_var_result(znode*result,zend_op*opline)/*{{{*/{opline->result_type=IS_VAR;//返回值类型设置为IS_VARopline->result.var=get_temporary_variable(CG(active_op_array));//这是返回值的个数,对应T位置GET_NODE(result,opline->result);}staticuint32_tget_temporary_variable(zend_op_array*op_array)/*{{{*/{return(uint32_t)op_array->T++;}返回值类型为IS_VAR,result.var为T的值,最后打印opline看看最终结果:下面我们给出对应的赋值操作指令图,如图:可以看出从图中可以看出生成的opline中的opcode等于38;op1的类型是IS_CV,op1.var对应vm_stack上的offset;op2的类型是IS_CONST,op2。constant对应op_array中literals数组的下标;result的类型为IS_VAR,result.var对应T的值;此时,handler的值为空。至此,编译阶段告一段落。参考资料:【PHP7源码解析】Zend虚拟机PHP7源码研究