|
很多人都想看PHP的源码,但是由于缺乏正确的方法,浅尝辄止,止步于想法。今天我们就浅谈一下(我研究也不深,只是把我所知道的与大家探讨一下)。
第一步:下载源码:
https://github.com/php/php-src
第二步:编译:
如何编译PHP请查阅官方文档
编译的时候请添加–debug=1的参数,这样才能更好的调试代码,不加的话,很多地方打断点无效,应该是被编译器优化掉了。
第三步:安装编辑器:
推荐clion,真正的高手都是用vim写c代码,查看c代码。我等不属于那一类人,还是老老实实用编辑器,个人看了好多个编辑器,感觉clion是最好的。
不过用clion存在一个问题,就是它吧cmake作为代码结构的解析工具,所以没有CMakeLists.txt的项目,它是没法去解析的,也就是找不到代码追踪关系,只能硬看。不过可以自己添加一个CMakeLists.txt。
大家可以看一下我整理的CMakeLists.txt
https://github.com/he1016060110/php-src/blob/learn/CMakeLists.txt
第四步:熟悉代码调试工具lldb和gdb
mac下的gdb不好用,用lldb
如果在linux下调试代码,请使用gdb
使用教程如下:
https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html
php为了方便gdb调试,专门给gdb写了一些便捷的方法,执行gdb的时候source一下就行。
文件是:
https://github.com/he1016060110/php-src/blob/master/.gdbinit
第五步:了解代码结构和调试
php7的代码编译执行是根据下面的流程进行的,先用flex和bison将PHP代码进行词法和语法分析,得到一颗抽象语法树,然后再编译语法树得到opcode,然后在顺序执行opcode

接下来我们一步步调试PHP代码,看看每一个步骤的代码该如果去调试,为了简单,我们使用php cli模式来调试(单进程好调试,fpm至少得有两个进程,一个master一个worker)
介绍一下代码的目录结构:
Zend:zend 引擎最核心的代码:比如编译、数组,对象的实现
main:一些最基础的关于协议之类的代码:比如fastcgi的接口
ext:扩展的类或者方法,比如pdo,pcre等等,里面有个目录非常特殊,standard,许多很多核心函数也保存在里面。
我们先来尝个鲜
比如var_dump,我们编辑一个最简单的文件a.php
<?php
$a = 1;
var_dump($a);

上面我们在zif_var_dump打了一个断点,执行到这个方法的时候,程序中断了,至于为什么是zif_前缀我们后面讲。我们找到var_dump的代码了,接着一步步调试即可。

gdb 执行p args[0]

得到zval的type为4,type为4的zval是什么呢,我么去看看zend_types.h,type为4的是long类型,属于int

我们再看zval的结构

type为long类型,我们就读取value元素的lval,于是在gdb里面执行:
p args[0].value.lval

得到要var_dump的对象$a的值为1
接下来我们来解释为什么是zif前缀了


根据宏定义可以得知:
PHP_FUNCTION(var_dump)宏展开为:zif_var_dump
那么调试对象的方法是什么呢,比如PDOStatement 类的fetchAll方法
根据宏定义,我们可以知道这个方法在c语言里面为:
zim_PDOStatement_fetchAll,在这个方法打断点就行了

查看语法抽象树的底层结构
我们就使用刚刚的a.php来了解大概如果得到抽象语法树。
先看下语法树大概是个什么样的东西(画的难看见谅)

