PHP内核分析之GDB使用(一)

1.PHP源码下载和安装

https://github.com/php/php-src/releases

$ ./configure --prefix=/usr/local/php7 --enable-debug --enable-fpm
$ make && sudo make install

2.环境工具介绍

CENTOS 7.2

PHP-7.4.1

GDB    命令行调试工具

CLion   图形界面调试工具 C C++开发工具

3.GDB使用说明

参考:https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html

gdb来排查比如这些问题:

  1. 某个php进程占用cpu 100%问题
  2. 出现core dump问题,比如“Segmentation fault”
  3. php扩展出现错误
  4. 死循环问题

一些快捷命令

  • p:print,打印C变量的值
  • c:continue 继续执行,到下一个断点处(或运行结束)
  • b:breakpoint,设置断点,可以按照函数名设置,如b zif_php_function,也可以按照源代码的行数指定断点,如b src/networker/Server.c:1000
  • t:thread,切换线程,如果进程拥有多个线程,可以使用t指令,切换到不同的线程
  • ctrl + c:中断当前正在运行的程序,和c指令配合使用
  • n:next,单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
  • s:step 单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
  • info threads:查看运行的所有线程
  • l:list,查看源码,可以使用l 函数名 或者 l 行号
  • bt:backtrace,查看运行时的函数调用栈。当程序出错后用于查看调用栈信息
  • finish:完成当前函数
  • f:frame,与bt配合使用,可以切换到函数调用栈的某一层
  • r:run,运行程序

gdb 调试php:

gdb有3种使用方式:

  1. 跟踪正在运行的PHP程序,使用 “gdb -p 进程ID” 进行附加到进程上
  2. 运行并调试PHP程序,使用 “gdb php -> run server.php” 进行调试
  3. 当PHP程序发生coredump后使用gdb加载core内存镜像进行调试 gdb php core

php在解释执行过程中,zend引擎用executor_globals变量保存了执行过程中的各种数据,包括执行函数、文件、代码行等。zend虚拟机是使用C编写,gdb来打印PHP的调用栈时,实际是打印的虚拟机的执行信息。

GDB调试PHP程序:

<?php
for($i = 0; $i < 10; $i++){
    echo $i."\n";
    sleep(3);
    if(in_array($i,[1,9,20])){
        print_r($i*$i);
        var_dump($i*$i);

        print $i*$i;
    }
[root@VM_0_15_centos test]# gdb php   
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /usr/local/php/bin/php...done.
(gdb) b zif_sleep
Breakpoint 1 at 0x7d1ec0: file /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c, line 4557.
(gdb) b zif_in_array
Breakpoint 2 at 0x7c51b0: file /root/oneinstack/src/php-7.3.5/ext/standard/array.c, line 1637.
(gdb) b zif_printf
Function "zif_printf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) b zif_echo
Function "zif_echo" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00000000007d1ec0 in zif_sleep 
                                                   at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557
2       breakpoint     keep y   0x00000000007c51b0 in zif_in_array 
                                                   at /root/oneinstack/src/php-7.3.5/ext/standard/array.c:1637
(gdb) 

Function "zif_echo" not defined. 这里大致可以看一下 echo print等不是函数了

然后开始调试

(gdb) run gdb.php 
Starting program: /usr/local/php/bin/php gdb.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
0

Breakpoint 1, zif_sleep (execute_data=0x7ffff1c1d140, return_value=0x7fffffffac30)
    at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557

打印返回值return_value

(gdb) p *return_value
$2 = {value = {lval = 0, dval = 0, counted = 0x0, str = 0x0, arr = 0x0, obj = 0x0, res = 0x0, ref = 0x0, 
    ast = 0x0, zv = 0x0, ptr = 0x0, ce = 0x0, func = 0x0, ww = {w1 = 0, w2 = 0}}, u1 = {v = {type = 1 '\001', 
      type_flags = 0 '\000', u = {call_info = 0, extra = 0}}, type_info = 1}, u2 = {next = 0, cache_slot = 0, 
    opline_num = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, 
    constant_flags = 0, extra = 0}}
(gdb) p return_value.value
$3 = {lval = 0, dval = 0, counted = 0x0, str = 0x0, arr = 0x0, obj = 0x0, res = 0x0, ref = 0x0, ast = 0x0, 
  zv = 0x0, ptr = 0x0, ce = 0x0, func = 0x0, ww = {w1 = 0, w2 = 0}}
(gdb) 

查看当前堆栈,PHP内核的执行过程

(gdb) bt
#0  zif_sleep (execute_data=0x7ffff1c1d140, return_value=0x7fffffffac30)
    at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557
#1  0x00007fffeb4a2e55 in xdebug_execute_internal (current_execute_data=0x7ffff1c1d140, 
    return_value=0x7fffffffac30) at /root/oneinstack/src/xdebug-2.7.2/xdebug.c:2050
#2  0x0000000000483041 in ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER ()
    at /root/oneinstack/src/php-7.3.5/Zend/zend_vm_execute.h:982
#3  0x0000000000955caf in execute_ex (ex=0x7ffff1c1d140)
    at /root/oneinstack/src/php-7.3.5/Zend/zend_vm_execute.h:55557
