Author: bobyzhang, Tencent IEG Operations Development Engineer
There is a debug mode when compiling PHP, which disables memory optimization, alerts for memory leaks, and disables call stack optimization to allow us to see the complete PHP C-level call stack.
Typically, I compile two versions of PHP (one normal and one with debug) in different directories, deciding which to use through export.
Using the php-config command, you can view the configure options, modifying the prefix and with-config-file-path to the new directory, then adding the –enable-debug command.
yongkbmaster ➜ ~ php-config
Usage: /data/env/runtime/php-7.1.33-debug/bin/php-config [OPTION]
Options:
--prefix [/data/env/runtime/php-7.1.33-debug]
--includes [-I/data/env/runtime/php-7.1.33-debug/include/php -I/data/env/runtime/php-7.1.33-debug/include/php/main -I/data/env/runtime/php-7.1.33-debug/include/php/TSRM -I/data/env/runtime/php-7.1.33-debug/include/php/Zend -I/data/env/runtime/php-7.1.33-debug/include/php/ext -I/data/env/runtime/php-7.1.33-debug/include/php/ext/date/lib]
--ldflags []
--libs [-lcrypt -lz -lexslt -lresolv -lcrypt -lrt -lldap -llber -lpng -lz -ljpeg -lcurl -lbz2 -lz -lrt -lm -ldl -lnsl -lxml2 -lz -lm -ldl -lgssapi_krb5 -lkrb5 -lk5crypto -lcom_err -lssl -lcrypto -lcurl -lxml2 -lz -lm -ldl -lfreetype -lxml2 -lz -lm -ldl -lxml2 -lz -lm -ldl -lcrypt -lxml2 -lz -lm -ldl -lxml2 -lz -lm -ldl -lxml2 -lz -lm -ldl -lxslt -lxml2 -lz -ldl -lm -lssl -lcrypto -lcrypt ]
--extension-dir [/data/env/runtime/php-7.1.33-debug/lib/php/extensions/debug-non-zts-20160303]
--include-dir [/data/env/runtime/php-7.1.33-debug/include/php]
--man-dir [/data/env/runtime/php-7.1.33-debug/php/man]
--php-binary [/data/env/runtime/php-7.1.33-debug/bin/php]
--php-sapis [ cli fpm phpdbg cgi]
--configure-options [--prefix=/data/env/runtime/php-7.1.33-debug --enable-debug --enable-phpdbg-debug --with-config-file-path=/data/env/runtime/php-7.1.33-debug/etc --with-curl --with-freetype-dir --with-gd --with-gettext --with-iconv-dir --with-kerberos --with-libdir=lib64 --with-libxml-dir --with-mysqli --with-openssl --with-pcre-regex --with-pdo-mysql --with-pdo-sqlite --with-pear --with-png-dir --with-jpeg-dir --with-xmlrpc --with-xsl --with-zlib --with-bz2 --with-mhash --enable-fpm --enable-bcmath --enable-libxml --enable-inline-optimization --enable-gd-native-ttf --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-shmop --enable-soap --enable-sockets --enable-sysvsem --enable-sysvshm --enable-xml --enable-zip --with-ldap]
--version [7.1.33]
--vernum [70133]
After modification, it should look like this, then compile and install to get the debug version.
--prefix=/data/env/runtime/php-7.1.33-debug --enable-debug --enable-phpdbg-debug --with-config-file-path=/data/env/runtime/php-7.1.33-debug/etc --with-curl --with-freetype-dir --with-gd --with-gettext --with-iconv-dir --with-kerberos --with-libdir=lib64 --with-libxml-dir --with-mysqli --with-openssl --with-pcre-regex --with-pdo-mysql --with-pdo-sqlite --with-pear --with-png-dir --with-jpeg-dir --with-xmlrpc --with-xsl --with-zlib --with-bz2 --with-mhash --enable-fpm --enable-bcmath --enable-libxml --enable-inline-optimization --enable-gd-native-ttf --enable-mbregex --enable-mbstring --enable-opcache --enable-pcntl --enable-shmop --enable-soap --enable-sockets --enable-sysvsem --enable-sysvshm --enable-xml --enable-zip --with-ldap
Seeing DEBUG in php –version indicates success.
yongkbmaster ➜ ~ /data/env/runtime/php-7.1.33-debug/bin/php --version
PHP 7.1.33 (cli) (built: Dec 29 2020 19:16:50) ( NTS DEBUG )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
Note: The extensions for the debug version need to be compiled and installed again; they cannot be copied from the normal version’s so. The installation method is the same as for regular extensions, and generally, no extra debug parameters are needed. If you need to debug extensions like swoole, you need to set the debug parameters for the extension, which can be referenced in the extension’s ./configure file description.
Using GDB
Here is a brief introduction to the basic usage of gdb; for more detailed usage, you can google it yourself.
Starting GDB
-
Attach to process
gdb -p {pid}
-
Run method
gdb php
run test3.php
-
Using core file
gdb -c core.8451
Breakpoints
-
break n: Set a breakpoint at line n (can include code path and code name)
//Note: Breakpoints can only be set in C code, not in PHP files; var.c:201 is the entry point for var_dump in php-7.1.33
break var.c:201
-
b fn1 if a > b: Set conditional breakpoint -
break func (abbreviated as b): Set a breakpoint at the entry of function func()
//Most PHP methods in C are prefixed with zif_ + PHP method name. For example, var_dump is called zif_var_dump in C.
break zif_var_dump
-
delete breakpoint number n: Remove the nth breakpoint -
disable breakpoint number n: Pause the nth breakpoint -
enable breakpoint number n: Activate the nth breakpoint -
clear line number n: Clear the breakpoint at line n -
info b (info breakpoints): Show the current program’s breakpoint settings -
delete breakpoints: Clear all breakpoints
Other Commands
-
list (abbreviated as l): Lists the source code of the program, displaying 10 lines by default. -
list line number: Displays the current file’s code around the specified line number. -
print a: Displays the value of a -
continue (abbreviated as c): Continues execution until the next breakpoint (or end of run). After setting a breakpoint, you need to press this. -
next (abbreviated as n): Next line in the current function -
step (abbreviated as s): Step into the function -
where/bt: List the current running stack;
PHP GDB Tools
This is the focus of this article PHP provides a set of small tools for gdb, located in the .gdbinit file in the source code directory, which can help us better debug the PHP source code.
Preparation
To better demonstrate, I will prepare a PHP file.
<?php
const A = 'test const';
const B = 'test const B';
class B {
public $a = 'test';
public function funB() {
var_dump('test funB');
}
}
class C extends B {
public function funC() {
var_dump('test funC');
}
}
a = 'test';
b = ['a1' => 1, 'a2' => 2];
c = new B();
d = [A, B];
e = new C();
f = $b;
var_dump($a, $b, $c, $d, $e, $f);
get_object_vars($e);
Start gdb and set 2 breakpoints.
gdb php //Note: Use the debug version here
(gdb) break var.c:211
Breakpoint 1 at 0x76e717: file /data/env/runtime/php-7.1.33-src/ext/standard/var.c, line 211.
(gdb) break zend_object_handlers.c:492
Breakpoint 2 at 0x86ce9d: file /data/env/runtime/php-7.1.33-src/Zend/zend_object_handlers.c, line 492.
(gdb) r test4.php
Then load the small tools.
source /data/env/runtime/php-7.1.33-src/.gdbinit
Usage
-
zbacktrace: Displays the current PHP call stack
(gdb) zbacktrace
[0x7ffff1614200] var_dump("test", array(2)[0x7ffff1614260], object[0x7ffff1614270], array(2)[0x7ffff1614280], object[0x7ffff1614290], array(2)[0x7ffff16142a0]) [internal function]
[0x7ffff1614030] (main) /root/test4.php:26
-
dump_bt: View the current call stack, similar to zbacktrace
(gdb) dump_bt executor_globals.current_execute_data
[0x7ffff1614200] var_dump("test", array(2)[0x7ffff1614260], object[0x7ffff1614270], array(2)[0x7ffff1614280], object[0x7ffff1614290], array(2)[0x7ffff16142a0]) [internal function]
[0x7ffff1614030] (main) /root/test4.php:26
-
printzv: Outputs the state of zend value
(gdb) printzv &args[0]
[0x7ffff1614250] (refcount=0) string: test
-
print_global_vars: Outputs global variables
(gdb) print_global_vars
Hash(13)[0x11bf0d0]: {
[0] _GET => [0x7ffff1657100] (refcount=2) array:
[1] _POST => [0x7ffff1657120] (refcount=2) array:
[2] _COOKIE => [0x7ffff1657140] (refcount=2) array:
[3] _FILES => [0x7ffff1657160] (refcount=2) array:
[4] argv => [0x7ffff1657180] (refcount=2) array:
[5] argc => [0x7ffff16571a0] long: 1
[6] _SERVER => [0x7ffff16571c0] (refcount=2) array:
[7] a => [0x7ffff16571e0] indirect: [0x7ffff1613080] (refcount=0) string: test
[8] b => [0x7ffff1657200] indirect: [0x7ffff1613090] (refcount=5) array:
[9] c => [0x7ffff1657220] indirect: [0x7ffff16130a0] (refcount=2) object(B) #2
[10] d => [0x7ffff1657240] indirect: [0x7ffff16130b0] (refcount=2) array:
[11] e => [0x7ffff1657260] indirect: [0x7ffff16130c0] (refcount=2) object(C) #3
[12] f => [0x7ffff1657280] indirect: [0x7ffff16130d0] (refcount=5) array:
-
print_const_table: Outputs defined constants
(gdb) print_const_table executor_globals.zend_constants
[0x14e8380]: {
Hash(2340)[0x14e8380]: {
[0] E_ERROR => [0x14fd660] long: 1
[1] E_RECOVERABLE_ERROR => [0x14fe8a0] long: 4096
[2] E_WARNING => [0x14fe900] long: 2
[3] E_PARSE => [0x14fe960] long: 4
[4] E_NOTICE => [0x14fe9c0] long: 8
[5] E_STRICT => [0x14fea20] long: 2048
[6] E_DEPRECATED => [0x14fea80] long: 8192
[7] E_CORE_ERROR => [0x14feae0] long: 16
[8] E_CORE_WARNING => [0x14feb40] long: 32
[9] E_COMPILE_ERROR => [0x14feba0] long: 64
[10] E_COMPILE_WARNING => [0x14fec10] long: 128
[11] E_USER_ERROR => [0x14fec70] long: 256
[12] E_USER_WARNING => [0x14fecd0] long: 512
[13] E_USER_NOTICE => [0x14fed30] long: 1024
[14] E_USER_DEPRECATED => [0x14feda0] long: 16384
[15] E_ALL => [0x14fee00] long: 32767
[16] DEBUG_BACKTRACE_PROVIDE_OBJECT => [0x14fee70] long: 1
[17] DEBUG_BACKTRACE_IGNORE_ARGS => [0x14feee0] long: 2
[18] true => [0x14fef70] bool: true
[19] false => [0x14ff000] bool: false
[20] ZEND_THREAD_SAFE => [0x14ff070] bool: false
[21] ZEND_DEBUG_BUILD => [0x14ff0e0] bool: true
[22] null => [0x14ff170] NULL
[23] PHP_VERSION => [0x1500380] (refcount=1) string: 7.1.33
......
-
print_zstr: Outputs zend string
(gdb) print_zstr args[0]
string(4) "test"
(gdb) print_zstr args[0] 2
string(4) "te..."
(gdb) print_zstr args[0] 4
string(4) "test"
-
print_cvs: Prints compiled variables and their values. It requires a zend_execute_data
type value. You can check the call stack first to see.
(gdb) bt //Here you can see #2 layer is the entry of zend_vm_execute, where there is a zend_execute_data type value.
#0 zif_var_dump (execute_data=0x7ffff1614120, return_value=0x7fffffffa9b0) at /data/env/runtime/php-7.1.33-src/ext/standard/var.c:209
#1 0x0000000000ab08d4 in ZEND_DO_ICALL_SPEC_RETVAL_UNUSED_HANDLER () at /data/env/runtime/php-7.1.33-src/Zend/zend_vm_execute.h:628
#2 0x0000000000ab01c3 in execute_ex (ex=0x7ffff1614030) at /data/env/runtime/php-7.1.33-src/Zend/zend_vm_execute.h:429
#3 0x0000000000ab02d5 in zend_execute (op_array=0x7ffff1672d00, return_value=0x0) at /data/env/runtime/php-7.1.33-src/Zend/zend_vm_execute.h:474
#4 0x0000000000a510f9 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /data/env/runtime/php-7.1.33-src/Zend/zend.c:1482
#5 0x00000000009c02f4 in php_execute_script (primary_file=0x7fffffffdf30) at /data/env/runtime/php-7.1.33-src/main/main.c:2577
#6 0x0000000000b31387 in do_cli (argc=2, argv=0x14e7f30) at /data/env/runtime/php-7.1.33-src/sapi/cli/php_cli.c:993
#7 0x0000000000b32346 in main (argc=2, argv=0x14e7f30) at /data/env/runtime/php-7.1.33-src/sapi/cli/php_cli.c:1381
(gdb) f 2 //Jump to #2 layer
#2 0x0000000000ab01c3 in execute_ex (ex=0x7ffff1614030) at /data/env/runtime/php-7.1.33-src/Zend/zend_vm_execute.h:429
429 ((opcode_handler_t)OPLINE->handler)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
(gdb) print_cvs ex //Output
Compiled variables count: 6
[0] 'a'
[0x7ffff1614080] (refcount=0) string: test
[1] 'b'
[0x7ffff1614090] (refcount=5) array: Hash(2)[0x7ffff170e300]: {
[0] a1 => [0x7ffff1793e20] long: 1
[1] a2 => [0x7ffff1793e40] long: 2
}
[2] 'c'
[0x7ffff16140a0] (refcount=2) object(B) #2
Properties Hash(1)[0x7ffff170e480]: {
[0] a => [0x7ffff1793f60] indirect: [0x7ffff170e388] (refcount=4) string: test
}
[3] 'd'
[0x7ffff16140b0] (refcount=2) array: Packed(2)[0x7ffff170e3c0]: {
[0] 0 => [0x7ffff1793688] (refcount=1) string: test const
[1] 1 => [0x7ffff17936a8] (refcount=1) string: test const B
}
[4] 'e'
[0x7ffff16140c0] (refcount=2) object(C) #3
Properties Hash(1)[0x7ffff170e4e0]: {
[0] a => [0x7ffff17940a0] indirect: [0x7ffff170e448] (refcount=4) string: test
}
[5] 'f'
[0x7ffff16140d0] (refcount=5) array: Hash(2)[0x7ffff170e300]: {
[0] a1 => [0x7ffff1793e20] long: 1
[1] a2 => [0x7ffff1793e40] long: 2
}
-
print_ht: Outputs HashTable, which is an important data structure in PHP, implementing PHP arrays. You can understand it as a C-level PHP array, which is widely used in the PHP source code to store various key-value or array structures.
(gdb) print_ht args[1].value
Hash(2)[0x7ffff170e300]: {
[0] a1 => [0x7ffff1793e20] long: 1
[1] a2 => [0x7ffff1793e40] long: 2
}
(gdb) print_ht args[3].value
Packed(2)[0x7ffff170e3c0]: {
[0] 0 => [0x7ffff1793688] (refcount=1) string: test const
[1] 1 => [0x7ffff17936a8] (refcount=1) string: test const B
}
-
print_htptr: Similar to print_ht, it outputs the address of zval rather than its value.
(gdb) print_htptr args[1].value
Hash(2)[0x7ffff170e300]: {
[0] a1 => 0x7ffff1793e20
[1] a2 => 0x7ffff1793e40
}
-
print_htstr: Similar to print_ht, but the HashTable stores C char instead of zval. However, this situation is rare in the source code; most cases storing strings use zend string directly.
(gdb) print_htstr &server->extension_mime_types
Hash(2)[0x11b9228]: {
[0] ez => application/andrew-inset
[1] aw => application/applixware
}
-
print_ft: Similar to print_ht, but the HashTable stores the address of zend_function
.
(gdb) print_ft &args[2].value.obj.ce.function_table
Hash(1)[0x7ffff1783210]: {
[0] funb => "funB"
}
-
print_inh: Outputs class-related information.
(gdb) print_inh &args[4].value.obj.ce
class C extends B {
class B {
}
}
-
print_pi: Outputs property-related information of an object. It requires passing a zend_property_info
type address, which is used in zend_object_handlers.c:492, and can be triggered in PHP by using get_object_vars($e).
(gdb) c
Continuing.
Breakpoint 2, zend_check_property_access (zobj=0x7ffff170e420, prop_info_name=0x7ffff173c2c0) at /data/env/runtime/php-7.1.33-src/Zend/zend_object_handlers.c:492
492 zend_string_release(member);
Latest Video from WeChat Channel