PHP kernel decryption series: Execution Process of zend_execute, kernel zend_execute

Source: Internet
Author: User
Tags sapi zts

PHP kernel decryption series: Execution Process of zend_execute, kernel zend_execute

PHP kernel decryption series: Execution Process of zend_execute

The interpreter engine finally executes the op function zend_execute. In fact, zend_execute is a function pointer. During engine initialization, zend_execute points to execute by default, this execute is defined in {PHPSRC}/Zend/zend_vm_execute.h:

ZEND_API void execute(zend_op_array *op_array TSRMLS_DC)  {      zend_execute_data *execute_data;      zend_bool nested = 0;      zend_bool original_in_execution = EG(in_execution);          if (EG(exception)) {          return;      }        EG(in_execution) = 1;    zend_vm_enter:      /* Initialize execute_data */      execute_data = (zend_execute_data *)zend_vm_stack_alloc(          ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) +          ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)) +          ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable)) * op_array->T TSRMLS_CC);        EX(CVs) = (zval***)((char*)execute_data + ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)));      memset(EX(CVs), 0, sizeof(zval**) * op_array->last_var);      EX(Ts) = (temp_variable *)(((char*)EX(CVs)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)));      EX(fbc) = NULL;      EX(called_scope) = NULL;      EX(object) = NULL;      EX(old_error_reporting) = NULL;      EX(op_array) = op_array;      EX(symbol_table) = EG(active_symbol_table);      EX(prev_execute_data) = EG(current_execute_data);      EG(current_execute_data) = execute_data;      EX(nested) = nested;      nested = 1;        if (op_array->start_op) {          ZEND_VM_SET_OPCODE(op_array->start_op);      } else {          ZEND_VM_SET_OPCODE(op_array->opcodes);      }        if (op_array->this_var != -1 && EG(This)) {          Z_ADDREF_P(EG(This)); /* For $this pointer */          if (!EG(active_symbol_table)) {              EX(CVs)[op_array->this_var] = (zval**)EX(CVs) + (op_array->last_var + op_array->this_var);              *EX(CVs)[op_array->this_var] = EG(This);          } else {              if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), (void**)&EX(CVs)[op_array->this_var])==FAILURE) {                  Z_DELREF_P(EG(This));              }          }      }        EG(opline_ptr) = &EX(opline);        EX(function_state).function = (zend_function *) op_array;      EX(function_state).arguments = NULL;            while (1) {          int ret;  #ifdef ZEND_WIN32          if (EG(timed_out)) {              zend_timeout(0);          }  #endif            if ((ret = EX(opline)->handler(execute_data TSRMLS_CC)) > 0) {              switch (ret) {                  case 1:                      EG(in_execution) = original_in_execution;                      return;                  case 2:                      op_array = EG(active_op_array);                      goto zend_vm_enter;                  case 3:                      execute_data = EG(current_execute_data);                  default:                      break;              }          }        }      zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn't happen");  }


The parameter of this function is op_array, which is a pointer to zend_op_array. op_array is generated during compilation. It is necessary to introduce the zend_op_array type.
Reference Source: Http://www.lai18.com/content/425167.html
Introduction to zend_op_array this type is defined in {PHPSRC}/Zend/zend_compile.h:

struct _zend_op_array {      /* Common elements */      zend_uchar type;      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;      zend_bool pass_rest_by_reference;      unsigned char return_reference;      /* END of common elements */        zend_bool done_pass_two;        zend_uint *refcount;        zend_op *opcodes;      zend_uint last, size;        zend_compiled_variable *vars;      int last_var, size_var;        zend_uint T;        zend_brk_cont_element *brk_cont_array;      int last_brk_cont;      int current_brk_cont;        zend_try_catch_element *try_catch_array;      int last_try_catch;        /* static variables support */      HashTable *static_variables;        zend_op *start_op;      int backpatch_count;        zend_uint this_var;        char *filename;      zend_uint line_start;      zend_uint line_end;      char *doc_comment;      zend_uint doc_comment_len;      zend_uint early_binding; /* the linked list of delayed declarations */        void *reserved[ZEND_MAX_RESERVED_RESOURCES];  };    typedef struct _zend_op_array zend_op_array;


This structure is complex. At present, we only introduce the most basic fields.

1. type:

The op_array type must be noted first. After a PHP code is compiled, although a zend_op_array pointer is returned, there may be more than one zend_op_array structure, through some fields in this structure, such as function_name and num_args, you may find that the zend_op_array structure seems to have some contact with the function. Indeed, the User-Defined Function, the methods of user-defined classes are all in a zend_op_array structure. These zend_op_array structures are stored in some places during compilation. For example, the user-defined functions are saved in GLOBAL_FUNCTION_TABLE, this is a global function symbol table. You can retrieve the function body from this table by using the function name. So what is the zend_op_array pointer returned after compilation? In fact, the zend_op_array returned after compilation is an entry for execution. It can also be considered as the outermost layer, that is, op_array composed of Global Code not in any function body. However, the global code, user-defined functions, and user-defined methods all have the same type value: 2. The macro with the type value can be defined:

#define ZEND_INTERNAL_FUNCTION              1  #define ZEND_USER_FUNCTION                  2  #define ZEND_OVERLOADED_FUNCTION            3  #define ZEND_EVAL_CODE                      4  #define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5


We can see that the global code, user functions, and user Methods correspond to ZEND_USER_FUNCTION, which is also the most common type. ZEND_EVAL_CODE corresponds to the PHP code in the eval function, so we can think of it, the PHP code in eval function parameters will also be compiled into a separate zend_op_array.

2. function_name

If op_array is generated by a user-defined function or by method compilation, this field corresponds to the function name. If it is a global code or eval part of the code, this field is controlled.

3. opcodes

This field type is zend_op *, so this is a zend_op array, which stores the op generated during the compilation process. If you do not know zend_op, you can refer to the OPcode introduction in the previous article, this field is the most important part. zend_execute is to execute the op saved here.

Now we have some knowledge about the op_array parameter, so we can start to execute.

The execution process details the declaration of some basic variables at the beginning of the execute function. zend_execute_data * execute_data is the data structure of the execution period, after initialization, this variable will be passed to the handler function of each op as a parameter. During execution, op may change the content in execute_data at any time.

The jump label zend_vm_enter of Row 3 serves as the entry for virtual machine execution. When function calling is involved in op, it may jump to this to execute the function body.

16th rows to 19th actions execute_data allocate space

Rows 21st to 32nd initialize execute_data and save the on-site work. To save the on-site work, you need to save the data during the current running period when entering the function call, it can be imagined that the process scheduling in the operating system is restored after the function call is completed. when the process is called up, it needs to save the context environment such as registers, when the process is called in, it is retrieved and executed again.

From row 41st to row 51st, the $ this variable is added to the current dynamic symbol table, which is required only when the object method is called.

The while infinite loop starting from row 58th is to start executing opcodes in op_array. In row 66th, the handler of the currently executed op is called:

EX(opline)->handler(execute_data TSRMLS_CC))


