付録B. gdb¶
B-1.gdb とは¶
C のソースコードをコンパイルすると、バイナリオブジェクトに変換されます。バイナリになった実行ファイルを C のソースレベルで追跡するツールとして、gdb が利用できます。
メモリ管理を自前で行なう必要のある C 言語で開発している以上、不正なメモリアクセスを意味する "Segmentation fault" エラーから逃れることはできません。これは "Segmentation Violation" や『メモリ保護違反』『一般保護違反』などとも呼ばれますが、以下 "SEGV" と記載します。
バイナリ実行時に SEGV 等の継続不能なエラーが発生した場合、Linux を始めとする Unix 由来の OS では、カレントディレクトリに「コアファイル」と呼ばれるメモリダンプファイルが生成されます。これを gdb で読み込むことにより、ソースコードレベルでエラーの発生箇所を特定できます。
この章では、gdb の使い方について簡単にご紹介します。
B-2.利用準備¶
gdb はデフォルトではインストールされていないかもしれませんが、 推奨環境 に従った場合、自動でインストールされるようにしています。入っていない場合は以下のコマンドでインストールしてください。
$ sudo yum install gdb
~/.gdbinit というファイルが存在する場合、gdb はそれを起動時にスタートアップスクリプトとして読み込みます。PHP Extension を開発する場合、 ひな形の作成 で使用した ext_skel がサンプルの ~/php/.gdbinit を生成しますので、これをホームディレクトリにコピーしておきます。
$ cp ~/php/.gdbinit ~
SEGV によって作られるコアファイルは一般ユーザーにとっては邪魔なだけなので、最近はデフォルトではコアファイルを作らないようになっています。この機能は Extension 開発には必要なので、シェルで有効にしておきます。
$ vi ~/.bashrc
ulimit -c unlimited ← なければこの行を追加
$ . ~/.bashrc ← カレントシェルに即時反映
$ ulimit -a ← 動作確認
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7276
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
"core file size" が unlimited になっていれば OK です。
B-3.コアファイルの利用例¶
以下に、SEGV が発生した際の、gdb によるデバッグの例を示します。
$ php g.php
Segmentation fault (コアダンプ)
$ ls core*
core.4799
SEGV が発生すると、カレントディレクトリに "core.PID" というファイル名でコアファイルが生成されます。俗に言う『コアを吐く』という現象です。PID はプロセス ID で、実行のたびに異なる値になります。コアファイルの中身は、SEGV でプロセスが強制終了された時点のプロセス内部のメモリ内容そのものです。これを利用して SEGV が発生した箇所を特定できます。
gdb は "gdb PHPバイナリ [コアファイル名]" で起動します。
$ gdb php core.4799
Reading symbols from /usr/local/bin/php...done.
[New LWP 4799]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
Core was generated by `php g.php\'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000000008a1f22 in zend_mm_free_heap (heap=0x7f1e93600040,
ptr=0x31cfde0,
__zend_filename=0x7f1e8ced4410 "/home/vagrant/php/ext/aha/aha.c",
__zend_lineno=550, __zend_orig_filename=0x0, __zend_orig_lineno=0)
at /home/vagrant/php/Zend/zend_alloc.c:1372
1372 zend_mm_page_info info = chunk->map[page_num];
gdb の起動時に、以下のように表示される場合があります。
Missing separate debuginfos, use: debuginfo-install keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.14.1-27.el7_3.x86_64 libcom_err-1.42.9-9.el7.x86_64 libselinux-2.5-6.el7.x86_64 ncurses-libs-5.9-13.20130511.el7.x86_64 openssl-libs-1.0.1e-60.el7_3.1.x86_64 pcre-8.32-15.el7_2.1.x86_64 readline-6.2-9.el7.x86_64
この場合、システムライブラリがデバッグ情報が含まれない状態でインストールされていますので、指示通り debuginfo-install でライブラリをインストールします。
$ sudo debuginfo-install keyutils-libs-1.5.8-3.el7.x86_64 krb5-libs-1.14.1-27.el7_3.x86_64 libcom_err-1.42.9-9.el7.x86_64 libselinux-2.5-6.el7.x86_64 ncurses-libs-5.9-13.20130511.el7.x86_64 openssl-libs-1.0.1e-60.el7_3.1.x86_64 pcre-8.32-15.el7_2.1.x86_64 readline-6.2-9.el7.x86_64
gdb にはさまざまコマンドが用意されていますが、ここでは bt(backtrace) コマンドにより、SEGV で止まった時点での呼び出しシーケンスを表示しています。
(gdb) bt
#0 0x00000000008a1f22 in zend_mm_free_heap (heap=0x7f1e93600040,
ptr=0x31cfde0,
__zend_filename=0x7f1e8ced4410 "/home/vagrant/php/ext/aha/aha.c",
__zend_lineno=550, __zend_orig_filename=0x0, __zend_orig_lineno=0)
at /home/vagrant/php/Zend/zend_alloc.c:1372
#1 0x00000000008a47d8 in _efree (ptr=0x31cfde0,
__zend_filename=0x7f1e8ced4410 "/home/vagrant/php/ext/aha/aha.c",
__zend_lineno=550, __zend_orig_filename=0x0, __zend_orig_lineno=0)
at /home/vagrant/php/Zend/zend_alloc.c:2433
#2 0x00007f1e8ced3a93 in zif_aha_MbStatusReceive (
execute_data=0x7f1e936140c0, return_value=0x7f1e93614090)
at /home/vagrant/php/ext/aha/aha.c:550
#3 0x000000000093a2a8 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER ()
at /home/vagrant/php/Zend/zend_vm_execute.h:675
#4 0x00000000009399cb in execute_ex (ex=0x7f1e93614030)
at /home/vagrant/php/Zend/zend_vm_execute.h:429
#5 0x0000000000939add in zend_execute (op_array=0x7f1e9367e000,
return_value=0x0) at /home/vagrant/php/Zend/zend_vm_execute.h:474
#6 0x00000000008db17c in zend_execute_scripts (type=8, retval=0x0,
file_count=3) at /home/vagrant/php/Zend/zend.c:1476
#7 0x0000000000848f91 in php_execute_script (primary_file=0x7fffd73a5520)
at /home/vagrant/php/main/main.c:2537
#8 0x00000000009baaae in do_cli (argc=2, argv=0x31cdba0)
---Type <return> to continue, or q <return> to quit---
at /home/vagrant/php/sapi/cli/php_cli.c:993
#9 0x00000000009bba6d in main (argc=2, argv=0x31cdba0)
at /home/vagrant/php/sapi/cli/php_cli.c:1381
B-4.gdb のコマンド¶
参照カウント法 のところで出てきた gc.refcount の動きを確かめようと思って、以下のような PHP スクリプトを書いてみました。
$ cat -n simple-copy.php
1 <?php
2 $a = "new string";
3 $b = $a;
これを例にして、gdb の使い方を見てみましょう。gdb の中で使えるコマンドは多数ありますが、ここでは新しいコマンドが出てくるたびに、逐次簡単な書式説明をしています。ここに出てこないコマンドは help で探してみてください。
まず、単に php を引数として gdb を起動します。
$ gdb php
Reading symbols from /usr/local/bin/php...done.
main() 関数の先頭にブレークポイントを設定します。
(gdb) b main
Breakpoint 1 at 0x9bb3f1: file /home/vagrant/php/sapi/cli/php_cli.c, line 1198.
main 関数のパスが表示されていますので、このソースファイルを別ウィンドウで表示しておくと理解が進みます。
break(b)¶
ブレークポイントを設定します。引数として関数名または [ソースファイル名:]行番号 を指定できます。ソースファイル名が省略されると、現在のソースが使われます。
動かしたい PHP スクリプトを指定して実行を開始します。
(gdb) run simple-copy.php
Starting program: /usr/local/bin/php simple-copy.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
Breakpoint 1, main (argc=2, argv=0x7fffffffe538)
at /home/vagrant/php/sapi/cli/php_cli.c:1198
1198 int exit_status = SUCCESS;
run¶
プログラムの実行を開始します。引数はコマンドラインで指定する引数と同じです。ブレークポイントに到達するまで実行を継続します。
現在のプロセスの実行状態を表示します。
(gdb) bt
#0 main (argc=2, argv=0x7fffffffe538)
at /home/vagrant/php/sapi/cli/php_cli.c:1198
(gdb) p *argv
$3 = 0x7fffffffe76f "/usr/local/bin/php"
(gdb) p argv[0]
$4 = 0x7fffffffe76f "/usr/local/bin/php"
(gdb) p argv[1]
$5 = 0x7fffffffe782 "simple-copy.php"
backtrace(bt)¶
バックトレースを表示します。
print(p)¶
その時点で可視(アクセス可能)なメモリの中身を表示します。
(gdb) l
1193 BOOL using_wide_argv = 0;
1194 # endif
1195 #endif
1196
1197 int c;
1198 int exit_status = SUCCESS;
1199 int module_started = 0, sapi_started = 0;
1200 char *php_optarg = NULL;
1201 int php_optind = 1, use_extended_info = 0;
1202 char *ini_path_override = NULL;
list(l)¶
現在行周辺のソースコードを表示します。
list(l) で表示されるのは、php バイナリの元になっている C のコードです。php バイナリから見ると simple-copy.php は単なるデータファイルに過ぎないので、list コマンドで中身を表示することはできません。ただし、シェルを経由して間接的に表示することはできます。
(gdb) !cat simple-copy.php
<?php
$a = "new string";
$b = $a;
next(n)¶
ソースコードレベルで1行実行します。関数の中には入りません。
(gdb) b 1212
Breakpoint 2 at 0x9bb445: file /home/vagrant/php/sapi/cli/php_cli.c, line 1212.
(gdb) c
Continuing.
Breakpoint 2, main (argc=2, argv=0x7fffffffe538)
at /home/vagrant/php/sapi/cli/php_cli.c:1212
1212 argv = save_ps_args(argc, argv);
continue(c)¶
次のブレークポイントまで、一気に実行します。
(gdb) b do_cli
Breakpoint 3 at 0x9ba05c: file /home/vagrant/php/sapi/cli/php_cli.c, line 664.
(gdb) c
Continuing.
Breakpoint 3, do_cli (argc=2, argv=0x1296bc0)
at /home/vagrant/php/sapi/cli/php_cli.c:664
664 int behavior = PHP_MODE_STANDARD;
適当にあたりをつけて、do_cli() まで進みました。中のソースを確認します。
(gdb) b 984
Breakpoint 4 at 0x9baa2c: file /home/vagrant/php/sapi/cli/php_cli.c, line 984.
(gdb) c
Continuing.
Breakpoint 4, do_cli (argc=2, argv=0x1296bc0)
at /home/vagrant/php/sapi/cli/php_cli.c:984
984 switch (behavior) {
(gdb) l
979 }
980
981 zend_is_auto_global_str(ZEND_STRL("_SERVER"));
982
983 PG(during_request_startup) = 0;
984 switch (behavior) {
985 case PHP_MODE_STANDARD:
986 if (strcmp(file_handle.filename, "-")) {
987 cli_register_file_handles();
988 }
981 行目の zend_is_auto_global_str(ZEND_STRL("_SERVER")) は、"_SERVER" スーパーグローバル変数を登録に行っているのではないかと想像できます。
(gdb) p behavior
$1 = 1
(gdb) p PHP_MODE_STANDARD
No symbol "PHP_MODE_STANDARD" in current context.
C のソース上は PHP_MODE_STANDARD となっていても、 #define で定義されたマクロはプリプロセッサ(コンパイルの前処理)により実際の値に置き換えられてからコンパイラに渡されるので、gdb 上では参照できません。
(gdb) n
986 if (strcmp(file_handle.filename, "-")) {
PHP_MODE_STANDARD のブロックに入りました。
(gdb) l 984,996
984 switch (behavior) {
985 case PHP_MODE_STANDARD:
986 if (strcmp(file_handle.filename, "-")) {
987 cli_register_file_handles();
988 }
989
990 if (interactive && cli_shell_callbacks.cli_shell_run) {
991 exit_status = cli_shell_callbacks.cli_shell_run();
992 } else {
993 php_execute_script(&file_handle);
994 exit_status = EG(exit_status);
995 }
996 break;
ここがメイン部分のようです。list 行番号,行番号 で指定範囲のソースを表示できます。注意深く1行ずつ実行します。
(gdb) n
987 cli_register_file_handles();
(gdb) n
990 if (interactive && cli_shell_callbacks.cli_shell_run) {
(gdb) n
993 php_execute_script(&file_handle);
ここでスクリプトを実行しているようです。中に入ってみます。
(gdb) s
php_execute_script (primary_file=0x7fffffffe180)
at /home/vagrant/php/main/main.c:2446
2446 zend_file_handle prepend_file = {{0}, NULL, NULL, 0, 0}, append_file = {{0}, NULL, NULL, 0, 0};
step(s)¶
関数の中に入る。
ソースファイルが main.c に切り替わりました。逐次エディタで該当箇所のソースコードを表示しながら追っていきます。
(gdb) l 2528,2538
2528 if (CG(start_lineno) && prepend_file_p) {
2529 int orig_start_lineno = CG(start_lineno);
2530
2531 CG(start_lineno) = 0;
2532 if (zend_execute_scripts(ZEND_REQUIRE, NULL, 1, prepend_file_p) == SUCCESS) {
2533 CG(start_lineno) = orig_start_lineno;
2534 retval = (zend_execute_scripts(ZEND_REQUIRE, NULL, 2, primary_file, append_file_p) == SUCCESS);
2535 }
2536 } else {
2537 retval = (zend_execute_scripts(ZEND_REQUIRE, NULL, 3, prepend_file_p, primary_file, append_file_p) == SUCCESS);
2538 }
ここにある zend_execute_scripts() がメインの実行ルーチンのようです。2箇所ありますので、その手前まで実行して、入り口を特定します。
(gdb) b 2528
Breakpoint 3 at 0x848eb2: file /home/vagrant/php/main/main.c, line 2528.
(gdb) c
Continuing.
Breakpoint 3, php_execute_script (primary_file=0x7fffffffe180)
at /home/vagrant/php/main/main.c:2528
2528 if (CG(start_lineno) && prepend_file_p) {
(gdb) n
2537 retval = (zend_execute_scripts(ZEND_REQUIRE, NULL, 3, prepend_file_p, primary_file, append_file_p) == SUCCESS);
入り口が特定できました。中に入ってみます。
(gdb) s
zend_execute_scripts (type=8, retval=0x0, file_count=3)
at /home/vagrant/php/Zend/zend.c:1463
1463 va_start(files, file_count);
Zend ディレクトリ内に入りました。ここからが Zend Engine のようです。いったん gdb を終了します。
(gdb) q
A debugging session is active.
Inferior 1 [process 4457] will be killed.
Quit anyway? (y or n) y
- quit(q)
- gdb を終了します。
B-5.zend_execute_scripts()¶
利用するスクリプトを再掲します。
$ cat -n simple-copy.php
1 <?php
2 $a = "new string";
3 $b = $a;
それでは、最初から実行してみます。ちなみに gdb では readline が使えますので、上下矢印キー(↑↓)で過去のコマンド履歴が呼び出せます。
$ gdb php
Reading symbols from /usr/local/bin/php...done.
(gdb) b zend_execute_scripts
Breakpoint 1 at 0x8db058: file /home/vagrant/php/Zend/zend.c, line 1463.
(gdb) run simple-copy.php
Starting program: /usr/local/bin/php simple-copy.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
Breakpoint 1, zend_execute_scripts (type=8, retval=0x0, file_count=3)
at /home/vagrant/php/Zend/zend.c:1463
1463 va_start(files, file_count);
メインの関数は数十行程度です。
(gdb) l 1456,1492
1456 ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */
1457 {
1458 va_list files;
1459 int i;
1460 zend_file_handle *file_handle;
1461 zend_op_array *op_array;
1462
1463 va_start(files, file_count);
1464 for (i = 0; i < file_count; i++) {
1465 file_handle = va_arg(files, zend_file_handle *);
1466 if (!file_handle) {
1467 continue;
1468 }
1469
1470 op_array = zend_compile_file(file_handle, type);
1471 if (file_handle->opened_path) {
1472 zend_hash_add_empty_element(&EG(included_files), file_handle->opened_path);
1473 }
1474 zend_destroy_file_handle(file_handle);
1475 if (op_array) {
1476 zend_execute(op_array, retval);
---Type <return> to continue, or q <return> to quit---
1477 zend_exception_restore();
1478 zend_try_exception_handler();
1479 if (EG(exception)) {
1480 zend_exception_error(EG(exception), E_ERROR);
1481 }
1482 destroy_op_array(op_array);
1483 efree_size(op_array, sizeof(zend_op_array));
1484 } else if (type==ZEND_REQUIRE) {
1485 va_end(files);
1486 return FAILURE;
1487 }
1488 }
1489 va_end(files);
1490
1491 return SUCCESS;
1492 }
for() ループで複数の PHP スクリプトファイルをひとつずつ処理しています。1470 行目の zend_compile_file() でファイルをコンパイルしてオペコードに変換し、1476 行目の zend_execute() でオペコードを逐次実行しているようです。
(gdb) p file_count
$6 = 3
PHP スクリプトファイルは 1 個しかないのに、ファイル数は 3 が渡ってきています。追ってみると、最初と最後のファイルは空のようでスキップされたため、 auto_prepend_file と auto_append_file の処理を行っているのではないかと思われます。
コンパイル処理はとりあえず実行時の追跡には関係ないので、zend_execute() の中に入っていきます。
B-6.zend_execute()¶
(gdb) b zend_execute
Breakpoint 2 at 0x9399fb: file /home/vagrant/php/Zend/zend_vm_execute.h, line 461.
(gdb) c
Continuing.
Breakpoint 2, zend_execute (op_array=0x7ffff3e7f000, return_value=0x0)
at /home/vagrant/php/Zend/zend_vm_execute.h:461
461 if (EG(exception) != NULL) {
(gdb) l 457,476
457 ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value)
458 {
459 zend_execute_data *execute_data;
460
461 if (EG(exception) != NULL) {
462 return;
463 }
464
465 execute_data = zend_vm_stack_push_call_frame(ZEND_CALL_TOP_CODE | ZEND_CALL_HAS_SYMBOL_TABLE,
466 (zend_function*)op_array, 0, zend_get_called_scope(EG(current_execute_data)), zend_get_this_object(EG(current_execute_data)) );
467 if (EG(current_execute_data)) {
468 execute_data->symbol_table = zend_rebuild_symbol_table();
469 } else {
470 execute_data->symbol_table = &EG(symbol_table);
471 }
472 EX(prev_execute_data) = EG(current_execute_data);
473 i_init_execute_data(execute_data, op_array, return_value);
474 zend_execute_ex(execute_data);
475 zend_vm_stack_free_call_frame(execute_data);
476 }
ざっと見る限り、実際に実行データを実行しているのは 474 行目の zend_execute_ex() のようです。
(gdb) b zend_execute_ex
Function "zend_execute_ex" not defined.
Make breakpoint pending on future shared library load? (y or [n]) n
どうも zend_execute_ex() は単なる関数ではないようで、gdb からは見えなくなっており、ブレークポイントが設定できません。grep で探してみると、これは関数ポインタで、実体は以下のところに定義がありました。
~/php$ grep -rI zend_execute_ex . | grep ZEND_API | grep -v extern
./Zend/zend_execute_API.c:ZEND_API void (*zend_execute_ex)(zend_execute_data *execute_data);
変数なので、以下で定義できるはずです。
(gdb) b *zend_execute_ex
Breakpoint 3 at 0x7fffed695dbb: file /home/vagrant/xdebug/xdebug.c, line 1590.
後から追加で導入した xdebug の中にブレークポイントが設定されてしまいました。この環境では追跡が困難になりそうなので、いったん Xdebug Extension の組み込みを解除してから、再度挑戦します。
(gdb) q
A debugging session is active.
Inferior 1 [process 4476] will be killed.
Quit anyway? (y or n) y
$ sudo vi /usr/local/lib/php.ini
$ cat /usr/local/lib/php.ini
extension=/home/vagrant/php/ext/my_ext/modules/my_ext.so
# zend_extension=/home/vagrant/xdebug/modules/xdebug.so
B-7.zend_execute_ex()¶
$ gdb php
Reading symbols from /usr/local/bin/php...done.
(gdb) b *zend_execute_ex
Breakpoint 1 at 0x0
(gdb) run simple-copy.php
Starting program: /usr/local/bin/php simple-copy.php
Warning:
Cannot insert breakpoint 1.
Error accessing memory address 0x0: 入力/出力エラーです.
起動直後は zend_execute_ex に値が設定されていないので、ブレークポイントとしては使えないようです。いったんブレークポイントを解除します。
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000000000
(gdb) delete 1
info¶
各種のステータスを表示します。引数として表示したい属性を指定します。
delete¶
ブレークポイントを削除します。引数として削除したいブレークポイントの番号を指定します。
いったん zend_execute() に入り、*zend_execute_ex に値が入っているのを確認後、再度ブレークポイントを設定します。
$ gdb php
Reading symbols from /usr/local/bin/php...done.
(gdb) b zend_execute
Breakpoint 1 at 0x9399fb: file /home/vagrant/php/Zend/zend_vm_execute.h, line 461.
(gdb) run simple-copy.php
Starting program: /usr/local/bin/php simple-copy.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
Breakpoint 1, zend_execute (op_array=0x7ffff3e7e000, return_value=0x0)
at /home/vagrant/php/Zend/zend_vm_execute.h:461
461 if (EG(exception) != NULL) {
(gdb) p zend_execute_ex
$1 = (void (*)(zend_execute_data *)) 0x939982 <execute_ex>
(gdb) p *zend_execute_ex
$2 = {void (zend_execute_data *)} 0x939982 <execute_ex>
(gdb) c
Continuing.
[Inferior 1 (process 4523) exited normally]
プロセスが正常終了してしまいました。どうもうまくいきません。しかたがないので今度は行番号で挑戦してみます。
(gdb) delete 1
(gdb) b zend_execute
Breakpoint 2 at 0x9399fb: file /home/vagrant/php/Zend/zend_vm_execute.h, line 461.
(gdb) run simple-copy.php
Starting program: /usr/local/bin/php simple-copy.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib64/libthread_db.so.1".
Breakpoint 2, zend_execute (op_array=0x7ffff3e7e000, return_value=0x0)
at /home/vagrant/php/Zend/zend_vm_execute.h:461
461 if (EG(exception) != NULL) {
(gdb) b 474
Breakpoint 3 at 0x939aca: file /home/vagrant/php/Zend/zend_vm_execute.h, line 474.
(gdb) c
Continuing.
Breakpoint 3, zend_execute (op_array=0x7ffff3e7e000, return_value=0x0)
at /home/vagrant/php/Zend/zend_vm_execute.h:474
474 zend_execute_ex(execute_data);
今度はうまく止まってくれたようです。中に入ってみます。
(gdb) s
execute_ex (ex=0x7ffff3e14030) at /home/vagrant/php/Zend/zend_vm_execute.h:411
411 const zend_op *orig_opline = opline;
(gdb) bt
#0 execute_ex (ex=0x7ffff3e14030)
at /home/vagrant/php/Zend/zend_vm_execute.h:411
#1 0x0000000000939add in zend_execute (op_array=0x7ffff3e7e000,
return_value=0x0) at /home/vagrant/php/Zend/zend_vm_execute.h:474
#2 0x00000000008db17c in zend_execute_scripts (type=8, retval=0x0,
file_count=3) at /home/vagrant/php/Zend/zend.c:1476
#3 0x0000000000848f91 in php_execute_script (primary_file=0x7fffffffe180)
at /home/vagrant/php/main/main.c:2537
#4 0x00000000009baaae in do_cli (argc=2, argv=0x1296bc0)
at /home/vagrant/php/sapi/cli/php_cli.c:993
#5 0x00000000009bba6d in main (argc=2, argv=0x1296bc0)
at /home/vagrant/php/sapi/cli/php_cli.c:1381
zend_execute() の直前に定義のある、 execute_ex() という関数(のようなもの?)の中で止まりました。ここが Zend Engine の中心のようですが、かなり難解なので、筆者の実力では gdb で追えるのはここまでのようです。ちなみに zend_vm_execute.h は6万ステップ以上あります。
Zend Engine のコアを追いかけるのは C 言語に精通していないと難しそうですが、PHP Extension の中を追跡する程度であれば gdb は非常に役に立ちますので、ぜひ使ってみてください。
B-8. Extension 開発用の .gdbinit¶
~/php (PHPのソースツリー)直下に .gdbinit が用意されています。これをホームディレクトリにコピーすることで、gdb 上でさまざまな機能が使えるようになります。:
$ cp ~/php/.gdbinit ~
以下のようなコマンドが用意されています。興味のあるかは試してみてください。
No. | コマンド名 | 説明 |
---|---|---|
1 | set_ts | リソースをセットします。プロセスが走っていない場合に gdb が
ts_resource_ex を呼び出すために重要です。ただし、フレーム
情報の引数からリソースを取得できる場合もあります。
|
2 | print_cvs | コンパイル後の変数とその値を表示します。zend_execute_data が
セットされている場合、そのスコープの範囲にあるコンパイル後の
値を表示します。パラメータが指定されない場合、スコープとして
current_execute_data を使用します。
使用法:print_cvs [zend_execute_data *]
|
3 | dump_bt | 現在の実行スタックをダンプします。
使用法:dump_bt executor_globals.current_execute_data
|
4 | printzv | zval の中身を表示します。 |
5 | print_const_table | 定数テーブルを表示します。 |
6 | print_ht | zval から作られた HashTable の要素をダンプします。 |
7 | print_htptr | ポインタから作られた HashTable の要素をダンプします。 |
8 | print_htstr | 文字列から作られた HashTable の要素をダンプします。 |
9 | print_ft | 関数テーブル( HashTable )をダンプします。 |
10 | print_inh | クラスの継承関係を表示します(?) |
11 | print_pi | 引数としてオブジェクトのプロパティへのポインタを受け取り、
プロパティの情報を表示します。
使用法:print_pi <ptr>
|
12 | printzn | znode の型とその中身を表示します。
使用法:printzn &opline->op1
|
13 | printzops | 現在の opline のオペランドをダンプします。 |
14 | print_zstr | zend_string の長さとその中身を表示します。
使用法:print_zstr <ptr> [最大長]
|
15 | zbacktrace | バックトレースを表示します。
このコマンドは、おおむね以下のショートカットです。
> (gdb) ____executor_globals
> (gdb) dump_bt $eg.current_execute_data
|
16 | lookup_root | ルートにおける refcounted を検索します。
使用法:lookup_root [ptr]
|