PHP Opcode内核实现 - [ PHP内核学习 ]

catalogue

1. Opcode简介
2. PHP中的Opcode
3. opcode翻译执行(即时解释执行)

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/?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

Copyright (c) 2016 Little5ann All rights reserved

时间: 2024-10-13 21:13:20

PHP Opcode内核实现 - [ PHP内核学习 ]的相关文章

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上)

LINUX内核分析第四周学习总结——扒开应用系统的三层皮(上) 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)用户态.内核态和中断处理过程 (二)系统调用概述 系统调用概述和系统调用的三层皮 (三)使用库函数API和C代码中嵌入汇编代码触发同一个系统调用 使用库函数API获取系统当前时间 C代码中嵌入汇编代码的方法(复习) 使用C代码中嵌入汇编代码触发系统调

LINUX内核分析第一周学习总结——计算机是如何工作的

LINUX内核分析第一周学习总结——计算机是如何工作的 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.汇编代码的工作过程 1.实验过程 int a(int x) { return x + 2; } int b(int x) { return a(x); } int main(void) { return b(5) + 1; } 汇编代码如下: 2.代码分析 二.计算机工作的

Linux内核project导论——linux学习和职业曲线(刚開始学习的人,中级,高级都可參考)

Linux世界介绍 给自己定级 门外汉: 不会安装操作系统 不会用虚拟机(安装和使用) 入门级: 熟悉常见的发行版,甚至装过而且能用一些特殊发行版(比如kali)做过一些简单的图形界面的使用. 会一些最基础的命令(比如cd.ps.top.ls.ifconfig等这个级别的) 基础级: 能够使用一些常见的命令(touch.tail.date.find.du.fdisk.less.pidof等这个级别的命令) 图形界面操作的比較熟练.而且能够相应一部分的后台命令. 知道一些经常使用的配置文件的作用(

linux内核设计与实现学习笔记-模块

模块 1.概念:  如果让LINUX Kernel单独运行在一个保护区域,那么LINUX Kernel就成为了“单内核”.    LINUX Kernel是组件模式的,所谓组件模式是指:LINUX Kernel在运行时,允许“代码”动态的插入或者移出Kernel.    所谓模块是指:相关的一些子程序,数据.入口点和出口点共同组合成的一个单一的二进制映像,也就是一个可装载的Kernel目标文件.    模块的支持,使得系统可以拥有一个最小的内核映像,并且通过模块的方式支持一些可选的特征和驱动程序

《信息安全系统设计基础+Linux 内核分析》第一次学习总结

<信息安全系统设计基础+Linux 内核分析>第一次学习总结 教材学习内容总结 学习了<庖丁解牛>的第一章.知道的概念有: 存储程序计算机 = 冯诺依曼计算机,主要思想是:将程序存放在计算机存储器中,然后按存储器中的程序的首地址来执行程序的第一条指令,接下来就是一步一步按照程序中的编写好的指令来一步一步执行,直至程序结束. 冯诺依曼体系结构的要点如下图.底层是:RAM,ROM,运算器(ALU),控制器,寄存器. 由图可知:寄存器是在CPU中的,而RAM,ROM不是在CPU中的,它们

Linux内核巨页代码学习

巨页的实现,涉及到两个模块:hugetlb和hugetlbfs. hugetlb相当于是huge page页面管理者,hugetlbfs则用于向用户提供一套基于文件系统的巨页使用界面,其下层功能的实现,则依赖于hugetlb. 1. hugetlb模块 struct hstate hstates[HUGE_MAX_HSTATE]; 上面定义了一个hstate数组. 每一个hstate相当于一个huge_page池.不同的hstate,其page size是不一样的,例如2M的,1G的等等. ma

【读书笔记】《Linux内核设计与实现》内核同步介绍&内核同步方法

简要做个笔记,以备忘. 需同步的原因是,我们并发访问了共享资源.我们将访问或操作共享资源的代码段称"临界区",如果两个执行线程处于同一临界区中同时执行,称"竞争条件".这里术语执行线程指任何正在执行的代码实例,如一个在内核执行的进程.一个中断处理程序或一个内核线程. 举个简单例子,i++操作.该操作可以转换为下面的机器指令序列: 1.得到当前变量i的值,并保存到一个寄存器. 2.将寄存器的值加1. 3.将i的新值写回到内存中. 当两个线程同时进入这个临界区,若i初值

零代价修复海量服务器的内核缺陷——UCloud内核热补丁技术揭秘

下述为UCloud资深工程师邱模炯在InfoQ架构师峰会上的演讲——<UCloud云平台的内核实践>中非常受关注的内核热补丁技术的一部分.给大家揭开了UCloud云平台内核技术的神秘面纱. 如何零代价修复海量服务器的Linux内核缺陷? 对于一个拥有成千上万台服务器的公司,Linux内核缺陷导致的死机屡见不鲜.让工程师们纠结的是,到底要不要通过给服务器升级内核来修复缺陷?升级意味者服务器重启.业务中断以及繁重的准备工作:不升级则担心服务器死机,同样造成业务中断和繁重的善后工作. 而在今天的云计

Linux 2.6 内核阅读笔记 内核同步

2014年7月26日 内核抢占和内核控制路径的设计 内核抢占的一种定义:如果进程正在内核态执行内核函数时,允许发生内核切换(就是被替换的进程是内核函数所在进程),这个内核就是抢占的. linux内核提供了内核抢占的开启和关闭功能,在current_thread_info的preempt_count字段大于0时,内核就是不能抢占的.可以通过preempt_disable和preempt_enable来增加这个字段来控制这个开关. 中断和软中断和抢占的控制主要使用到current_thread_in

Windows内核函数(3) - 内核模式下的注册表操作

Windows内核函数(3) - 内核模式下的注册表操作 2010-12-13 13:37:16|  分类: 驱动编程 |  标签:status  hkey  ulsize  注册  kdprint  |举报|字号 订阅 注册表里的几个概念: 1.       创建关闭注册表项 NTSTATUS   ZwCreateKey(    OUT PHANDLE  KeyHandle,    IN ACCESS_MASK  DesiredAccess, //访问权限,一般为KEY_ALL_ACCLESS