GDB で php プロセスの覗き見

phpGDBデバッグするのが便利。 以下のエントリで手法が説明されている

qiita.com

blog.anatoo.jp

stackoverflow.com

以降、私が書くのはこれらのエントリに書かれている手順を真似たものなので、詳細を知るにあたっては是非リンク先も読んでほしい

検証: GDB で実行中の php プロセスを覗き見る

GDB は実行中のプロセスを解析の対象とすることもできる。このエントリでは、無限ループするバグった phpGDB で覗いてみよう

<?php

function third($bar = "") { 
  for (;;) {
  }
}

function second() { 
  third();
}

function first() { 
  second();
}

first();

検証環境

先に書いた無限ループするコードを /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 や メモリを食い続けてるプロセスがいるんだけど。どのメソッドか分からない 😲 」のようなケースに遭遇するだろう。こういうテクニックを知っていると調査・原因の究明に役に立つだろう
  • 実際、そういうケースの解決に用いたので ブログにまとめておこうと思った次第