如何调试PHP的Core之获取基本信息

作者: IT技术博客大学习  更新时间:2019-08-20 22:28:54  原文链接


其实一直想写这个系列, 但是一想到这个话题的宽泛性, 我就懒得想了.

今天我也不打算全部讲如何调试一个PHP的Core文件, 也不会介绍什么是Coredump, 选择一个相对比较简单的方向来介绍, 那就是如何从PHP的Core文件中获取一些对我们重演这个Core有帮助的信息.

在这个过程中, 会涉及到对PHP的函数调用, PHP的传参, PHP的一些全局变量的知识, 这些知识在我之前的文章中都有过涉及, 大家可以翻阅: 深入理解PHP原理之函数 深入理解PHP原理之变量作用域 等等.

首先, 让我们生成一个供我们举例子的Core文件:

  1. <?php
  2. function recurse ( $num ) {
  3.        recurse (++ $num );
  4. }
  5. recurse ( 0 );

运行这个PHP文件:

  1. $ php test.php
  2. Segmentation fault (core dumped

这个PHP因为无线递归, 会导致爆栈, 从而造成 segment fault而在PHP的当前工作目录产生Coredump文件(如果你的系统没有产生Coredump文件, 那请查询ulimit的相关设置).

好, 现在, 让我们删除掉这个test.php, 忘掉上面的代码, 我们现在仅有的是这个Core文件, 任务是, 找出这个Core产生的原因, 以及发生时候的状态.

首先, 让我们用gdb打开这个core文件:

  1. $ gdb php -c core.3165

会看到很多的信息, 首先让我们注意这段:

  1. Core was generated by `php test.php'.
  2. Program terminated with signal 11, Segmentation fault

他告诉我们Core发生的原因:”Segmentation fault”.

一般来说, 这种Core是最常见的, 解引用空指针, double free, 以及爆栈等等, 都会触发SIGSEGV, 继而默认的产生Coredump.

现在让我们看看Core发生时刻的堆栈:

  1. #0 execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:53
  2. 53          memset(EX(CVs), 0, sizeof(zval**) * op_array->last_var);
  3. (gdb) bt
  4. #0 execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:53
  5. #1 0x00000000006ea263 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fbf400210) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:234
  6. #2 0x00000000006e9f61 in execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:92
  7. #3 0x00000000006ea263 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fbf400440) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:234
  8. #4 0x00000000006e9f61 in execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:92
  9. #5 0x00000000006ea263 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fbf400670) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:234
  10. ....

不停的按回车, 可以看到堆栈很深, 不停的是zend_do_fcall_common_helper_SPEC和execute的重复, 那么这基本就能断定是因为产生了无穷大的递归(不能一定说是无穷递归, 比如我之前文章中介绍 深悉正则(pcre)最大回溯/递归限制 ). 从而造成爆栈产生的Core.

Ok, 那么现在让我们看看, Core发生在PHP的什么函数中:

  1. (gdb)f 1
  2. #1 0x00000000006ea263 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fbf400210) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:234
  3. 234               zend_execute(EG(active_op_array) TSRMLS_CC);
  4. (gdb) p execute_data->function_state.function->common->function_name
  5. $3 = 0x2a95b65a78 "recurse"
  6. (gdb) p execute_data->function_state.function->op_array->filename
  7. $4 = 0x2a95b632a0 "/home/laruence/test.php"
  8. (gdb) p execute_data->function_state.function->op_array->line_start
  9. $5 =

现在我们得到, 在调用的PHP函数是recurse, 这个函数定义在/home/laruence/test.php的第二行

经过重复验证几个frame, 我们可以看出, 一直是在重复调用这个PHP函数.

要注意的是, 为了介绍查看执行信息的原理, 我才采用原生的gdb的print来查看, 其实我们还可以使用PHP源代码中提供的.gdbinit(gdb命令编写脚本), 来简单的获取到上面的信息:

  1. (gdb) source /home/laruence/package/php-5.2.14/.gdbinit
  2. (gdb) zbacktrace
  3. [0xbf400210] recurse() /home/laruence/test.php:3
  4. [0xbf400440] recurse() /home/laruence/test.php:3
  5. [0xbf400670] recurse() /home/laruence/test.php:3
  6. [0xbf4008a0] recurse() /home/laruence/test.php:3
  7. [0xbf400ad0] recurse() /home/laruence/test.php:3
  8. [0xbf400d00] recurse() /home/laruence/test.php:3
  9. [0xbf400f30] recurse() /home/laruence/test.php:3
  10. [0xbf401160] recurse() /home/laruence/test.php:3
  11. ....

OK, 我们现在知道, 问题发生在/home/laruence/test.php的recurse函数的递归调用上了.

现在, 让我们来看看, 在调用这个函数的时候的参数是什么?

要注意的是, PHP的参数传递是依靠一个全局Stack来完成的, 所以, 不会和gdb所看到的backtrace简单的一一对应:

  1. //先看看, 最后一次函数调用的参数数目是多少
  2. (gdb) p (int )*(executor_globals->argument_stack->top_element - 2)
  3. $13 = 1
  4. //再看看, 最后一次函数调用的参数是什么
  5. (gdb) p **(zval **)(executor_globals->argument_stack->top_element - 3)
  6. $2 = {value = {lval = 22445, dval = 1.1089303420906779e-319, str = {val = 0x57ad <Address 0x57ad out of bounds>, len = 7}, ht = 0x57ad, obj = {handle = 22445, handlers = 0x7}},
  7.   refcount = 2, type = 1 '\001', is_ref = 0 '\0'

好, 我们现在得到, 最后一次调用的参数是一个整数, 数值是22445

到了这一步, 我们就得到了这个Core发生的时刻的PHP层面的相关信息, 接下来, 就可以交给对应的PHP开发工程师来排查, 这个参数下, 可能造成的无穷大递归的原因, 从而修复这个问题..

觉得文章有用?立即:和朋友一起 共学习 共进步!

建议继续学习:

  1. 深入理解Nginx之调试优化技巧    (阅读:5930)
  2. 内存越界的概念和调试方法    (阅读:5711)
  3. 网络数据包调试利器之wireshark    (阅读:5277)
  4. GDB中应该知道的几个调试方法    (阅读:4787)
  5. webapp网页调试工具Chrome Devtools    (阅读:4052)
  6. php调试利器之phpdbg    (阅读:3852)
  7. 如何在Windows下编译或调试MySQL    (阅读:3028)
  8. 又一个PHP低概率Core的分析(PHP内存管理)    (阅读:2787)
  9. FirePHP,给力的调试工具    (阅读:2405)
  10. PHP stream未能及时清理现场导致Core的bug    (阅读:1784)

QQ技术交流群:445447336,欢迎加入!

扫一扫订阅我的微信号:IT技术博客大学习