这里阅读的 php 版本为 PHP-7.1.0 RC3,阅读代码的平台为 linux。
我们研究下反射这个扩展。
反射这个扩展目录是存在在:ext/reflection。其实里面的代码很简单。一个. h 文件,一个 .c 文件。
我们先看下. c 文件中,会看到很多 ZEND_METHOD
- ZEND_METHOD(reflection_function, getReturnType) {...
- }
对应的宏:
- #define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(ZEND_MN(classname##_##name))#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)#define ZEND_MN(name) zim_##name#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data * execute_data,
- zval * return_value
这里的 ## 代表的是连接,展开实际上就是:
- void zim_reflection_function_getReturnType(zend_execute_data * execute_data, zval * return_value)
总而言之,我们这里是使用 ZEND_METHOD 定义了一个函数 zim_reflection_function_getReturnType,那从执行代码是怎么调用到这里的呢?
好吧,所以我们这里是看不到扩展的调用堆栈的。那我们用 gdb 看下调用堆栈。
写个使用反射扩展的脚本:
- 1 < ?php 2 3 class B 4 {
- 5 public
- function test() : B 6 {
- 7 8
- }
- 9
- }
- 10 11
- function getB() : B 12 {
- 13 14
- }
- 15 16 $rc = new ReflectionMethod('B', 'test');
- 17 var_dump((string) $rc - >getReturnType(), $rc - >getReturnType());
- 18 19 $rc = new ReflectionFunction('getB');
- 20 var_dump((string) $rc - >getReturnType(), $rc - >getReturnType());
使用 gdb 进行打点,我们看了下 getReturnType 的扩展定义,里面有个在扩展代码中的函数 reflection_type_factory,就使用这个打点了。
- (gdb) b reflection_type_factory(gdb) run - f / home / xiaoju / software / php7 / demo / echo.php(gdb) s(gdb) bt#0 reflection_type_factory(fptr = 0x7ffff6004210, closure_object = 0x0, arg_info = 0x7ffff6079048, object = 0x7ffff60140d0) at / home / xiaoju / webroot / php - src / php - src - master / ext / reflection / php_reflection.c: 1280#1 0x0000000000760d23 in ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER(execute_data = 0x7ffff6014030) at / home / xiaoju / webroot / php - src / php - src - master / Zend / zend_vm_execute.h: 1097#2 0x000000000073fc88 in execute_ex(ex = <value optimized out > ) at / home / xiaoju / webroot / php - src / php - src - master / Zend / zend_vm_execute.h: 432#3 0x000000000078b670 in zend_execute(op_array = 0x7ffff60782a0, return_value = <value optimized out > ) at / home / xiaoju / webroot / php - src / php - src - master / Zend / zend_vm_execute.h: 474#4 0x00000000006e48a3 in zend_execute_scripts(type = 8, retval = 0x0, file_count = 3) at / home / xiaoju / webroot / php - src / php - src - master / Zend / zend.c: 1464#5 0x0000000000684870 in php_execute_script(primary_file = 0x7fffffffe090) at / home / xiaoju / webroot / php - src / php - src - master / main / main.c: 2541#6 0x000000000078e9ea in do_cli(argc = 3, argv = 0xee1bc0) at / home / xiaoju / webroot / php - src / php - src - master / sapi / cli / php_cli.c: 994#7 0x000000000078f1ea in main(argc = 3, argv = 0xee1bc0) at / home / xiaoju / webroot / php - src / php - src - master / sapi / cli / php_cli.c: 1387(gdb)
好了,很清晰可以看到这个脉络:
- main - >do_cli - >php_execute_scripts - >zend_execute - >execute_ex - >ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER - >reflection_type_factory
对于 main, do_cli, php_execute_scripts, zend_execute, execute_ex 根据前面的 main 函数分析,我们很容易能够理解各个函数的作用。换句话说,execute_ex 才是实际上调用 opcode 最终最重要的函数。
对照这个脚本的 opcode:
- L1 - 21 {
- main
- } () / home / xiaoju / software / php7 / demo / echo.php - 0x7fd6a127f000 + 30 ops L3#0 NOP L11#1 NOP L16#2 NEW "ReflectionMethod"@1 L16#3 SEND_VAL_EX "B"1 L16#4 SEND_VAL_EX "test"2 L16#5 DO_FCALL L16#6 ASSIGN $rc@1 L17#7 INIT_FCALL 112 "var_dump"L17#8 INIT_METHOD_CALL $rc "getReturnType"L17#9 DO_FCALL@4 L17#10 CAST@4~5 L17#11 SEND_VAL~5 1 L17#12 INIT_METHOD_CALL $rc "getReturnType"L17#13 DO_FCALL@6 L17#14 SEND_VAR@6 2 L17#15 DO_ICALL L19#16 NEW "ReflectionFunction"@8 L19#17 SEND_VAL_EX "getB"1 L19#18 DO_FCALL L19#19 ASSIGN $rc@8 L20#20 INIT_FCALL 112 "var_dump"L20#21 INIT_METHOD_CALL $rc "getReturnType"L20#22 DO_FCALL@11 L20#23 CAST@11~12 L20#24 SEND_VAL~12 1 L20#25 INIT_METHOD_CALL $rc "getReturnType"L20#26 DO_FCALL@13 L20#27 SEND_VAR@13 2 L20#28 DO_ICALL L21#29 RETURN 1
可以看到这个 $rc->getReturnType() 相对应的 opcode 是在 #9 DO_FCALL
好了,我们从 execute_ex 开始跟,可以简化成:
- // 最核心的执行opcode的函数
- ZEND_API void execute_ex(zend_execute_data * ex) {...
- while (1) {
- int ret;
- if (UNEXPECTED((ret = ((opcode_handler_t) OPLINE - >handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) {...
- }
- }...
- }
这里的 handler 每个 opcode 的 op 对应一个 handler,比如 DO_FCALL 对应的 handler 就是 ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER(和刚才的 bt 现显示的堆栈一样)
简化下伪代码如下:
- // DO_FCALL这个opcode对应的处理函数
- static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETVAL_USED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) {...
- if (EXPECTED(fbc - >type == ZEND_USER_FUNCTION)) { // 如果是用户定义的函数
- ...zend_execute_ex(call);...
- } else if (EXPECTED(fbc - >type < ZEND_USER_FUNCTION)) { // 如果是内部函数
- ...
- if (!zend_execute_internal) {
- fbc - >internal_function.handler(call, ret); // 执行这个internal_function所定义的handler函数,这个就是实际的调用方法了,命名为:zim_[class]_function_[function]
- } else {
- zend_execute_internal(call, ret);
- }...
- } else {
- /* ZEND_OVERLOADED_FUNCTION */
- ...
- if (UNEXPECTED(!zend_do_fcall_overloaded(fbc, call, ret))) {
- HANDLE_EXCEPTION();
- }...
- }
- fcall_end: ...ZEND_VM_SET_OPCODE(opline + 1);
- ZEND_VM_CONTINUE(); // 下一条op
- }
可以看到,这个函数里面就有一个 fbc->internal_function.handler,这里的 internal_function 对应的函数名就是 zim_reflection_function_getReturnType,和我们扩展模块里面定义的函数对应上了。可以说,这里就进入了扩展里面了。
来源: http://www.cnblogs.com/yjf512/p/6120856.html