问题 如何在lldb中的实时运行脚本上转储PHP回溯?


我正在玩耍 LLDB(调试器) 我做了以下实验。

  1. 运行PHP脚本:

    php -r "sleep(1000);"
    

    要么:

    php -r "function r(){sleep(1000);}r();"
    
  2. 在另一个控制台上,我直接打电话 zif_debug_backtrace() 从 lldb

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

以上工作,但过程停止时出现以下警告:

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

我不太清楚为什么脚本必须停止以及我需要做些什么来实现透明度(不影响脚本)?

附:呼叫时也是如此 zif_debug_print_backtrace() 并在打电话时 custom_backtrace() 它显示: Backtrace null function called。我在用着 xdebug 如果改变了什么。

也许我需要调用不同的函数 zend_fetch_debug_backtrace (看到: image dump symtab)?或者使用正确的参数,如果是的话,哪一个?

我只对...感兴趣 lldb/gdb 解决方案,以打印回溯。


类似的方法适用于Ruby,例如:

  1. 跑: ruby -e 'sleep 1000'
  2. 在另一个终端: echo 'call (void)rb_backtrace()' | lldb -p $(pgrep -nf ruby)

7793
2017-08-05 09:53


起源



答案:


你不能像这样调用内部函数,内部函数需要像frame,return value等等......不要这样做。

有一个 .gdbinit 用php分发,里面有一个名为的函数 zbacktrace,你可以把它移植到lldb。

您可以做的另一件事情(可能更容易)就是调用生成跟踪的API函数,但要正确调用它。

这是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 

而对于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)

既然你问过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)

8
2017-10-11 16:31





我玩了一下,发现它是如何工作的:

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

对于上面的例子,这不会打印任何东西,因为没有回溯。但是当你运行这样的脚本时:

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

然后lldb命令将导致PHP进程输出:

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

瞧。不幸的是,这也会使脚本崩溃。

它确实有效 gdb但是:

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

分离脚本时继续运行。 (使用PHP 5.6.14(DEBUG)在Debian上测试)


1
2017-10-09 16:01



谢谢你的回复。我已经测试了它仍然会杀死原始进程,所以它基本上以相同的方式工作。它有可能不会杀死原始进程吗? - kenorb
我不认为这是可以避免的。 PHP是单线程的,因此只要您想在外部执行命令,就需要停止脚本。 - akirk
它适用于Ruby: ruby -e 'sleep 1000',和 echo 'call (void)rb_backtrace()' | lldb -p $(pgrep -nf ruby)。 - kenorb
已经 echo detach | lldb -p $(pgrep -fn php) 杀死这个过程。 - akirk
同 gdb 它有效,我已经扩展了我的答案。 - akirk


答案:


你不能像这样调用内部函数,内部函数需要像frame,return value等等......不要这样做。

有一个 .gdbinit 用php分发,里面有一个名为的函数 zbacktrace,你可以把它移植到lldb。

您可以做的另一件事情(可能更容易)就是调用生成跟踪的API函数,但要正确调用它。

这是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 

而对于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)

既然你问过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)

8
2017-10-11 16:31





我玩了一下,发现它是如何工作的:

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

对于上面的例子,这不会打印任何东西,因为没有回溯。但是当你运行这样的脚本时:

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

然后lldb命令将导致PHP进程输出:

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

瞧。不幸的是,这也会使脚本崩溃。

它确实有效 gdb但是:

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

分离脚本时继续运行。 (使用PHP 5.6.14(DEBUG)在Debian上测试)


1
2017-10-09 16:01



谢谢你的回复。我已经测试了它仍然会杀死原始进程,所以它基本上以相同的方式工作。它有可能不会杀死原始进程吗? - kenorb
我不认为这是可以避免的。 PHP是单线程的,因此只要您想在外部执行命令,就需要停止脚本。 - akirk
它适用于Ruby: ruby -e 'sleep 1000',和 echo 'call (void)rb_backtrace()' | lldb -p $(pgrep -nf ruby)。 - kenorb
已经 echo detach | lldb -p $(pgrep -fn php) 杀死这个过程。 - akirk
同 gdb 它有效,我已经扩展了我的答案。 - akirk