php 调试指南(Xdebug版)(续)

欢迎转载,但请在开头或结尾注明原文出处【blog.chaosjohn.com】

前言

在前文 php 调试指南(Xdebug版) 开头,笔者吹了一句 ”吃透本文,没有人将比你更懂 Php Xdebug 调试“。没想到打脸来的如此之快,这才过了三四天,我发现今天的我比写前文时的我,更懂 了。

关于 PhpStorm 2020.3 和 Xdebug 3

在前文中,笔者用的还是 PhpStorm 2020.2.4,所以结合 Xdebug 3.0.0 在使用 Web Server Debug Validation 进行 调试环境验证 时,提示类似 Xdebug port is invalid 的报错,分析是 不兼容 3.0.0 版本 导致的。所以降级成了 Xdebug 2.9.8 才顺利写完了文章。

写完前文的第二天,PhpStorm 2020.3就发布了,新增

  • PHP 8 的支持
  • Xdebug 3 的支持

在重新用 PHPBrew 安装 PHP 8 + Xdebug 3.0.0 环境并且用 Nginx + php-fpm 部署起来后,Web Server Debug Validation 成功验证。

关于 Web Server Debug Validation 功能

笔者在前文写完后,在升级 PhpStorm 2020.3 之前,笔者重新安装了 PHP 7.4.13 + Xdebug 3.0.0 环境,在忽略 验证报错 的情况下直接开启调试,是完全没有任何问题的。所以,该功能只是 验证调试环境,在调试之前仅作参考,不影响实际调试。

关于 远程调试

笔者在写前文关于 远程调试 的几节时,参考了网上很多文章,看似吃透了,其实不然,原因有这样几点:

IDE 结合 Xdebug 调试的原理

  • IDE(本身或利用插件) 打开本地的 9000 端口并进行监听(Xdebug 2.X 默认为 9000,Xdebug 3.X 默认为 9003,但均可修改)
  • IDE(本身或利用插件) 做好路径映射(path mapping),即本地在IDE中打开的项目目录与远程服务器上的项目目录做一一映射,例如
    • macOS 本地:/Users/chaos/Work/php/demos/debug/
    • Linux 服务器:/home/chaos/Work/php/demos/debug/
  • 本地向服务器发送请求时带上 Cookie: XDEBUG_SESSION=IDEKEY
  • 服务器接受到请求时,经历了 Nginx -> php-fpm 后到达 XdebugXdebug 检测到 XDEBUG_SESSIONcookie,认为这条请求是带着 调试目的 来的,同时挂起 PHP解释器 进一步处理请求
  • 然后 Xdebugphp.ini 中获取目标地址或从 $_SERVER 里获取到请求的来源地址(比如 223.104.148.182)作为目标地址,然后就向目标地址的 9000 端口发起建立 调试连接
  • 本地的 IDE(本身或利用插件) 发现监听的 9000 端口有 调试连接 建立,判断一下 XDEBUG_SESSION 是否为自己预设的 IDE key
    • 如果不是预设 IDE key,通过 9000 端口上的 调试连接 告诉服务器的 Xdebug,“不归我管,我不处理”,然后双方协商一下断连接
    • 如果是预设 IDE key,同时发现本地打了断点或者本地设置了 "stopOnEntry": true (VSCode) / Break at first line in PHP scripts (PhpStorm),则告诉服务器的 Xdebug,“收到,调试准备就绪”,然后双方协商一下进入调试状态

真实环境下 远程调试陷阱

聪明的小伙伴可能已经发现上述 调试流程 里存在的一个 陷阱:即服务器要向本地请求建立 调试连接,但问题是,现在所有的 家用/企业 网络环境下,所谓的 本地 都在上级路由器的 NAT 下,根本就没有暴露在公网的 IP地址,所以 本地9000 端口对于服务器来说,是不可达的,想要访问,做梦!

填坑 陷阱

笔者重新模拟了真实的调试环境,即 本地macOS远程服务器 选了两台 Linux,一台是笔者在公司搭建的 物理机,另一台是公司购买的阿里云 云主机

填坑的最终目的,是要使得 本地9000 端口暴露给服务器,让其直接可达。

