当前位置: 移动技术网 > IT编程>开发语言>PHP > PHP内核学习教程之php opcode内核实现

PHP内核学习教程之php opcode内核实现

2017年12月12日  | 移动技术网IT编程  | 我要评论

素衣白马指天下txt,f1西班牙正赛,双生水莽

opcode是计算机指令中的一部分,用于指定要执行的操作, 指令的格式和规范由处理器的指令规范指定。 除了指令本身以外通常还有指令所需要的操作数,可能有的指令不需要显式的操作数。 这些操作数可能是寄存器中的值,堆栈中的值,某块内存的值或者io端口中的值等等。

通常opcode还有另一种称谓:字节码(byte codes)。 例如java虚拟机(jvm),.net的通用中间语言(cil: common intermeditate language)等等。

1. opcode简介

opcode是计算机指令中的一部分,用于指定要执行的操作, 指令的格式和规范由处理器的指令规范指定。 除了指令本身以外通常还有指令所需要的操作数,可能有的指令不需要显式的操作数。 这些操作数可能是寄存器中的值,堆栈中的值,某块内存的值或者io端口中的值等等

通常opcode还有另一种称谓: 字节码(byte codes)。 例如java虚拟机(jvm),.net的通用中间语言(cil: common intermeditate language)等等

php中的opcode则属于前面介绍中的后着,php是构建在zend虚拟机(zend vm)之上的。php的opcode就是zend虚拟机中的指令(基于zend的中间代码)

relevant link:


2. php中的opcode

0x1: 数据结构

在php实现内部,opcode由如下的结构体表示

\php-5.6.17\zend\zend_compile.h

struct _zend_op 
{
opcode_handler_t handler; // 执行该opcode时调用的处理函数
znode_op op1; // opcode所操作的操作数
znode_op op2; // opcode所操作的操作数
znode_op result;
ulong extended_value;
uint lineno;
zend_uchar opcode; // opcode代码
zend_uchar op1_type;
zend_uchar op2_type;
zend_uchar result_type;
}; 

和cpu的指令类似,有一个标示指令的opcode字段,以及这个opcode所操作的操作数,php不像汇编那么底层, 在脚本实际执行的时候可能还需要其他更多的信息,extended_value字段就保存了这类信息, 其中的result域则是保存该指令执行完成后的结果

例如如下代码是在编译器遇到print语句的时候进行编译的函数

\php-5.6.17\zend\zend_compile.c

void zend_do_print(znode *result, const znode *arg tsrmls_dc) /* {{{ */
{ 
//新创建一条zend_op 
zend_op *opline = get_next_op(cg(active_op_array) tsrmls_cc);

//将新建的zend_op的返回值类型设置为临时变量(is_tmp_var),因为print中的内存仅仅为了临时输出,并不需要保存
opline->result_type = is_tmp_var;
//为临时变量申请空间
opline->result.var = get_temporary_variable(cg(active_op_array));
//指定opcode为zend_print
opline->opcode = zend_print;
//将传递进来的参数赋值给这条opcode的第一个操作数
set_node(opline->op1, arg);
set_unused(opline->op2);
get_node(result, opline->result);
}

0x2: opcode类型: zend_op->zend_uchar opcode

比对汇编语言的概念,每个opcode都对应于一个类型,表明该opcpde的"操作指令",opcode的类型为zend_uchar,zend_uchar实际上就是unsigned char,此字段保存的整形值即为op的编号,用来区分不同的op类型,opcode的可取值都被定义成了宏

/zend/zend_vm_opcodes.h

