Skip to content

PHPDBG 调试器

PHPDBG 是 PHP 官方内置的交互式调试器,作为 PHP 的一个 SAPI(Server Application Programming Interface)随 PHP 一起编译安装。与 Xdebug 的断点调试不同,PHPDBG 采用命令行交互方式,更适合快速排查脚本问题、分析执行流程和内存使用情况。PHPDBG 自 PHP 5.4 起引入,在 PHP 8.x 中持续改进。

前置知识

  • 已完成 PHP 的基本安装(参考 Unix/macOS 安装
  • 了解 PHP 脚本的执行流程和调试基本概念
  • 熟悉命令行操作和交互式程序

基础概念

PHPDBG 是什么

PHPDBG 是一个通过命令行交互的 PHP 调试器,它的特点包括:

  • 内置:随 PHP 源码一起发布,无需额外安装
  • 轻量:相比 Xdebug 的远程调试,PHPDBG 没有网络通信开销
  • 交互式:通过命令行交互控制执行流程
  • CLI 专用:仅适用于命令行脚本调试,不支持 Web 请求调试

PHPDBG vs Xdebug

对比项PHPDBGXdebug
安装方式PHP 内置(编译时启用)独立扩展(PECL 安装)
调试方式命令行交互式IDE 集成(GUI)
适用场景CLI 脚本调试Web 应用 + CLI 调试
断点功能基本断点、条件断点高级断点、调用栈断点
性能分析简单的内存分析完整的 Profiling(Cachegrind)
学习曲线较低(命令行友好)较高(需要 IDE 配合)
远程调试不支持支持
性能开销中等(debug 模式)

使用场景选择

  • CLI 脚本快速调试:PHPDBG 更方便,无需 IDE 即可快速排查问题
  • Web 应用调试:Xdebug 是唯一选择
  • 性能分析:Xdebug 的 Profiling 功能更全面
  • 快速原型验证:PHPDBG 的交互式环境更灵活

安装配置

检查 PHPDBG 是否可用

bash
# 检查 PHPDBG 是否已编译
phpdbg -v

# 输出示例:
# phpdbg 1.1.1 (Jun  4 2024 10:00:00)
# PHP 8.2.20, Copyright (c) The PHP Group

如果提示 phpdbg: command not found,说明 PHP 编译时未启用 PHPDBG。

编译安装 PHPDBG

bash
# 从源码编译 PHP 时启用 PHPDBG
# 在 ./configure 时添加 --enable-phpdbg

./configure \
    --prefix=/usr/local/php \
    --enable-phpdbg \
    # ... 其他选项

make -j$(nproc)
sudo make install

# 验证
phpdbg -v

通过包管理器安装

bash
# Ubuntu/Debian
# PHPDBG 通常已包含在 php-cli 包中
sudo apt install php8.2-cli -y
phpdbg -v

# macOS (Homebrew)
brew install php@8.2
phpdbg -v

# CentOS/RHEL
sudo dnf install php-cli -y
phpdbg -v

交互式调试

启动 PHPDBG

bash
# 方式 1:直接启动 PHPDBG(交互模式)
phpdbg

# 方式 2:调试指定脚本
phpdbg script.php

# 方式 3:传递参数给脚本
phpdbg script.php arg1 arg2

# 方式 4:通过 stdin 执行代码
echo '<?php echo "Hello\n";' | phpdbg

常用命令

PHPDBG 提供了一组简洁的命令来控制执行流程:

执行控制

# 执行程序(run)
run               # 运行到结束
r                 # run 的简写
run arg1 arg2     # 带参数运行

# 单步执行
step              # 单步执行(进入函数内部)
s                 # step 的简写
next              # 单步执行(不进入函数)
n                 # next 的简写
until             # 执行到指定行或循环结束
u                 # until 的简写
continue          # 继续执行到下一个断点
c                 # continue 的简写
finish            # 执行到当前函数返回
f                 # finish 的简写
leave             # 执行并跳出当前函数
q                 # leave 的简写

断点管理

# 设置断点
break script.php:10        # 在指定文件的指定行设置断点
b script.php:10            # 简写

# 条件断点
break script.php:10 if $x > 5

# 在函数入口设置断点
break func_name

# 方法断点
break ClassName::methodName

# 临时断点(命中一次后自动删除)
tbreak script.php:10

# 查看所有断点
info break                 # 列出所有断点

# 删除断点
break del 1                # 删除编号为 1 的断点
break clear script.php:10  # 删除指定位置的断点

变量检查

# 打印变量值
print $variableName
p $variableName

# 打印表达式
print strlen($string)
p count($array)

# 打印对象属性
print $object->property

# 打印静态属性
print ClassName::$staticProp

# 列出所有变量
info vars                  # 列出当前作用域的所有变量
info locals                # 列出局部变量
info globals               # 列出全局变量

# 查看调用栈
backtrace                  # 打印当前调用栈
bt                        # 简写

其他实用命令

# 查看源代码
list                       # 查看当前行周围的代码
l                          # 简写
list 50                    # 查看第 50 行附近的代码
list function_name         # 查看函数源码

# 执行 PHP 代码
eval echo $variable;       # 在当前上下文中执行 PHP 代码
ev print_r($array);        # 简写

# 查看类信息
info classes               # 列出所有已加载的类
info class ClassName       # 查看类的详细信息
info methods ClassName     # 查看类的所有方法

# 查看函数信息
info functions             # 列出所有已定义的函数

# 帮助
help                       # 查看所有命令
help break                 # 查看 break 命令的详细帮助

# 退出 PHPDBG
quit                        # 退出
q                           # 简写

实战示例

调试一个 PHP 脚本

创建一个用于调试的脚本:

php
<?php
declare(strict_types=1);

// debug-example.php
function calculatePrice(float $basePrice, int $quantity, float $discount): float
{
    $subtotal = $basePrice * $quantity;
    $discountAmount = $subtotal * $discount;
    $finalPrice = $subtotal - $discountAmount;

    return $finalPrice;
}

function formatPrice(float $price): string
{
    return '$' . number_format($price, 2);
}

$items = [
    ['name' => 'Widget A', 'price' => 29.99, 'quantity' => 3, 'discount' => 0.1],
    ['name' => 'Widget B', 'price' => 49.99, 'quantity' => 1, 'discount' => 0.0],
    ['name' => 'Widget C', 'price' => 9.99, 'quantity' => 10, 'discount' => 0.2],
];

$totalPrice = 0.0;

foreach ($items as $item) {
    $itemTotal = calculatePrice(
        $item['price'],
        $item['quantity'],
        $item['discount']
    );
    $totalPrice += $itemTotal;
    echo sprintf(
        "%s: %s (x%d, %.0f%% off)\n",
        $item['name'],
        formatPrice($itemTotal),
        $item['quantity'],
        $item['discount'] * 100
    );
}

echo "---\n";
echo "Total: " . formatPrice($totalPrice) . "\n";

使用 PHPDBG 调试此脚本:

bash
# 启动 PHPDBG
phpdbg debug-example.php

# PHPDBG 交互式调试过程:
prompt> break debug-example.php:11
# 在 calculatePrice 函数的第一行设置断点

prompt> run
# 运行脚本,会在断点处暂停

prompt> print $basePrice
# 查看参数值

prompt> print $quantity
prompt> print $discount

prompt> next
# 单步执行,不进入子表达式

prompt> print $subtotal
# 查看 subtotal 的值

prompt> continue
# 继续执行,到下一次断点命中

prompt> quit
# 退出调试

使用条件断点

bash
phpdbg script.php

# 设置条件断点:只在 price > 30 时暂停
prompt> break script.php:30 if $item['price'] > 30.0

# 运行脚本
prompt> run

# 只有当 $item['price'] > 30 的条件满足时才会暂停

调试内存问题

php
<?php
declare(strict_types=1);

// memory-test.php
function generateLargeArray(int $size): array
{
    $data = [];
    for ($i = 0; $i < $size; $i++) {
        $data[] = str_repeat('x', 1024); // 每个元素 1KB
    }
    return $data;
}

function processData(array $data): string
{
    $result = '';
    foreach ($data as $item) {
        $result .= $item;
    }
    return $result;
}

$memoryBefore = memory_get_usage();
$data = generateLargeArray(5000);
$memoryAfter = memory_get_usage();

echo "内存使用: " . number_format($memoryAfter - $memoryBefore) . " bytes\n";
echo "数组大小: " . count($data) . "\n";

processData($data);
echo "处理后内存: " . number_format(memory_get_usage()) . " bytes\n";
bash
phpdbg memory-test.php

prompt> break memory-test.php:8
prompt> run
prompt> print memory_get_usage()
prompt> next
prompt> next
prompt> next
prompt> next
prompt> print memory_get_usage()
prompt> continue

使用 eval 在调试中执行代码

prompt> ev echo "Current memory: " . memory_get_usage(true) . "\n";
# 在当前上下文中执行任意 PHP 代码

prompt> ev $data = array_slice($data, 0, 10); print_r($data);
# 修改变量并查看结果

prompt> ev var_dump($GLOBALS);
# 查看全局变量

与 Xdebug 的对比

各自优势

PHPDBG 的优势

  • 无需额外安装,PHP 内置
  • 轻量,没有网络通信开销
  • 命令行操作,适合 SSH 远程调试
  • 启动速度快

Xdebug 的优势

  • IDE 集成,图形化调试体验
  • 支持断点调试 Web 请求
  • 完整的性能分析功能(Cachegrind 格式)
  • 代码覆盖率分析
  • 浏览器触发调试

组合使用建议

┌─────────────────────────────────────┐
│          PHP 调试工具选择            │
├─────────────────┬───────────────────┤
│  CLI 脚本调试    │  Web 应用调试      │
│                 │                   │
│  PHPDBG (快速)  │  Xdebug (必须)     │
│  Xdebug (详细)  │                   │
├─────────────────┼───────────────────┤
│  快速检查变量    │  性能分析           │
│                 │                   │
│  PHPDBG eval    │  Xdebug Profiler  │
├─────────────────┼───────────────────┤
│  服务器上调试     │  IDE 开发调试       │
│                 │                   │
│  PHPDBG         │  Xdebug + IDE     │
└─────────────────┴───────────────────┘

注意事项

PHPDBG 的限制

  1. 仅 CLI:PHPDBG 只能调试命令行脚本,无法调试 Web 请求
  2. 无远程调试:不支持远程调试,必须在执行脚本的服务器上操作
  3. 无 GUI:纯命令行界面,不如 IDE 集成调试直观
  4. 部分扩展不兼容:某些 PHP 扩展可能与 PHPDBG 不兼容

平台支持

平台支持情况
Linux完全支持
macOS支持(可能需要从源码编译)
Windows实验性支持,稳定性可能有问题
Docker支持(确保编译时启用 --enable-phpdbg)

常见问题

问题解决方案
phpdbg: command not found重新编译 PHP 并启用 --enable-phpdbg
调试时出现段错误可能与某些扩展不兼容,尝试禁用扩展排查
macOS Homebrew 版本无 PHPDBGHomebrew 默认可能不启用,需从源码编译
断点不生效确保文件路径正确,PHPDBG 使用绝对路径匹配

最佳实践

  1. 快速排查问题:当需要在服务器上快速排查 CLI 脚本问题时,PHPDBG 是最快捷的工具,无需安装 Xdebug 和配置 IDE。

  2. 搭配 Xdebug 使用:PHPDBG 和 Xdebug 不是互斥的。CLI 脚本用 PHPDBG 快速调试,Web 应用用 Xdebug + IDE 进行深度调试。

  3. 利用 eval 命令:PHPDBG 的 eval 命令非常强大,可以在断点处执行任意 PHP 代码来检查和修改变量状态。

  4. SSH 远程调试:通过 SSH 连接到服务器后直接使用 PHPDBG 调试脚本,无需端口转发和 IDE 连接。

  5. 脚本调试自动化:PHPDBG 支持从文件读取命令,可以实现自动化调试流程:

bash
# 创建调试命令文件
cat > debug-commands.txt << 'EOF'
break script.php:20
run
print $variable
continue
quit
EOF

# 使用命令文件自动化调试
phpdbg -c debug-commands.txt script.php

下一节

PHPDBG 适合命令行脚本的交互式调试。如果你需要更灵活的交互式环境来实验 PHP 代码,请继续阅读:

  • PHP REPL — PHP 交互式运行环境,适合代码实验和快速验证

参考链接