用gdb调试php程序

有一个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&)'

 

可以让程序运行到这个函数时停止。




Generating a gdb backtrace

Noticing PHP crashes

There's no absolute way to know that PHP is crashing, but there may be signs. Typically, if you access a page that is always supposed to generate output (has a leading HTML block, for example), and suddenly get "Document contains no data" from your browser, it may mean that PHP crashes somewhere along the execution of the script. Another way to tell that PHP is crashing is by looking at the Apache error logs, and looking for SEGV (Apache 1.2) or Segmentation Fault (Apache 1.3).

Important!

To get a backtrace with correct information you must have PHP configured with  --enable-debug !

If you don't have a core file yet:

  • Remove any limits you may have on core dump size from your shell:
    • tcsh: unlimit coredumpsize
    • bash/sh: ulimit -c unlimited
  • Ensure that the directory in which you're running PHP, or the PHP-enabled httpd, has write permissions for the user who's running PHP.
  • Cause PHP to crash:
    • PHP CGI: Simply run php with the script that crashes it
    • PHP Apache Module: Run httpd -X, and access the script that crashes PHP

Generic way to get a core on Linux

  • Set up the core pattern (run this command as root):
    • echo "<cores dir>/core-%e.%p" > /proc/sys/kernel/core_pattern
    • make sure the directory is writable by PHP
  • Set the ulimit (see above how to do it).
  • Restart/rerun PHP.

After that any process crashing in your system, including PHP, will leave its core file in the directory you've specified in core_pattern.

Once you have the core file:

  • Run gdb with the path to the PHP or PHP-enabled httpd binary, and path to the core file. Some examples:
    • 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
  • At the gdb prompt, run:
    • (gdb) bt

If you can't get a core file:

  • Run httpd -X under gdb with something like:
    • gdb /usr/local/apache/sbin/httpd
    • (gdb) run -X
  • Then use your web browser and access your server to force the crash. You should see a gdb prompt appear and some message indicating that there was a crash. At this gdb prompt, type:
    • (gdb) bt
    • or, running from the commandline
      • gdb /home/user/dev/php-snaps/sapi/cli/php
        • (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.

Locating which function call caused a segfault:

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 updown orframe commands. Below is an example gdb session that can be used as a guideline on how to handle your segfault.

  • Sample gdb session
    
    (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程序是一样的步骤了。祝大家调的开心^_^



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值