源码版本:php-7.1.0
电脑环境:Deepin15.11
GDB版本:8.3.1
GCC版本:6.3.0
PHP的zval数据结构
PHP中的所有变量,都会保存在一个zval结构体中。
typedef struct _zval_struct zval;
具体的结构体内容为:
struct _zval_struct {
zend_value value;
union {
struct {
ZEND_ENDIAN_LOHI_4( //这里是用来区分大小端的
zend_uchar type, /* type是用于标注变量的类型 */
...
} v;
uint32_t type_info;
} u1;
union {
...
} u2;
};
_zval_struct结构体包含三个内容,value u1和u2。
value 变量包含的具体数据,u1用来区分变量的类型,u2是一个辅助工具。
其中,u1的type一共有10个值,对应PHP中的几种数据类型。
#define IS_UNDEF 0 //undefined
#define IS_NULL 1 //null
#define IS_FALSE 2 //false
#define IS_TRUE 3 //true
#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 //引用
zval中的value内容是这样的一个结构体,除long类型和double类型之外,都是通过指针的方式来实现的。
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
...
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
指针类型的zend_string、zend_array、zend_object均有结构体的定义,以zend_string为例:
struct _zend_string {
zend_refcounted_h gc; // 引用计数
zend_ulong h; /* hash value */
size_t len;
char val[1]; //可变长度的字符串内容
};
通过GDB深入理解PHP的数据结构
当一个变量被分配的时候,首先会初始化一个空的zval,u1的type为IS_UNDEF。然后在进行语法解析的时候,根据值来判断变量的类型,更改zval.u1.type为对应类型。同时将变量值赋值给value中的对应字段。
我们可以通过一段PHP代码来操作一下PHP的zval类型。
<?php
$a=1;
echo $a;
通过gdb来启动PHP,然后在echo处打下断点
(gdb) b ZEND_ECHO_SPEC_CV_HANDLER
Breakpoint 1 at 0x5bdf2f: file /program/php-7.1.0/Zend/zend_vm_execute.h, line 34640.
运行 php文件
(gdb) r echo.php
Starting program: /program/bin/php/php7/bin/php echo.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER () at /program/php-7.1.0/Zend/zend_vm_execute.h:34640
34640 SAVE_OPLINE();
继续执行下一步,取出当前的变量
(gdb) n
34641 z = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);
(gdb)
34643 if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p z
$1 = (zval *) 0x7ffff3814080
(gdb) p *z
$2 = {value = {lval = 1, dval = 4.9406564584124654e-324, counted = 0x1, str = 0x1, arr = 0x1, obj = 0x1, res = 0x1, ref = 0x1, ast = 0x1, zv = 0x1, ptr = 0x1, ce = 0x1, func = 0x1, ww = {w1 = 1, w2 = 0}}, u1 = {
v = {type = 4 '\004', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 4}, u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0,
access_flags = 0, property_guard = 0}}
可以看到当前的变量的u1.type为4,并且其value.lval为1。
(gdb) n
34650 zend_string *str = _zval_get_string_func(z); //这里是将内容强转为字符串
(gdb)
34652 if (ZSTR_LEN(str) != 0) {
(gdb) p str
$3 = (zend_string *) 0x7ffff3802d40
(gdb) p *str
$4 = {gc = {refcount = 1, u = {v = {type = 6 '\006', flags = 0 '\000', gc_info = 0}, type_info = 6}}, h = 0, len = 1, val = "1"}
最终 $4的结果,对应的正是zend_string的结构体。
网友评论