搜索
首页后端开发php教程PHP内核学习教程之php opcode内核实现_php实例

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:

http://www.luocong.com/learningopcode/doc/1._%E4%BB%80%E4%B9%88%E6%98%AFOpCode%EF%BC%9F.htm

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/&#63;p=chapt02/02-03-02-opcode 

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

Relevant Link:

http://www.php-internals.com/book/&#63;p=chapt02/02-03-03-from-opcode-to-handler

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

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
如何防止会话固定攻击?如何防止会话固定攻击?Apr 28, 2025 am 12:25 AM

防止会话固定攻击的有效方法包括:1.在用户登录后重新生成会话ID;2.使用安全的会话ID生成算法;3.实施会话超时机制;4.使用HTTPS加密会话数据,这些措施能确保应用在面对会话固定攻击时坚不可摧。

您如何实施无会话身份验证?您如何实施无会话身份验证?Apr 28, 2025 am 12:24 AM

实现无会话身份验证可以通过使用JSONWebTokens(JWT)来实现,这是一种基于令牌的认证系统,所有的必要信息都存储在令牌中,无需服务器端会话存储。1)使用JWT生成和验证令牌,2)确保使用HTTPS防止令牌被截获,3)在客户端安全存储令牌,4)在服务器端验证令牌以防篡改,5)实现令牌撤销机制,如使用短期访问令牌和长期刷新令牌。

PHP会议有哪些常见的安全风险?PHP会议有哪些常见的安全风险?Apr 28, 2025 am 12:24 AM

PHP会话的安全风险主要包括会话劫持、会话固定、会话预测和会话中毒。1.会话劫持可以通过使用HTTPS和保护cookie来防范。2.会话固定可以通过在用户登录前重新生成会话ID来避免。3.会话预测需要确保会话ID的随机性和不可预测性。4.会话中毒可以通过对会话数据进行验证和过滤来预防。

您如何销毁PHP会议?您如何销毁PHP会议?Apr 28, 2025 am 12:16 AM

销毁PHP会话需要先启动会话,然后清除数据并销毁会话文件。1.使用session_start()启动会话。2.用session_unset()清除会话数据。3.最后用session_destroy()销毁会话文件,确保数据安全和资源释放。

如何更改PHP中的默认会话保存路径?如何更改PHP中的默认会话保存路径?Apr 28, 2025 am 12:12 AM

如何改变PHP的默认会话保存路径?可以通过以下步骤实现:在PHP脚本中使用session_save_path('/var/www/sessions');session_start();设置会话保存路径。在php.ini文件中设置session.save_path="/var/www/sessions"来全局改变会话保存路径。使用Memcached或Redis存储会话数据,如ini_set('session.save_handler','memcached');ini_set(

您如何修改PHP会话中存储的数据?您如何修改PHP会话中存储的数据?Apr 27, 2025 am 12:23 AM

tomodifyDataNaphPsession,startTheSessionWithSession_start(),然后使用$ _sessionToset,修改,orremovevariables.1)startThesession.2)setthesession.2)使用$ _session.3)setormodifysessessvariables.3)emovervariableswithunset()

举一个在PHP会话中存储数组的示例。举一个在PHP会话中存储数组的示例。Apr 27, 2025 am 12:20 AM

在PHP会话中可以存储数组。1.启动会话,使用session_start()。2.创建数组并存储在$_SESSION中。3.通过$_SESSION检索数组。4.优化会话数据以提升性能。

垃圾收集如何用于PHP会议?垃圾收集如何用于PHP会议?Apr 27, 2025 am 12:19 AM

PHP会话垃圾回收通过概率机制触发,清理过期会话数据。1)配置文件中设置触发概率和会话生命周期;2)可使用cron任务优化高负载应用;3)需平衡垃圾回收频率与性能,避免数据丢失。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器