#define zend_nop 0
#define zend_add 1
#define zend_sub 2
#define zend_mul 3
#define zend_div 4
#define zend_mod 5
#define zend_sl 6
#define zend_sr 7
#define zend_concat 8
#define zend_bw_or 9
#define zend_bw_and 10
#define zend_bw_xor 11
#define zend_bw_not 12
#define zend_bool_not 13
#define zend_bool_xor 14
#define zend_is_identical 15
#define zend_is_not_identical 16
#define zend_is_equal 17
#define zend_is_not_equal 18
#define zend_is_smaller 19
#define zend_is_smaller_or_equal 20
#define zend_cast 21
#define zend_qm_assign 22
#define zend_assign_add 23
#define zend_assign_sub 24
#define zend_assign_mul 25
#define zend_assign_div 26
#define zend_assign_mod 27
#define zend_assign_sl 28
#define zend_assign_sr 29
#define zend_assign_concat 30
#define zend_assign_bw_or 31
#define zend_assign_bw_and 32
#define zend_assign_bw_xor 33
#define zend_pre_inc 34
#define zend_pre_dec 35
#define zend_post_inc 36
#define zend_post_dec 37
#define zend_assign 38
#define zend_assign_ref 39
#define zend_echo 40
#define zend_print 41
#define zend_jmp 42
#define zend_jmpz 43
#define zend_jmpnz 44
#define zend_jmpznz 45
#define zend_jmpz_ex 46
#define zend_jmpnz_ex 47
#define zend_case 48
#define zend_switch_free 49
#define zend_brk 50
#define zend_cont 51
#define zend_bool 52
#define zend_init_string 53
#define zend_add_char 54
#define zend_add_string 55
#define zend_add_var 56
#define zend_begin_silence 57
#define zend_end_silence 58
#define zend_init_fcall_by_name 59
#define zend_do_fcall 60
#define zend_do_fcall_by_name 61
#define zend_return 62
#define zend_recv 63
#define zend_recv_init 64
#define zend_send_val 65
#define zend_send_var 66
#define zend_send_ref 67
#define zend_new 68
#define zend_init_ns_fcall_by_name 69
#define zend_free 70
#define zend_init_array 71
#define zend_add_array_element 72
#define zend_include_or_eval 73
#define zend_unset_var 74
#define zend_unset_dim 75
#define zend_unset_obj 76
#define zend_fe_reset 77
#define zend_fe_fetch 78
#define zend_exit 79
#define zend_fetch_r 80
#define zend_fetch_dim_r 81
#define zend_fetch_obj_r 82
#define zend_fetch_w 83
#define zend_fetch_dim_w 84
#define zend_fetch_obj_w 85
#define zend_fetch_rw 86
#define zend_fetch_dim_rw 87
#define zend_fetch_obj_rw 88
#define zend_fetch_is 89
#define zend_fetch_dim_is 90
#define zend_fetch_obj_is 91
#define zend_fetch_func_arg 92
#define zend_fetch_dim_func_arg 93
#define zend_fetch_obj_func_arg 94
#define zend_fetch_unset 95
#define zend_fetch_dim_unset 96
#define zend_fetch_obj_unset 97
#define zend_fetch_dim_tmp_var 98
#define zend_fetch_constant 99
#define zend_goto 100
#define zend_ext_stmt 101
#define zend_ext_fcall_begin 102
#define zend_ext_fcall_end 103
#define zend_ext_nop 104
#define zend_ticks 105
#define zend_send_var_no_ref 106
#define zend_catch 107
#define zend_throw 108
#define zend_fetch_class 109
#define zend_clone 110
#define zend_return_by_ref 111
#define zend_init_method_call 112
#define zend_init_static_method_call 113
#define zend_isset_isempty_var 114
#define zend_isset_isempty_dim_obj 115
#define zend_pre_inc_obj 132
#define zend_pre_dec_obj 133
#define zend_post_inc_obj 134
#define zend_post_dec_obj 135
#define zend_assign_obj 136
#define zend_instanceof 138
#define zend_declare_class 139
#define zend_declare_inherited_class 140
#define zend_declare_function 141
#define zend_raise_abstract_error 142
#define zend_declare_const 143
#define zend_add_interface 144
#define zend_declare_inherited_class_delayed 145
#define zend_verify_abstract_class 146
#define zend_assign_dim 147
#define zend_isset_isempty_prop_obj 148
#define zend_handle_exception 149
#define zend_user_opcode 150
#define zend_jmp_set 152
#define zend_declare_lambda_function 153
#define zend_add_trait 154
#define zend_bind_traits 155
#define zend_separate 156
#define zend_qm_assign_var 157
#define zend_jmp_set_var 158
#define zend_discard_exception 159
#define zend_yield 160
#define zend_generator_return 161
#define zend_fast_call 162
#define zend_fast_ret 163
#define zend_recv_variadic 164
#define zend_send_unpack 165
#define zend_pow 166
#define zend_assign_pow 167 

0x3: opcode执行句柄: zend_op->handler

op的执行句柄,其类型为opcode_handler_t

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

struct _zend_op_array 
{
/* common elements */
zend_uchar type;
const char *function_name; // 如果是用户定义的函数则,这里将保存函数的名字
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
/* end of common elements */
zend_uint *refcount;
zend_op *opcodes; // opcode数组
zend_uint last;
zend_compiled_variable *vars;
int last_var;
zend_uint t;
zend_uint nested_calls;
zend_uint used_stack;
zend_brk_cont_element *brk_cont_array;
int last_brk_cont;
zend_try_catch_element *try_catch_array;
int last_try_catch;
zend_bool has_finally_block;
/* static variables support */
hashtable *static_variables;
zend_uint this_var;
const char *filename;
zend_uint line_start;
zend_uint line_end;
const char *doc_comment;
zend_uint doc_comment_len;
zend_uint early_binding; /* the linked list of delayed declarations */
zend_literal *literals;
int last_literal;
void **run_time_cache;
int last_cache_slot;
void *reserved[zend_max_reserved_resources];
}; 

整个php脚本代码被编译后的opcodes保存在这里,在执行的时候由下面的execute函数执行

zend_api void execute(zend_op_array *op_array tsrmls_dc)
{
// ... 循环执行op_array中的opcode或者执行其他op_array中的opcode
}

每条opcode都有一个opcode_handler_t的函数指针字段,用于执行该opcode,php有三种方式来进行opcode的处理

1. call: php默认使用call的方式,也就是函数调用的方式
2. switch: 由于opcode执行是每个php程序频繁需要进行的操作,可以使用switch或者goto的方式来分发
3. goto: 通常goto的效率相对会高一些,不过效率是否提高依赖于不同的cpu
实际上我们会发现,在/zend/zend_language_parser.c中就是zend的opcode翻译解释执行过程,其中包含了call、switch、goto三种opcode执行方式

这就是php为什么称之为解释型语言的内核原理,php在完成lex词法解析后,在语法解析即生成产生式的时候,直接通过call、switch、goto的方式调用zend api进行即使解释执行

relevant link:

http://www.nowamagic.net/librarys/veda/detail/1325
http://php.net/manual/zh/internals2.opcodes.list.php
http://www.nowamagic.net/librarys/veda/detail/1543
http://www.nowamagic.net/librarys/veda/detail/1324
http://www.nowamagic.net/librarys/veda/detail/1543 
http://www.laruence.com/2008/06/18/221.html
http://www.php-internals.com/book/?p=chapt02/02-03-02-opcode 

3. opcode翻译执行(即时解释执行)

relevant link:

http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler

以上所述本文给大家介绍的php内核学习教程之php opcode内核实现的相关知识,希望对大家有所帮助。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网