对于PHP开发者来说,单步断点Debug调试并不是我们的必修课,但是Java、C#、C++等静态语言会经常进行这种调试。调试。其实我们的PHP也支持这种调试方式,特别是对于了解一些开源框架,或者有很深层次的bug跟踪的时候,断点调试会非常有用。很多接触过PHP断点调试的人一定用过大名鼎鼎的XDebug。但我们今天要说的不是这个扩展,而是PHP官方源码中已经集成的另一个调试工具,最重要的是,它在调试时看到的是更底层的opcode执行过程。话不多说,下面直接进入phpdbg工具的学习!!phpdbg命令行功能在我们安装完PHP之后,phpdbg这个工具会默认可用。直接在命令行运行就会进入这个工具。%phpdbg[欢迎使用phpdbg,交互式PHP调试器,v0.5.0]要获得使用phpdbg的帮助,请键入“help”并按回车键[请向报告错误]否错了,默认是PHP安装自带的。如果你的环境变量中没有这个工具命令,你可以在PHP安装目录下的bin/目录下找到。进入phpdbg环境后,我们可以通过help查看其运行说明。prompt>helpphpdbg是一个轻量级、功能强大、易用的PHP5.4+调试平台,支持如下命令:InformationlistlistPHPsourceinfo显示调试会话信息printshowopcodesframeselectastackframe和printastackframesummarygeneratorshowactivegeneratorsorselectageneratorframebackshowsthecurrentbacktracehelpprovidehelponatopic...帮助文档很长,具体内容可以自行查看,有个帮助命令可以让我们看看许多缩写命令,我们主要使用这些缩写命令别名。prompt>helpaliases下面是所有支持的命令的别名,简短版本eexecsetexecutioncontextsstepstepstepthroughexecutionccontinuecontinueexecutionrrunattemptexecutionuuntilcontinuepastthecurrentl命令的介绍和查看很简单,然后我们如何调试PHP文件?这是我们最关心的。在调试一个文件的时候,我们需要把它加载到当前的执行环境中。可以使用e命令指定当前phpdbg环境下加载的文件,也可以使用-e指定运行phpdbg时加载的文件。%phpdbg-ePHPDebuginteractiveextension.php[欢迎使用phpdbg,交互式PHP调试器,v0.5.0]要获得使用phpdbg的帮助,请键入“help”并按回车键[请向][成功编译/Users/zhangyue/MyDoc/blog文章/dev-blog/php/202006/source/PHPDebug交互扩展.php]prompt>这里我们使用第二种方法,在启动时使用phpdbg,使用-e参数指定要加载的文件。正常的断点设置加载文件,进入命令行,我们就可以进行断点调试了。首先,我们使用代码设置断点。在上面的测试文件中,我们使用下面的方式来定义断点。echo111;phpdbg_break_file("PHPDebuginteractiveextension.php",3);echo222;phpdbg_break_file("PHPDebuginteractiveextension.php",6);phpdbg_break_file()函数是定义一个断点,它有两个参数,第一个参数为文件名,不能乱填。第二个参数是断点的行号。接下来,在命令行上,我们运行速记运行命令r两次。prompt>r111[Breakpoint#0addedat/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebuginteractiveextension.php:3]222[Breakpoint#1addedat/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebuginteractiveextension.php:6][脚本正常结束]提示>r[Breakpoint#0at/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php:3,hits:1]>00003:echo111;00004:phpdbg_break_file("PHPDebugInteractiveExtension.php",3);00005:prompt>可以看出,在第一次运行r时,phpdbg扫描了一次整个文件,输出了当前的两个断点信息。然后再次运行r,定位到第3行,也就是第一个断点的位置。接下来我们要进行单步调试,我们直接使用step的简写命令s。提示>s[L30x10ecae220ECHO111/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebug交互扩展.php]111[L40x10ecae240EXT_STMT/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php]>00004:phpdbg_break_file("PHPDebugInteractiveExtension.php",3);00005:00006:echo222;prompt>断点位置往下运行,果然符合我们的预期,一行一行的单步运行开始了。在上面的输出中,我们看到了操作码运行的状态。例如L30x10ecae220ECHO这一行,表示第3行执行了ECHO操作。是不是感觉很高大上。一路往下,最后我们结束这个断点调试,phpdbg环境退出run运行时。......prompt>s[Scriptendednormally]prompt>s[Notrunning]prompt>这样,一次调试就完成了。当我们不想在第一个断点处单步调试,想直接跳到下一个断点时,可以使用continue的简写命令c,直接跳到下一个断点。提示>r你真的要重新开始执行吗?(typeyorn):y[Breakpoint#0at/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php:3,hits:1]>00003:echo111;00004:phpdbg_break_file("PHPDebug交互扩展.php",3);00005:prompt>c111[Breakpointat/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php:3exists][Breakpoint#1at/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php:6,hits:1]>00006:echo222;00007:phpdbg_break_file("PHPDebugInteractiveExtension.php",6);00008:prompt>另一个命令是直接查看当前加载的文件环境中的所有断点信息。提示>信息中断----------------------------------------------文件断点:#0/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebug交互扩展.php:3#1/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebuginteractiveextension.php:6以上是简单的行断点设置和调试步骤。当然,我们只是测试一个简单的文件。对于一个复杂的框架系统,断点的设置和调试会复杂很多。但是,相应的,我们可以看到底层opcode代码的执行情况,也可以让我们对测试的内容有更深入的了解。方法断点和运行步骤分析接下来我们设置一个方法断点,一步步观察操作码。$i=1;//phpdbg-ePHP\调试交互扩展.phpfunctiontestFunc(){global$i;$i+=3;回声“这是testFunc!我:”。$i,PHP_EOL;}testFunc();phpdbg_break_function('testFunc');在PHP代码中,我们使用phpdbg_break_function()为这个testFunc()方法设置断点。当代码中调用这个函数时,就会进入这个断点。prompt>r[Breakpoint#0intestFunc()at/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebuginteractiveextension.php:11,hits:1]>00011:functiontestFunc(){00012:全局$i;00013:$i+=3;prompt>s[L120x109eef620EXT_STMT/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php]>00012:global$i;00013:$i+=3;00014:echo"ThisistestFunc!i:".$i,PHP_EOL;prompt>s[L120x109eef640BIND_GLOBAL$i"i"/Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug交互扩展.php][L130x109eef660EXT_STMT/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebuginteractiveextension.php]>00013:$i+=3;00014:回声“这是testFunc!我:".$i,PHP_EOL;00015:}直接执行两个s单步,可以看到global$i对应的opcode操作为BIND_GLOBAL,继续向下操作。提示>s[L130x109eef680ASSIGN_ADD$i3/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebug交互扩展.php][L140x109eef6a0EXT_STMT/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php]>00014:echo"ThisistestFunc!i:".$i,PHP_EOL;00015:}00016:prompt>s[L140x109eef6c0CONCAT"ThisistestFunc!"+$i~1/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebuginteractiveextension.php][L140x109eef6e0ECHO~1/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php]这是testFunc!我:4[L140x109eef700EXT_STMT/Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug交互扩展.php][L140x109eef720ECHO"\n"/Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug交互扩展.php][L150x109eef740EXT_STMT/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebug交互扩展.php]>00015:}00016:00017:testFunc();第13行执行$i+=3的操作,对应的opcode操作为ASSIGN_ADD,相加值为3。继续s后,执行第14行。注意这里有两步操作。一旦是CONCAT,一旦是ECHO,然后代码正常输出打印语句。从上面的调试步骤,可以清楚的看到PHP在opcode层面的一步步执行情况。就像XDebug一样,每次执行都会有相关的变量和运行信息输出。类函数断点设置类函数的断点设置其实和上面的方法断点函数是一样的,非常简单方便。classA{functiontestFuncA(){echo"ThisisclassAtestFuncA!",PHP_EOL;}}$a=newA;$a->testFuncA();phpdbg_break_method('A','testFuncA');这里就不贴调试代码了,大家可以自己试试。在命令行中添加断点除了在PHP代码中给出固定的断点外,我们还可以在命令行中添加断点,比如我们去掉之前的方法断点功能。然后在命令行指定给方法加断点。prompt>btestFunc#3[Breakpoint#1addedattestFunc#3]3这是什么意思?其实就是说我们在方法体内部的第3行加了一个断点。即我们在$i+=3;这行加了一个断点;行数从方法定义的行开始计算,从1开始,如果不加这个行号,则直接从方法定义的行开始。prompt>r[断点#0在testFunc#3(opline0x1050ef660)处解决][断点#0在testFunc#3(opline0x1050ef660)处解决][断点#0在testFunc#3(opline0x1050ef660)处解决][断点#0已解决attestFunc#3(opline0x1050ef660)][testFunc()#3中的断点#3at/Users/zhangyue/MyDoc/blogarticle/dev-blog/php/202006/source/PHPDebugInteractiveExtension.php:13,命中:1]>00013:$i+=3;00014:echo"ThisistestFunc!i:".$i,PHP_EOL;00015:}执行r之后,我们直接定位到testFun()方法中的第三行。小结今天我们只是简单学习了phpdbg这个工具的使用方法。从help命令可以看出,这个工具还有很多选项参数,可以帮助我们完成很多调试工作。这里只是和大家做个介绍,以后在学习的过程中再次接触到的时候会继续深入研究。测试代码:https://github.com/zhangyue0503/dev-blog/blob/master/php/202006/source/PHPDebug%E4%BA%92%E5%8A%A8%E6%89%A9%E5%B1%95.php参考文档:https://www.php.net/manual/zh/intro.phpdbg.php各媒体平台均可搜索【硬核项目经理】