基本信息
- PHP 版本:7.1.0
- 调试环境:Ubuntu(WSL)
- 调试工具:GDB、Clion
编译 PHP
下载并安装 PHP:
$ wget http://cn2.php.net/distributions/php-7.1.0.tar.gz
$ tar -xzvf php-7.1.0.tar.gz
$ cd php-7.1.0
$ ./configure --prefix=$HOME/php-7.1.0/build --enable-fpm
注意:$HOME/php-7.1.0/build
是 PHP 执行文件和库文件安装的目录,可以自定义。--enable-fpm
表示同时安装 php-fpm。
接下来修改编译优化等级,方便我们在调试的过程中查看变量的内容。
首先在 php-7.1.0/Makefile
文件中搜索 CFLAGS_CLEAN
,然后将 -02
改为 -o0
,表示不需要优化。
CFLAGS_CLEAN = -I/usr/include -g -O0 -fvisibility=hidden -DZEND_SIGNALS $(PROF_FLAGS)
执行 make 命令:
$ make && make install
执行完成后,就可以在 php-7.1.0/build/bin
目录下看到 PHP 相关的可执行文件了。
pear peardev pecl phar phar.phar php php-cgi php-config phpdbg phpize
调试 PHP
使用 Clion 进行调试
首先用 Clion 打开 php-7.1.0
目录,Clion 会在根目录下自动创建 CMakeLists.txt 文件,复制下面的内容覆盖 CMakeLists.txt 中的内容:
cmake_minimum_required(VERSION 3.10)
project(php_7_1_0)
set(CMAKE_CXX_STANDARD 14)
set(PHP_SOURCE .)
file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE})
file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/ext)
file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/Zend)
file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/sapi)
file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/pear)
file(GLOB_RECURSE HEAD_FILES FOLLOW_SYMLINKS ${PHP_SOURCE}/TSRM)
file(GLOB_RECURSE SRC_LIST FOLLOW_SYMLINKS
${PHP_SOURCE}/*.c
${PHP_SOURCE}/ext/*.c
${PHP_SOURCE}/main/*.c
${PHP_SOURCE}/Zend/*.c
${PHP_SOURCE}/sapi/*.c
${PHP_SOURCE}/pear/*.c
${PHP_SOURCE}/TSRM/*.c
)
include_directories(${PHP_SOURCE})
include_directories(${PHP_SOURCE}/ext)
include_directories(${PHP_SOURCE}/main)
include_directories(${PHP_SOURCE}/Zend)
include_directories(${PHP_SOURCE}/sapi)
include_directories(${PHP_SOURCE}/pear)
include_directories(${PHP_SOURCE}/TSRM)
add_executable(${PROJECT_NAME} ${SRC_LIST})
add_custom_target(makefile COMMAND make && make install WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
注意:/home/ubuntu/php-7.1.0
要替换成你自己的 PHP 所在的路径。
点击右上角绿色锤子旁边的下拉框 => Edit Configurations => makefile,按照下面的步骤进行操作:
- Target:makefile(一般不用动)
- Executable:点击右边的
...
按钮,选择 php-7.1.0/bin/php 文件 - Program arguments:输入你要调试的 PHP 脚本的文件名,比如 index.php
- Working directory:选择上面 PHP 脚本所在的目录
- 删除 Before launch 中的 Build,避免每次调试都重新构建 PHP
点击右上角绿色的小虫子或者使用快捷键 Shift + F9
开始调试
使用 GDB 进行调试
用来测试的 index.php :
<?php
$a = 'hello';
var_dump($a);
开始 GDB 调试:
gdb /php-7.1.0/build/bin/php
使用 b 命令在 var_dump 处打断点:
(gdb) b zif_var_dump
Breakpoint 1 at 0x48f3bc: file /home/ubuntu/php-7.1.0/ext/standard/var.c, line 200.
为啥这里是 zif_var_dump?
因为 zif_var_dump 是 var_dump 函数的底层实现, 是由 PHP_FUNCITON(var_dump) 宏展开后的名称,所以直接搜 zif_var_dump 是搜不到的。
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
/* PHP-named Zend macro wrappers */
#define PHP_FUNCTION ZEND_FUNCTION
PHP_FUNCTION(var_dump)
{
zval *args;
int argc;
int i;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &argc) == FAILURE) {
return;
}
for (i = 0; i < argc; i++) {
php_var_dump(&args[i], 1);
}
}
开始运行 PHP 文件:
(gdb) r index.php
Starting program: /home/ubuntu/php-7.1.0/build/bin/php index.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Breakpoint 1, zif_var_dump (execute_data=0x7ffffb6130d0, return_value=0x7ffffffea400)
at /home/ubuntu/php-7.1.0/ext/standard/var.c:200
200 {
代码执行到 zif_var_dump 就停止了,说明命中了断点。我们可以使用 l 命令查看附近的源码:
(gdb) l
195 /* }}} */
196
197 /* {{{ proto void var_dump(mixed var)
198 Dumps a string representation of variable to output */
199 PHP_FUNCTION(var_dump)
200 {
201 zval *args;
202 int argc;
203 int i;
204
使用 bt 命令查看调用栈:
(gdb) bt
#0 zif_var_dump (execute_data=0x7ffffb6130d0, return_value=0x7ffffffea400)
at /home/ubuntu/php-7.1.0/ext/standard/var.c:200
#1 0x0000000008642151 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER ()
at /home/ubuntu/php-7.1.0/Zend/zend_vm_execute.h:628
#2 0x000000000864038e in execute_ex (ex=0x7ffffb613030) at /home/ubuntu/php-7.1.0/Zend/zend_vm_execute.h:429
#3 0x0000000008640d05 in zend_execute (op_array=0x7ffffb67e000, return_value=0x0)
at /home/ubuntu/php-7.1.0/Zend/zend_vm_execute.h:474
#4 0x00000000085a2ca6 in zend_execute_scripts (type=8, retval=0x0, file_count=3)
at /home/ubuntu/php-7.1.0/Zend/zend.c:1474
#5 0x00000000084eb1ce in php_execute_script (primary_file=0x7ffffffecd00) at /home/ubuntu/php-7.1.0/main/main.c:2533
#6 0x0000000008743462 in do_cli (argc=2, argv=0x90c1ef0) at /home/ubuntu/php-7.1.0/sapi/cli/php_cli.c:990
#7 0x000000000874483e in main (argc=2, argv=0x90c1ef0) at /home/ubuntu/php-7.1.0/sapi/cli/php_cli.c:1378
使用 u 命令让代码运行到指定的行:
(gdb) u 209
zif_var_dump (execute_data=0x7ffffb6130d0, return_value=0x7ffffffea400)
at /home/ubuntu/php-7.1.0/ext/standard/var.c:209
209 for (i = 0; i < argc; i++) {
使用 p 命令打印变量 $a 的值:
(gdb) p args[0].value.str.val
$1 = "h"
使用 @ 指定输出长度:
(gdb) p args[0].value.str.val@5
$2 = {"h", "e", "l", "l", "o"}