语法树叶子节点的种类在zend_ast.h里面定义的,具体内容如下
enum _zend_ast_kind {
/* special nodes */
ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT,
ZEND_AST_ZNODE,
/* declaration nodes */
ZEND_AST_FUNC_DECL,
ZEND_AST_CLOSURE,
ZEND_AST_METHOD,
ZEND_AST_CLASS,
/* list nodes */
ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT,
ZEND_AST_ARRAY,
ZEND_AST_ENCAPS_LIST,
ZEND_AST_EXPR_LIST,
ZEND_AST_STMT_LIST,
ZEND_AST_IF,
ZEND_AST_SWITCH_LIST,
ZEND_AST_CATCH_LIST,
ZEND_AST_PARAM_LIST,
ZEND_AST_CLOSURE_USES,
ZEND_AST_PROP_DECL,
ZEND_AST_CONST_DECL,
ZEND_AST_CLASS_CONST_DECL,
ZEND_AST_NAME_LIST,
ZEND_AST_TRAIT_ADAPTATIONS,
ZEND_AST_USE,
/* 0 child nodes */
ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_TYPE,
/* 1 child node */
ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_CONST,
ZEND_AST_UNPACK,
ZEND_AST_UNARY_PLUS,
ZEND_AST_UNARY_MINUS,
ZEND_AST_CAST,
ZEND_AST_EMPTY,
ZEND_AST_ISSET,
ZEND_AST_SILENCE,
ZEND_AST_SHELL_EXEC,
ZEND_AST_CLONE,
ZEND_AST_EXIT,
ZEND_AST_PRINT,
ZEND_AST_INCLUDE_OR_EVAL,
ZEND_AST_UNARY_OP,
ZEND_AST_PRE_INC,
ZEND_AST_PRE_DEC,
ZEND_AST_POST_INC,
ZEND_AST_POST_DEC,
ZEND_AST_YIELD_FROM,
ZEND_AST_GLOBAL,
ZEND_AST_UNSET,
ZEND_AST_RETURN,
ZEND_AST_LABEL,
ZEND_AST_REF,
ZEND_AST_HALT_COMPILER,
ZEND_AST_ECHO,
ZEND_AST_THROW,
ZEND_AST_GOTO,
ZEND_AST_BREAK,
ZEND_AST_CONTINUE,
/* 2 child nodes */
ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_PROP,
ZEND_AST_STATIC_PROP,
ZEND_AST_CALL,
ZEND_AST_CLASS_CONST,
ZEND_AST_ASSIGN,
ZEND_AST_ASSIGN_REF,
ZEND_AST_ASSIGN_OP,
ZEND_AST_BINARY_OP,
ZEND_AST_GREATER,
ZEND_AST_GREATER_EQUAL,
ZEND_AST_AND,
ZEND_AST_OR,
ZEND_AST_ARRAY_ELEM,
ZEND_AST_NEW,
ZEND_AST_INSTANCEOF,
ZEND_AST_YIELD,
ZEND_AST_COALESCE,
ZEND_AST_STATIC,
ZEND_AST_WHILE,
ZEND_AST_DO_WHILE,
ZEND_AST_IF_ELEM,
ZEND_AST_SWITCH,
ZEND_AST_SWITCH_CASE,
ZEND_AST_DECLARE,
ZEND_AST_USE_TRAIT,
ZEND_AST_TRAIT_PRECEDENCE,
ZEND_AST_METHOD_REFERENCE,
ZEND_AST_NAMESPACE,
ZEND_AST_USE_ELEM,
ZEND_AST_TRAIT_ALIAS,
ZEND_AST_GROUP_USE,
/* 3 child nodes */
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_STATIC_CALL,
ZEND_AST_CONDITIONAL,
ZEND_AST_TRY,
ZEND_AST_CATCH,
ZEND_AST_PARAM,
ZEND_AST_PROP_ELEM,
ZEND_AST_CONST_ELEM,
/* 4 child nodes */
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_FOREACH,
};接下来我们开始分析a.php解析后得到的语法树
首先我们在terminal执行gdb php
然后执行set args a.php
b zend_compile
然后一直输入n,直到zend_compile_top_stmt的前一句
接下来执行以下语句
p (char *)((zend_ast_zval)(((zend_ast_list*)compiler_globals.ast).child[0].child[0].child[0])).val.value.str.val
得到a
p ((zend_ast_zval *)(((zend_ast_list)compiler_globals.ast).child[0].child[1])).val.value.lval
得到1
p (char *)((zend_ast_zval)(((zend_ast_list*)compiler_globals.ast).child[1].child[0])).val.value.str.val
得到:var_dump
p (char *)((zend_ast_zval)(((zend_ast_list)(((zend_ast_list)compiler_globals.ast).child[1].child[1])).child[0].child[0])).val.value.str.val
得到a
至于每一个child是什么,我们自己一个个去调试下,对应着代码就直到了。
查看opcode
查看php的安装目录,有一个phpdbg的文件,我们用它,可以很轻松的得到一个PHP文件有哪些opcode。
 |
|