Then, if the return value of handler is less than 0, the loop continues. If the return value is greater than 0, a switch structure is entered:

When the return value is 1: The execute function will return, and the execution will end.

When the return value is 2: op_array is reset and jumps to zend_vm_enter. This is generally a function call or code in the eval function execution. The op_array of the related function will be executed in the new context.

When the returned value is 3: The loop body continues to execute. Of course, before the execution continues, EX (opline) has moved one (multiple) bit backward ), that is, a new opline has been directed to, and the new opline

When other values are returned: the end loop. An error is returned. return is used to end the loop. That is, 1 is returned.

Specific values returned in the op handler are defined as macros, for example, defined in {PHPSRC}/Zend/zend_execute.c:

#define ZEND_VM_NEXT_OPCODE() /      CHECK_SYMBOL_TABLES() /      EX(opline)++; /      ZEND_VM_CONTINUE()    #define ZEND_VM_SET_OPCODE(new_op) /      CHECK_SYMBOL_TABLES() /      EX(opline) = new_op    #define ZEND_VM_JMP(new_op) /      CHECK_SYMBOL_TABLES() /      if (EXPECTED(!EG(exception))) { /          EX(opline) = new_op; /      } /      ZEND_VM_CONTINUE()    #define ZEND_VM_INC_OPCODE() /      EX(opline)++


