PHP之钩子
挂钩到函数的施行
userland和内部函数的施行由Zend引擎中的两个函数处置,您可以用本人的实现更换这两个函数。覆盖此钩子的扩展的主要用例是通用函数级评测、调试和面向方面的编程。
钩子在 Zend/zend_execute.h
中定义:
ZEND_API extern void (*zend_execute_ex)(zend_execute_data *execute_data);ZEND_API extern void (*zend_execute_internal)(zend_execute_data *execute_data, zval *return_value);
假如要覆盖这些函数指针,则必需在 Minit 中施行此操纵,由于 Zend Engine 中的其他决策是按照指针可否被覆盖这一事实提早做出的。
覆盖的平常模式是这样的:
static void (*original_zend_execute_ex) (zend_execute_data *execute_data);static void (*original_zend_execute_internal) (zend_execute_data *execute_data, zval *return_value);void my_execute_internal(zend_execute_data *execute_data, zval *return_value);void my_execute_ex (zend_execute_data *execute_data);PHP_MINIT_FUNCTION(my_extension){ REGISTER_INI_ENTRIES(); original_zend_execute_internal = zend_execute_internal; zend_execute_internal = my_execute_internal; original_zend_execute_ex = zend_execute_ex; zend_execute_ex = my_execute_ex; return SUCCESS;}PHP_MSHUTDOWN_FUNCTION(my_extension){ zend_execute_internal = original_zend_execute_internal; zend_execute_ex = original_zend_execute_ex; return SUCCESS;}
覆盖 zend_execute_ex
的一个缺陷是它将 Zend Virtual Machine 运转时的行动更换为使用递归,而不是在不分开说明器轮回的状况下处置调取。此外,没有覆盖zend_execute_ex
的 PHP 引擎也可以生成更优化的函数调取操纵码。
这些挂钩对机能非常敏锐,详细取决于原始函数封装代码的复杂性。
覆盖内部功效
在覆盖施行钩子时,扩展可以记载每个函数调取,你还可以覆盖会员域,中心和扩展函数(和办法)的各个函数指针。假如扩展仅需要拜访特定的内部函数调取,则具有更好的机能特点。
#if PHP_VERSION_ID < 70200typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS);#endif zif_handler original_handler_var_dump;ZEND_NAMED_FUNCTION(my_overwrite_var_dump){ // 假如我们想调取原始函数 original_handler_var_dump(INTERNAL_FUNCTION_PARAM_PASSTHRU);}PHP_MINIT_FUNCTION(my_extension){ zend_function *original; original = zend_hash_str_find_ptr(EG(function_table), "var_dump", sizeof("var_dump")-1); if (original != NULL) { original_handler_var_dump = original->internal_function.handler; original->internal_function.handler = my_overwrite_var_dump; }}
覆盖类办法时,可以在 zend_class_entry
上寻到函数表:
zend_class_entry *ce = zend_hash_str_find_ptr(CG(class_table), "PDO", sizeof("PDO")-1);if (ce != NULL) { original = zend_hash_str_find_ptr(&ce->function_table, "exec", sizeof("exec")-1); if (original != NULL) { original_handler_pdo_exec = original->internal_function.handler; original->internal_function.handler = my_overwrite_pdo_exec; }}
修改抽象语法树(AST)
当 PHP 7编译 PHP 代码时,它会先将其转换为抽象语法树(AST),然后终究生成耐久储备在 Opcache 中的操纵码。zend_ast_process
钩子会被每个已编译的足本调取,并同意你在解析和创立 AST 之后修改 AST。
这是要使用的最复杂的钩子之一,由于它需要完全理解 AST。在此处创立无效的 AST 大概会致使非常行动或崩溃。
最好看看使用此钩子的示例扩展:
- Google Stackdriver PHP调试器扩展
- 基于 Stackdriver 的带有 AST 的概念验证器
熟知足本/文件编译
每当会员足本调取include
/require
或其对应的include_once
/require_once
时,PHP内核都会在指针zend_compile_file处调取该函数
处置此恳求。参数是文件句柄,结果是zend_op_array
。
zend_op_array * my_extension_compile_file(zend_file_handle * file_handle,int类型);
PHP中心中有两个扩展实现了此挂钩:dtrace和opcache。
-假如您使用环境变量USE_ZEND_DTRACE
启动PHP足本并使用dtrace支撑编译了PHP,则dtrace_compile_file
用于Zend / zend_dtrace.c
。
-Opcache将操纵数组储备在同享内存中以获得更好的机能,因此,每当足本被编译时,其终究的操纵数组都会从缓存中得到效劳,而不是从新编译。您可以在ext / opcache / ZendAccelerator.c
中寻到此实现。
-名为compile_file
的默许实现是Zend / zend_language_scanner.l
中扫描程序代码的一部分。
实施此挂钩的用例是Opcode Accelerating,PHP代码加密/解密,调试或概要剖析。
您可以随时在施行PHP进程时更换该挂钩,并且更换后编译的所有PHP足本都将由该挂钩的实现处置。
始终调取原始函数指针非常重要,不然PHP将没法再编译足本,并且Opcache将不再起作用。
此处的扩展覆盖次序也很重要,由于您需要知道是要在Opcache此前还是之后注册钩子,由于Opcache假如在其同享内存缓存中寻到操纵码数组条目,则不会调取原始函数指针。 Opcache将其钩子注册为启动后钩子,该钩子在扩展的minit阶段之后运转,因此默许状况下,缓存足本时将不再调取该钩子。
调取错误处置程序时的通知
与PHP会员区set_error_handler()
函数相似,扩展可以通过实现zend_error_cb
钩子将本身注册为错误处置程序:
ZEND_API void(* zend_error_cb)(int类型,const char * error_filename,const uint32_t error_lineno,const char * format,va_list args);
type
变量对应于E _ *
错误常量,该常量在PHP会员区中也可用。
PHP中心和会员态错误处置程序之间的关系很复杂:
1.假如未注册任何会员级错误处置程序,则始终调取zend_error_cb
。
2.假如注册了userland错误处置程序,则关于E_ERROR
,E_PARSE
,E_CORE_ERROR
,E_CORE_WARNING
,E_COMPILE_ERROR的所有错误
和E_COMPILE_WARNING
始终调取zend_error_cb
挂钩。
3.关于所有其他错误,仅在会员态处置程序失败或返回false
时调取zend_error_cb
。
别的,由于Xdebug本身复杂的实现,它以不调取之前注册的内部处置程序的方式覆盖错误处置程序。
因此,覆盖此挂钩不是很可靠。
再次覆盖应当以尊敬原始处置程序的方式停止,除非您想完全更换它:
void(* original_zend_error_cb)(int类型,const char * error_filename,const uint error_lineno,const char * format,va_list args);void my_error_cb(int类型,const char * error_filename,const uint error_lineno,const char * format,va_list args){ //我的非凡错误处置 original_zend_error_cb(type,error_filename,error_lineno,format,args);}PHP_MINIT_FUNCTION(my_extension){ original_zend_error_cb = zend_error_cb; zend_error_cb = my_error_cb; return SUCCESS;}PHP_MSHUTDOWN(my_extension){ zend_error_cb = original_zend_error_cb;}
该挂钩主要用于为非常跟踪或利用程序机能治理软件实施集中式非常跟踪。
激发非常时的通知
每当PHP Core或Userland代码激发非常时,都会调取zend_throw_exception_hook
并将非常作为参数。
这个钩子的签名非常简便:
void my_throw_exception_hook(zval * exception){ if(original_zend_throw_exception_hook!= NULL){ original_zend_throw_exception_hook(exception); }}
该挂钩没有默许实现,假如未被扩展覆盖,则指向NULL
。
static void(* original_zend_throw_exception_hook)(zval * ex);void my_throw_exception_hook(zval * exception);PHP_MINIT_FUNCTION(my_extension){ original_zend_throw_exception_hook = zend_throw_exception_hook; zend_throw_exception_hook = my_throw_exception_hook; return SUCCESS;}
假如实现此挂钩,请留意不管可否捕捉到非常,都会调取此挂钩。将非常暂时储备在此处,然后将其与错误处置程序挂钩的实现结合起来以检查非常可否未被捕捉并致使足本休止,依然有用。
实现此挂钩的用例包罗调试,日志记载和非常跟踪。
挂接到eval()
PHPeval
不是内部函数,而是一种非凡的说话结构。因此,您没法通过zend_execute_internal
或通过覆盖其函数指针来连接它。
挂钩到eval的用例并不多,您可以将其用于概要剖析或出于平安目的。假如更换其行动,请留意大概需要评估其他扩展名。一个示例是Xdebug,它使用它施行断点前提。
extern ZEND_API zend_op_array *(* zend_compile_string)(zval * source_string,char * filename);
挂入垃圾收集器
当可收集对象的数目到达必然阈值时,引擎本身会调取gc_collect_cycles()
或隐式地触发PHP垃圾收集器。
为了使您理解垃圾收集器的工作方式或剖析其机能,可以覆盖施行垃圾收集操纵的函数指针挂钩。从理论上讲,您可以在此处实现本人的垃圾收集算法,但是假如有必要对引擎停止其他更换,则这大概实际上并不成行。
int(* original_gc_collect_cycles)(无效);int my_gc_collect_cycles(无效){ original_gc_collect_cycles();}PHP_MINIT_FUNCTION(my_extension){ original_gc_collect_cycles = gc_collect_cycles; gc_collect_cycles = my_gc_collect_cycles; return SUCCESS;}
覆盖中止处置程序
当施行器全局EG(vm_interrupt)
设定为1时,将调取一次中止处置程序。在施行会员域代码期间,将在常规检查点对它停止检查。引擎使用此挂钩通过信号处置程序实现PHP施行超时,该信号处置程序在到达超时连续时间后将中止设定为1。
当更平安地清算或实现本人的超时处置时,这有助于将信号处置延迟到运转时施行的后期。通过设定此挂钩,您不会不测禁用PHP的超时检查,由于它具有自定义处置的优先级,该优先级高于对zend_interrupt_function
的任何覆盖。
ZEND_API void(* original_interrupt_function)(zend_execute_data * execute_data);void my_interrupt_function(zend_execute_data * execute_data){ if(original_interrupt_function!= NULL){ original_interrupt_function(execute_data); }}PHP_MINIT_FUNCTION(my_extension){ original_interrupt_function = zend_interrupt_function; zend_interrupt_function = my_interrupt_function; return SUCCESS;}
##更换操纵码处置程序
TODO
以上就是PHP之钩子的具体内容,更多请关注百分百源码网其它相关文章!