有一个php程序,命令行执行时如下:
[root@h10-vm08 demo]# php ParseFile.php xxxx.ykml
*** glibc detected *** free(): invalid pointer: 0xb7869b7c ***
Aborted
[root@h10-vm08 demo]#
这个php程序调用的php extension的so文件中有非法访问指针错误,但错误发生在什么地方?用gdb来定位这类问题是最方便的。
[root@h10-vm08 demo]# gdb php
(gdb) run ParseFile.php xxxx.ykml
Starting program: /home/y/bin/php ParseFile.php xxxx.ykml
[Thread debugging using libthread_db enabled]
[New Thread -1208723776 (LWP 3225)]
*** glibc detected *** free(): invalid pointer: 0xb783cbc4 ***
Program received signal SIGABRT, Aborted.
[Switching to Thread -1208723776 (LWP 3225)]
0x005847a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
(gdb)
(gdb) bt
#0 0x005847a2 in _dl_sysinfo_int80 () from /lib/ld-linux.so.2
#1 0x004477a5 in raise () from /lib/tls/libc.so.6
#2 0x00449209 in abort () from /lib/tls/libc.so.6
#3 0x0047b71a in __libc_message () from /lib/tls/libc.so.6
#4 0x00481fbf in _int_free () from /lib/tls/libc.so.6
#5 0x0048233a in free () from /lib/tls/libc.so.6
#6 0x02f37b56 in FBMLStyleSheet::TranslateURL () from /home/y/lib/php/20060613/fbml.so
#7 0x02f453a5 in CSSParserImpl::ParseURL () from /home/y/lib/php/20060613/fbml.so
#8 0x02f44585 in CSSParserImpl::ParseVariant () from /home/y/lib/php/20060613/fbml.so
#9 0x02f461c0 in CSSParserImpl::ParseSingleValueProperty () from /home/y/lib/php/20060613/fbml.so
......
这样就得到了栈的轨迹,定位到了发生错误的函数。
下断点:
(gdb) b 'CSSParserImpl::ParseURL(unsigned int&, nsCSSValue&)'
可以让程序运行到这个函数时停止。
--enable-debug
!
After that any process crashing in your system, including PHP, will leave its core file in the directory you've specified in core_pattern.
gdb /usr/local/apache/sbin/httpd /usr/local/apache/sbin/core
gdb /home/user/dev/php-snaps/sapi/cli/php /home/user/dev/testing/core
(gdb) bt
gdb /usr/local/apache/sbin/httpd
(gdb) bt
(gdb) run /path/to/script.php
(gdb) bt
This should generate a backtrace, that you should submit in the bug report, along with any other details you can give us about your setup, and offending script.
You can locate the function call that caused a segfault, easily, with gdb. First, you need a core file or to generate a segfault under gdb as described above.
In PHP, each function is executed by an internal function called execute()
and has its own stack. Each line generated by the bt
command represents a function call stack. Typically, you will see several execute()
lines when you issue bt
. You are interested in the last execute()
stack (i.e. smallest frame number). You can move the current working stack with the up
, down
orframe
commands. Below is an example gdb session that can be used as a guideline on how to handle your segfault.
(gdb) bt
#0 0x080ca21b in _efree (ptr=0xbfffdb9b) at zend_alloc.c:240
#1 0x080d691a in _zval_dtor (zvalue=0x8186b94) at zend_variables.c:44
#2 0x080cfab3 in _zval_ptr_dtor (zval_ptr=0xbfffdbfc) at zend_execute_API.c:274
#3 0x080f1cc4 in execute (op_array=0x816c670) at ./zend_execute.c:1605
#4 0x080f1e06 in execute (op_array=0x816c530) at ./zend_execute.c:1638
#5 0x080f1e06 in execute (op_array=0x816c278) at ./zend_execute.c:1638
#6 0x080f1e06 in execute (op_array=0x8166eec) at ./zend_execute.c:1638
#7 0x080d7b93 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at zend.c:810
#8 0x0805ea75 in php_execute_script (primary_file=0xbffff650) at main.c:1310
#9 0x0805cdb3 in main (argc=2, argv=0xbffff6fc) at cgi_main.c:753
#10 0x400c91be in __libc_start_main (main=0x805c580 , argc=2, ubp_av=0xbffff6fc,
init=0x805b080 <_init>, fini=0x80f67b4 <_fini>, rtld_fini=0x4000ddd0 <_dl_fini>,
stack_end=0xbffff6ec) at ../sysdeps/generic/libc-start.c:129
(gdb) frame 3
#3 0x080f1cc4 in execute (op_array=0x816c670) at ./zend_execute.c:1605
(gdb) print (char *)(executor_globals.function_state_ptr->function)->common.function_name
$14 = 0x80fa6fa "pg_result_error"
(gdb) print (char *)executor_globals.active_op_array->function_name
$15 = 0x816cfc4 "result_error"
(gdb) print (char *)executor_globals.active_op_array->filename
$16 = 0x816afbc "/home/yohgaki/php/DEV/segfault.php"
(gdb)
In this session, frame 3 is the last execute()
call. The frame 3
command moves the current working stack to the proper frame.
print (char *)(executor_globals.function_state_ptr->function)->common.function_name
prints the function name. In the sample gdb session, the pg_result_error()
call is causing the segfault. You can print any internal data that you like, if you know the internal data structure. Please do not ask how to use gdb or about the internal data structure. Refer to gdb manual for gdb usage and to the PHP source for the internal data structure.
.so写好了是给php脚本调用的,如果php脚本执行崩掉了,.so也只能在进程中饮恨而终,这时候php脚本调试经常用的echo, print_r, var_dump都派不上用场了。即使能打印一点儿错误log出来,但也是只见表象,不知内情,根本解决不了一些诡异的bug。还好我们有gdb,下面就通过4步搞定php c extension的调试。
1.准备可调试的.so
在config.m4中加上下面的配置信息
./configure –enable-debug //和调试c程序的-g是一样的效果
make //生成带调试信息的.so
make install //安装.so到php解释器可以加载的路径
2.通过nm查看
要调试就要设置断点,要设置断点就要知道符号,php扩展中为了保证函数不和c库中的符号重复,在导出函数前都加上统一前缀zif,为了知道待调试.so都有哪些符号,nm命令再合适不过了。nm命令用来列出目标文件(.a或.so)的符号清单,包含函数或类名,如下图:
3.加载php解释器和.so到gdb
是时候加载php解释器到gdb下了,这要用到gdb的file命令:file /usr/bin/php 这里的php解释器不需要有调试符号,但要确保其加载了待调试的.so(可以通过php –m 命令参考)。
4.break设置断点,运行php脚本进行调试
都准备就绪了,设置断点吧,用从nm查看到的符号。设置好就run吧: run *.php 这条命令是将.php脚本作为参数传递给php解释器,让php解释器执行*.php脚本,并在断点处停止。然后就list, print, next把bug都找出来吧。
设置断点时一定要选择y,因为断点是设置在扩展so里面,必须load之后才能才能找到。
其他的就和调试c程序是一样的步骤了。祝大家调的开心^_^