C-shnyデバッガーでのPHP:Zend Engineの内部を掘ります

どういうわけか私は問題に直面しなければなりませんでした:PHP Webクローラーはそれ自体で正常に動作し、動作し、その後突然(3〜6時間の作業の後)何かを停止し、100%CPUを消費し始めます。 そのような問題を探す方法は? サイクルのどこに行くのかをどうやって知るのですか? しかし、デバッガーでPHPに接続し、そこから必要なものをすべて見つけたらどうでしょうか。 カットの下の詳細。



この状況で何ができますか?



ここには多くのオプションはありません。スクリプト全体のログにエントリを配置し、彼が停止したものを確認できます。 このことから、どこでどのようにハングするのかを推測できます。 非常に長い時間がかかります-ログにエントリを置き、ハングアップし、見た、十分な情報がなかった、さらに多くのエントリを整理したなど -したがって、他に適切なものがない場合は、このオプションを後で使用します。

これにxdebugを使用しても動作しません-私が理解しているように、既に実行中のPHPスクリプトに接続する機能がありません。 また、スクリプトを既にxdebugの下で実行している場合は、「実行」をクリックすることはできません。その後、ハングしたときに「一時停止」をクリックします。xdebugでは、



アイデア-GDBを使用してみてください!



私の主な仕事はPHPに関連していますが、GCCでC ++を書く必要があります(これは本当に好きなことです)。 gdbを使用してサーバー上で直接c ++プログラムをデバッグした経験があります-これはそれほど難しくなく、実際、gdbデバッガーはコンソールプログラムにとって非常に便利です。 それでは、PHPスクリプトを使用してデバッグしてみませんか? 同時に、PHPの内部を少し活発に掘り下げることもできます。



何が必要ですか



サーバーへのsshアクセスが必要です。 ルートは必要ありません-ローカルですべてを行うことができます。 だから:



Gdb


管理者にインストールを依頼するか、ローカルでコンパイルしてインストールすることができます。 管理者に尋ねました。



デバッグ情報でコンパイルされたPHP


実際、デバッグバージョンは必要ありません。 必要なのは、-gスイッチを使用してPHPをコンパイルすることだけです。 何らかの理由で、PHP 5.2.17はこのキーを使用してデバッグアセンブリにアセンブルされなかったため、問題が大幅に促進されました。通常のバージョンで使用されているのと同じ拡張機能を使用できました。 私の知る限り、PHPをデバッグバージョンでアセンブルした場合、これらの同じ拡張機能を使用することはできません。PHPに付属する拡張機能を使用する必要があります。

今後は、PHPをビルドするためにlibxml2が必要だと言います。 さらに、問題はlibcurlにあることが判明したため、さらにデバッグアセンブリにlibcurlを組み込み、その中に入るようにしました。

そのため、収集します(メモリから書き込みますので、不正確になる可能性があります)。

$ wget <libxml2 download url> $ tar -xzf libxml2-2.7.8.tar.gz $ cd libxml2-2.7.8 $ ./configure --prefix=$HOME/libs $ make && make install
      
      





 $ wget <libcurl download url> $ tar -xzf curl-7.18.2.tar.gz $ cd curl-7.18.2 $ ./configure --prefix=$HOME/libs --enable-debug $ make && make install
      
      





PHPビルドはもう少し複雑です。debianのphp.iniファイルへのパス、コンパイルされたlibxml2へのパス、コンパイルされたlibcurlへのパスを指定する必要があります。

 $ wget <php-5.2.17 download url> $ tar -xzf php-5.2.17.tar.gz $ cd php-5.2.17 $ ./configure --disable-debug --with-config-file-path=/etc/php5/cli --with-config-file-scan-dir=/etc/php5/cli/conf.d --with-libxml-dir=$HOME/libs --disable-pdo --with-curl=$HOME/libs $ make
      
      





繰り返します。 --disable-debugを使用してPHPをコンパイルし(同時に-gコンパイラオプションを指定した)、既製のモジュールをすべて結合することは、PHPをすべてのモジュールと完全にローカルにインストールするよりも簡単でした。 したがって、私はインストールを行いませんでした。 --prefix = $ HOME / libsオプションで設定してmake installする方が良いかもしれませんが、上記でやったことで目的に十分であることがわかりました。

すべてコンパイル済み-PHPを実行します。 ここでも、物事はそれほどスムーズではありません。拡張機能の場所を伝えるオプションがすぐに見つからなかったため、PHPを起動するたびにこのディレクトリを指定する必要がありました。

 $ php/php-5.2.17/sapi/cli/php -d extension_dir=/usr/lib/php5/20060613 PHP Warning: Module 'curl' already loaded in Unknown on line 0
      
      





curlのエラーは理解できます-すでに組み込みのcurlモジュールでPHPをコンパイルしているため、外部curl.soに接続しようとすると、このようなエラーが表示されます。 一般的には大丈夫です。

すべてをアセンブリしたら、実行してバグをキャッチできます。



実際には、起動とデバッグ



読者に不必要な情報を与えすぎないように、PHPで小さなスクリプトを作成し、gdbを使用してデバッグ機能を確認できるようにしました。

 <?php class A { protected $_a = NULL; public function __construct($a) { $this->_a = $a; } public function run() { while (true) { sleep(1); } } } class B { protected $_a = NULL; protected $_b = NULL; public function __construct() { $this->_b = rand(1000, 9999); $this->_a = new A(rand(1000, 9999)); } public function run() { $this->_a->run(); } } $b = new B; $b->run();
      
      





