PHP 7 安装和调试
学习了 PHP 7 的新特性后,再来了解 PHP 7 的编译安装和调试方式。
编译安装
以 Linux 环境为例来进行安装。
首先下载 PHP 7。在 http://php.net/releases/ 上能够获取各个版本的 PHP 源码和修改记录(建议对 PHP 源码感兴趣的读者关注一下修改记录,以了解 PHP 源码开发者的开发思路)。本书以 7.1.0 版本为例,下载源码包并编译安装(源码包 URL 为 http://cn2.php.net/distributions/php-7.1.0.tar.gz )。
$ wget http://cn2.php.net/distributions/php-7.1.0.tar.gz
$ tar -zxvf php-7.1.0.tar.gz
$ cd php-7.1.0
$ ./configure --prefix=$HOME/php7/book/php-7.1.0/output --enable-fpm
bash
注意:默认情况下,make install
命令会把执行文件和库文件安装到 /usr/local/bin
和 /usr/local/lib
目录。为了后续研究方便,我们使用 --prefix
将 PHP 7 安装到当前目录的 output
目录下,同时安装 php-fpm
。
执行 make
命令:
$ make && make install
$ cd output
$ ls
bin etc include lib php sbin var
bash
到此,完成了 php-7.1.0 的编译安装,生成的可执行文件 php-fpm
在 sbin
中,其他部分在 bin
目录下:
pear peardev pecl phar phar.phar php php-cgi php-config phpdbg phpize
bash
其中,php
是 CLI
模式下的 PHP
脚本执行程序。
PEAR(PHP Extension and Application Repository, PHP 扩展与应用库),是 PHP 官方开源类库,可以使用 pear list
列出所有已经安装的包。通过 pear install
可以安装需要的包。
PECL
是 PHP 的扩展库,可以通过 PEAR
的 Package Manager 的管理方式来下载和安装扩展代码。
以安装 yaconf
为例:
$ ./pecl install yaconf
...
install ok: channel://pecl.php.net/yaconf-1.0.6
configuration option "php_ini" is not set to php.ini location
You should add "extension=yaconf.so" to php.ini
bash
php-config
是输出 PHP 编译信息的辅助命令。
phpdbg
是一个轻量级,具有丰富功能的调试平台。PHP 5.4 以上版本支持,比如可以使用它查看 opcode
:
$ phpdbg -p* t.php
function name: (null)
L1-5 {main}()
L2 #0 ASSIGN $a 1
L3 #1 ECHO $a
L5 #2 RETURN 1
bash
phpdbg
的其他功能可以通过 phpdbg --help
查看。
phpize
命令用来动态安装扩展,如果在安装 PHP 时没有安装某个扩展,可以通过这个命令随时安装。
使用GDB调试PHP 7
GDB
是一个由 GNU 开源组织发布的、UNIX/Linux 操作系统下的、基于命令行的、功能强大的程序调试工具。当程序发生 coredump
,通过 GDB
可以从 core
文件中复现场景,定位问题。
这里演示一下如何通过 GDB
来调试 PHP 程序。首先编写一段简单的代码 test.php
:
<?php
$a = '1';
echo $a;
php
下面开始进行 GDB
调试,运行 gdb php
:
$ gdb php (gdb)
bash
使用 b
命令在 main
函数入口增加断点:
(gdb) b main Breakpoint 1 at 0x797df0: file /home/vagrant/php7/php-7.1.0/sapi/cli/php_cli.c,line 1181.
bash
使用 r
命令运行 test.php
:
(gdb) r test.php Starting program: /home/vagrant/php7/php-7.1.0/output/bin/php test.php [Thread debugging using libthread_db enabled] Breakpoint 1, main (argc=2, argv=0x7fffffffe1b8) at /home/vagrant/php7/php-7.1.0/sapi/cli/php_cli.c:1181 1181 {
bash
从上面的输出中可以看到,代码执行在 main
函数处停止。接下来,使用 n
命令执行下一步:
(gdb) n
1288 memcpy(ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0"));
bash
使用 p
命令查看某个变量的信息:
(gdb) p ini_entries
$1 = 0x10c2150
"html _errors=0\nregister_argc_argv=1\nimplicit_flush=1\noutput_buffering=0\nmax_execution_time=0\nmax_input_time=-1\n"
(gdb)
bash
如果出现 <value optimized out>
,是由于 GCC 编译器在编译过程中默认使用 -O2
优化选项所致,使用 -O0
选项可以关闭编译器的优化。在这里,通过修改 MakeFile
禁止编译器优化。查找 CFLAGS_CLEAN
:
CFLAGS_CLEAN = -I/usr/include -g -O2-fvisibility=hidden -DZEND_SIGNALS $(PROF_FLAGS)
makefile
将其中的 -O2
改为 -O0
,然后执行 make clean && make && make install
。
另外,对于在 php-fpm
下运行的 PHP 程序如何调试呢?
在本地建立一个名为 www.local 的本地项目,来演示 php-fpm
运行模式下的调试:
$ mkdir /data/htdocs/www.local $ touch /data/htdocs/www.local/index.php
bash
略过 Nginx 的配置过程,着重看一下 php-fpm 的配置:
$ vim ~/php7/book/php-7.1.0/output/conf/php-fpm.conf // 添加以下配置项 [www.local] pm=static pm.max_children=1 pm.start_servers=1 pm.min_spare_servers=1 pm.max_spare_servers=1
bash
这段配置设定 php-fpm 的运行模式为 static
,其最大进程数为 1
、启动进程数为 1
、最大和最小的空余进程数为 1
。为什么要这么设定呢?这是为了保证 GDB
调试的进程一定是我们当前访问的进程。
完成 Nginx
和 php-fpm
的配置以后,重启这两个服务,可以看到 www.local
的项目只有一个进程,其 pid
为 4459
(后文还会用到该 pid
):
$ systemctl restart php-fpm.service $ systemctl restart nginx.service $ ps aux|grep php-fpm root 4458 0.0 0.8343816 4056 ? Ss 13:11 0:00 php-fpm: master process (/home/vagrant/php7/book/php-7.1.0/output/conf/php-fpm.conf) www 4459 0.0 1.0344012 5140 ? S 13:11 0:00 php-fpm: pool www.local
bash
接下来开始调试,执行如下命令:
$ gdb php (gdb) attach 4459 Attaching to process 4459 Reading symbols ...
如果没有报错,当前 GDB
已经 attach
到 www.local
的 php-fpm 进程上了。新开一个终端 2
执行 “curl www.local”
或者使用浏览器访问 www.local
,然后回到终端 1
,就可以和 CLI
模式一样进行调试了。
在学习和研究 PHP 7 的过程中,经常需要查看 opcodes
,除了上文提到的 phpdbg
可以查看,另外还有一个 vld
扩展也非常好用,下面介绍下 vld
扩展。
vld扩展
PHP 代码的执行实际上是在执行代码解析后的各种 opcode
。通过 vld
扩展可以很方便地看到执行过程中的 opcode
。扩展可以从 https://github.com/derickr/vld 下载安装,下面是安装示例:
$ git clone https://github.com/derickr/vld.git
$ cd vld
$ /home/vagrant/php7/book/php-7.1.0/output/phpize
$ ./configure --with-php-config=/home/vagrant/php7/book/php-7.1.0/output/php-config --enable-vld
$ make && make install
bash
到这里,扩展就安装完成了,接下来只需要在 PHP
的配置文件 php.ini
中启用该扩展即可:
extension=vld.so
bash
然后执行下边的命令:
$ php -m | grep vld
bash
看到有 vld
的输出,即表示扩展启用成功。
现在来写一段简单的 PHP 代码,看看生成的 opcode
:
<?php
$str = 'hello php7';
var_dump($str);
php
保存这段代码为 vld.php
,然后在命令行执行:
$ php -dvld.active=1 vld.php
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 62) Position 1 = -2
filename: /home/vagrant/vld.php
function name: (null)
number of ops: 5
compiled vars: !0 = $str
line #* E I O op fetch ext return operands
-------------------------------------------------------------------
3 0 E > ASSIGN !0, 'hello+php7'
4 1 INIT_FCALL 'var_dump'
2 SEND_VAR !0
3 DO_ICALL
6 4 > RETURN 1
branch: # 0; line: 3- 6; sop: 0; eop: 4; out1: -2
path #1: 0,
string(10) "hello php7"
bash
从上边的输出可以看到这段代码一共有 5
个 opcode
。
vld
扩展有下边几个参数。
-
vld.active
:是否在执行 PHP 的同时激活 vld——1 激活,0 不激活(默认不激活)。 -
vld.execute
:是否输出程序的执行结果——1 输出,0 不输出(默认输出)。 -
vld.verbosity
:显示更详细的opcode
信息,开启后可以显示每个opcode
的操作数的类型等信息。例如:
3 0 E > ASSIGN OP1[IS_CV !0 ] OP2[IS_CONST (0) 'hello+php7' ]
bash -
vld.skip_prepend
:是否跳过php.ini
配置文件中auto_prepend_file
配置项指定的文件,默认为0
,即不跳过包含的文件。vld.execute
为0
时有效; -
vld.skip_append
:是否跳过php.ini
配置文件中auto_append_file
指定的文件,默认为0
,即不跳过包含的文件。vld.execute
为0
时有效; -
vld.format
:是否启用自定义输出格式——1 启用,0不启用(默认不启用); -
vld.col_sep
:自定义输出格式间隔符,vld.format
为 1 时有效; -
vld.save_dir
:指定文件输出的路径,默认路径为/tmp
; -
vld.save_paths
:控制是否输出dot
语言文件,默认为 0,表示不输出; -
vld.dump_paths
:控制是否输出分支及路径信息——1 输出,0 不输出(默认输出)。
在命令行执行以下命令:
这样就可以生成一张调用图片,如图1-1所示。 ![]() Figure 1. 图1-1 vld 生成的图片
|
介绍完 PHP 7 的安装和调试后,下面介绍几种不同平台上的代码阅读工具,基于它们,可以有效地提高源码阅读的效率。