Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dump PHP backtrace on live running script in lldb?

I'm playing around with LLDB (debugger) and I did the following experiment.

  1. Run PHP script as:

    php -r "sleep(1000);"
    

    or:

    php -r "function r(){sleep(1000);}r();"
    
  2. On another console, I've called directly zif_debug_backtrace() from lldb:

    echo 'call (void)zif_debug_backtrace()' | lldb -p $(pgrep -fn php)
    

Above worked, however the process stopped with the following warning:

Warning: sleep() expects at most 2 parameters, 1606408648 given in Command line code on line 1
Call Stack:
    0.0016     235152   1. {main}() Command line code:0
    0.0021     235248   2. sleep(1000) Command line code:1

I'm not quite sure why the script had to stop and what I need to do to achieve transparency (without affecting the script)?

P.S. The same happening when calling zif_debug_print_backtrace() and when calling custom_backtrace() it is showing: Backtrace null function called. I'm using xdebug if that change anything.

Maybe I need to call a different function like zend_fetch_debug_backtrace (see: image dump symtab)? Or use the right arguments, if so, which one?

I'm only interested in lldb/gdb solutions in order to print the backtrace.


Similar approach works in Ruby, e.g.:

  1. Run: ruby -e 'sleep 1000'.
  2. In another terminal: echo 'call (void)rb_backtrace()' | lldb -p $(pgrep -nf ruby).
like image 223
kenorb Avatar asked Aug 05 '15 09:08

kenorb


2 Answers

You can't call internal functions like that, internal functions expect things like frame, return value and so on ... don't' do it.

There is a .gdbinit distributed with php, in it there is a function named zbacktrace, you could port that to lldb.

Another thing you could do, which is likely easier, is just call the API function that generates a trace, but call it properly.

Here it is for GDB (PHP7):

define ztrace
    set $var = malloc(sizeof(zval))
    call zend_fetch_debug_backtrace($var, 0, 0, 0)
    call php_var_dump($var, 0)
    call _zval_ptr_dtor($var, 0, 0)
    call free($var)
end

document ztrace
    show a debug backtrace
end 

And for LLDB (PHP7):

(lldb) expr zval $var;
(lldb) expr zend_fetch_debug_backtrace(&$var, 0, 0, 0)
(lldb) expr php_var_dump(&$var, 0)
(lldb) expr _zval_ptr_dtor(&$var, 0, 0)

Since you asked, LLDB for PHP5.6 (no-zts):

(lldb) expr zval *$zp = (zval*) malloc(sizeof(zval))
(lldb) expr zend_fetch_debug_backtrace($zp, 0, 0, 0)
(lldb) expr php_var_dump(&$zp, 0)
(lldb) expr _zval_ptr_dtor(&$zp, 0, 0)
(lldb) expr free($zp)
like image 109
Joe Watkins Avatar answered Nov 16 '22 04:11

Joe Watkins


I played a bit around with this and found out how it works:

echo 'call (void)zif_debug_print_backtrace(0)' | lldb -p $(pgrep -fn php)

For your example of above this will not print anything because there is no backtrace. But when you run a script like this:

php -r "function r(){sleep(1000);}r();"

Then the lldb command will cause the PHP process to output:

#0  r() called at [Command line code:1]

Voila. Unfortunately this also crashes the script.

It does work with gdb, though:

echo 'call zif_debug_print_backtrace(0,0,0,0,0)' | gdb -p $(pgrep -fn php)

when detaching the script continues to run. (tested on Debian with PHP 5.6.14 (DEBUG))

like image 43
akirk Avatar answered Nov 16 '22 03:11

akirk