>백엔드 개발 >PHP 튜토리얼 >PHP 커널 복호화 시리즈: zend_execute 실행 프로세스

PHP 커널 복호화 시리즈: zend_execute 실행 프로세스

WBOY
WBOY원래의
2016-08-08 09:22:341614검색

PHP 커널 복호화 시리즈: zend_execute의 실행 과정

인터프리터 엔진이 최종적으로 op를 실행하는 함수는 zend_execute이다. zend_execute가 기본적으로 실행을 가리킬 때 이 실행은 {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"); }
이 함수의 매개변수는 op_array이며, 이는 zend_op_array.op_array는 컴파일 과정에서 사용되며, zend_op_array 유형을 도입해야 합니다.
참조 소스: http://www.lai18.com/content/425167.html

zend_op_array 소개

이 유형은 {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;
이 구조는 비교적 복잡합니다. 현재로서는 가장 기본적인 분야만 소개합니다.
1.type:
op_array의 유형입니다. 가장 먼저 주목해야 할 점은 PHP 코드가 컴파일된 후 zend_op_array 포인터가 반환되더라도 실제로는 하나 이상의 zend_op_array 구조가 생성될 수 있다는 것입니다. 이 구조 function_name, num_args 등과 같은 일부 필드입니다. 이 zend_op_array 구조는 함수와 특정 연결이 있는 것처럼 보일 수 있습니다. 실제로 사용자 정의 함수와 사용자 정의 클래스 메소드는 모두 zend_op_array 구조입니다. 예를 들어, 사용자 정의 함수는 전역 함수 기호 테이블인 GLOBAL_FUNCTION_TABLE에 저장되며 함수 이름을 통해 이 테이블에서 검색할 수 있습니다. 그렇다면 컴파일 후 반환되는 zend_op_array 포인터는 무엇일까요? 사실 컴파일 후 반환되는 zend_op_array는 실행을 위한 진입점이기도 합니다. 즉, 어떤 함수에도 없는 전역 코드로 구성된 op_array라고 볼 수도 있습니다. 몸. 그러나 전역 코드, 사용자 정의 함수 및 사용자 정의 메서드는 모두 동일한 유형 값(2)을 갖습니다. , 유형의 가능한 값에 대한 매크로 정의는 다음과 같습니다.
#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
전역 코드, 사용자 함수 및 사용자 메소드가 모두 가장 일반적인 유형이기도 한 ZEND_USER_FUNCTION에 해당하는 것을 볼 수 있습니다. ZEND_EVAL_CODE는 함수의 eval PHP 코드에 해당하므로 eval 함수 매개변수의 PHP 코드도 별도의 zend_op_array로 컴파일될 것이라고 상상할 수 있습니다.
2.function_name
op_array가 사용자 정의 함수 또는 메소드를 컴파일하여 생성된 경우 이 필드는 함수의 이름에 해당합니다. 전역 코드이거나 eval 부분의 코드인 경우 이 필드는 다음과 같습니다. 제어.
3.opcodes
이 필드 유형은 zend_op *이므로 zend_op의 배열입니다. 이 배열은 이 컴파일 과정에서 생성된 ops를 저장합니다. zend_op를 모르는 경우 이전 기사 소개를 읽을 수 있습니다. OPcode에서 이 필드는 가장 중요한 부분이며, zend_execute는 궁극적으로 여기에 저장된 작업을 실행합니다.
이제 op_array 매개변수에 대한 기본적인 이해가 끝났으므로 실행에 들어갑니다.

실행과정에 대한 자세한 설명

execute函数开始的时候是一些基础变量的申明,其中zend_execute_data *execute_data;是执行期的数据结构,此变量在进行一定的初始化之后将会被传递给每个op的handler函数作为参数,op在执行过程中随时有可能改变execute_data中的内容。
第14行zend_vm_enter 这个跳转标签是作为虚拟机执行的入口,当op中涉及到函数调用的时候,就有可能会跳转到这里来执行函数体。
第16行到第19行为execute_data分配空间
第21行到第32行主要是对execute_data进行一些初始化,以及保存现场工作,要保存现场是因为在进入函数调用的时候,需要保存当前一些运行期间的数据,在函数调用结束之后再进行还原,可以想象为操作系统中进程调度,当进程在调出的时候需要保存寄存器等上下文环境,而当进程被调入的时候再取出来继续执行。
第41行到第51行主要是在当前动态符号表中加入$this变量,这个是在调用对象的方法时才有必要进行。
第58行开始的while无限循环就是开始执行op_array中的opcodes了,在第66行中调用当前执行的op的handler:
EX(opline)->handler(execute_data TSRMLS_CC))
然后如果handler的返回值小于0则循环继续,如果大于0则进入一个switch结构:
当返回值为1时:execute函数将返回,执行也就结束了。
当返回值为2时:op_array被重新设置,并跳转到zend_vm_enter ,这个一般是函数调用或则执行eval函数中的代码,将在新的上下文执行相关函数的op_array
当返回值为3时:循环体继续继续执行,当然再继续执行之前,EX(opline)已经往后移了一位(可能多位),也就是已经指向了后面一个新的opline,于是继续执行新的opline
当返回其他值时:结束循环,报错,结束应该用return,也就是返回1
在op的handler中返回特定的值都被定义成了宏,例如{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)++
以及在{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);
简单介绍功能
ZEND_VM_NEXT_OPCODE():移动到下一条op,返回0,不进入switch,循环继续(这个是最常用到的)
ZEND_VM_SET_OPCODE(new_op):当前opline设置成new_op
ZEND_VM_JMP(new_op) :当前opline设置成new_op,返回0,不进入switch,循环继续
ZEND_VM_INC_OPCODE():仅仅移动到下一条op

执行环境的切换

在前面的内容已经提到,用户自定义函数,类方法,eval的代码都会编译成单独的op_array,那么当进行函数调用等操作时,必然涉及到调用前的op_array执行环境和新的函数的op_array执行环境的切换,这一段我们将以调用用户自定义函数来介绍整个切换过程如何进行。
介绍此过程前必须了解执行环境的相关数据结构,涉及到执行环境的数据结构主要有两个:
1. 执行期全局变量结构
相关的定义在{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
这里是一个条件编译,ZTS表示线程安全启用,为了简化,我们这里以非线程安全模式的情况下来介绍,那么执行期的全局变量就是executor_globals,其类型为zend_executor_globals, zend_executor_globals的定义在{PHPSRC}/Zend/zend_globals.h,结构比较庞大,这里包含了整个执行期需要用到的各种变量,无论是哪个op_array在执行,都共用这一个全局变量,在执行过程中,此结构中的一些成员可能会改变,比如当前执行的op_array字段active_op_array,动态符号表字段active_symbol_table可能会根据不同的op_array而改变,This指针会根据在不同的对象环境而改变。
另外还定义了一个EG宏来取此变量中的字段值,此宏是针对线程安全和非线程安全模式的一个封装。
2.每个op_array自身的执行数据
针对每一个op_array,都会有自己执行期的一些数据,在函数execute开始的时候我们能看到zend_vm_enter跳转标签下面就会初始一个局部变量execute_data,所以我们每次切换到新的op_array的时候,都会为新的op_array建立一个execute_data变量,此变量的类型为zend_execute_data的指针,相关定义在{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; };
可以用EX宏来取其中的值:#define EX(element) execute_data->element
这里只简单介绍其中两个字段:
 opline: 当前正在执行的op。
 prev_execute_data:  op_array环境切换的时候,这个字段用来保存切换前的op_array,此字段非常重要,他能将每个op_array的execute_data按照调用的先后顺序连接成一个单链表,每当一个op_array执行结束要还原到调用前op_array的时候,就通过当前的execute_data中的prev_execute_data字段来得到调用前的执行器数据。
在executor_globals中的字段current_execute_data就是指向当前正在执行的op_array的execute_data。  
再正式介绍之前还需要简单的介绍一下用户自定义函数的调用过程,详细的过程以后再函数章节中专门介绍,这里简单的说明一下:
在调用函数的时候,比如test()函数,会先在全局函数符号表中根据test来搜索相关的函数体,如果搜索不到则会报错函数没有定义,找到test的函数体之后,取得test函数的op_array,然后跳转到execute中的goto标签:zend_vm_enter,于是就进入到了test函数的执行环境。
下面我们将以一段简单的代码来介绍执行环境切换过程,例子代码:

这段代码非常简单,这样方便我们介绍原理,复杂的代码读者可以举一反三。此代码编译之后会生成两个op_array,一个是全局代码的op_array,另外一个是test函数的op_array,其中全局代码中会通过函数调用进入到test函数的执行环境,执行结束之后,会返回到全局代码,然后代码结束。
下面我们分几个阶段来介绍这段代码的过程,然后从中可以知道执行环境切换的方法。
1. 进入execute函数,开始执行op_array ,这个op_array就是全局代码的op_array,我们暂时称其为op_array1
首先在execute中为op_array1建立了一个execute_data数据,我们暂时命名为execute_data1,然后进行相关的初始化操作,其中比较重要的是:
EX(op_array) = op_array; // 设置op_array字段为当前执行的op_array,也就是全局代码的op_array1 EX(prev_execute_data) = EG(current_execute_data);//将全局执行数据中保存的当前op_array执行数据保存到op_array1的execute_data1的prev_execute_data字段,由于这是执行的第一个op_array,所以prev_execute_data实际上是空值,然后将执行期全局变量的current_execute_data设置成execute_data1,然后设置execute_data1的当前执行op,这样就可以开始执行当前的op了
2. 在op_array1执行到test函数调用的的时候,首先从全局函数符号表中找到test的函数体,将函数体保存在execute_data1的function_state字段,然后从函数体中取到test的op_array,我们这里用op_array2来表示,并将op_array2赋值给EG(active_op_array):
EG(active_op_array) = &EX(function_state).function->op_array;
于是执行期全局变量的动态op_array字段指向了函数test的op_array,然后用调用ZEND_VM_ENTER();这个时候会先回到execute函数中的switch结构,并且满足以下case
case 2: op_array = EG(active_op_array); goto zend_vm_enter;
EG(active_op_array)之前已经被我们设置为test函数的op_array2,于是在函数execute中,op_array变量就指向了test的op_array2,然后跳转到zend_vm_enter。
3. 跳转到zend_vm_enter之后其实又回到了类似1中的步骤,此时为test的op_array2建立了它的执行数据execute_data,我们这里用execute_data2来表示。1과 조금 다른 점은 EX(prev_execute_data) = EG(current_execute_data); 이때 current_execute_data = excute_data1이고, 그 다음에는 EG(current_execute_data) = excute_data이므로 current_execute_data는 다음과 같다. Execute_data2를 테스트하고 전역 코드의 Execute_data1이 Execute_data2의 prev_execute_data 필드에 저장됩니다. 이때 환경전환이 완료되어 테스트 기능이 실행되기 시작한다.
4. 테스트 함수가 실행되면 전역 코드 실행 환경인 호출 전 실행 환경으로 돌아갑니다. 이 단계에서 가장 중요한 작업은 EG(current_execute_data) = EX(prev_execute_data)입니다. 3번의 (prev_execute_data)를 글로벌 코드의 Execute_data1로 설정하였기 때문에 현재 실행 데이터가 글로벌 코드의 실행 데이터가 됩니다. 이로써 함수 테스트 실행 환경은 성공적으로 글로벌 코드 실행 환경으로 복귀됩니다. 이렇게 하면 실행 환경 전환 프로세스가 완료됩니다. 깊은 함수 호출의 경우 실행 데이터로 구성된 단일 연결 목록이 더 길어집니다.

확장 읽기

이 기사의 주제 목록은 다음과 같습니다.

PHP 핵심 탐색: SAPI 인터페이스에서 시작
PHP 핵심 탐색: 요청의 시작과 끝
PHP 코어 탐색: 일회성 요청 수명 주기
PHP 커널 탐색: 단일 프로세스 SAPI 수명 주기
PHP 커널 탐색: 다중 프로세스/스레드 SAPI 수명 주기
PHP 커널 탐색: Zend 엔진
PHP 커널 탐색 : SAPI 재검토
PHP 커널 탐색: Apache 모듈 소개
PHP 커널 탐색: mod_php5를 통한 PHP 지원
PHP 커널 탐색: Apache 실행 및 후크 기능
PHP 커널 탐색: 임베디드 PHP
PHP 커널 탐색: PHP FastCGI
PHP 커널 탐색: PHP 스크립트 실행 방법
PHP 커널 탐색: PHP 스크립트 실행 세부 정보
PHP 커널 탐색: Opcode OpCode
PHP 커널 탐색: PHP의 Opcode
PHP 커널 탐색: 인터프리터의 실행 프로세스
PHP 커널 탐색: 변수 개요
PHP 커널 탐색: 변수 저장소 및 유형
PHP 커널 탐색: PHP의 해시 테이블
PHP 커널 탐색: Zend 이해
PHP 커널 탐색: PHP 해시 알고리즘 설계
PHP 커널 탐색: 해시 테이블 번역 기사
PHP 커널 탐색: 해시 충돌 공격이란 무엇입니까?
PHP 커널 탐색: 상수 구현
PHP 커널 탐색: 변수 저장
PHP 커널 탐색: 변수 유형
PHP 커널 탐색: 변수의 값 연산
PHP 커널 탐색: 변수 생성
PHP 커널 탐색: 사전 정의된 변수
PHP 커널 탐색: 변수 검색
PHP 커널 탐색: 변수의 유형 변환
PHP 커널 탐색: 약한 유형 변수 구현
PHP 커널 탐색: 구현 정적 변수
PHP 코어 탐색: 변수 유형 힌트
PHP 코어 탐색: 가변 수명 주기
PHP 코어 탐색: 변수 할당 및 소멸
PHP 코어 탐색: 변수 범위
PHP 코어 탐색: 이상한 변수 이름
PHP 코어 탐색: 변수 값 및 유형 저장
PHP 코어 탐색: 전역 변수 전역
PHP 코어 탐색: 변수 유형 변환
PHP 코어 탐색: 메모리 관리 시작
PHP 커널 탐색: Zend Memory Manager
PHP 커널 탐색: PHP 메모리 관리
PHP 커널 탐색: 메모리 적용 및 파괴
PHP 커널 탐색: 참조 계산 및 쓰기 시 복사
PHP 커널 탐색: PHP5. 3의 가비지 수집 메커니즘
PHP 커널 탐색: 메모리 관리의 캐시
PHP 커널 탐색: 쓰기 시 복사 COW 메커니즘
PHP 커널 탐색: 배열 및 연결 목록
PHP 코어 탐색: 해시 테이블 사용 API
PHP 코어 탐색: 배열 연산
PHP 코어 탐색: 배열 소스 코드 분석
PHP 코어 탐색: 함수 분류
PHP 코어 탐색: 함수 내부 구조
PHP 코어 탐색: 함수 구조 변환
PHP 코어 탐색: 함수 정의 프로세스
PHP 코어 탐색: 함수의 매개변수
PHP 코어 탐색: zend_parse_parameters 함수
PHP 코어 탐색: 함수 반환 값
PHP 코어 탐색: 형식 매개변수 반환 값
PHP 코어 탐색: 함수 호출 및 실행
PHP 코어 탐색: 참조 및 함수 실행
PHP 코어 탐색: 익명 함수 및 클로저
PHP 코어 탐색: 객체 지향 시작
PHP 핵심 탐색: 클래스 구조 및 구현
PHP 핵심 탐색: 클래스의 멤버 변수
PHP 핵심 탐색: 클래스의 멤버 메서드
PHP 핵심 탐색: 클래스 프로토타입 zend_class_entry
PHP 핵심 탐색 : 클래스 정의
PHP 핵심 탐색: 접근 제어
PHP 핵심 탐색: 상속, 다형성 및 추상 클래스
PHP 핵심 탐색: 매직 함수 및 지연 바인딩
PHP 핵심 탐색: 예약 클래스 및 특수 클래스
PHP 핵심 탐색: 객체
PHP 핵심 탐색: 객체 인스턴스 생성
PHP 핵심 탐색: 객체 속성 읽기 및 쓰기
PHP 핵심 탐색: 네임스페이스
PHP 코어 탐색: 인터페이스 정의
PHP 코어 탐색: 인터페이스 상속 및 구현
PHP 코어 탐색: 리소스 리소스 유형
PHP 코어 탐색: Zend 가상 머신
PHP 코어 탐색: 가상 머신의 어휘 분석
PHP 커널 탐색: 구문 분석 가상 머신
PHP 커널 탐색: 중간 코드 opcode 실행
PHP 커널 탐색: 코드 암호화 및 복호화
PHP 커널 탐색: zend_execute의 특정 실행 프로세스
PHP 커널 탐색: 변수 참조 및 계산 규칙
PHP 커널 탐색: 새로운 가비지 수집 메커니즘 설명

위 내용은 PHP 커널 복호화 시리즈: zend_execute의 실행 과정을 관련 내용을 포함하여 소개하고 있으며, PHP 튜토리얼에 관심이 있는 친구들에게 도움이 되기를 바랍니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.