php內核分析(七)-擴展


這里閱讀的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,和我們擴展模塊里面定義的函數對應上了。可以說,這里就進入了擴展里面了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM