摘要:Xdebug远程调试原理与实践

1.前言

世界上有两种人:一种了解debug工具的好处,另一种则反之。
那么debug工具到底有哪些优点呢?

  • 在日常开发中洞悉大型架构的流程及设计;
  • 在日常开发中快速debug,省时省力,更能在debug过程中掌握程序运行流程及上下文;
  • debug驱动型开发习惯的养成

在PHP语言中有个叫xdebug的扩展,是业界最常用的PHP debug工具之一。xdebug有很多实用的特性,但本文只着重介绍远程调试这一特性的使用。

注意:

  • 本文提到的IDE,意即:集成开发环境,例如PHPStrom、Eclipse就是很常见的IDE
  • PHPStrom和IDE在本文中可能会被混用,但他们都代表PHPStrom

2.xdebug特性

  • var_dump展示优化 >link
  • PHP输出notice、error等信息时,调用栈可定制化展示 >link
  • 函数调用报告 >link
  • 代码覆盖率报告 >link
  • 程序分析报告(profiler)>link
  • 远程调试。本篇内容将着重介绍这一功能。如果你想获取有关如何配置可多人同时调试一台Web服务器的DBGp代理信息的话,也推荐你读完本文,这将对你后续的代理配置有一定帮助。

3.远程调试

xdebug的远程调试默认的通信协议为DBGp协议,它是基于XML文本传输的应用层协议,用于程序语言与IDE间的调试通信。

DBGP协议规定:IDE向程序的调试器发送的内容为ascii命令,调试器向IDE发送的内容为XML数据

xdebug远程调试原理与实践1.jpg

以下为基于DBGp协议进行debug的通信伪内容的示例。其中"DBG"是指程序调试器(比如xdebug);"IDE:"是指IDE向DBG(xdebug)发送消息";"DBG:"是指DBG(xdebug)向IDE响应消息

IDE:  feature_get supports_async
DBG:  yes
IDE:  breakpoint_set file_path,file_line_no
DBG:  ok
IDE:  stdin redirect
DBG:  ok
IDE:  stderr redirect
DBG:  ok
IDE:  run
DBG:  stdin data...
DBG:  stdin data...
DBG:  reached breakpoint, pause running
IDE:  get context variables
DBG:  ok, here they are
IDE:  evaluate this expression
DBG:  stderr data...
DBG:  ok, done
IDE:  stop
DBG:  good bye

由于DBGp协议是明文传输的,所以只需监听IDE的9000端口(IDE用于调试通信的端口)上的TCP通信,你便能一窥它的详细通信内容。如下方所示为局部通信内容截图。其中:172.17.0.2为Web服务器的IP,192.168.65.2是IDE所在开发机上的IP。

xdebug远程调试原理与实践2.jpg

3.1 xdebug调试(session)的生命周期

断点调试是有生命周期的。断点调试始于:手动或自动开启调试session,终止于:手动终止程序或程序自动运行退出。下方为xdebug的session状态流转图:

xdebug远程调试原理与实践3.jpg

xdebug调试的开始有以下两种方式

3.1.1 外部触发xdebug启动调试

  • 请求的GET、POST的query中包含XDEBUG_SESSION_START=sessName参数即可启动xdebug调试
  • Cookie中包含XDEBUG_SESSION=sessName即可启动xdebug调试

3.1.2 xdebug始终自启动调试(推荐使用)

配置文件中设置xdebug.remote_autostart=1,则PHP每次执行脚本都会启动xdebug调试(没错,在命令行直接运行任务脚本也会启动调试)

3.2 xdebug主动连接IDE的两种方式

xdebug的DBGp协议的第一步,是自xdebug启动调试session后,根据给定配置,主动连接IDE。在网上的相关教程中,我们往往会看到各式各样的xdebug.remote_host相关的配置,
那么这些配置到底是起了什么作用呢?

这里就要讲到xdebug主动连接IDE的两种不同方式了,因为xdebug.remote_host相关的配置就是为了让xdebug可以连接到IDE的9000端口上。

3.2.1 方式一:根据客户端请求IP连接IDE(设置xdebug.remote_connect_back=1)

xdebug连接IDE的方式一的时序图如下:

xdebug远程调试原理与实践4.png

说明:

  • 开发者发起URL请求后,xdebug通过解析$_SERVERmap中的HTTP_X_FORWARDED_FOR和REMOTE_ADDR字段获取开发者所在的IP地址
  • xdebug获取到开发者IP地址后,通过IP及IDE暴露的9000接口,与IDE建立连接,并进行后续通信
  • 发起URL请求的机器与IDE必须属于同一IP(即同一台机器)
  • 本方式适合IP变化频繁,或多个开发者共享同一台Web服务器等情况的使用

3.2.2方式二:根据指定IP连接IDE(设置xdebug.remote_host=x.x.x.x)

xdebug连接IDE的方式二的时序图如下:

xdebug远程调试原理与实践5.png

说明:

  • 以上两种连接方式,方式一优先级高于方式二。如下方配置所示,第二行配置将被忽略:

    xdebug.remote_connect_back = 1

    xdebug.remote_host = x.x.x.x

  • 如需使用方式二(固定IP),需将xdebug.remote_connect_back置为0
  • 第二种连接方式,适合开发机IP基本不变的开发者使用

3.3 PHPStrom配置xdebug调试

3.3.1 方式一(xdebug.remote_connect_back = 1)的配置

xdebug.ini中的设置:

