- 本文地址: https://www.laruence.com/2011/06/23/2057.html
- 转载请注明出处
其实一直想写这个系列, 但是一想到这个话题的宽泛性, 我就有点感觉无法组织.
今天我也不打算全部讲如何调试一个PHP的Core文件, 也不会介绍什么是Coredump, 选择一个相对比较简单的方向来介绍, 那就是如何从PHP的Core文件中获取一些对我们重演这个Core有帮助的信息.
在这个过程中, 会涉及到对PHP的函数调用, PHP的传参, PHP的一些全局变量的知识, 这些知识在我之前的文章中都有过涉及, 大家可以翻阅: 深入理解PHP原理之函数 深入理解PHP原理之变量作用域等等.
首先, 让我们生成一个供我们举例子的Core文件:
<?php function recurse($num) { recurse(++$num); } recurse(0);
运行这个PHP文件:
$ php test.php Segmentation fault (core dumped)
这个PHP因为无线递归, 会导致爆栈, 从而造成 segment fault而在PHP的当前工作目录产生Coredump文件(如果你的系统没有产生Coredump文件, 那请查询ulimit的相关设置).
好, 现在, 让我们删除掉这个test.php, 忘掉上面的代码, 我们现在仅有的是这个Core文件, 任务是, 找出这个Core产生的原因, 以及发生时候的状态.
首先, 让我们用gdb打开这个core文件:
$ gdb php -c core.31656
会看到很多的信息, 首先让我们注意这段:
Core was generated by `php test.php'. Program terminated with signal 11, Segmentation fault.
他告诉我们Core发生的原因:"Segmentation fault".
一般来说, 这种Core是最常见的, 解引用空指针, double free, 以及爆栈等等, 都会触发SIGSEGV, 继而默认的产生Coredump.
现在让我们看看Core发生时刻的堆栈:
#0 execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:53 53 memset(EX(CVs), 0, sizeof(zval**) * op_array->last_var); (gdb) bt #0 execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:53 #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 #2 0x00000000006e9f61 in execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:92 #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 #4 0x00000000006e9f61 in execute (op_array=0xdc9a70) at /home/laruence/package/php-5.2.14/Zend/zend_vm_execute.h:92 #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 .....
不停的按回车, 可以看到堆栈很深, 不停的是zend_do_fcall_common_helper_SPEC和execute的重复, 那么这基本就能断定是因为产生了无穷大的递归(不能一定说是无穷递归, 比如我之前文章中介绍深悉正则(pcre)最大回溯/递归限制). 从而造成爆栈产生的Core.
Ok, 那么现在让我们看看, Core发生在PHP的什么函数中, 在PHP中, 对于FCALL_* Opcode的handler来说, execute_data代表了当前函数调用的一个State, 这个State中包含了信息:
(gdb)f 1 #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 234 zend_execute(EG(active_op_array) TSRMLS_CC); (gdb) p execute_data->function_state.function->common->function_name $3 = 0x2a95b65a78 "recurse" (gdb) p execute_data->function_state.function->op_array->filename $4 = 0x2a95b632a0 "/home/laruence/test.php" (gdb) p execute_data->function_state.function->op_array->line_start $5 = 2
现在我们得到, 在调用的PHP函数是recurse, 这个函数定义在/home/laruence/test.php的第二行
经过重复验证几个frame, 我们可以看出, 一直是在重复调用这个PHP函数.
要注意的是, 为了介绍查看执行信息的原理, 我才采用原生的gdb的print来查看, 其实我们还可以使用PHP源代码中提供的.gdbinit(gdb命令编写脚本), 来简单的获取到上面的信息:
(gdb) source /home/laruence/package/php-5.2.14/.gdbinit (gdb) zbacktrace [0xbf400210] recurse() /home/laruence/test.php:3 [0xbf400440] recurse() /home/laruence/test.php:3 [0xbf400670] recurse() /home/laruence/test.php:3 [0xbf4008a0] recurse() /home/laruence/test.php:3 [0xbf400ad0] recurse() /home/laruence/test.php:3 [0xbf400d00] recurse() /home/laruence/test.php:3 [0xbf400f30] recurse() /home/laruence/test.php:3 [0xbf401160] recurse() /home/laruence/test.php:3 .....
关于.gdbinit, 是一段小小的脚本文件, 定义了一些方便我们去调试PHP的Core, 大家也可以用文本编辑器打开, 看看里面定义的一些快捷的命令, 一般来说, 我常用的有:
zbacktrace print_ht**系列 zmemcheck
OK, 回归正题, 我们现在知道, 问题发生在/home/laruence/test.php的recurse函数的递归调用上了.
现在, 让我们来看看, 在调用这个函数的时候的参数是什么?
PHP的参数传递是依靠一个全局Stack来完成的, 也就是EG(argument_stack), EG在非多线程情况下就是executor_globals, 它保持了很多执行状态. 而argument_statck就是参数的传递栈, 保存着对应PHP函数调用层数相当的调用参数.
要注意的是, 这个PHP函数调用堆栈(层数)不和gdb所看到的backtrace简单的一一对应, 所以参数也不能直接和gdb的backtrace对应起来, 需要单独分析:
//先看看, 最后一次函数调用的参数数目是多少 (gdb) p (int )*(executor_globals->argument_stack->top_element - 2) $13 = 1 //再看看, 最后一次函数调用的参数是什么 (gdb) p **(zval **)(executor_globals->argument_stack->top_element - 3) $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}}, refcount = 2, type = 1 '\001', is_ref = 0 '\0'}
好, 我们现在得到, 最后一次调用的参数是一个整数, 数值是22445
到了这一步, 我们就得到了这个Core发生的时刻的PHP层面的相关信息, 接下来, 就可以交给对应的PHP开发工程师来排查, 这个参数下, 可能造成的无穷大递归的原因, 从而修复这个问题..
后记: 调试PHP的Core是一个需要丰富经验的过程, 也许我今天介绍的这个例子太简单, 但是只要经常去挑战, 在遇到不懂的相关的知识的时候, 勇于去追根究底, 我相信大家终都可以成PHP Core杀手..
各位大佬,
我的程序在 cli模式下运行产生了core文件,但是用gdb调试并没有显示函数信息,而是显示的 ??
php版本为 7.3.33,源码编译安装的php,编译的时候启用了 –enable-debug 参数,运行环境为 centos 7,运行在容器环境,
尝试了进入gdb后,执行以下命令,设置共享库目录,还是不管用
set solib-search-path /opt/php7/lib/php/extensions/debug-non-zts-20180731/
各位大佬有遇到过吗?怎么解决的呀?
Research studies indicate that customers are getting comfortable with undertaking basic banking procedures on their mobile app user acquisition. Most common banking services performed on mobile phones have traditionally been SMS-based inquiries using text commands, that have typically been restricted
Thanks bird brother.
鸟哥求帮助
代码中有死循环,按照这篇教程,使用zbacktrace输出,可是行数太多(core文件200+M),无法看到初始调用过程,该怎么办
(看不太懂C语言调用过程)
解决了,修改.gdbinit文件中的方法,核心的东西看不懂,但是写个判断还行。以php-7.3.2源码为例
①第620行define zbacktrace方法体修改,增加输入参数
dump_bt $eg.current_execute_data $arg0
②第65行插入
set $ax_lines = $arg1
set $ax_cur_line = 0
之后在while体中增加if判断,跳过多少行即可
if $ax_cur_line > $ax_lines
#中间是大段的原来的代码,用以转化
end
#然后变量自增即可
set $ax_cur_line = $ax_cur_line + 1
③使用的时候,原来的zbacktrace后边加跳过多少行即可,如
zbacktrace 10000 #就是说从10001行输出
[…] 如何调试PHP的Core之获取基本信息 […]
[…] 如何调试PHP的Core之获取基本信息 […]
https://bugs.php.net/bugs-generating-backtrace.php
–enable-debug 编译的版本disable了这个,坑了好久。
如果不能出core dump,或者core dump 大小为0
可以试试这样
1. ulimit -c unlimited
2. 如果是用共享盘的方式运行的,试试改变下运行目录(移出共享目录,移到本地目录)
My brother suggested I may like this blog.
He used to be totally right. This submit truly
made my day. You cann’t believe just how a lot time I
had spent for this information! Thank you!
Thanks for finally talking about > 如何调试PHP的Core之获取基本信息 | 风雪之隅 < Loved it!
爆了堆栈,貌似core都出问题了
http://my.oschina.net/lampdraem/blog/535600?fromerr=hIF4jR2m
鸟哥V5
[…] 如果你需要更多.gdbinit脚本信息请自行google,或者去鸟哥的博客看看. […]
同样的做下,为什么不抛出coredump呢?我测试过C语言的,可以有,但是PHP的没有,求指点。
Fatal error: Allowed memory size of 134217728 bytes exhausted at /home/data/packages/php-5.4.26/Zend/zend_execute.h:184 (tried to allocate 130968 bytes) in /root/test.php on line 3
这个太强大了,正好在自己程序的debug中遇到。
跑这段代码。。。没出现segment fault.而是直接down机。
[…] 风雪之隅 » PHP源码分析 Posted in: php / Tagged: 如何调试PHP的Core之获取基本信息 […]
[…] 如何调试PHP的Core之获取基本信息 If you enjoyed this post, make sure you subscribe to my RSS feed! 相关帖子:No Related Post This entry was posted in PHP by deli. Bookmark the permalink. […]
When I tried that infinite recursion example, the system gave me an error like:
“HP Fatal error: Allowed memory size of 134217728 bytes exhausted ” instead of “Segmentation Fault” and refused to generate dump core file, although I had proper settings for ulimit.
Is this example platform dependent, BTW I am using a Mac.
Thanks
[…] 我曾经介绍过如何通过PHP的Core文件获取信息:如何调试PHP的Core之获取基本信息, 对于调用参数这块, […]
[…] 我曾经介绍过如何通过PHP的Core文件获取信息:如何调试PHP的Core之获取基本信息, 对于调用参数这块, […]
[…] 如何调试PHP的Core之获取基本信ö… […]
[…] 本文地址: http://www.laruence.com/2011/06/23/2057.html […]
[…] 如何调试PHP的Core之获取基本信息 | 风雪之隅 […]
太好了,正找这方面的资料
哗,沙发哇,鸡冻,又学到了。.gdbinit很给力。