dec++如何查看机器指令_如何查看和调试PHP源码(转存

很多人都想看PHP的源码,但是由于缺乏正确的方法,浅尝辄止,止步于想法。今天我们就浅谈一下(我研究也不深,只是把我所知道的与大家探讨一下)。

第一步:下载源码:https://github.com/php/php-src

第二步:编译:
如何编译PHP请查阅官方文档
编译的时候请添加–debug=1的参数,这样才能更好的调试代码,不加的话,很多地方打断点无效,应该是被编译器优化掉了。

第三步:安装编辑器:
推荐clion,真正的高手都是用vim写c代码,查看c代码。我等不属于那一类人,还是老老实实用编辑器,个人看了好多个编辑器,感觉clion是最好的。
不过用clion存在一个问题,就是它吧cmake作为代码结构的解析工具,所以没有CMakeLists.txt的项目,它是没法去解析的,也就是找不到代码追踪关系,只能硬看。不过可以自己添加一个CMakeLists.txt。
大家可以看一下我整理的CMakeLists.txthttps://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

4f617504d0ef915613b1eed98d1c3ca3.png

接下来我们一步步调试PHP代码,看看每一个步骤的代码该如果去调试,为了简单,我们使用php cli模式来调试(单进程好调试,fpm至少得有两个进程,一个master一个worker)

介绍一下代码的目录结构:
Zend:zend 引擎最核心的代码:比如编译、数组,对象的实现
main:一些最基础的关于协议之类的代码:比如fastcgi的接口
ext:扩展的类或者方法,比如pdo,pcre等等,里面有个目录非常特殊,standard,许多很多核心函数也保存在里面。

我们先来尝个鲜
比如var_dump,我们编辑一个最简单的文件a.php
<?php
$a = 1;
var_dump($a);

07421bbb8b107fad89bfb201bc9a5095.png

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

8c9460f8001574096f18436f1b0d7fd1.png

gdb 执行p args[0]

3b14bbd54db26f271a9c9863376f9f4e.png

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

377cbb1a32feae08a16d30c9ce282000.png

我们再看zval的结构

632f102d4f38be82a45144cf70bd7ad9.png

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

19a42844d38f3dd9ab694276ed550524.png

得到要var_dump的对象$a的值为1

接下来我们来解释为什么是zif前缀了

9fc670015b6888034ac8f7a19be2f913.png

536da5527b974b68031bed5aaa97b518.png

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

8b8d4ee2a6d9b802bf5e5e8d2645c83f.png

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

a327b7b4faac455cb3ca5611dd6b105e.png

语法树叶子节点的种类在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。

40bbbb1a2edbf0f4012c0e8105259aa5.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值