这里分:

  1. 本地和远程服务器同处一个局域网内,例如,都加入同一个 VPN 网络,本地通过 VPN 分配给服务器的 私网IP 访问服务器,服务器的 Xdebug 解析到的来源地址则也是通过 VPN 分配给本地的 私网IP,直接可达。
  2. 路由器本身从 ISP(宽带运营商) 通过 PPPoE 拨号 获取到了 公网IP,然后路由器上通过 端口映射DMZ 模式,将本地的 9000 端口,映射到路由器的 9000 端口,这样服务器也可通过 公网IP:9000 访问到本地的 9000 端口。(该方式最推荐,但是在国内可行度不高,因为国内 IP地址池 即将枯竭,所以很难从运营商处申请到 公网IP
  3. 其他环境只能借助 移花接木大法:借助 SSH 反向隧道,在本地和服务器之间建立一条 TCP通道,将本地的 9000 端口映射到服务器的 9000 端口。这样的话,服务器上的 Xdebug 访问 localhost:9000 就等于访问到了 IDE本地9000 端口。(借用 JetBrains 官方文档里的一副插图)
    JetBrains 官方文档关于 SSH 隧道的插图

在这里,笔者将前两种环境归纳为 回程网络直接可达,否则则为 回程网络不可直达

PhpStorm 的特殊配置

在分别罗列远程调试的具体参数配置之前,笔者还得额外将 PhpStorm 的特殊配置单独拎出来阐述一下。

PhpStorm 调试的 目标服务器,以 Server 的存在进行配置,具体位于 偏好设置Languages and Frameworks | PHP | Servers 下。详见 JetBrains 官方文档,原文是这样的:

On this page, configure HTTP access for debugging engines to interact with local and remote Web servers and set correspondence between files on the server and their local copies in the PhpStorm project.

在本页,为调试配置 HTTP 访问,用以在 本地远程 Web 服务器 之间交互,以及为 远程服务器的文件 与其在 PhpStorm 工程 中打开的 本地拷贝 设置关联。

PhpStorm 调试的特殊配置:在远程服务器上的 php-fpm 配置文件里添加这两行:

clear_env = no
env["PHP_IDE_CONFIG"] = "serverName=UbuntuServer"

这里 UbuntuServer 是自定义的服务器名称,自行更改。

然后在 PhpStorm 偏好设置的 Languages and Frameworks | PHP | Servers 添加一个 Server

  • nameUbuntuServer
  • HostPort 笔者亲测可填任意合法值,PhpStorm 不校验,所以笔者都填写了 0
  • Debugger 选择 Xdebug
  • 勾选上 Use path mappings,并且设置好 本地文件目录路径服务器文件目录路径 的映射(比如笔者本地的 /Users/chaos/Work/php/demos/debug/ 与服务器的 /home/chaos/Work/php/demos/debug/
PhpStorm 里配置 Server

VSCode 的配置

只需要比本地调试多配置一个 路径映射,即 pathMappings 键值对,附上 launch.json 文件内容:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for XDebug",
      "type": "php",
      "request": "launch",
      "port": 9000,
      // "stopOnEntry": true,
      "pathMappings": {
        "/home/chaos/Work/php/demos/debug/": "${workspaceRoot}/",
      }
    },
    {
      "name": "Launch currently open script",
      "type": "php",
      "request": "launch",
      "program": "${file}",
      "cwd": "${fileDirname}",
      "port": 9000
    }
  ]
}

这里的 "/home/chaos/Work/php/demos/debug/": "${workspaceRoot}/""/home/chaos/Work/php/demos/debug/": "/Users/chaos/Work/php/demos/debug/" 的简化形式,${workspaceRoot} 则为 VSCode 中打开的 项目根目录

服务器的 php.ini 在不同情况下的配置

情况一:回程网络直接可达

  • Xdebug 2.X
[xdebug]
xdebug.remote_enable=1
xdebug.remote_connect_back=1

| remote_enable=1 表示开启远程调试

| remote_connect_back=1 表示获取请求发起地址(从 $_SERVER['HTTP_X_FORWARDED_FOR']$_SERVER['REMOTE_ADDR'] 中获取),反向访问发起地址的 9000 端口建立调试连接

  • Xdebug 3.X
[xdebug]
xdebug.mode=debug
xdebug.discover_client_host=true
xdebug.port=9000

| mode=debug 取代了 2.X 版本的 remote_enable=1

| discover_client_host=true 取代了 2.X 版本的 xdebug.remote_connect_back=1

| port=9000 是个人喜好,在 3.X 中默认为 9003。笔者倾向于保持与 2.X 一致,这样同一份 IDE 配置可以同时调试 2.X 版本和 3.X 版本

情况二:回程网络不可直达

先用 SSH 隧道 反向将本地的 9000 端口映射到服务器的 9000 端口上

ssh -g -N -R 9000:127.0.0.1:9000 user@server
  • Xdebug 2.X (Method A)
[xdebug]
xdebug.remote_enable=1
xdebug.remote_host=127.0.0.1

| remote_host=127.0.0.1 表示直接向 127.0.0.1 这个地址请求建立调试连接

  • Xdebug 2.X (Method B)
