对于 PHP 开发者来说,单步的断点 Debug 调试并不是我们的必修课,而 Java 、 C# 、 C++ 这些静态语言则会经常性地进行这种调试。其实,我们 PHP 也是支持这类调试方式的,特别是对于了解一些开源框架,或者有非常深层次的 Bug 跟踪时,断点调试会非常有用。

不少接触过 PHP 断点调试的一定都用过鼎鼎大名的 XDebug 。不过我们今天讲的并不是这款扩展,而是另一个已经集成到 PHP 官方源码中的调试工具,并且,最重要的是,它调试时看到的内容是更为底层的 opcode 执行过程。话不多说,我们直接进入到 phpdbg 这款工具的学习中吧!!

phpdbg 命令行功能

在我们安装好 PHP 后,默认就有了 phpdbg 这个工具。直接在命令行运行就会进入这个工具。

% phpdbg
[Welcome to phpdbg, the interactive PHP debugger, v0.5.0]
To get help using phpdbg type "help" and press enter
[Please report bugs to <http://bugs.php.net/report.php>]

没错,它就是随 PHP 安装的时候默认自带的,如果你的环境变量中没有这个工具命令的话,可以在 PHP 安装目录的 bin/ 目录下面找到。

在进入 phpdbg 环境后,我们使用 help 就可以查看它的操作说明。

prompt> help

phpdbg is a lightweight, powerful and easy to use debugging platform for PHP5.4+
It supports the following commands: Information
list list PHP source
info displays information on the debug session
print show opcodes
frame select a stack frame and print a stack frame summary
generator show active generators or select a generator frame
back shows the current backtrace
help provide help on a topic
……

帮忙文档非常长,大家可以自己查看具体的内容,其中有一个 help 命令可以让我们看到许多简写的命令,我们主要使用这些简写的命令别名就可以。

prompt> help aliases
Below are the aliased, short versions of all supported commands
e exec set execution context
s step step through execution
c continue continue execution
r run attempt execution
u until continue past the current l

命令的简介和查看都很简单,那么我们要如何来调试 PHP 文件呢?这个才是我们最关心的事情。在调试一个文件的时候,我们需要将它载入到当前的执行环境中。可以在当前 phpdbg 环境中使用 e 命令指定文件进行载入,也可以在运行 phpdbg 的时候通过 -e 来指定需要载入的文件。

% phpdbg -e PHPDebug互动扩展.php
[Welcome to phpdbg, the interactive PHP debugger, v0.5.0]
To get help using phpdbg type "help" and press enter
[Please report bugs to <http://bugs.php.net/report.php>]
[Successful compilation of /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
prompt>

这里我们使用的是第二种方式,在启动 phpdbg 时使用 -e 参数来指定需要载入的文件。

普通断点设置

载入了文件,进入了命令行,我们就可以进行断点调试了。首先,我们使用代码方式来设置断点。在上面的测试文件中,我们使用下面的方式来定义断点。

echo 111;
phpdbg_break_file("PHPDebug互动扩展.php", 3); echo 222;
phpdbg_break_file("PHPDebug互动扩展.php", 6);

phpdbg_break_file() 函数就是来定义断点的,它有两个参数,第一个参数是文件名,这个不能乱填。第二个参数是断点的行号。

接下来,在命令行中,我们运行两次简写的 run 命令 r 。

prompt> r
111
[Breakpoint #0 added at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:3]
222
[Breakpoint #1 added at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:6]
[Script ended normally] prompt> r
[Breakpoint #0 at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:3, hits: 1]
>00003: echo 111;
00004: phpdbg_break_file("PHPDebug互动扩展.php", 3);
00005:
prompt>

可以看出,在第一次运行 r 的时候, phpdbg 将整个文件进行了一次扫描并输出了当前的两个断点信息。然后再运行一次 r 则定位到了第3行,也就是第一个断点的位置。接下来,我们就要进行单步调试了,我们直接使用 step 的简写命令 s 。

prompt> s
[L3 0x10ecae220 ECHO 111 /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
111
[L4 0x10ecae240 EXT_STMT /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
>00004: phpdbg_break_file("PHPDebug互动扩展.php", 3);
00005:
00006: echo 222;
prompt>

断点位置向下运行了,果然是符合我们的预期,开始了一行一行的单步运行。在上面输出的内容中,我们看到了 opcode 运行的状态。比如 L3 0x10ecae220 ECHO 这一行指的就是第 3 行执行了 ECHO 操作。是不是感觉非常高大上。

一路 s 下来,走到最后我们就结束了这次断点调试,phpdbg 环境将退出 run 运行时。

……
prompt> s
[Script ended normally]
prompt> s
[Not running]
prompt>

这样,一趟调试就完成了。当我们在第一个断点不想单步调试,想直接进入下一个断点,就可以使用 continue 的简写命令 c 来直接跳到下一个断点。

prompt> r
Do you really want to restart execution? (type y or n): y
[Breakpoint #0 at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:3, hits: 1]
>00003: echo 111;
00004: phpdbg_break_file("PHPDebug互动扩展.php", 3);
00005: prompt> c
111
[Breakpoint at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:3 exists]
[Breakpoint #1 at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:6, hits: 1]
>00006: echo 222;
00007: phpdbg_break_file("PHPDebug互动扩展.php", 6);
00008:
prompt>

另外还有一个命令就是可以直接看到当前载入的文件环境中的所有断点信息的。

prompt> info break
------------------------------------------------
File Breakpoints:
#0 /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:3
#1 /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:6

以上,就是一个简单的行断点设置以及调试步骤。当然,我们只是针对一个简单文件的测试,对于复杂的框架型系统,断点的设置和调试就会复杂很多,不过相应地,我们能够看到底层的 opcode 代码的执行情况,也能让我们对所测试的内容有更加深入的了解。

方法断点及运行步骤分析

接下来我们来设置一个方法断点,并一步步观察 opcode 的情况。

$i = 1;
// phpdbg -e PHP\ Debug互动扩展.php
function testFunc(){
global $i;
$i += 3;
echo "This is testFunc! i:" . $i, PHP_EOL;
} testFunc();
phpdbg_break_function('testFunc');

在 PHP 代码中,我们使用 phpdbg_break_function() 来给这个 testFunc() 方法设置一个断点。当代码中调用这个函数的时候,就会进入这个断点中。

prompt> r
[Breakpoint #0 in testFunc() at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:11, hits: 1]
>00011: function testFunc(){
00012: global $i;
00013: $i += 3; prompt> s
[L12 0x109eef620 EXT_STMT /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
>00012: global $i;
00013: $i += 3;
00014: echo "This is testFunc! i:" . $i, PHP_EOL; prompt> s
[L12 0x109eef640 BIND_GLOBAL $i "i" /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
[L13 0x109eef660 EXT_STMT /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
>00013: $i += 3;
00014: echo "This is testFunc! i:" . $i, PHP_EOL;
00015: }

直接进行了两次 s 单步,可以看到 global $i 对应的 opcode 操作是 BIND_GLOBAL 。继续向下操作。

prompt> s
[L13 0x109eef680 ASSIGN_ADD $i 3 /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
[L14 0x109eef6a0 EXT_STMT /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
>00014: echo "This is testFunc! i:" . $i, PHP_EOL;
00015: }
00016: prompt> s
[L14 0x109eef6c0 CONCAT "This is testFunc!"+ $i ~1 /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
[L14 0x109eef6e0 ECHO ~1 /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
This is testFunc! i:4
[L14 0x109eef700 EXT_STMT /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php]
[L14 0x109eef720 ECHO "\n" /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php] [L15 0x109eef740 EXT_STMT /Users/zhangyue/MyDoc/博客文章/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 一样,每一次的执行都会有相关的变量、操作的信息输出。

类函数断点设置

类函数的断点设置其实就和上面的方法断点函数一样,非常的简单方便。

class A{
function testFuncA(){
echo "This is class A testFuncA!", PHP_EOL;
}
}
$a = new A;
$a->testFuncA();
phpdbg_break_method('A', 'testFuncA');

这里就不贴出调试的代码了,大家可以自己尝试一下。

命令行增加断点

除了在 PHP 代码中给出固定的断点之外,我们还可以在命令行中进行断点的增加,比如我们去掉之前的方法断点函数。然后在命令行中指定在方法中增加一个断点。

prompt> b testFunc#3
[Breakpoint #1 added at testFunc#3]

3 这是什么意思呢?其实就是说我们在这个方法体内部的第 3 行增加一个断点。也就是说,我们在 $i += 3; 这一行增加了一个断点。行数是从方法定义那一行开始算的并且是从 1 开始,如果不加这个行数,就是直接从方法定义那一行开始。

prompt> r
[Breakpoint #0 resolved at testFunc#3 (opline 0x1050ef660)]
[Breakpoint #0 resolved at testFunc#3 (opline 0x1050ef660)]
[Breakpoint #0 resolved at testFunc#3 (opline 0x1050ef660)]
[Breakpoint #0 resolved at testFunc#3 (opline 0x1050ef660)]
[Breakpoint #0 in testFunc()#3 at /Users/zhangyue/MyDoc/博客文章/dev-blog/php/202006/source/PHPDebug互动扩展.php:13, hits: 1]
>00013: $i += 3;
00014: echo "This is testFunc! 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

关注公众号:【硬核项目经理】获取最新文章

添加微信/QQ好友:【xiaoyuezigonggong/149844827】免费得PHP、项目管理学习资料

知乎、公众号、抖音、头条搜索【硬核项目经理】

B站ID:482780532

PHPDebug互动扩展【phpdbg】功能浅析的更多相关文章

  1. BrnShop开源网上商城第六讲:扩展视图功能

    在正式讲解扩展视图功能以前,我们有必要把视图的工作原理简单说明下.任何一个视图都会被翻译成一个c#类,并保存到指定的位置,然后被编译.这也就是为什么能在视图中包含c#代码片段的原因.下面我们通过一个项 ...

  2. 关于ligerui 中 grid 表格的扩展搜索功能在远程数据加载时无法使用的解决办法

    要想使用grid里的扩展搜索功能,除了要引用ligerui主要的js文件外,还必须引入下面的JS文件: 1.Source\demos\filter\ligerGrid.showFilter.js 2. ...

  3. php扩展Redis功能

    php扩展Redis功能 1 首先,查看所用php编译版本V6/V9 在phpinfo()中查看 2 下载扩展 地址:https://github.com/nicolasff/phpredis/dow ...

  4. 沉淀,再出发:Docker的功能浅析

    沉淀,再出发:Docker的功能浅析 一.前言 这段时间一直在使用docker,发现docker的技术有很多,从最开始的将自己的程序打包成docker中的镜像,然后上传和下载镜像并使用,再到后来的在集 ...

  5. DEVOPS技术实践_17:Jenkins使用扩展邮件功能发送邮件

    一 环境准备 1.1 安装插件Email Extension 系统管理-管理插件-安装Email Extension插件 1.2 配置 配置jenkins邮箱的全局配置:系统管理-系统设置-完成邮箱配 ...

  6. 利用Mixins扩展类功能

    8.18 利用Mixins扩展类功能 - python3-cookbook 3.0.0 文档 https://python3-cookbook.readthedocs.io/zh_CN/latest/ ...

  7. JavaScript扩展原型链浅析

    前言 上文对原型和原型链做了一些简单的概念介绍和解析,本文将浅析一些原型链的扩展. javaScript原型和原型链 http://lewyon.xyz/prototype.html 扩展原型链 使用 ...

  8. 适用于 Windows 的虚拟机扩展和功能

    Azure 虚拟机扩展是小型应用程序,可在Azure 虚拟机上提供部署后配置和自动化任务. 例如,如果虚拟机要求安装软件.防病毒保护或进行 Docker 配置,便可以使用 VM 扩展来完成这些任务. ...

  9. EnyimMemcached扩展 遍历功能

    Memcached本身对外提供的命令不多,也就add.get.set.incr.decr.replace.delete.stats等几个,客户端对这些操作进行了封装,总体来说调用还是很简单的. 初看了 ...

随机推荐

  1. git submodule 操作

    git submodule foreach git status 举一反三,对所有子库的操作,都可以使用 git submodule foreach 做前缀 foreach,可以记忆为for each ...

  2. 流动的观察者模式 | Flutter 设计模式

    观察者模式,又称发布订阅模式,是一种行为设计模式--你可以定义一种订阅机制,可在对象事件发生时通知多个 观察 该对象的其他对象. 观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主 ...

  3. stm32 串口接收一次后再也无法接受,接受都为0

    经检测为串口接受后进入别的程序,开辟了2048的临时数组,因为堆栈溢出.stm32总共堆栈为

  4. 题解 P3942 将军令

    题解 首先看到这题 \(k=1\) 时,就是一道 小胖守皇宫,那么由 \(k=1\) 联想到 \(k=2...20\) 发现可以树形 \(DP\) 但转移方程太难想,不太适合考场做. 考虑贪心: 对所 ...

  5. noip 模拟 6

    果然考试一多就改不过来了 考试经过 上来看题,T1似乎是一个计数题,但看见1e9的数据范围就觉得不可做,拿了20部分分匆忙跑路 T2是个图论题,不过一看统计种类就发现是自己不会的东西,瞄准30分冲了一 ...

  6. 基于WindowsService的WebSocket编程Demo

    一.什么是WebSocket WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器与服务器全双工(full-duplex)通信--允许服务器主动发送信息给客户端.说了半天也就是说有了它 ...

  7. CSS基本语法(慕课网学习笔记)

    CSS的声明,内外联样式以及CSS的优先级 css学习.html <!DOCTYPE html> <html lang="en"> <head> ...

  8. http请求包含哪几个部分(请求行、请求头、请求体)

    http协议报文     1.请求报文(请求行/请求头/请求数据/空行)         请求行             求方法字段.URL字段和HTTP协议版本             例如:GET ...

  9. 前端axios请求二进制数据流转换生成PDF文件空白问题(终极解决方案)

    本文章共1570字,预计阅读时间1 - 3分钟. 问题场景: axios请求二进制数据转换生成PDF空白问题,使用axios请求后端接口,后端返回的二进制流文件,需要转换成PDF,但是在postman ...

  10. AQS实现原理

    AQS实现原理 AQS中维护了一个volatile int state(共享资源)和一个CLH队列.当state=1时代表当前对象锁已经被占用,其他线程来加锁时则会失败,失败的线程被放入一个FIFO的 ...