そのため、スクリプトを実行します。

 $ php/php-5.2.17/sapi/cli/php -d extension_dir=/usr/lib/php5/20060613 test/test.php
      
      





プロセスのPIDを見て、別のターミナルでGDBを実行します。

 $ ps auwx | grep test.php $ gdb GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. ...... This GDB was configured as "x86_64-linux-gnu". (gdb)
      
      





プロセスに添付します。

 (gdb) attach 7455 Attaching to process 7455 Reading symbols from /<homedir>/php/php-5.2.17/sapi/cli/php...done. ..... Reading symbols from /<homedir>/libs/lib/libcurl.so.4...done. Loaded symbols for /<homedir>/libs/lib/libcurl.so.4 ..... 0x00007fd9e6c22040 in nanosleep () from /lib/libc.so.6 (gdb)
      
      





[lib]からシンボルを読むという行が表示されたら..... done-すべてうまくいき、このバイナリを安全にデバッグできます。

バックトレースを見る

(gdb) bt

#0 0x00007fd9e6c22040 in nanosleep () from /lib/libc.so.6

#1 0x00007fd9e6c21e97 in sleep () from /lib/libc.so.6

#2 0x0000000000587277 in zif_sleep (ht=1, return_value=0x278c010, return_value_ptr=0x0, this_ptr=0x0,

return_value_used=0) at /[homedir]/php/php-5.2.17/ext/standard/basic_functions.c:4794

#3 0x000000000068a733 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fff0b7d6310)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:200

#4 0x0000000000690204 in ZEND_DO_FCALL_SPEC_CONST_HANDLER (execute_data=0x7fff0b7d6310)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:1740

#5 0x000000000068a221 in execute (op_array=0x278ad38)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:92

#6 0x00007fd9e655b90f in zend_oe () from /usr/lib/php5/20060613/ZendOptimizer.so

#7 0x000000000068a886 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fff0b7d6570)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:234

#8 0x000000000068b3af in ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER (execute_data=0x7fff0b7d6570)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:322

#9 0x000000000068a221 in execute (op_array=0x278b8c0)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:92

#10 0x00007fd9e655b90f in zend_oe () from /usr/lib/php5/20060613/ZendOptimizer.so

#11 0x000000000068a886 in zend_do_fcall_common_helper_SPEC (execute_data=0x7fff0b7d68a0)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:234

#12 0x000000000068b3af in ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER (execute_data=0x7fff0b7d68a0)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:322

#13 0x000000000068a221 in execute (op_array=0x2787b88)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:92

#14 0x00007fd9e655b90f in zend_oe () from /usr/lib/php5/20060613/ZendOptimizer.so

#15 0x0000000000665598 in zend_execute_scripts (type=8, retval=0x0, file_count=3)

at /[homedir]/php/php-5.2.17/Zend/zend.c:1134

#16 0x0000000000615608 in php_execute_script (primary_file=0x7fff0b7d8ee0)

at /[homedir]/php/php-5.2.17/main/main.c:2036

#17 0x00000000006dfa82 in main (argc=4, argv=0x7fff0b7d90f8)

at /[homedir]/php/php-5.2.17/sapi/cli/php_cli.c:1165

(gdb)







まず、execute()[Zend / zend_vm_execute.h:92]内のフレームに関心があります。 これらは、PHP関数の呼び出しです。 PHPスクリプトで現在の位置を確認する方法:

(gdb) f 13

#13 0x000000000068a221 in execute (op_array=0x2d3fb88)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:92

92 if (EX(opline)->handler(&execute_data TSRMLS_CC) > 0) {

(gdb) print execute_data.function_state.function->common.scope->name

$20 = 0x2d423a0 "B"

(gdb) print execute_data.function_state.function->common.function_name

$21 = 0x2d43790 "run"

(gdb) print execute_data.opline->lineno

$22 = 28

(gdb) f 9

#9 0x000000000068a221 in execute (op_array=0x2d438c0)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:92

92 if (EX(opline)->handler(&execute_data TSRMLS_CC) > 0) {

(gdb) print execute_data.function_state.function->common.scope->name

$23 = 0x2d42380 "A"

(gdb) print execute_data.function_state.function->common.function_name

$24 = 0x2d44c48 "run"

(gdb) print execute_data.opline->lineno

$25 = 23

(gdb) f 5

#5 0x000000000068a221 in execute (op_array=0x2d42d38)

at /[homedir]/php/php-5.2.17/Zend/zend_vm_execute.h:92

92 if (EX(opline)->handler(&execute_data TSRMLS_CC) > 0) {

(gdb) print execute_data.function_state.function->common.function_name

$26 = 0x770781 "sleep"

(gdb) print execute_data.opline->lineno

$27 = 10

(gdb)







いくつかの説明:f [number]は特定のフレームに私たちを投げ、print [chetatam]-このフレームの範囲内にあるキャラクターを印刷します。

上記の例では、クラス名、メソッド/関数名、および呼び出される行番号を取得しました(フレーム5では、組み込みのsleep()関数であるため、クラス名は定義されていません)。 実際、バックトレースPHPスクリプトを取得しました。 すでにこの情報に基づいて、記事の冒頭で説明したとらえどころのないバグの原因がどこから来たのかを理解できます。



今日は以上です。 このトピックに興味がある場合は、次回、変数の内容を調べる方法と、PHPで配列がどのように配置されるかを説明します。 ご清聴ありがとうございました。 誰かがこの資料に興味を持っていたことを願っています。



All Articles