php语言

php内核分析之zval

时间:2024-09-07 14:45:34 php语言 我要投稿
  • 相关推荐

php内核分析之zval

  学习PHP的同学对php内核方面的知识也许了解的还不是很清楚,那么下面小编就php内核之zval展开分析,希望对大家有用,更多内容请关注应届毕业生网!

  这里阅读的php版本为PHP-7.1.0 RC3,阅读代码的平台为linux

  实际上,从这个函数开始,就已经进入到了zend引擎的范围了。

  zend_eval_string_ex(exec_direct, NULL, "Command line code", 1)

  实际上是调用Zend/zend_execute_API.c

  zend_eval_stringl_ex(str, strlen(str), retval_ptr, string_name, handle_exceptions);

  再进去是调用

  result = zend_eval_stringl(str, str_len, retval_ptr, string_name);

  这里的retval_ptr为NULL,string_name为"Command line code", str为"echo 12;"

  zend_eval_stringl

  其实这个函数主流程并不复杂。简化下来就如下

  ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char *string_name) /* {{{ */

  {

  ...

  new_op_array = zend_compile_string(&pv, string_name); // 这个是把php代码编译成为opcode的过程

  ...

  zend_execute(new_op_array, &local_retval); // 这个是具体的.执行过程,执行opcode,把结果存储到local_retval中

  ...

  retval = SUCCESS;

  return retval;

  }

  先把php编译为opcode,然后执行这个opcode。只是这个函数有一些关键的结构需要理一下。

  zval

  我们会看到

  zval local_retval;

  这样的变量,然后会对这个变量进行如下操作:

  ZVAL_UNDEF(&local_retval);

  ZVAL_NULL(z)

  ZVAL_FALSE(z)

  ZVAL_TRUE(z)

  ZVAL_BOOL(z, b)

  ZVAL_LONG(z, l)

  ZVAL_DOUBLE(z, d)

  ZVAL_STR(z, s)

  ZVAL_INTERNED_STR(z, s)

  ZVAL_NEW_STR(z, s)

  ZVAL_STR_COPY(z, s)

  ZVAL_ARR(z, a)

  ZVAL_NEW_ARR(z)

  ZVAL_NEW_PERSISTENT_ARR(z)

  ZVAL_OBJ(z, o)

  ZVAL_RES(z, r)

  ZVAL_NEW_RES(z, h, p, t)

  ZVAL_NEW_PERSISTENT_RES(z, h, p, t)

  ZVAL_REF(z, r)

  ZVAL_NEW_EMPTY_REF(z)

  ZVAL_NEW_REF(z, r)

  ZVAL_NEW_PERSISTENT_REF(z, r)

  ZVAL_NEW_AST(z, a)

  ZVAL_INDIRECT(z, v)

  ZVAL_PTR(z, p)

  ZVAL_FUNC(z, f)

  ZVAL_CE(z, c)

  ZVAL_ERROR(z)

  php是一个弱类型的语言,它可以用一个$var来代表string,int,array,object等。这个就是归功于zval_struct结构

  // zval的结构

  struct _zval_struct {

  zend_value value; // 存储具体值,它的结构根据类型不同而不同

  union {

  struct {

  ZEND_ENDIAN_LOHI_4(

  zend_uchar type, // 这个位置标记了这个val是什么类型的(IS_STRING/IS_INT)

  zend_uchar type_flags, // 这个位置标记了这个val是什么属性 (IS_CALLABLE等)

  zend_uchar const_flags, // 常量的一些属性 (IS_CONSTANT_CLASS)

  zend_uchar reserved) // 保留的一些字段

  } v;

  uint32_t type_info; // 类型的一些额外信息

  } u1; // 保存类型的一些关键信息

  union {

  uint32_t next; // 如果是在hash链表中,这个指针代表下一个元素的index

  uint32_t cache_slot; /* literal cache slot */

  uint32_t lineno; /* line number (for ast nodes) */

  uint32_t num_args; /* arguments number for EX(This) */

  uint32_t fe_pos; /* foreach position */

  uint32_t fe_iter_idx; /* foreach iterator index */

  uint32_t access_flags; /* class constant access flags */

  uint32_t property_guard; /* single property guard */

  } u2; // 一些附属字段

  };

  这个接口最重要的两个字段是 value,存储变量的值。另一个是u1.v.type 存储变量的`类型。这里,value也是一个结构

  typedef union _zend_value {

  zend_long lval; /* long value */

  double dval; /* double value */

  zend_refcounted *counted;

  zend_string *str; // string

  zend_array *arr; // array

  zend_object *obj; // object

  zend_resource *res; // resource

  zend_reference *ref; // 指针

  zend_ast_ref *ast; // ast指针

  zval *zv;

  void *ptr;

  zend_class_entry *ce; // class实体

  zend_function *func; // 函数实体

  struct {

  uint32_t w1;

  uint32_t w2;

  } ww;

  } zend_value;

  如果u1.v.type == IS_STRING, 那么value.str就是指向了zend_string结构。好了,php的垃圾回收是通过引用计数来进行的,这个引用计数的计数器就放在zval.value.counted里面。

  我们对zval设置的时候设置了一些宏来进行设置,比如:ZVAL_STRINGL是设置string,我们仔细看下调用堆栈:

  ZVAL_STRINGL(&pv, str, str_len); // 把pv设置为string类型,值为str

  这个函数就是把pv设置为zend_string类型

  // 带字符串长度的设置zend_sting类型的zval

  #define ZVAL_STRINGL(z, s, l) do { \

  ZVAL_NEW_STR(z, zend_string_init(s, l, 0)); \

  } while (0)

  注意到,这里使用了一个写法,do {} while(0) 来设置一个宏,这个是C里面比较好的写法,这样写,能保证宏中定义的东西在for,if,等各种流程语句中不会出现语法错误。不过其实我们学习代码的时候,可以忽略掉这个框框写法。

  zend_string_init(s, l, 0)

  ...

  // 从char* + 长度 + 是否是临时变量(persistent为0表示最迟这个申请的空间在请求结束的时候就进行释放),转变为zend_string*

  static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent)

  {

  zend_string *ret = zend_string_alloc(len, persistent); // 申请空间,申请的大小为zend_string结构大小(除了val)+ len + 1

  memcpy(ZSTR_VAL(ret), str, len);

  ZSTR_VAL(ret)[len] = '\0';

  return ret;

  }

  这个函数可以看的点有几个:

  persistent

  这个参数是用来代表申请的空间是不是“临时”的。这里说的临时是zend提供的一种内存管理器,相关请求数据只服务于单个请求,最迟会在请求结束的时候释放。

  临时内存申请对应的函数为:

  void *emalloc(size_t size)

  而永久内存申请对应的函数为:

  malloc

  zend_string_alloc

  static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent)

  {

  zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);

  GC_REFCOUNT(ret) = 1;

  GC_TYPE_INFO(ret) = IS_STRING | ((persistent ? IS_STR_PERSISTENT : 0) << 8);

  zend_string_forget_hash_val(ret);

  ZSTR_LEN(ret) = len;

  return ret;

  }

  我们先看看zend_string的结构:

  // 字符串

  struct _zend_string {

  zend_refcounted_h gc; // gc使用的被引用的次数

  zend_ulong h; // 如果这个字符串作为hashtable的key在查找时候需要重复计算它的hash值,所以保存一份在这里

  size_t len; // 字符串长度

  char val[1]; // 柔性数组,虽然我们定义了数组只有一个元素,但是在实际分配内存的时候,会分配足够的内存

  };

  _ZSTR_STRUCT_SIZE(len) gc+h+len的空间,最后给了val留了len+1的长度

  #define _ZSTR_STRUCT_SIZE(len) (_ZSTR_HEADER_SIZE + len + 1)

  ## GC_REFCOUNT(ret) = 1;

  #define GC_REFCOUNT(p) (p)->gc.refcount

  这里就看到一个结构zend_refcounted_h

  typedef struct _zend_refcounted_h {

  uint32_t refcount; // 真正的计数

  union {

  struct {

  ZEND_ENDIAN_LOHI_3(

  zend_uchar type, // 冗余了zval中的类型值

  zend_uchar flags, // used for strings & objects中有特定作用

  uint16_t gc_info) // 在GC缓冲区中的.索引位置

  } v;

  uint32_t type_info; // 冗余zval中的type_info

  } u; // 类型信息

  } zend_refcounted_h;

  回到我们的实例,我们调用的是

  zend_string_init(s, l, 0) // s=char*(echo 12;) l=8

  返回的zend_string实际值为:

  struct _zend_string {

  struct {

  uint32_t refcount; // 1

  union {

  struct {

  ZEND_ENDIAN_LOHI_3(

  zend_uchar type, // IS_STRING

  zend_uchar flags,

  uint16_t gc_info)

  } v;

  uint32_t type_info; //IS_STRING | 0 => IS_STRING

  } u;

  } gc;

  zend_ulong h; // 0

  size_t len; // 8

  char val[1]; // echo 12;\0

  };

  结合到zval里面,那么ZVAL_STRINGL(&pv, str, str_len);返回的zval为

  // zval的结构

  struct _zval_struct {

  union _zend_value {

  zend_long lval;

  double dval;

  zend_refcounted *counted;

  zend_string *str; // 指向到上面定义的那个zend_string中

  zend_array *arr;

  zend_object *obj;

  zend_resource *res;

  zend_reference *ref;

  zend_ast_ref *ast;

  zval *zv;

  void *ptr;

  zend_class_entry *ce;

  zend_function *func;

  struct {

  uint32_t w1;

  uint32_t w2;

  } ww;

  } value;

  union {

  struct {

  ZEND_ENDIAN_LOHI_4(

  zend_uchar type,

  zend_uchar type_flags,

  zend_uchar const_flags,

  zend_uchar reserved)

  } v;

  uint32_t type_info; // IS_STRING_EX

  } u1;

  union {

  uint32_t next;

  uint32_t cache_slot;

  uint32_t lineno;

  uint32_t num_args;

  uint32_t fe_pos;

  uint32_t fe_iter_idx;

  uint32_t access_flags;

  uint32_t property_guard;

  } u2;

  };

  这里,就对zval结构有初步了解了。

  另外建议记住几个常用的类型,后续调试的时候会很有用

  /* regular data types */

  #define IS_UNDEF 0

  #define IS_NULL 1

  #define IS_FALSE 2

  #define IS_TRUE 3

  #define IS_LONG 4

  #define IS_DOUBLE 5

  #define IS_STRING 6

  #define IS_ARRAY 7

  #define IS_OBJECT 8

  #define IS_RESOURCE 9

  #define IS_REFERENCE 10

  /* constant expressions */

  #define IS_CONSTANT 11

  #define IS_CONSTANT_AST 12

【php内核分析之zval】相关文章:

php内核分析之扩展10-03

php内核分析之sapi-module-struct10-17

php内核分析之ZTS和zend-try07-18

PHP递归效率分析08-25

PHP与ASP的分析对比10-27

分析PHP队列是什么10-05

php中引用的用法分析06-22

PHP页面漏洞分析的方法08-13

PHP面试之php自带的几个防止sql注入的函数06-14

PHP循环语句用法之—for循环08-08