这里阅读的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,和我们扩展模块里面定义的函数对应上了。可以说,这里就进入了扩展里面了。

php内核分析(七)-扩展的更多相关文章

  1. LINUX内核分析第七周学习总结:可执行程序的装载

    LINUX内核分析第七周学习总结:可执行程序的装载 韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

  2. Linux内核分析(七)----并发与竞态

    原文:Linux内核分析(七)----并发与竞态 Linux内核分析(七) 这两天家里的事好多,我们今天继续接着上一次的内容学习,上次我们完善了字符设备控制方法,并深入分析了系统调用的实质,今天我们主 ...

  3. 《Linux内核分析》第七周学习总结

    <Linux内核分析>第七周学习总结                         ——可执行程序的装载 姓名:王玮怡  学号:20135116 一.理论部分总结 (一)可执行程序的装载 ...

  4. 《Linux内核分析》第七周学习笔记

    <Linux内核分析>第七周学习笔记 可执行程序的装载 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/co ...

  5. Linux内核分析 第七周 可执行程序的装载

    张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第七 ...

  6. 《Linux内核分析》第七周: 可执行程序的装载

    LINUX内核分析第七周学习总结--可执行程序的装载 杨舒雯(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/ ...

  7. 20135327郭皓--Linux内核分析第七周 可执行程序的装载

    第七周 可执行程序的装载 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 ...

  8. LINUX内核分析第七周学习总结

    LINUX内核分析第七周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...

  9. Linux内核分析第七周———可执行程序的装载

    Linux内核分析第七周---可执行程序的装载 李雪琦+原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/US ...

  10. Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序

    Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...

随机推荐

  1. C#基础篇 - 理解委托和事件

    1.委托 委托类似于C++中的函数指针(一个指向内存位置的指针).委托是C#中类型安全的,可以订阅一个或多个具有相同签名方法的函数指针.简单理解,委托是一种可以把函数当做参数传递的类型.很多情况下,某 ...

  2. OpenCV人脸识别LBPH算法源码分析

    1 背景及理论基础 人脸识别是指将一个需要识别的人脸和人脸库中的某个人脸对应起来(类似于指纹识别),目的是完成识别功能,该术语需要和人脸检测进行区分,人脸检测是在一张图片中把人脸定位出来,完成的是搜寻 ...

  3. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...

  4. 一个表缺失索引发的CPU资源瓶颈案例

    背景 近几日,公司的应用团队反应业务系统突然变慢了,之前是一直比较正常.后与业务部门沟通了解详情,得知最近生意比较好,同时也在做大的促销活动,使得业务数据处理的量出现较大的增长,最终系统在处理时出现瓶 ...

  5. [Android]使用Dagger 2进行依赖注入 - Producers(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/6234811.html 使用Dagger 2进行依赖注入 - P ...

  6. Ubuntu下开启php调试模式,显示报错信息

    在Ubuntu下php的缺省设置是不显示错误信息的,如果程序出错会显示“无法处理此请求的错误提示”,这在开发环境下非常不方便. 其实我们只要编辑下apache的配置文件就好 1.我的apache 配置 ...

  7. #26 fibonacci seqs

    Difficulty: Easy Topic: Fibonacci seqs Write a function which returns the first X fibonacci numbers. ...

  8. kafka

    2016-11-13  20:48:43 简单说明什么是kafka? Apache kafka是消息中间件的一种,我发现很多人不知道消息中间件是什么,在开始学习之前,我这边就先简单的解释一下什么是消息 ...

  9. Egret3D研究报告(二)从Unity3D导出场景到Egret3D

    Egret3D暂时没有场编的计划,但是我们知道unity3D是一个很好的场编. 有一些游戏即使不是使用Unity3D开发,也使用Unity3D做场编.这里就不点名了,而且并不在少数. 我们就这么干. ...

  10. ASP.NET MVC 视图(一)

    ASP.NET MVC 视图(一) 前言 从本篇开始就进入到了MVC中的视图部分,在前面的一些篇幅中或多或少的对视图和视图中的一些对象的运用进行了描述,不过毕竟不是视图篇幅说的不全面,本篇首先为大家讲 ...