And defined in {PHPSRC}/Zend/zend_vm_execute.c:

#define ZEND_VM_CONTINUE()   return 0  #define ZEND_VM_RETURN()     return 1  #define ZEND_VM_ENTER()      return 2  #define ZEND_VM_LEAVE()      return 3  #define ZEND_VM_DISPATCH(opcode, opline) return zend_vm_get_opcode_handler(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);


Brief Introduction

ZEND_VM_NEXT_OPCODE (): moves to the next op, returns 0, does not enter switch, and continues the loop (this is the most common)

ZEND_VM_SET_OPCODE (new_op): The current opline is set to new_op.

ZEND_VM_JMP (new_op): The current opline is set to new_op, 0 is returned, no switch is entered, and the loop continues

ZEND_VM_INC_OPCODE (): Only move to the next op

As mentioned above, the user-defined functions, class methods, and eval code are compiled into a separate op_array, so when performing function calls and other operations, it must involve switching between the op_array execution environment before the call and the op_array execution environment of the new function. In this section, we will call user-defined functions to introduce how the entire switchover process is implemented.

Before introducing this process, you must understand the relevant data structure of the execution environment. There are two data structures involved in the execution environment:

1. Global variable structure during execution

The related definitions are in {PHPSRC}/Zend/zend_globals_macros.h:

/* Executor */  #ifdef ZTS  # define EG(v) TSRMG(executor_globals_id, zend_executor_globals *, v)  #else  # define EG(v) (executor_globals.v)  extern ZEND_API zend_executor_globals executor_globals;  #endif


Here is a conditional compilation. ZTS indicates that the thread is enabled safely. to simplify it, we will introduce it in Non-thread security mode. The global variable in the execution period is executor_globals, and its type is zend_executor_globals, the definition of zend_executor_globals is defined in {PHPSRC}/Zend/zend_globals.h, which has a large structure. It contains various variables required during the entire execution period, no matter which op_array is being executed, all share this global variable. Some members in this structure may change during execution, for example, the currently executed op_array field active_op_array, the field active_symbol_table in the dynamic symbol table may be changed according to different op_array, and This pointer will be changed according to different object environments.

In addition, an EG macro is defined to obtain the field value in this variable. This macro is an encapsulation of thread security and non-thread security mode.

2. Execution data of each op_array

For each op_array, there will be some data during its own execution period. At the beginning of the execute function, we can see that under the zend_vm_enter jump tag, a local variable execute_data will be initially created, so every time we switch to the new op_array, we will create an execute_data variable for the new op_array. The variable type is zend_execute_data pointer, which is defined in {PHPSRC}/Zend/zend_compile.h:

struct _zend_execute_data {      struct _zend_op *opline;      zend_function_state function_state;      zend_function *fbc; /* Function Being Called */      zend_class_entry *called_scope;      zend_op_array *op_array;      zval *object;      union _temp_variable *Ts;      zval ***CVs;      HashTable *symbol_table;      struct _zend_execute_data *prev_execute_data;      zval *old_error_reporting;      zend_bool nested;      zval **original_return_value;      zend_class_entry *current_scope;      zend_class_entry *current_called_scope;      zval *current_this;      zval *current_object;      struct _zend_op *call_opline;  };


You can use the EX macro to obtain the value: # define EX (element) execute_data-> element

Here, we will briefly introduce two fields:

Opline: the op currently being executed.

Prev_execute_data: When the op_array environment is switched, this field is used to save the op_array before the switchover. This field is very important. It connects the execute_data of each op_array into a single-chain table according to the call sequence, each time an op_array execution ends, it is restored to the op_array before the call, the pre-call executor data is obtained through the prev_execute_data field in the current execute_data.