[xdebug]
xdebug.remote_enable=1
xdebug.remote_connect_back=1
xdebug.remote_addr_header="HTTP_X_XDEBUG_REMOTE_ADDR"

| remote_connect_back=1 表示获取请求发起地址,反向访问发起地址的 9000 端口建立调试连接

| xdebug.remote_addr_header="HTTP_X_XDEBUG_REMOTE_ADDR" 表示优先从 _SERVER['HTTP_X_XDEBUG_REMOTE_ADDR'] 获取发起地址,获取不到再去 `_SERVER['HTTP_X_FORWARDED_FOR']$_SERVER['REMOTE_ADDR']` 中寻找

| 调试请求类似于 $ curl server.com:8000 -b XDEBUG_SESSION=IDEKEY -H "X-Xdebug-Remote-Addr: 127.0.0.1"

| 注意:HTTP- 发送请求时不用添加,因为对于未自定义的 headerNginx 会自动加上前缀 HTTP_,并全部大写,横线 转为 下划线,存入 $_SERVER 全局变量中

  • Xdebug 3.X (Method A)
[xdebug]
xdebug.mode=debug
xdebug.port=9000
xdebug.client_host=127.0.0.1

| client_host=127.0.0.1 表示直接告诉 Xdebug,发起地址就是 127.0.0.1,你往这个地址请求建立调试连接就可以了

  • Xdebug 3.X (Method B)
[xdebug]
xdebug.mode=debug
xdebug.port=9000
xdebug.discover_client_host=true
xdebug.client_discovery_header="HTTP_X_XDEBUG_REMOTE_ADDR"

| client_discovery_header="HTTP_X_XDEBUG_REMOTE_ADDR" 取代了 2.X 版本的 xdebug.remote_addr_header="HTTP_X_XDEBUG_REMOTE_ADDR"


其中 Xdebug 2.X (Method B)Xdebug 3.X (Method B) 还有些许不同:

  • Xdebug 2.X (Method B) 如果按配置规则没找到请求发起地址,它不会降级使用 127.0.0.1/localhost 作为请求发起地址
  • Xdebug 3.X (Method B) 如果按配置规则没找到请求发起地址,它会降级使用 127.0.0.1/localhost 作为请求发起地址

多人调试 - DBGp

先附上参考文档

这块我不仅会略过,我还会狠狠的吐槽一下。先来看一下上述 "Multiuser debugging via Xdebug proxies" 这篇文里的一张插图


PhpStorm 提供的 DBGp 原理图

看似很美妙是不是,在笔者下载了 dbgpProxy 并且反复实验后,发现这个工具真的是神坑。

笔者在那台阿里云上运行 ./dbgpProxy -i 0.0.0.0:9001 -s 127.0.0.1:9000 后,从本地的 PhpStorm 带着自定义IDE key "PS" 发起调试请求,结果 dbgpProxy 日志打印 Connecting to 112.3.2.42:9000,而笔者所在的本地宽带并没有公网IP,112.3.2.42 这个IP是多层 NAT 之前的IP,肯定访问不进来,只能上 SSH 隧道方案

阿里云运行 dbgpProxy

那这个 dbgpProxy 的意义何在呢?笔者在网上找到了 Xdebug 的作者 Derick Rethans(他同时设计了 DBGp 协议)的一篇文章 - Debugging with multiple users,在文中,他描绘了 DBDp 的使用场景:

Running a DBGp proxy also allows you to avoid NAT issues where (as seen from PHP+Xdebug on the server) all connections seem to come from the same IP (because your internal network is NATted). In this case, you can simple run the dbgp proxy on your NAT machine, configure xdebug.remote_host setting to the IP address of your NAT machine, and configure the IDEs to connect to the proxy running at <NAT-machine>:9001.

大致解释一下:

  • 你的路由器得从ISP处获取到公网IP
  • 在你的路由器上运行 dbgpProxy
  • 路由器下所有人的的 IDEDBGp Proxy 配置Host 填写路由器网关,即路由器内网地址

到这里,你可以发现,搭建 DBGp调试环境 非常苛刻,结合 公网IP路由器运行 dbgpProxy 这两点看,只有 软路由 可以满足,因为 dbgpProxy 的二进制可执行文件只有 Windows / macOS / Linux x86-64 版本。所以大部分人/公司 可以洗洗睡了。

结语

为了写这两篇文,笔者是翻来覆去做了很多实验,还整理了很多知识点,打了很多草稿(真正意义上的纸质草稿),最后附上最满意的一张草稿。


笔者最满意的一张草稿

希望读者们能有所收获,感谢阅读!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容