言午月月鸟
编程,带娃以及思考人生
首页
编程
带娃
思考人生
编程画图秀
GDB调试PHP常用命令
dingusxp
2693
## 是什么 > GDB(GNU symbolic debugger)是 GNU Project 调试器,它使你可以查看另一个程序在“执行”期间正在执行的操作–或该程序崩溃时正在执行的操作。 ## 常用命令 ```JavaScript // start gdb php -c core.xxx // 加载core文件 gdb php -p [pid] // 追踪运行的php进程 gdb php // 进入后,运行 run [参数,如文件名] // 常用 bt(where) // 打印调用堆栈;可以 bt full 打印详细信息 f(frame) n // 进入到堆栈 n l(list) [x] // 显示附近源代码。默认为当前位置;参数可以指定为具体行号,也可以是函数名 p(print) x // x是变量名,表示打印变量x的值 q(quit) // 退出gdb // debug b(break) x, // x是行号或函数,表示在对应的位置设置断点 r(run) [arg] // 运行 后面可以接参数 n(next) [n] // 执行下N(默认1)行 s(step) [n] // 执行下一行,跟 n 的差别是,如果碰到 函数 会进入 u(until) n // 执行到指定行 c(continue) // (继续)执行到下一个断点 info b // 显示当前设置的断点 delete n // 删除第n个断点 clear x // 删除 x 标记断点 // thread info thread // 显示线程 t(thread) n // 切换到线程 n // php .gdb source /path/to/php-source/.gdbinit // 加载php gdb调试实用命令 zbacktrace // 打印PHP调用堆栈 printzv 0xAA // 打印PHP变量 print_ft 0xAA // 打印Hash Table // 小技巧: // 特殊变量:executor_globals / compiler_globals print_ht executor_globals.function_table print_ht executor_globals.class_table print_ht executor_globals.zend_constants // 特殊变量:execute_data p *execute_data->func->common->function_name // 当前调用函数 p *execute_data->prev_execute_data->func->common->function_name // 上一个调用函数 // 特殊断点: b php_execute_script // 设置好 按 c,再执行 p *primary_file 可以看到当前执行脚本 b zend_execute_scripts // 设置好 按 n 进入,可以查看加载的文件。然后再设置 zend_execute / zend_execute_ex 断点,可以详细追踪具体执行过程 b zif_sleep // sleep b zif_in_array // in_array b zif_print_r // print_r b zif_var_dump // var_dump // 取字符串(示例) (gdb) p ce->name $1 = (zend_string *) 0x56391bf1e750 (gdb) p *ce->name $2 = {gc = {refcount = 1, u = {v = {type = 6 '\006', flags = 7 '\a', gc_info = 0}, type_info = 1798}}, h = 11724180756364348405, len = 29, val = "I"} (gdb) p *ce->name->val@29 $3 = "ImagickPixelIteratorException" ``` ## 辅助知识 **PHP内核重要结构** : zend_val, zend_array, zend_class_entry, zend_op_array, zend_executor_globals 等,可以阅读 [PHP内核剖析](https://www.kancloud.cn/nickbai/php7/363255) **PHP执行流程** : ![](https://public-pkg-1252772859.cos.ap-guangzhou.myqcloud.com/dingusxp-blog/misc/php_core_workflow.png) ## 示例 ### 示例1: core 文件分析 ```Bash gdb php -c core.9729 ... Core was generated by `xxx.php args`. Program terminated with signal 6, Aborted. #0 0x00007fd5d6011207 in raise () from /lib64/libc.so.6 ### // ↑ 可以得到运行的脚本,异常终止的信号 (gdb) bt #0 0x00007fd5d6011207 in raise () from /lib64/libc.so.6 #1 0x00007fd5d60128f8 in abort () from /lib64/libc.so.6 #2 0x00007fd5d6053d27 in __libc_message () from /lib64/libc.so.6 #3 0x00007fd5d605a5d4 in malloc_printerr () from /lib64/libc.so.6 #4 0x000056391af2ad72 in zend_hash_destroy (ht=0x56391bf1f918) at /usr/src/debug/php-7.1.33/Zend/zend_hash.c:1246 #5 0x000056391af0cc93 in destroy_zend_class (zv=
) at /usr/src/debug/php-7.1.33/Zend/zend_opcode.c:332 #6 0x000056391af2ad72 in zend_hash_destroy (ht=0x56391bdc1a10) at /usr/src/debug/php-7.1.33/Zend/zend_hash.c:1246 #7 0x000056391af19411 in zend_shutdown () at /usr/src/debug/php-7.1.33/Zend/zend.c:882 #8 0x000056391aeb684b in php_module_shutdown () at /usr/src/debug/php-7.1.33/main/main.c:2445 #9 0x000056391ad4b015 in main (argc=5, argv=0x56391bdc1640) at /usr/src/debug/php-7.1.33/sapi/cli/php_cli.c:1396 ### // ↑ 终止时的调用堆栈。请求已经结束,在回收资源了。可以关注下 f5 && f4 ↓ (gdb) f 5 #5 0x000056391af0cc93 in destroy_zend_class (zv=
) at /usr/src/debug/php-7.1.33/Zend/zend_opcode.c:332 332 zend_hash_destroy(&ce->properties_info); (gdb) p ce->name $1 = (zend_string *) 0x56391bf1e750 (gdb) p *ce->name $2 = {gc = {refcount = 1, u = {v = {type = 6 '\006', flags = 7 '\a', gc_info = 0}, type_info = 1798}}, h = 11724180756364348405, len = 29, val = "I"} (gdb) p *ce->name->val@29 $3 = "ImagickPixelIteratorException" (gdb) f 4 #4 0x000056391af2ad72 in zend_hash_destroy (ht=0x56391bf1f918) at /usr/src/debug/php-7.1.33/Zend/zend_hash.c:1246 1246 ht->pDestructor(&p->val); (gdb) l 1241 } 1242 } while (++p != end); 1243 } 1244 } else if (HT_IS_WITHOUT_HOLES(ht)) { 1245 do { 1246 ht->pDestructor(&p->val); 1247 if (EXPECTED(p->key)) { 1248 zend_string_release(p->key); 1249 } 1250 } while (++p != end); (gdb) p p->val $4 = {value = {lval = 94803281960016, dval = 4.6839044729445103e-310, counted = 0x56391bf1dc50, str = 0x56391bf1dc50, arr = 0x56391bf1dc50, obj = 0x56391bf1dc50, res = 0x56391bf1dc50, ref = 0x56391bf1dc50, ast = 0x56391bf1dc50, zv = 0x56391bf1dc50, ptr = 0x56391bf1dc50, ce = 0x56391bf1dc50, func = 0x56391bf1dc50, ww = { w1 = 468835408, w2 = 22073}}, u1 = {v = {type = 17 '\021', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 17}, u2 = {next = 1, cache_slot = 1, lineno = 1, num_args = 1, fe_pos = 1, fe_iter_idx = 1, access_flags = 1, property_guard = 1, extra = 1}} (gdb) p p->val->value->ptr $5 = (void *) 0x56391bf1dc50 (gdb) p *p->val->value->ptr Attempt to dereference a generic pointer. (gdb) p p->key $8 = (zend_string *) 0x56391be6b370 (gdb) p *p->key $9 = {gc = {refcount = 14, u = {v = {type = 6 '\006', flags = 1 '\001', gc_info = 0}, type_info = 262}}, h = 9223372247584098196, len = 5, val = "t"} (gdb) p *p->key->val@5 $10 = "trace" ``` ### 示例2: 通过 gdb 调试了解PHP执行流程 t.php ```PHP opened_path); 1479 } 1480 zend_destroy_file_handle(file_handle); 1481 if (op_array) { 1482 **zend_execute** (op_array, retval); 1483 zend_exception_restore(); 1484 zend_try_exception_handler(); 1485 if (EG(exception)) { 1486 zend_exception_error(EG(exception), E_ERROR); 1487 } ### ... // ↓ 查看 zend_execute 方法源码,了解流程。按 n 往下走,到 zend_execute_ex ,按 s 进入前可以打印 op_array 和 execute_data 看看 (gdb) 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); (gdb) 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 } (gdb) p op_array $1 = (zend_op_array *) 0x7ffff3a6ac40 (gdb) p *op_array $2 = {type = 2 '\002', arg_flags = "\000\000", fn_flags = 134217728, function_name = 0x0, scope = 0x0, prototype = 0x0, num_args = 0, required_num_args = 0, arg_info = 0x0, refcount = 0x7ffff3a6c000, last = 9, opcodes = 0x7ffff3afac00, last_var = 1, T = 2, vars = 0x7ffff3af2198, last_live_range = 0, last_try_catch = 0, live_range = 0x0, try_catch_array = 0x0, static_variables = 0x0, filename = 0x7ffff3ac8258, line_start = 1, line_end = 10, doc_comment = 0x0, early_binding = 4294967295, last_literal = 5, literals = 0x7ffff3ac9b90, cache_size = 8, run_time_cache = 0x0, reserved = {0x0, 0x0, 0x0, 0x0}} ### // ↑ 整个脚本解析得到9条指令,可以 p *op_array->opcodes@9 看具体的指令 (gdb) p execute_data $3 = (zend_execute_data *) 0x7ffff3a12030 (gdb) p *execute_data $4 = {opline = 0x7ffff3afac00, call = 0x0, return_value = 0x0, func = 0x7ffff3a6ac40, This = {value = {lval = 0, dval = 0, counted = 0x0, str = 0x0, arr = 0x0, obj = 0x0, res = 0x0, ref = 0x0, ast = 0x0, zv = 0x0, ptr = 0x0, ce = 0x0, func = 0x0, ww = {w1 = 0, w2 = 0}}, u1 = {v = {type = 0 '\000', type_flags = 0 '\000', const_flags = 19 '\023', reserved = 0 '\000'}, type_info = 1245184}, u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}, prev_execute_data = 0x0, symbol_table = 0x555555c004b0
, run_time_cache = 0x7ffff3af21a0, literals = 0x7ffff3b0df00} ### ...// ↓ 进入并查看 execute_ex 方法源码。里面有个 while(1) 循环,里面有 ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 就是具体执行语句 (gdb) 416 #else 417 zend_execute_data *execute_data = ex; 418 #endif 419 420 421 LOAD_OPLINE(); 422 ZEND_VM_LOOP_INTERRUPT_CHECK(); 423 424 while (1) { 425 #if !defined(ZEND_VM_FP_GLOBAL_REG) || !defined(ZEND_VM_IP_GLOBAL_REG) (gdb) 426 int ret; 427 #endif 428 #if defined(ZEND_VM_FP_GLOBAL_REG) && defined(ZEND_VM_IP_GLOBAL_REG) 429 ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 430 if (UNEXPECTED(!OPLINE)) { 431 #else 432 if (UNEXPECTED((ret = ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) != 0)) { 433 #endif 434 #ifdef ZEND_VM_FP_GLOBAL_REG 435 execute_data = orig_execute_data; ### // ↓ 可以加载 php 源码里的 .gdbinit ,有若干实用命令 (gdb) gdb /path/to/php-source/.gdbinit (gdb) zbacktrace [0x7ffff3a120b0] plus(5, 10) /tmp/php/t.php:4 [0x7ffff3a12030] (main) /tmp/php/t.php:8 (gdb) print_cvs Compiled variables count: 2 [0] 'a' [0x7ffff3a12100] long: 5 [1] 'b' [0x7ffff3a12110] long: 10 (gdb) p (zval *)0x7ffff3a12110 $5 = (zval *) 0x7ffff3a12110 (gdb) p *$5 $6 = {value = {lval = 10, dval = 4.9406564584124654e-323, counted = 0xa, str = 0xa, arr = 0xa, obj = 0xa, res = 0xa, ref = 0xa, ast = 0xa, zv = 0xa, ptr = 0xa, ce = 0xa, func = 0xa, ww = {w1 = 10, w2 = 0}}, u1 = {v = {type = 4 '\004', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 4}, u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}} ### ...// 如果感兴趣还可以 s 进入 ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); 查看具体的执行 ### // ↓ 示例,最后一步,进入 echo 打印函数返回结果 (gdb) s ZEND_RETURN_SPEC_TMP_HANDLER () at /usr/src/debug/php-7.1.33/Zend/zend_vm_execute.h:12481 12481 retval_ptr = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1); ### ...// ↓ 可以看到计算结果 type=4 (长整形),在输出前先通过 _zval_get_string_func 转换成了 type=6 (字符串) (gdb) n 51335 zend_string *str = _zval_get_string_func(z); (gdb) n 51337 if (ZSTR_LEN(str) != 0) { (gdb) p *z $7 = {value = {lval = 15, dval = 7.4109846876186982e-323, counted = 0xf, str = 0xf, arr = 0xf, obj = 0xf, res = 0xf, ref = 0xf, ast = 0xf, zv = 0xf, ptr = 0xf, ce = 0xf, func = 0xf, ww = {w1 = 15, w2 = 0}}, u1 = {v = {type = 4 '\004', type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 4}, u2 = { next = 21845, cache_slot = 21845, lineno = 21845, num_args = 21845, fe_pos = 21845, fe_iter_idx = 21845, access_flags = 21845, property_guard = 21845, extra = 21845}} (gdb) p *str $8 = {gc = {refcount = 1, u = {v = {type = 6 '\006', flags = 0 '\000', gc_info = 0}, type_info = 6}}, h = 0, len = 2, val = "1"} (gdb) p *str->val@2 $9 = "15" ### // ↓ 也可以调皮下,修改内存改变返回结果,最后输出就变成了 99 而不是 15 (gdb) p str->val[0]='9' $12 = 57 '9' (gdb) p str->val[1]='9' $13 = 57 '9' (gdb) c Continuing. 99 [Inferior 1 (process 9737) exited normally] ``` ## 参考文章&扩展阅读 ↓ 官方手册。教大家提问时如何提供gdb信息。 [https://bugs.php.net/bugs-generating-backtrace.php](https://bugs.php.net/bugs-generating-backtrace.php) ↓ PHP7内核剖析手册,值得好好看看。 [https://www.kancloud.cn/nickbai/php7/363267](https://www.kancloud.cn/nickbai/php7/363267) ↓ 实例教大家如何配合 gdb 去理解PHP源码。 [https://zhuanlan.zhihu.com/p/64144202](https://zhuanlan.zhihu.com/p/64144202)
粤ICP备19051469号-1
Copyright©dingusxp.com - All Rights Reserved
Template by
OS Templates