In executor_globals, the field current_execute_data points to the execute_data of the op_array being executed.

Before the formal introduction, we also need to briefly introduce the calling process of user-defined functions. The detailed process will be further described in the function section. Here is a brief description:

When calling a function, such as the test () function, the system first searches for the relevant function bodies in the global function symbol table based on test. If no function is defined, the system reports an error, find the test function body, obtain the op_array of the test function, and jump to the goto label zend_vm_enter in execute. Then, the test function execution environment is entered.

Next we will introduce the process of Environment switching with a simple piece of code. The example code is as follows:

<?php  $a = 123;    test();    function test()  {      return 1;  }  ?>


This code is very simple, so that we can easily introduce the principles. Readers of complicated code can draw a line from the other. After the code is compiled, two op_arrays are generated. One is the op_array of the global code and the other is the op_array of the test function. In the global code, function calls are used to enter the test function execution environment, after the execution is complete, the global code is returned and the code is complete.

Next we will introduce the process of this Code in several stages, and then we can know the method for performing environment switching.

1. Enter the execute function and start executing op_array. This op_array is the op_array of the global code. We call it op_array1 for the time being.

First, an execute_data data is created for op_array1 in execute. We temporarily name it execute_data1 and perform the initialization operations. What is important is:

EX (op_array) = op_array; // set the op_array field to the currently executed op_array, that is, op_array1EX (prev_execute_data) = EG (current_execute_data) of the global code ); // Save the current op_array execution data saved in the global execution data to the prev_execute_data field of execute_data1 of op_array1. Because this is the first op_array to be executed, prev_execute_data is actually null, set current_execute_data of the global variable in the execution period to execute_data1, and then set the current execution op of execute_data1, so that you can start executing the current op.


2. when op_array1 executes the test function call, first find the test function body from the global function symbol table and save the function body in the function_state field of execute_data1, then we get the op_array of test from the function body. Here we use op_array2 to represent it and assign op_array2 to EG (active_op_array ):

EG(active_op_array) = &EX(function_state).function->op_array;


Therefore, during the execution period, the dynamic op_array field of the global variable points to the op_array of the test function, and then calls ZEND_VM_ENTER (). At this time, the switch structure of the execute function is returned, and the following cases are met:

case 2:     op_array = EG(active_op_array);     goto zend_vm_enter;


We have already set EG (active_op_array) as op_array2 of the test function. Therefore, in the execute function, the op_array variable points to op_array2 of test, and then jumps to zend_vm_enter.

