php を GDB でデバッグするのが便利。 以下のエントリで手法が説明されている
以降、私が書くのはこれらのエントリに書かれている手順を真似たものなので、詳細を知るにあたっては是非リンク先も読んでほしい
検証: GDB で実行中の php プロセスを覗き見る
GDB は実行中のプロセスを解析の対象とすることもできる。このエントリでは、無限ループするバグった php を GDB で覗いてみよう
<?php function third($bar = "") { for (;;) { } } function second() { third(); } function first() { second(); } first();
検証環境
- CentOS7.4 (1708)
- php-5.4.16-42.el7.x86_64
- httpd-2.4.6-67.el7.centos.2.x86_64
- gdb-7.6.1-100.el7.x86_64
先に書いた無限ループするコードを /var/www/html/index.php
に置いて検証する
1..gdbinit を手に入れる
.gdbinit
を手に入れる際は 実行中の php とバージョンを合わせること 📝
wget https://raw.githubusercontent.com/php/php-src/PHP-5.4.16/.gdbinit
2. 検証用のコードにリクエストを出す
curl 'http://localhost?username=hiboma'
3. gdb でアタッチする
$ sudo gdb -q --init-command=/home/vagrant/.gdbinit -p <リクエストを受けた httpd の pid>
4. gdb であれこれして中身を覗く
先の Qiita のエントリのコマンドをぽちぽちしていく。バックトレースを取れるの、すごい便利
(gdb) zbacktrace [0x7f3edef8c458] third() /var/www/html/index.php:5 [0x7f3edef8c3a0] third() /var/www/html/index.php:10 [0x7f3edef8c2e8] second() /var/www/html/index.php:14 [0x7f3edef8c230] first() /var/www/html/index.php:17
いろいろとワラワラと表示させる。現在の関数名がとれる
(gdb) set print pretty on (gdb) print *executor_globals->active_op_array $2 = { type = 2 '\002', function_name = 0x7f3edefc2de8 "third", ⭐ scope = 0x0, fn_flags = 134217728, prototype = 0x0, num_args = 1, required_num_args = 0, arg_info = 0x7f3edefc45f8, refcount = 0x7f3edefc2e08, opcodes = 0x7f3edefc2e28, last = 6, vars = 0x7f3edefc41d8, last_var = 2, T = 1, brk_cont_array = 0x7f3edefc3a38, last_brk_cont = 1, try_catch_array = 0x0, last_try_catch = 0, static_variables = 0x0, this_var = 4294967295, filename = 0x7f3edefc1b98 "/var/www/html/index.php", ⭐ line_start = 3, line_end = 7, doc_comment = 0x0, doc_comment_len = 0, early_binding = 4294967295, literals = 0x7f3edefc4368, last_literal = 4, run_time_cache = 0x0, last_cache_slot = 0, reserved = {0x0, 0x0, 0x0, 0x0} }
hmhm … HTTP リクエスト周りのいろいろ役に立ちそうなデータをとれる
(gdb) print sapi_globals->request_info $3 = { request_method = 0x55cc687545f0 "GET", ⭐ query_string = 0x55cc6875af80 "username=hiboma", ⭐ post_data = 0x0, raw_post_data = 0x0, cookie_data = 0x0, content_length = 0, post_data_length = 0, raw_post_data_length = 0, path_translated = 0x55cc6875afa0 "/var/www/html/index.php", ⭐ request_uri = 0x55cc6875af90 "/index.php", content_type = 0x0, headers_only = 0 '\000', no_headers = 0 '\000', headers_read = 0 '\000', post_entry = 0x0, content_type_dup = 0x0, auth_user = 0x0, auth_password = 0x0, auth_digest = 0x0, argv0 = 0x0, current_user = 0x0, current_user_length = 0, argc = 0, argv = 0x0, proto_num = 1000 }
(gdb) print_ht executor_globals->symbol_table->pListHead [0x7f3edefbea90] { "_POST\0" => [0x7f3edefbeae8] (refcount=2) array(0): "_COOKIE\0" => [0x7f3edefbebc8] (refcount=2) array(0): "_FILES\0" => [0x7f3edefbeca8] (refcount=2) array(0): "_SERVER\0" => [0x7f3edefbee48] (refcount=2) array(27): "_REQUEST\0" => [0x7f3edefc1920] (refcount=1) array(1): }
セッションを使ってないので ps_globals.http_session_vars
は参照できないのかな
(gdb) printzv ps_globals.http_session_vars [(nil)] Cannot access memory at address 0x14
とりあえずこんな感じ。
どこらへんの変数を覗き見たらいいかは、状況に応じて変わるだろう。GDB を使うことでこのようにアプローチができることを心に留めておくのが肝要と思う
感想
- Web サービスをやってると「productoin サーバで やたらと CPU や メモリを食い続けてるプロセスがいるんだけど。どのメソッドか分からない 😲 」のようなケースに遭遇するだろう。こういうテクニックを知っていると調査・原因の究明に役に立つだろう
- 実際、そういうケースの解決に用いたので ブログにまとめておこうと思った次第