zend_extension = xdebug.so 
xdebug.remote_enable = 1 ;开启远程调试 
xdebug.remote_autostart = 1 ;自动启动调试 
xdebug.remote_mode = req ;通过请求触发调试,另一种方式jit:遇到错误时触发 
xdebug.remote_connect_back = 1 ;使用方式一连接 
xdebug.remote_port = 9000 
xdebug.remote_handler = dbgp ;xdebug与PHPStorm的debug协议 
xdebug.idekey = "PHPSTORM" 

3.3.2 方式二(xdebug.remote_host = x.x.x.x)的配置

xdebug.ini中的设置:

zend_extension=xdebug.so 
xdebug.remote_enable = 1 
xdebug.remote_autostart = 1 
xdebug.remote_mode = req 
xdebug.remote_connect_back = 0 ;关闭方式一连接 
xdebug.remote_host = 192.168.35.103 ;使用方式二连接 
xdebug.remote_port = 9000 
xdebug.remote_handler = dbgp 
xdebug.idekey = "PHPSTORM" 

3.3.3 设置未生效?问题排查

有时我们已经按照教程设置好了一切,但打开IDE的断点调试,仍然没有任何效果。

我们会疑惑,程序为什么没有在运行到断点后暂停下来?除了检查各种配置是否正确,开发者还有哪些工具、信息可以拿来推断到底是哪一部分出了问题?

其实知道了上述连接原理后,我们可以通过TCP工具监听9000端口上的网络通信,通过通信的内容,我们可以把问题细化到由于以下原因,导致的xdebug调试未生效,进而逐个解决问题:

  • 9000端口的网络连接并未发起,则推断是xdebug端的配置有问题,有可能是:1)xdebug设置有误;2)xdebug扩展未加载;3)PHP-fpm未启动;4)Nginx未启动
  • 9000端口上发生了网络连接,但很快断掉了。有可能是:1)IDE的debug工具并未开启导致IDE的9000端口不可用;2)IDE的端口设置不是9000;3)Web服务器并不能与IDE所在网络的指定端口通信
  • 9000端口上发生了密密麻麻的网络通信。。。额,这个应该是IDE没有打断点吧?

在这之前,我们需要打开IDE的debug开关,在代码入口第一行打上断点,然后SSH进入Web服务器所在机器(通常是虚拟机,或docker容器),通过tcpdump工具进行问题排查。

tcpdump是TCP层通信的监听工具,由于DBGp协议是建立在TCP之上的应用层协议,所以可以通过更底层的工具监听DBGp协议的通信,上方截图中的DBGp协议局部通信内容,就是用tcpdump工具抓取的。

由于tcpdump一次只能监听一个网卡上的网络通信,所以需要通过ifconfig命令获取Web服务器的IP经由的网卡

通过ifconfig命令可以确定Web服务器的IP经由的网卡为eth1。下面开始debug环节

xdebug远程调试原理与实践6.png

3.3.4 深度自检:在PHPStrom中入口代码第一行打断点,为什么代码未在断点处暂停

这种情况是大部分新手和新安装xdebu后的开发者会遇到的问题。遇到这种问题,该如何排查呢?

1)确定xdebug是否向PHPStrom发起网络连接。如果没有,则需要依次检查xdebug的设置是否正确。

  • 在Web服务器命令行输入如下命令(注意网卡名称eth1应情况而异),开始监听xdebug在9000端口上的通信:tcpdump -i eth1 -nn -A -tttt port 9000
  • 输入上述命令后,在开发机中访问URL,然后回到Web服务器命令行,查看是否有网络请求发起。如果命令行没有任何输出,则说明xdebug配置有误。请开始步骤A自检:
    xdebug扩展是否加载?在phpinfo()中查看即可确定

xdebug设置是否按上方给出的两份设置正确填写?在phpinfo()中也可以看到这些信息,请查看每项设置是否有多余字符出现?
2)在完成步骤A自检后,重复1的操作,继续监听tcpdump -i eth1 -nn -A -tttt port 9000。命令行输出结果如下图所示。其中192.168.33.10的机器为Web服务器,192.168.33.1的机器为IDE所在开发机,从图中信息可以看出Web服务器主动向开发机上的9000端口发起连接请求,但开发机拒绝了该连接请求(Flags [R.])

xdebug远程调试原理与实践7.png

遇到这种情况,请开始步骤B自检:

  • curl -v telnet://192.168.33.1:9000。如果返回结果有couldn't connect to host ,则说明xdebug并不能使用给定IP端口与IDE通信。
    造成这的原因可能有:网络不可达(是否能ping通,如无法ping通,请换用其他可ping通的开发机IP,毕竟一个开发机可能有多个IP,最后使用方式二固定IP设置即可);

IDE端口设置并不是9000;
或者IDE并未打开调试模式,如何打开请看下图

xdebug远程调试原理与实践8.png

3)步骤B自检查完成后,再次在Web服务器命令行监听9000端口的通信:tcpdump -i eth1 -nn -A -tttt port 9000。这一次,大量的连接信息开始输出,恭喜你,xdebug已经设置好了

xdebug远程调试原理与实践9.png

4)PHPStrom中接收到xdebug的网络连接后,如何设置与WebServer上代码的一一匹配

xdebug远程调试原理与实践10.png

总结:遇到xdebug设置相关的问题,最重要的是了解debug底层通信协议和原理,再通过TCP/IP抓包工具(比如tcpdump)进行问题排查及定位。通过上述步骤的排查,应该可以定位无法debug原因,并最终成功使用xdebug调试代码了。祝顺利~

转载自:Xdebug远程调试原理与实践 | 蓝枫铭的技术博客,感谢作者