gdbでphpプログラムをデバッグする

8707 ワード

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 "/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  or frame  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はもう役に立たない.エラーロゴを少し印刷しても、表象だけで内情が分からず、奇妙なバグは解決できません.gdbがあるので、php c extensionのデバッグを4ステップで行います.
    1.デバッグ可能なものを用意する.soはconfig.m 4に以下の構成情報を加える
    ./configure–enable-debug//とcプログラムをデバッグする-gは同じ効果です
    make//デバッグ情報付きの生成.so
    インストール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でバグを見つけましょう.
    ブレークポイントを設定するときはyを選択しなければなりません.ブレークポイントは拡張soに設定されているので、loadしてから見つけなければなりません.
    その他はcプログラムをデバッグするのと同じ手順です.皆さん楽しんでくださいね^^;