php IR 之对ArrayFetchDim赋值翻译

php IR 之 $a[1]=111 翻译过程

发现自己看过php 虚拟机一些部分好多都忘了(,最近需要根据php AST 写一个自己的IR,来分析一些东西,于是看看原本的php 原本的Interpreter是怎么翻译的,找找感觉,以后可能也经常发一些有趣的php ast里面节点的翻译过程,以便自己忘记了看,也分享给大家.... 感觉近几年static analysis 会大火 0),很多人越来越关注PL了。不能掉队...

0x01 基本AST如下

                     +-----------------+         
                     | zend_ast_assign |         
                     +-----------------+         
                        /-    --\                
                    /---         -----\          
           +--------------+         +-----------+
           | zend_ast_dim |         |   111     |
           +--------------+         +-----------+
              /--- --\                           
       /-------       ---\                       
+------+               +-------+                 
|  $a  |               |   1   |                 
+------+               +-------+                 

注意const 和 cv 都是单纯的znode 基本操作数

0x02 将AST转换成IR

​ 过程中我探究过的问题:

  1. CG里面有一个delayed_oplines_stack ,没有细究在做个别节点翻译时会用到这个栈,最后把这个栈里面的内容都复制到真正的opline_array 里面,知道这里为什么吗?我想了一会为什么这样,我自己也用双栈在某些地方,我用双栈是为了部分计数,他这里其实是为了维护某些opcode的顺序:

    $b="m";
    $c="c";
    $a[$b.$c]=$b.$c;
    //看上面的例子,关注第三行
    //直觉上可以先翻译等式左边,拿到一个目标引用,而后再翻译等式右边得到一个值引用,这里我们假设这里没有delayed_oplines_stack这个,会是什么效果?
      //ASSIGN        		!0, 'm'       //1
      //ASSIGN        		!1, 'c'		 //2
      //CONCAT 		~5      !0, !1		 //3
      //ASSIGN_DIM          !2, ~5		 //4
      //CONCAT      ~7      !0, !1      //5
      //OP_DATA             111         //6
    
    //这里就出现问题了,第4条需要和第6应该要紧密结合在一起,语义出现问题了
    //所以这里在一开始就把ASSIGN_DIM先放到delayed_oplines_stack,等到等式两边翻译结束,在把它拿出来,最后加一个OP_DATA.
    //有点绕,可能现在知道,以后又忘了 ((
    
  2. 在翻译zend_ast_dim中,当结束翻译zend_ast_dim 会产生一条opcode 为ZEND_FETCH_DIM_W 三地址。(zend_delayed_compile_dim)

  3. ZEND_FETCH_DIM_W会变成ZEND_ASSIGN_DIM.

  4. 最后还有一条OP_DATA

完整IR:

ASSIGN_DIM 	!1, 1
OP_DATA    	111

0x03 如何确定每条IR的handler

这里我php默认用handler调度是 hybrid模式,hybrid模式是goto 和 switch的混合,hybird模式下每条IR的handler是具体的labels地址, 供goto使用。

  1. Hybrid模式的初始化,记录一下:

    void zend_vm_init(){
    	...
    	#if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)
    	zend_opcode_handler_funcs = labels;
    	zend_spec_handlers = specs;
    	execute_ex(NULL);
      //注意 这里执行了一个空参数的execute_ex
    }
    
    void execute_ex(zend_execute_data *ex){
      ...
      #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID)
    		if (UNEXPECTED(execute_data == NULL)) {//对应上前面的NULL为了这里的初始化	
          labels[]={
            ...
          }
          
          zend_opcode_handlers = (const void **) labels;
          zend_handlers_count = sizeof(labels) / sizeof(void*);
          memset(&hybrid_halt_op, 0, sizeof(hybrid_halt_op));
          hybrid_halt_op.handler = (void*)&&HYBRID_HALT_LABEL;
          goto HYBRID_HALT_LABEL; //直接退出
        }
    }
    
    
  2. 记录一下遗忘的handler映射寻址

    ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler(zend_op* op)
    {
    	...
    	op->handler = zend_opcode_handlers[zend_vm_get_opcode_handler_idx(zend_spec_handlers[opcode], op)];
    }
    
    //zend_opcode_handlers 已经初始化了(goto的labels),剩下的工作就是计算索引
    //zend_spec_handlers[opcode] 确定一个具体opcode handler相对位置,并且确定这个opcode 是几操作数敏感的 和 一些额外的属性
    //比如这里的zend_assign_dim => 736 | SPEC_RULE_OP1 | SPEC_RULE_OP2 | SPEC_RULE_OP_DATA
    
    uint32_t ZEND_FASTCALL zend_vm_get_opcode_handler_idx(uint32_t spec, const zend_op* op){
    	static const int zend_vm_decode[] = {
    		_UNUSED_CODE, /* 0 = IS_UNUSED  */
    		_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              */
    		_CV_CODE      /* 8 = IS_CV      */
    	};//0 1 2 4 8 做了一个小映射到0-4
    	uint32_t offset = 0;
    	if (spec & SPEC_RULE_OP1) offset = offset * 5 + zend_vm_decode[op->op1_type];
    	if (spec & SPEC_RULE_OP2) offset = offset * 5 + zend_vm_decode[op->op2_type]; // 5*5 笛卡尔积
      if (spec & SPEC_EXTRA_MASK) {
    		...
    		} else if (spec & SPEC_RULE_OP_DATA) { //这里zend_assign_dim 考虑后一个OP_DATA的属性来计算索引
    			offset = offset * 5 + zend_vm_decode[(op + 1)->op1_type];
    		}
      	....
    	}
    	return (spec & 0xffff) + offset; //opcode 相对位置 + offset 
    //done
    }
    
1 Like