3. Jump to zend_vm_enter and return to the step similar to Step 1. At this time, the execution data execute_data is created for op_array2 of test, which is represented by execute_data2. In some differences from 1, EX (prev_execute_data) = EG (current_execute_data); at this time, current_execute_data = execute_data1, that is, the execution data of the global code, and then EG (current_execute_data; in this way, current_execute_data is equal to the execution data execute_data2 of test, and the execute_data1 of global code is saved in the prev_execute_data field of execute_data2. At this time, the Environment switch has been completed, and the test function is executed.

4. after the test function is executed, it is returned to the execution environment before the call, that is, the global code execution environment. The most important operation in this stage is EG (current_execute_data) = EX (prev_execute_data ); in 3, EX (prev_execute_data) has been set as the execute_data1 of the global code, so the current execution data becomes the execution data of the global code, in this way, the global code execution environment is successfully returned from the test execution environment.

In this way, the switching process of the execution environment is complete. for deep-level function calls, the principle is the same. A single-chain table consisting of execute_data will be longer.

The list of topics for reading this article is as follows:

PHP kernel exploration: Starting from the SAPI Interface
PHP kernel exploration: start and end of a request
PHP kernel exploration: one request Lifecycle
PHP kernel exploration: single-process SAPI Lifecycle
PHP kernel exploration: SAPI lifecycle of multiple processes/threads
PHP kernel exploration: Zend Engine
PHP kernel exploration: Exploring SAPI again
PHP kernel exploration: Apache module Introduction
PHP kernel exploration: Support for PHP through mod_php5
PHP kernel exploration: Apache running and Hook Functions
PHP kernel exploration: Embedded PHP
PHP kernel exploration: FastCGI of PHP
PHP kernel exploration: how to execute PHP scripts
PHP kernel exploration: execution details of PHP scripts
PHP kernel exploration: OpCode
PHP kernel exploration: opcode in PHP
PHP kernel exploration: Interpreter Execution Process
PHP kernel exploration: Variable Overview
PHP kernel exploration: Variable storage and type
PHP kernel exploration: hash table in PHP
PHP kernel exploration: Understanding the hash table in Zend
PHP kernel exploration: PHP Hash Algorithm Design
PHP kernel exploration: translating a HashTables article
PHP kernel exploration: What is a hash collision attack?
PHP kernel exploration: constant implementation
PHP kernel exploration: Variable Storage
PHP kernel exploration: variable type
PHP kernel exploration: variable value operations
PHP kernel exploration: Variable Creation
PHP kernel exploration: predefined Variables
PHP kernel exploration: Variable Retrieval
PHP kernel exploration: variable type conversion
PHP kernel exploration: Implementation of weak type variables
PHP kernel exploration: Implementation of static variables
PHP kernel exploration: variable type prompt
PHP kernel exploration: Variable Lifecycle
PHP kernel exploration: Variable assignment and destruction
PHP kernel exploration: variable scope
PHP kernel exploration: Strange variable name
PHP kernel exploration: variable value and type storage
PHP kernel exploration: Global variable
PHP kernel exploration: variable type conversion
PHP kernel exploration: Memory Management
PHP kernel exploration: Zend Memory Manager
PHP kernel exploration: PHP Memory Management
PHP kernel exploration: Memory application and destruction
PHP kernel exploration: reference count and write-time Replication
PHP kernel exploration: the garbage collection mechanism of PHP5.3
PHP kernel exploration: cache in memory management
PHP kernel exploration: COW mechanism for writing
PHP kernel exploration: array and linked list
PHP kernel exploration: using the hash table API
PHP kernel exploration: Array Operations
PHP kernel exploration: array source code analysis
PHP kernel exploration: Function Classification
PHP kernel exploration: internal structure of functions
PHP kernel exploration: Function Structure Conversion
PHP kernel exploration: process of defining functions
PHP kernel exploration: function parameters
PHP kernel exploration: zend_parse_parameters Function
PHP kernel exploration: function return value
PHP kernel exploration: form parameter return value
PHP kernel exploration: function call and execution
PHP kernel exploration: Reference and function execution
PHP kernel exploration: anonymous functions and closures
PHP kernel exploration: Object-oriented
PHP kernel exploration: class structure and implementation
PHP kernel exploration: class member variables
PHP kernel exploration: class member Methods
PHP kernel exploration: Class prototype zend_class_entry
PHP kernel exploration: Class Definition
PHP kernel exploration: Access Control
PHP kernel exploration: inheritance, polymorphism and abstract class
PHP kernel exploration: Magic functions and latency binding
PHP kernel exploration: Reserved class and Special Class
PHP kernel exploration: Object
PHP kernel exploration: Creating object instances
PHP kernel exploration: Object attribute read/write
PHP kernel exploration: namespace
PHP kernel exploration: defining Interfaces
PHP kernel exploration: inheritance and implementation Interfaces
PHP kernel exploration: resource Type
PHP kernel exploration: Zend Virtual Machine
PHP kernel exploration: Virtual Machine lexical Parsing
PHP kernel exploration: Virtual Machine syntax analysis
PHP kernel exploration: execution of intermediate code opcode
PHP kernel exploration: code encryption and decryption
PHP kernel exploration: Specific execution process of zend_execute
PHP kernel exploration: variable reference and counting rules
PHP kernel exploration: New garbage collection mechanism description

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.