当前位置:首页 > PHP > 正文内容

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

phpmianshi3年前 (2018-04-02)PHP1687

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
即可解决该问题!


版权声明:本文由PHP面试资料网发布,如需转载请注明出处。
分享给朋友:

相关文章

PHP中 array_walk array_map array_filter区别

array_walk:array_walk — 使用用户自定义函数对数组中的每个元素做回调处理1. 用户自定义的函数处理每一个元素2. 直接修改原数组,不会创建新的数组3. 可以传递额外的参数更多信息...

php-fpm backlog参数优化

php-fpm backlog参数优化

一、问题分析       1、分析php-fpm.slow.log发现没有执行慢的地方,然后把目光放到了nginx 与php建立连接的阶段上,使用tcpdump...

PHP内核分析之深入理解字符串(七)

一、字符串的结构struct _zend_string {     zend_refcounted_h gc; &nb...

PHP内核分析之源码目录结构 (二)

一、目录概览以php-7.4.1为例,目录多达十多个,下面介绍主要目录。├── build   linux下编译相关的目录├── ext      P...

Windows下nginx+fastcgi+php的并发阻塞问题

同事接到一个需求,需要调用同一个项目的另一个接口,于是CURL调用接口。代码撸完了,本地测试一下 浏览器一直转圈圈直到超时…… 百思不得其解主要是windows+nginx开发环境遇到的问题,本人很少...

理解PHP中的Generator

PHP中Generator,似乎是在5.5版中引入了。PHP中的协程必须依赖于Generator来实现,所以我觉得有必要先专门写一篇文章介绍Generator。Generator这个单词在这里对应的中...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。