#4  0x00007fffeb4a2499 in xdebug_execute_ex (execute_data=0x7ffff1c1d030)
    at /root/oneinstack/src/xdebug-2.7.2/xdebug.c:1928
#5  0x000000000095e0a8 in zend_execute (op_array=op_array@entry=0x7ffff1c74380, 
    return_value=return_value@entry=0x0) at /root/oneinstack/src/php-7.3.5/Zend/zend_vm_execute.h:60881
#6  0x00000000008d9274 in zend_execute_scripts (type=type@entry=8, retval=retval@entry=0x0, 
    file_count=file_count@entry=3) at /root/oneinstack/src/php-7.3.5/Zend/zend.c:1568
#7  0x000000000087d100 in php_execute_script (primary_file=primary_file@entry=0x7fffffffd120)
    at /root/oneinstack/src/php-7.3.5/main/main.c:2630
#8  0x0000000000960445 in do_cli (argc=2, argv=0x11e7c40)
    at /root/oneinstack/src/php-7.3.5/sapi/cli/php_cli.c:997
#9  0x000000000048cdaf in main (argc=2, argv=0x11e7c40) at /root/oneinstack/src/php-7.3.5/sapi/cli/php_cli.c:1389

使用zbacktrace更简单的调试:
php源代码中还提供了zbacktrace这样的方便的对gdb命令的封装的工具。zbacktrace是PHP源码包提供的一个gdb自定义指令,功能与bt指令类似,与bt不同的是zbacktrace看到的调用栈是PHP函数调用栈,而不是c函数。zbacktrace可以直接看到当前执行函数、文件名和行数,简化了直接使用gdb命令的很多步骤。在php-src的根目录中有一个.gdbinit文件,我的是用oneinstack安装的,所以文件目录为:source /root/oneinstack/src/php-7.3.5/.gdbinit,里面提供了20多个 gdb 的自定义命令,用于方便PHP的调试

输入:

(gdb) source /root/oneinstack/src/php-7.3.5/.gdbinit
(gdb) zbacktrace
[0x7ffff1c1d140] sleep(3) [internal function]
[0x7ffff1c1d030] (main) /data/wwwroot/test/gdb.php:4
(gdb) c
Continuing.
4

Breakpoint 1, zif_sleep (execute_data=0x7ffff1c1d140, return_value=0x7fffffffac30)
    at /root/oneinstack/src/php-7.3.5/ext/standard/basic_functions.c:4557
4557    {
(gdb) zbacktrace
[0x7ffff1c1d140] in_array(4, array(3)[0x7ffff1c1d1a0]) [internal function]
[0x7ffff1c1d030] (main) /data/wwwroot/test/gdb.php:5 
(gdb) printzv 0x7ffff1c1d1a0
[0x7ffff1c1d1a0] (refcount=2) array:     Packed(3)[0x7ffff1c5f310]: {
      [0] 0 => [0x7ffff1c65648] long: 1
      [1] 1 => [0x7ffff1c65668] long: 9
      [2] 2 => [0x7ffff1c65688] long: 20
}

1. print_cvs 打印当前执行环境中已编译的PHP变量, 如:

(gdb) print_cvs
Compiled variables count: 0

2. printzv 打印指定的PHP变量, 需要指定地址, 如打印一个数组:
(gdb) printzv 0x7ffff1c1d1a0
[0x7ffff1c1d1a0] (refcount=2) array:     Packed(3)[0x7ffff1c5f310]: {
      [0] 0 => [0x7ffff1c65648] long: 1
      [1] 1 => [0x7ffff1c65668] long: 9
      [2] 2 => [0x7ffff1c65688] long: 20
}

3. 打印PHP的函数调用栈, 如:
(gdb) zbacktrace
[0x7ffff1c1d140] in_array(4, array(3)[0x7ffff1c1d1a0]) [internal function]
[0x7ffff1c1d030] (main) /data/wwwroot/test/gdb.php:5

4. print_ft 打印函数表( HashTable )
(gdb) set $eg = executor_globals
(gdb) print $eg.function_table  
$6 = (HashTable *) 0xa5bd450
(gdb) print_ft $eg.function_table
[0xa5bd450] {
“zend_version\0” => “zend_version”
“func_num_args\0” => “func_num_args”
“func_get_arg\0” => “func_get_arg”
“func_get_args\0” => “func_get_args”
“strlen\0” => “strlen”
“strcmp\0” => “strcmp”
“strncmp\0” => “strncmp”
“strcasecmp\0” => “strcasecmp”
“strncasecmp\0” => “strncasecmp”
“each\0” => “each”

一些使用gdb排查问题例子:

还可以加一下监控watch、设置一些调试变量set 等等
其他的调试工具还有 strace 查看系统调用、ltrace 查看类库的调用、vld查看opcode

常见问题:

一、Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64

在centos7上面gdb调试程序时候,报错信息是:
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64

解决方案:
1 先修改"/etc/yum.repos.d/CentOS-Debuginfo.repo"文件的 enable=1;有时候该文件不存在,则需要手工创建此文件并加入以下内容:

[debug]
name=CentOS-7 - Debuginfo
baseurl=http://debuginfo.centos.org/7/$basearch/ 
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-Debug-7 
enabled=1

2 执行sudo yum install -y glibc
3 执行debuginfo-install glibc
即可解决该问题!

发布了79 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/tiancityycf/article/details/103660111