GDB with PHP
Some handy things to know about using GDB to debug PHP.
Basics
- Compile PHP with
export CFLAGS=-ggdb3
. - The usual reason to use GDB to debug PHP is because PHP is segfaulting. Run PHP under GDB until it segfaults, then run "bt" to show the C backtrace. That gives you an idea of the problem location in the C code. Sometimes it's useful to also know what the problem was in the PHP code. You can use the phpbt macro given below.
- If the backtrace is corrupted, then give logging a go instead to identify the PHP code. For instance, you can use my magic segfault tracker (link comes from year 2006)
Backtraces by calling functions
(gdb) call zif_debug_print_backtrace(0,(zval*)0,(zval**)0, (zval*)0, 0)
#0 Language->getLanguageName() called at [/usr/local/apache/common-local/php-1.5/includes/Parser.php:1660]
#1 Parser->replaceInternalLinks() called at [/usr/local/apache/common-local/php-1.5/includes/Parser.php:965]
#2 Parser->internalParse() called at [/usr/local/apache/common-local/php-1.5/includes/Parser.php:299]
#3 Parser->parse() called at [/home/wikipedia/common/php-1.5/maintenance/eval.php(48) : eval()'d code:1]
#4 eval() called at [/home/wikipedia/common/php-1.5/maintenance/eval.php:48]
This only works if you don't have any output buffer. You can call any function that doesn't touch the return value this way. However most functions do try to write to the return value, so for that you need a target zval for the second and third parameters.
set $zp = emalloc(sizeof(zval))
set $zpp = emalloc(sizeof(zval*))
set *(zval**)$zpp = $zp
call zif_ob_end_clean(0, $zp, $zpp, (zval*)0, 0)
call zif_debug_print_backtrace(0,(zval*)0,(zval**)0, (zval*)0, 0)
OMG 1337 haxor! Anyone would think C was an interpreted language.
But what about when running under apache? How do we get the output to go somewhere we can see it, instead of going back to apache?
(gdb) set output_globals.php_body_write = php_default_output_func
That would send all output to stderr, skipping the output buffer. But apache redirects stderr -- lsof says:
httpd 25969 apache 0u CHR 136,0 2 /dev/pts/0 httpd 25969 apache 1u CHR 136,0 2 /dev/pts/0 httpd 25969 apache 2w CHR 1,3 2366 /dev/null
Never fear, a few syscalls and all will be well:
call close(2)
call dup2(1,2)
set output_globals.php_body_write = php_default_output_func
call zif_debug_print_backtrace(0,(zval*)0,(zval**)0, (zval*)0, 0)
Backtraces by inspecting data
The following user-defined GDB commands are also available with
source ~tstarling/php.gdb
Function names
Switch to a frame with an execute_data local variable
define phpbt
set $ed=execute_data
while $ed
print ((zend_execute_data *)$ed)->function_state.function->common.function_name
set $ed = ((zend_execute_data *)$ed)->prev_execute_data
end
end
Example:
(gdb) phpbt
$1 = 0x2aaaaf33360f "preg_split"
$2 = 0x2aaab018fc48 "extractTagsAndParams"
$3 = 0x2aaab0194d40 "strip"
$4 = 0x2aaab0182780 "parse"
$5 = 0x2aaab0449740 "addWikiTextTitle"
$6 = 0x2aaab0448530 "addWikiText"
$7 = 0x2aaab06d2800 "showEditForm"
$8 = 0x2aaab06a0698 "edit"
$9 = 0x2aaab06a02b8 "submit"
$10 = 0x2aaab04203e0 "performAction"
$11 = 0x2aaab040a470 "initialize"
$12 = 0x0
Symbol table variable names
define hashkeys
set $p = (HashTable*)$arg0->pListHead
while $p
output (Bucket*)$p
echo \t
x/s (char*)(((Bucket*)$p)->arKey)
set $p = ((Bucket*)$p)->pListNext
end
end
Example:
(gdb) hashkeys execute_data->symbol_table
(struct bucket *) 0xc40b28 0xc40b68: "t"
(struct bucket *) 0xc40bd8 0xc40c18: "fname"
(struct bucket *) 0xc40ff8 0xc41038: "td"
(struct bucket *) 0xc40de8 0xc40e28: "ltd"
(struct bucket *) 0x17b48f8 0x17b4938: "tr"
(struct bucket *) 0x17b2da8 0x17b2de8: "ltr"
(struct bucket *) 0x1528d18 0x1528d58: "has_opened_tr"
(struct bucket *) 0x1528ad8 0x1528b18: "indent_level"
(struct bucket *) 0x17b4fe8 0x17b5028: "x"
(struct bucket *) 0x17b3298 0x17b32d8: "k"
(struct bucket *) 0x10623f8 0x1062438: "fc"
(struct bucket *) 0xc32c28 0xc32c68: "matches"
(struct bucket *) 0xc34768 0xc347a8: "attributes"
Symbol table data
define bucketdata
print **(zval**)(((struct bucket *) $arg0)->pData)
end
Example following on from previous:
(gdb) bucketdata 0xc40bd8
$175 = {value = {lval = 12802728, dval = 6.3253880778498107e-317, str = {
val = 0xc35aa8 "Parser::doTableStuff", len = 20}, ht = 0xc35aa8, obj = {handle = 12802728,
handlers = 0x14}}, refcount = 1, type = 6 '\006', is_ref = 0 '\0'}
See also
- Phabricator #php-segfault project used to track segfaults we encounter
- Blog Post: Investigate a PHP segmentation fault (Antoine "hashar" Musso, July 2023)