]> granicus.if.org Git - php/commitdiff
Merge branch 'master' of sapi/phpdbg into PHP-5.6
authorBob Weinand <bobwei9@hotmail.com>
Fri, 20 Dec 2013 13:27:51 +0000 (14:27 +0100)
committerBob Weinand <bobwei9@hotmail.com>
Fri, 20 Dec 2013 13:27:51 +0000 (14:27 +0100)
Including phpdbg.

48 files changed:
.gdbinit [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.phpdbginit [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
Changelog.md [new file with mode: 0644]
Makefile.frag [new file with mode: 0644]
README.md [new file with mode: 0644]
config.m4 [new file with mode: 0644]
config.w32 [new file with mode: 0644]
phpdbg.c [new file with mode: 0644]
phpdbg.h [new file with mode: 0644]
phpdbg.init.d [new file with mode: 0755]
phpdbg_bp.c [new file with mode: 0644]
phpdbg_bp.h [new file with mode: 0644]
phpdbg_break.c [new file with mode: 0644]
phpdbg_break.h [new file with mode: 0644]
phpdbg_cmd.c [new file with mode: 0644]
phpdbg_cmd.h [new file with mode: 0644]
phpdbg_frame.c [new file with mode: 0644]
phpdbg_frame.h [new file with mode: 0644]
phpdbg_help.c [new file with mode: 0644]
phpdbg_help.h [new file with mode: 0644]
phpdbg_info.c [new file with mode: 0644]
phpdbg_info.h [new file with mode: 0644]
phpdbg_list.c [new file with mode: 0644]
phpdbg_list.h [new file with mode: 0644]
phpdbg_opcode.c [new file with mode: 0644]
phpdbg_opcode.h [new file with mode: 0644]
phpdbg_print.c [new file with mode: 0644]
phpdbg_print.h [new file with mode: 0644]
phpdbg_prompt.c [new file with mode: 0644]
phpdbg_prompt.h [new file with mode: 0644]
phpdbg_set.c [new file with mode: 0644]
phpdbg_set.h [new file with mode: 0644]
phpdbg_utils.c [new file with mode: 0644]
phpdbg_utils.h [new file with mode: 0644]
test.php [new file with mode: 0644]
tests/commands/0001_basic.test [new file with mode: 0644]
tests/commands/0002_set.test [new file with mode: 0644]
tests/commands/0101_info.test [new file with mode: 0644]
tests/commands/0102_print.test [new file with mode: 0644]
tests/commands/0103_register.test [new file with mode: 0644]
tests/commands/0104_clean.test [new file with mode: 0644]
tests/commands/0105_clear.test [new file with mode: 0644]
tests/commands/0106_compile.test [new file with mode: 0644]
tests/run-tests.php [new file with mode: 0644]
travis/ci.sh [new file with mode: 0755]
web-bootstrap.php [new file with mode: 0644]

diff --git a/.gdbinit b/.gdbinit
new file mode 100644 (file)
index 0000000..401a4bb
--- /dev/null
+++ b/.gdbinit
@@ -0,0 +1,10 @@
+define ____phpdbg_globals
+       if basic_functions_module.zts
+               if !$tsrm_ls
+                       set $tsrm_ls = ts_resource_ex(0, 0)
+               end
+               set $phpdbg = ((zend_phpdbg_globals*) (*((void ***) $tsrm_ls))[phpdbg_globals_id-1])
+       else
+               set $phpdbg = phpdbg_globals
+       end
+end
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..297efcb
--- /dev/null
@@ -0,0 +1,5 @@
+.libs/
+./phpdbg
+*.lo
+*.o
+build
diff --git a/.phpdbginit b/.phpdbginit
new file mode 100644 (file)
index 0000000..1ad3521
--- /dev/null
@@ -0,0 +1,105 @@
+##########################################################
+# .phpdbginit
+#
+# Lines starting with # are ignored
+# Code must start and end with <: and :> respectively
+##########################################################
+# Place initialization commands one per line
+##########################################################
+# exec sapi/phpdbg/test.php
+# set color prompt white-bold
+# set color notice green
+# set color error red
+
+##########################################################
+# Embedding code in .phpdbginit
+##########################################################
+<:
+/*
+* This embedded PHP is executed at init time
+*/
+
+/*
+* Functions defined and registered by init
+* will persist across cleans
+*/
+
+/*
+function my_debugging_function() 
+{
+       var_dump(func_get_args());
+}
+*/
+
+/* phpdbg_break(PHPDBG_METHOD, "phpdbg::method"); */
+/* phpdbg_break(PHPDBG_FUNC, "my_global_function"); */
+/* phpdbg_break(PHPDBG_FILE, "/path/to/file.php:10"); */
+
+/*
+ If readline is loaded, you might want to setup completion:
+*/
+if (function_exists('readline_completion_function')) {
+       readline_completion_function(function(){
+               return array_merge(
+                       get_defined_functions()['user'],
+                       array_keys(get_defined_constants())
+               );
+       });
+}
+
+/*
+ Setting argv made trivial ...
+ argv 1 2 3 4
+ ^ set argv for next execution
+ argv
+ ^ unset argv for next execution
+*/
+function argv()
+{
+       $argv = func_get_args();
+       
+       if (!$argv) {
+               $_SERVER['argv'] = array();
+               $_SERVER['argc'] = 0;
+               return;
+       }
+       
+       $_SERVER['argv'] = array_merge
+       (
+               array("phpdbg"),
+               $argv
+       ); 
+       $_SERVER['argc'] = count($_SERVER['argv']);
+       
+       return $_SERVER['argv'];
+}
+:>
+##########################################################
+# Now carry on initializing phpdbg ...
+##########################################################
+# R my_debugging_function
+# R argv
+
+##########################################################
+# PHP has many functions that might be useful
+# ... you choose ...
+##########################################################
+# R touch
+# R unlink
+# R scandir
+# R glob
+
+##########################################################
+# Remember: *you have access to the shell*
+##########################################################
+# The output of registered function calls is not,
+# by default, very pretty (unless you implement
+# and register a new implementation for phpdbg)
+# The output of shell commands will usually be more
+# readable on the console
+##########################################################
+# TLDR; if you have a good shell, use it ...
+##########################################################
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..3534028
--- /dev/null
@@ -0,0 +1,3 @@
+language: c
+
+script: ./travis/ci.sh
diff --git a/Changelog.md b/Changelog.md
new file mode 100644 (file)
index 0000000..c5d8b51
--- /dev/null
@@ -0,0 +1,52 @@
+ChangeLog for phpdbg
+====================
+
+Version 0.3.0 2013-00-00
+------------------------
+
+1. Added ability to disable an enable a single breakpoint
+2. Added ability to override SAPI name
+3. Added extended conditional breakpoint support "break at"
+4. Fix loading of zend extnsions with -z
+5. Fix crash when loading .phpdbginit with command line switch
+6. Fix crash on startup errors
+7. Added init.d for remote console (redhat)
+8. Added phpdbg_exec userland function
+9. Added testing facilities
+10. Added break on n-th opline support
+11. Improved trace output
+
+Version 0.2.0 2013-11-31
+------------------------
+
+1. Added "break delete <id>" command
+2. Added "break opcode <opcode>" command
+3. Added "set" command - control prompt and console colors
+4. .phpdbginit now searched in (additional) ini dirs
+5. Added source command - load additional .phpdbginit script during session
+6. Added remote console mode
+7. Added info memory command
+
+Version 0.1.0 2013-11-23
+------------------------
+
+1. New commands:
+  - until    (continue until the current line is executed)
+  - frame    (switch to a frame in the current stack for inspection)
+  - info     (quick access to useful information on the console)
+  - finish   (continue until the current function has returned)
+  - leave    (continue until the current function is returning)
+  - shell    (shell a command)
+  - register (register a function for use as a command)
+2. Added printers for class and method
+3. Make uniform commands and aliases where possible
+4. Include all alias information and sub-command information in help
+5. Added signal handling to break execution (ctrl-c)
+6. Fixed #13 (Output Buffering Control seems fail)
+7. Fixed #14 (Fixed typo in Makefile.frag)
+
+
+Version 0.0.1 2013-11-15
+------------------------
+
+1. Initial features
diff --git a/Makefile.frag b/Makefile.frag
new file mode 100644 (file)
index 0000000..5be6d5b
--- /dev/null
@@ -0,0 +1,28 @@
+phpdbg: $(BUILD_BINARY)
+
+phpdbg-shared: $(BUILD_SHARED)
+
+$(BUILD_SHARED): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_PHPDBG_OBJS)
+       $(BUILD_PHPDBG_SHARED)
+
+$(BUILD_BINARY): $(PHP_GLOBAL_OBJS) $(PHP_BINARY_OBJS) $(PHP_PHPDBG_OBJS)
+       $(BUILD_PHPDBG)
+
+install-phpdbg: $(BUILD_BINARY)
+       @echo "Installing phpdbg binary:         $(INSTALL_ROOT)$(bindir)/"
+       @$(mkinstalldirs) $(INSTALL_ROOT)$(bindir)
+       @$(mkinstalldirs) $(INSTALL_ROOT)$(localstatedir)/log
+       @$(mkinstalldirs) $(INSTALL_ROOT)$(localstatedir)/run
+       @$(INSTALL) -m 0755 $(BUILD_BINARY) $(INSTALL_ROOT)$(bindir)/$(program_prefix)phpdbg$(program_suffix)$(EXEEXT)
+
+clean-phpdbg:
+       @echo "Cleaning phpdbg object files ..."
+       find sapi/phpdbg/ -name *.lo -o -name *.o | xargs rm -f
+
+test-phpdbg:
+       @echo "Running phpdbg tests ..."
+       @$(top_builddir)/sapi/cli/php sapi/phpdbg/tests/run-tests.php --phpdbg sapi/phpdbg/phpdbg
+
+.PHONY: clean-phpdbg test-phpdbg
+
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..e7e5c73
--- /dev/null
+++ b/README.md
@@ -0,0 +1,83 @@
+The interactive PHP debugger
+============================
+
+Implemented as a SAPI module, phpdbg can excert complete control over the environment without impacting the functionality or performance of your code.
+
+phpdbg aims to be a lightweight, powerful, easy to use debugging platform for PHP 5.4+
+
+[![phpdbg on travis-ci](https://travis-ci.org/krakjoe/phpdbg.png?branch=master)](https://travis-ci.org/krakjoe/phpdbg)
+
+Features
+========
+
+ - Stepthrough Debugging
+ - Flexible Breakpoints (Class Method, Function, File:Line, Address, Opcode)
+ - Easy Access to PHP with built-in eval()
+ - Easy Access to Currently Executing Code
+ - Userland API
+ - SAPI Agnostic - Easily Integrated
+ - PHP Configuration File Support
+ - JIT Super Globals - Set Your Own!!
+ - Optional readline Support - Comfortable Terminal Operation
+ - Remote Debugging Support - Bundled Java GUI
+ - Easy Operation - See Help :)
+
+Planned
+=======
+
+ - Improve Everything :)
+
+Installation
+============
+
+To install **phpdbg**, you must compile the source against your PHP installation sources, and enable the SAPI with the configure command.
+
+```
+cd /usr/src/php-src/sapi
+git clone https://github.com/krakjoe/phpdbg
+cd ../
+./buildconf --force
+./configure --enable-phpdbg
+make -j8
+make install-phpdbg
+```
+
+Where the source directory has been used previously to build PHP, there exists a file named *config.nice* which can be used to invoke configure with the same
+parameters as were used by the last execution of *configure*.
+
+**Note:** PHP must be configured with the switch --with-readline for phpdbg to support history, autocompletion, tab-listing etc.
+
+Command Line Options
+====================
+
+The following switches are implemented (just like cli SAPI):
+
+ - -n ignore php ini
+ - -c search for php ini in path
+ - -z load zend extension
+ - -d define php ini entry
+
+The following switches change the default behaviour of phpdbg:
+
+ - -v disables quietness
+ - -s enabled stepping
+ - -e sets execution context
+ - -b boring - disables use of colour on the console
+ - -I ignore .phpdbginit (default init file)
+ - -i override .phpgdbinit location (implies -I)
+ - -O set oplog output file
+ - -q do not print banner on startup
+ - -r jump straight to run
+ - -E enable step through eval()
+ - -l listen ports for remote mode
+ - -a listen address for remote mode
+ - -S override SAPI name
+
+**Note:** Passing -rr will cause phpdbg to quit after execution, rather than returning to the console.
+
+Getting Started
+===============
+
+See the website for tutorials/documentation
+
+http://phpdbg.com
diff --git a/config.m4 b/config.m4
new file mode 100644 (file)
index 0000000..274e640
--- /dev/null
+++ b/config.m4
@@ -0,0 +1,61 @@
+dnl
+dnl $Id$
+dnl
+
+PHP_ARG_ENABLE(phpdbg, for phpdbg support,
+[  --enable-phpdbg         Build phpdbg], yes, yes)
+
+PHP_ARG_ENABLE(phpdbg-debug, for phpdbg debug build,
+[  --enable-phpdbg-debug         Build phpdbg in debug mode], no, no)
+
+if test "$PHP_PHPDBG" != "no"; then
+  AC_DEFINE(HAVE_PHPDBG, 1, [ ])
+
+  if test "$PHP_PHPDBG_DEBUG" != "no"; then
+    AC_DEFINE(PHPDBG_DEBUG, 1, [ ])
+  else
+    AC_DEFINE(PHPDBG_DEBUG, 0, [ ])
+  fi
+
+  PHP_PHPDBG_CFLAGS="-D_GNU_SOURCE"
+  PHP_PHPDBG_FILES="phpdbg.c phpdbg_prompt.c phpdbg_help.c phpdbg_break.c phpdbg_print.c phpdbg_bp.c phpdbg_opcode.c phpdbg_list.c phpdbg_utils.c phpdbg_info.c phpdbg_cmd.c phpdbg_set.c phpdbg_frame.c"
+
+  PHP_SUBST(PHP_PHPDBG_CFLAGS)
+  PHP_SUBST(PHP_PHPDBG_FILES)
+
+  PHP_ADD_MAKEFILE_FRAGMENT([$abs_srcdir/sapi/phpdbg/Makefile.frag])
+  PHP_SELECT_SAPI(phpdbg, program, $PHP_PHPDBG_FILES, $PHP_PHPDBG_CFLAGS, [$(SAPI_PHPDBG_PATH)])
+
+  BUILD_BINARY="sapi/phpdbg/phpdbg"
+  BUILD_SHARED="sapi/phpdbg/libphpdbg.la"
+
+  BUILD_PHPDBG="\$(LIBTOOL) --mode=link \
+        \$(CC) -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \
+                \$(PHP_GLOBAL_OBJS) \
+                \$(PHP_BINARY_OBJS) \
+                \$(PHP_PHPDBG_OBJS) \
+                \$(EXTRA_LIBS) \
+                \$(PHPDBG_EXTRA_LIBS) \
+                \$(ZEND_EXTRA_LIBS) \
+         -o \$(BUILD_BINARY)"
+
+  BUILD_PHPDBG_SHARED="\$(LIBTOOL) --mode=link \
+        \$(CC) -shared -Wl,-soname,libphpdbg.so -export-dynamic \$(CFLAGS_CLEAN) \$(EXTRA_CFLAGS) \$(EXTRA_LDFLAGS_PROGRAM) \$(LDFLAGS) \$(PHP_RPATHS) \
+                \$(PHP_GLOBAL_OBJS) \
+                \$(PHP_BINARY_OBJS) \
+                \$(PHP_PHPDBG_OBJS) \
+                \$(EXTRA_LIBS) \
+                \$(PHPDBG_EXTRA_LIBS) \
+                \$(ZEND_EXTRA_LIBS) \
+                \-DPHPDBG_SHARED \
+         -o \$(BUILD_SHARED)"
+
+  PHP_SUBST(BUILD_BINARY)
+  PHP_SUBST(BUILD_SHARED)
+  PHP_SUBST(BUILD_PHPDBG)
+  PHP_SUBST(BUILD_PHPDBG_SHARED)
+fi
+
+dnl ## Local Variables:
+dnl ## tab-width: 4
+dnl ## End:
diff --git a/config.w32 b/config.w32
new file mode 100644 (file)
index 0000000..2903150
--- /dev/null
@@ -0,0 +1,19 @@
+ARG_ENABLE('phpdbg', 'Build phpdbg', 'yes');
+ARG_ENABLE('phpdbgs', 'Build phpdbg shared', 'no');
+
+PHPDBG_SOURCES='phpdbg.c phpdbg_prompt.c phpdbg_cmd.c phpdbg_info.c phpdbg_help.c phpdbg_break.c phpdbg_print.c phpdbg_bp.c phpdbg_opcode.c phpdbg_list.c phpdbg_utils.c phpdbg_set.c phpdbg_frame.c';
+PHPDBG_DLL='php' + PHP_VERSION + 'phpdbg.dll';
+PHPDBG_EXE='phpdbg.exe';
+
+if (PHP_PHPDBG == "yes") {
+       /* build phpdbg binary */
+    SAPI('phpdbg', PHPDBG_SOURCES, PHPDBG_EXE);
+    ADD_FLAG("LIBS_PHPDBG", "ws2_32.lib user32.lib");
+}
+
+if (PHP_PHPDBGS == "yes") {
+       SAPI('phpdbgs', PHPDBG_SOURCES, PHPDBG_DLL, '/D PHP_PHPDBG_EXPORTS /I win32');
+       ADD_FLAG("LIBS_PHPDBGS", "ws2_32.lib user32.lib");
+}
+
+
diff --git a/phpdbg.c b/phpdbg.c
new file mode 100644 (file)
index 0000000..17193ed
--- /dev/null
+++ b/phpdbg.c
@@ -0,0 +1,1308 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef ZEND_SIGNALS
+# include <signal.h>
+#endif
+#include "phpdbg.h"
+#include "phpdbg_prompt.h"
+#include "phpdbg_bp.h"
+#include "phpdbg_break.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_set.h"
+
+/* {{{ remote console headers */
+#ifndef _WIN32
+#      include <sys/socket.h>
+#      include <sys/select.h>
+#      include <sys/time.h>
+#      include <sys/types.h>
+#      include <unistd.h>
+#      include <arpa/inet.h>
+#endif /* }}} */
+
+ZEND_DECLARE_MODULE_GLOBALS(phpdbg);
+
+static zend_bool phpdbg_booted = 0;
+
+#if PHP_VERSION_ID >= 50500
+void (*zend_execute_old)(zend_execute_data *execute_data TSRMLS_DC);
+#else
+void (*zend_execute_old)(zend_op_array *op_array TSRMLS_DC);
+#endif
+
+static inline void php_phpdbg_globals_ctor(zend_phpdbg_globals *pg) /* {{{ */
+{
+       pg->prompt[0] = NULL;
+       pg->prompt[1] = NULL;
+
+       pg->colors[0] = NULL;
+       pg->colors[1] = NULL;
+       pg->colors[2] = NULL;
+
+       pg->exec = NULL;
+       pg->exec_len = 0;
+       pg->ops = NULL;
+       pg->vmret = 0;
+       pg->bp_count = 0;
+       pg->lcmd = NULL;
+       pg->flags = PHPDBG_DEFAULT_FLAGS;
+       pg->oplog = NULL;
+       pg->io[PHPDBG_STDIN] = NULL;
+       pg->io[PHPDBG_STDOUT] = NULL;
+       pg->io[PHPDBG_STDERR] = NULL;
+       memset(&pg->lparam, 0, sizeof(phpdbg_param_t));
+       pg->frame.num = 0;
+} /* }}} */
+
+static PHP_MINIT_FUNCTION(phpdbg) /* {{{ */
+{
+       ZEND_INIT_MODULE_GLOBALS(phpdbg, php_phpdbg_globals_ctor, NULL);
+#if PHP_VERSION_ID >= 50500
+       zend_execute_old = zend_execute_ex;
+       zend_execute_ex = phpdbg_execute_ex;
+#else
+       zend_execute_old = zend_execute;
+       zend_execute = phpdbg_execute_ex;
+#endif
+
+       REGISTER_STRINGL_CONSTANT("PHPDBG_VERSION", PHPDBG_VERSION, sizeof(PHPDBG_VERSION)-1, CONST_CS|CONST_PERSISTENT);
+       
+       REGISTER_LONG_CONSTANT("PHPDBG_FILE",   FILE_PARAM, CONST_CS|CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("PHPDBG_METHOD", METHOD_PARAM, CONST_CS|CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("PHPDBG_LINENO", NUMERIC_PARAM, CONST_CS|CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("PHPDBG_FUNC",   STR_PARAM, CONST_CS|CONST_PERSISTENT);
+
+       REGISTER_LONG_CONSTANT("PHPDBG_COLOR_PROMPT", PHPDBG_COLOR_PROMPT, CONST_CS|CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("PHPDBG_COLOR_NOTICE", PHPDBG_COLOR_NOTICE, CONST_CS|CONST_PERSISTENT);
+       REGISTER_LONG_CONSTANT("PHPDBG_COLOR_ERROR",  PHPDBG_COLOR_ERROR, CONST_CS|CONST_PERSISTENT);
+
+       return SUCCESS;
+} /* }}} */
+
+static void php_phpdbg_destroy_bp_file(void *brake) /* {{{ */
+{
+       zend_hash_destroy((HashTable*)brake);
+} /* }}} */
+
+static void php_phpdbg_destroy_bp_symbol(void *brake) /* {{{ */
+{
+       efree((char*)((phpdbg_breaksymbol_t*)brake)->symbol);
+} /* }}} */
+
+static void php_phpdbg_destroy_bp_opcode(void *brake) /* {{{ */
+{
+       efree((char*)((phpdbg_breakop_t*)brake)->name);
+} /* }}} */
+
+
+static void php_phpdbg_destroy_bp_methods(void *brake) /* {{{ */
+{
+       zend_hash_destroy((HashTable*)brake);
+} /* }}} */
+
+static void php_phpdbg_destroy_bp_condition(void *data) /* {{{ */
+{
+       phpdbg_breakcond_t *brake = (phpdbg_breakcond_t*) data;
+
+       if (brake) {
+               if (brake->ops) {
+                       TSRMLS_FETCH();
+
+                       destroy_op_array(
+                                       brake->ops TSRMLS_CC);
+                       efree(brake->ops);
+               }
+               efree((char*)brake->code);
+       }
+} /* }}} */
+
+static void php_phpdbg_destroy_registered(void *data) /* {{{ */
+{
+       TSRMLS_FETCH();
+
+       zend_function *function = (zend_function*) data;
+
+       destroy_zend_function(
+               function TSRMLS_CC);
+} /* }}} */
+
+static PHP_RINIT_FUNCTION(phpdbg) /* {{{ */
+{
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE],   8, NULL, php_phpdbg_destroy_bp_file, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], 8, NULL, php_phpdbg_destroy_bp_symbol, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], 8, NULL, php_phpdbg_destroy_bp_methods, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], 8, NULL, php_phpdbg_destroy_bp_methods, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], 8, NULL, php_phpdbg_destroy_bp_methods, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], 8, NULL, NULL, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], 8, NULL, php_phpdbg_destroy_bp_opcode, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], 8, NULL, php_phpdbg_destroy_bp_methods, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], 8, NULL, php_phpdbg_destroy_bp_condition, 0);
+       zend_hash_init(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], 8, NULL, NULL, 0);
+       
+       zend_hash_init(&PHPDBG_G(seek), 8, NULL, NULL, 0);
+       zend_hash_init(&PHPDBG_G(registered), 8, NULL, php_phpdbg_destroy_registered, 0);
+
+       return SUCCESS;
+} /* }}} */
+
+static PHP_RSHUTDOWN_FUNCTION(phpdbg) /* {{{ */
+{
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
+       zend_hash_destroy(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]);
+       zend_hash_destroy(&PHPDBG_G(seek));
+       zend_hash_destroy(&PHPDBG_G(registered));
+
+       if (PHPDBG_G(exec)) {
+               efree(PHPDBG_G(exec));
+               PHPDBG_G(exec) = NULL;
+       }
+
+       if (PHPDBG_G(prompt)[0]) {
+               free(PHPDBG_G(prompt)[0]);
+       }
+       if (PHPDBG_G(prompt)[1]) {
+               free(PHPDBG_G(prompt)[1]);
+       }
+
+       PHPDBG_G(prompt)[0] = NULL;
+       PHPDBG_G(prompt)[1] = NULL;
+
+       if (PHPDBG_G(oplog)) {
+               fclose(
+                               PHPDBG_G(oplog));
+               PHPDBG_G(oplog) = NULL;
+       }
+
+       if (PHPDBG_G(ops)) {
+               destroy_op_array(PHPDBG_G(ops) TSRMLS_CC);
+               efree(PHPDBG_G(ops));
+               PHPDBG_G(ops) = NULL;
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+/* {{{ proto mixed phpdbg_exec(string context) 
+       Attempt to set the execution context for phpdbg
+       If the execution context was set previously it is returned
+       If the execution context was not set previously boolean true is returned 
+       If the request to set the context fails, boolean false is returned, and an E_WARNING raised */
+static PHP_FUNCTION(phpdbg_exec) 
+{
+       char *exec = NULL;
+       zend_ulong exec_len = 0L;
+       
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &exec, &exec_len) == FAILURE) {
+               return;
+       }
+       
+       {
+               struct stat sb;
+               zend_bool result = 1;
+               
+               if (VCWD_STAT(exec, &sb) != FAILURE) {
+                       if (sb.st_mode & (S_IFREG|S_IFLNK)) {
+                               if (PHPDBG_G(exec)) {
+                                       ZVAL_STRINGL(return_value, PHPDBG_G(exec), PHPDBG_G(exec_len), 1);
+                                       efree(PHPDBG_G(exec));
+                                       result = 0;
+                               }
+                       
+                               PHPDBG_G(exec) = estrndup(exec, exec_len);
+                               PHPDBG_G(exec_len) = exec_len;
+                       
+                               if (result) 
+                                       ZVAL_BOOL(return_value, 1);
+                       } else {
+                               zend_error(
+                                       E_WARNING, "Failed to set execution context (%s), not a regular file or symlink", exec);
+                               ZVAL_BOOL(return_value, 0);
+                       }
+               } else {
+                       zend_error(
+                               E_WARNING, "Failed to set execution context (%s) the file does not exist", exec);
+
+                       ZVAL_BOOL(return_value, 0);
+               }
+       }
+} /* }}} */
+
+/* {{{ proto void phpdbg_break([integer type, string expression])
+    instructs phpdbg to insert a breakpoint at the next opcode */
+static PHP_FUNCTION(phpdbg_break)
+{
+       if (ZEND_NUM_ARGS() > 0) {
+               long type;
+               char *expr = NULL;
+               zend_uint expr_len = 0;
+               phpdbg_param_t param;
+
+               if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ls", &type, &expr, &expr_len) == FAILURE) {
+                       return;
+               }
+
+               phpdbg_parse_param(expr, expr_len, &param TSRMLS_CC);
+
+               switch (type) {
+                       case METHOD_PARAM:
+                               phpdbg_do_break_method(&param, NULL TSRMLS_CC);
+                               break;
+
+                       case FILE_PARAM:
+                               phpdbg_do_break_file(&param, NULL TSRMLS_CC);
+                               break;
+
+                       case NUMERIC_PARAM:
+                               phpdbg_do_break_lineno(&param, NULL TSRMLS_CC);
+                               break;
+
+                       case STR_PARAM:
+                               phpdbg_do_break_func(&param, NULL TSRMLS_CC);
+                               break;
+
+                       default: zend_error(
+                                                        E_WARNING, "unrecognized parameter type %ld", type);
+               }
+
+               phpdbg_clear_param(&param TSRMLS_CC);
+
+       } else if (EG(current_execute_data) && EG(active_op_array)) {
+               zend_ulong opline_num = (EG(current_execute_data)->opline -
+                               EG(active_op_array)->opcodes);
+
+               phpdbg_set_breakpoint_opline_ex(
+                               &EG(active_op_array)->opcodes[opline_num+1] TSRMLS_CC);
+       }
+} /* }}} */
+
+/* {{{ proto void phpdbg_clear(void)
+   instructs phpdbg to clear breakpoints */
+static PHP_FUNCTION(phpdbg_clear)
+{
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
+} /* }}} */
+
+/* {{{ proto void phpdbg_color(integer element, string color) */
+static PHP_FUNCTION(phpdbg_color)
+{
+       long element;
+       char *color;
+       zend_uint color_len;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ls", &element, &color, &color_len) == FAILURE) {
+               return;
+       }
+
+       switch (element) {
+               case PHPDBG_COLOR_NOTICE:
+               case PHPDBG_COLOR_ERROR:
+               case PHPDBG_COLOR_PROMPT:
+                       phpdbg_set_color_ex(element, color, color_len TSRMLS_CC);
+               break;
+
+               default: zend_error(E_ERROR, "phpdbg detected an incorrect color constant");
+       }
+} /* }}} */
+
+/* {{{ proto void phpdbg_prompt(string prompt) */
+static PHP_FUNCTION(phpdbg_prompt)
+{
+       char *prompt;
+       zend_uint prompt_len;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &prompt, &prompt_len) == FAILURE) {
+               return;
+       }
+
+       phpdbg_set_prompt(prompt TSRMLS_CC);
+} /* }}} */
+
+ZEND_BEGIN_ARG_INFO_EX(phpdbg_break_arginfo, 0, 0, 0)
+       ZEND_ARG_INFO(0, type)
+       ZEND_ARG_INFO(0, expression)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(phpdbg_color_arginfo, 0, 0, 0)
+       ZEND_ARG_INFO(0, element)
+       ZEND_ARG_INFO(0, color)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(phpdbg_prompt_arginfo, 0, 0, 0)
+       ZEND_ARG_INFO(0, string)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(phpdbg_exec_arginfo, 0, 0, 0)
+       ZEND_ARG_INFO(0, context)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(phpdbg_clear_arginfo, 0, 0, 0)
+ZEND_END_ARG_INFO()
+
+zend_function_entry phpdbg_user_functions[] = {
+       PHP_FE(phpdbg_clear, phpdbg_clear_arginfo)
+       PHP_FE(phpdbg_break, phpdbg_break_arginfo)
+       PHP_FE(phpdbg_exec,  phpdbg_exec_arginfo)
+       PHP_FE(phpdbg_color, phpdbg_color_arginfo)
+       PHP_FE(phpdbg_prompt, phpdbg_prompt_arginfo)
+#ifdef  PHP_FE_END
+       PHP_FE_END
+#else
+       {NULL,NULL,NULL}
+#endif
+};
+
+static zend_module_entry sapi_phpdbg_module_entry = {
+       STANDARD_MODULE_HEADER,
+       PHPDBG_NAME,
+       phpdbg_user_functions,
+       PHP_MINIT(phpdbg),
+       NULL,
+       PHP_RINIT(phpdbg),
+       PHP_RSHUTDOWN(phpdbg),
+       NULL,
+       PHPDBG_VERSION,
+       STANDARD_MODULE_PROPERTIES
+};
+
+static inline int php_sapi_phpdbg_module_startup(sapi_module_struct *module) /* {{{ */
+{
+       if (php_module_startup(module, &sapi_phpdbg_module_entry, 1) == FAILURE) {
+               return FAILURE;
+       }
+       
+       phpdbg_booted=1;
+       
+       return SUCCESS;
+} /* }}} */
+
+static char* php_sapi_phpdbg_read_cookies(TSRMLS_D) /* {{{ */
+{
+       return NULL;
+} /* }}} */
+
+static int php_sapi_phpdbg_header_handler(sapi_header_struct *h, sapi_header_op_enum op, sapi_headers_struct *s TSRMLS_DC) /* {{{ */
+{
+       return 0;
+}
+/* }}} */
+
+static int php_sapi_phpdbg_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) /* {{{ */
+{
+       /* We do nothing here, this function is needed to prevent that the fallback
+        * header handling is called. */
+       return SAPI_HEADER_SENT_SUCCESSFULLY;
+}
+/* }}} */
+
+static void php_sapi_phpdbg_send_header(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC) /* {{{ */
+{
+}
+/* }}} */
+
+static void php_sapi_phpdbg_log_message(char *message TSRMLS_DC) /* {{{ */
+{
+       /*
+       * We must not request TSRM before being boot
+       */
+       if (phpdbg_booted) {
+               phpdbg_error("%s", message);
+       } else fprintf(stdout, "%s\n", message);
+}
+/* }}} */
+
+static int php_sapi_phpdbg_deactivate(TSRMLS_D) /* {{{ */
+{
+       fflush(stdout);
+       if(SG(request_info).argv0) {
+               free(SG(request_info).argv0);
+               SG(request_info).argv0 = NULL;
+       }
+       return SUCCESS;
+}
+/* }}} */
+
+static void php_sapi_phpdbg_register_vars(zval *track_vars_array TSRMLS_DC) /* {{{ */
+{
+       unsigned int len;
+       char   *docroot = "";
+
+       /* In phpdbg mode, we consider the environment to be a part of the server variables
+       */
+       php_import_environment_variables(track_vars_array TSRMLS_CC);
+
+       if (PHPDBG_G(exec)) {
+               len = PHPDBG_G(exec_len);
+               if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF",
+                                       &PHPDBG_G(exec), PHPDBG_G(exec_len), &len TSRMLS_CC)) {
+                       php_register_variable("PHP_SELF", PHPDBG_G(exec),
+                                       track_vars_array TSRMLS_CC);
+               }
+               if (sapi_module.input_filter(PARSE_SERVER, "SCRIPT_NAME",
+                                       &PHPDBG_G(exec), PHPDBG_G(exec_len), &len TSRMLS_CC)) {
+                       php_register_variable("SCRIPT_NAME", PHPDBG_G(exec),
+                                       track_vars_array TSRMLS_CC);
+               }
+
+               if (sapi_module.input_filter(PARSE_SERVER, "SCRIPT_FILENAME",
+                                       &PHPDBG_G(exec), PHPDBG_G(exec_len), &len TSRMLS_CC)) {
+                       php_register_variable("SCRIPT_FILENAME", PHPDBG_G(exec),
+                                       track_vars_array TSRMLS_CC);
+               }
+               if (sapi_module.input_filter(PARSE_SERVER, "PATH_TRANSLATED",
+                                       &PHPDBG_G(exec), PHPDBG_G(exec_len), &len TSRMLS_CC)) {
+                       php_register_variable("PATH_TRANSLATED", PHPDBG_G(exec),
+                                       track_vars_array TSRMLS_CC);
+               }
+       }
+
+       /* any old docroot will doo */
+       len = 0U;
+       if (sapi_module.input_filter(PARSE_SERVER, "DOCUMENT_ROOT",
+                               &docroot, len, &len TSRMLS_CC)) {
+               php_register_variable("DOCUMENT_ROOT", docroot, track_vars_array TSRMLS_CC);
+       }
+}
+/* }}} */
+
+static inline int php_sapi_phpdbg_ub_write(const char *message, unsigned int length TSRMLS_DC) /* {{{ */
+{
+       return phpdbg_write("%s", message);
+} /* }}} */
+
+#if PHP_VERSION_ID >= 50700
+static inline void php_sapi_phpdbg_flush(void *context TSRMLS_DC)  /* {{{ */
+{
+#else
+static inline void php_sapi_phpdbg_flush(void *context)  /* {{{ */
+{
+       TSRMLS_FETCH();
+#endif
+
+       fflush(PHPDBG_G(io)[PHPDBG_STDOUT]);
+} /* }}} */
+
+/* {{{ sapi_module_struct phpdbg_sapi_module
+*/
+static sapi_module_struct phpdbg_sapi_module = {
+       "phpdbg",                       /* name */
+       "phpdbg",                       /* pretty name */
+
+       php_sapi_phpdbg_module_startup, /* startup */
+       php_module_shutdown_wrapper,    /* shutdown */
+
+       NULL,                           /* activate */
+       php_sapi_phpdbg_deactivate,     /* deactivate */
+
+       php_sapi_phpdbg_ub_write,       /* unbuffered write */
+       php_sapi_phpdbg_flush,          /* flush */
+       NULL,                           /* get uid */
+       NULL,                           /* getenv */
+
+       php_error,                      /* error handler */
+
+       php_sapi_phpdbg_header_handler, /* header handler */
+       php_sapi_phpdbg_send_headers,   /* send headers handler */
+       php_sapi_phpdbg_send_header,    /* send header handler */
+
+       NULL,                           /* read POST data */
+       php_sapi_phpdbg_read_cookies,   /* read Cookies */
+
+       php_sapi_phpdbg_register_vars,  /* register server variables */
+       php_sapi_phpdbg_log_message,    /* Log message */
+       NULL,                           /* Get request time */
+       NULL,                           /* Child terminate */
+       STANDARD_SAPI_MODULE_PROPERTIES
+};
+/* }}} */
+
+const opt_struct OPTIONS[] = { /* {{{ */
+       {'c', 1, "ini path override"},
+       {'d', 1, "define ini entry on command line"},
+       {'n', 0, "no php.ini"},
+       {'z', 1, "load zend_extension"},
+       /* phpdbg options */
+       {'q', 0, "no banner"},
+       {'e', 1, "exec"},
+       {'v', 0, "disable quietness"},
+       {'s', 0, "enable stepping"},
+       {'b', 0, "boring colours"},
+       {'i', 1, "specify init"},
+       {'I', 0, "ignore init"},
+       {'O', 1, "opline log"},
+       {'r', 0, "run"},
+       {'E', 0, "step-through-eval"},
+       {'S', 1, "sapi-name"},
+#ifndef _WIN32
+       {'l', 1, "listen"},
+       {'a', 1, "address-or-any"},
+#endif
+       {'V', 0, "version"},
+       {'-', 0, NULL}
+}; /* }}} */
+
+const char phpdbg_ini_hardcoded[] =
+"html_errors=Off\n"
+"register_argc_argv=On\n"
+"implicit_flush=On\n"
+"display_errors=Off\n"
+"log_errors=On\n"
+"max_execution_time=0\n"
+"max_input_time=-1\n\0";
+
+/* overwriteable ini defaults must be set in phpdbg_ini_defaults() */
+#define INI_DEFAULT(name, value) \
+        Z_SET_REFCOUNT(tmp, 0); \
+        Z_UNSET_ISREF(tmp); \
+        ZVAL_STRINGL(&tmp, zend_strndup(value, sizeof(value)-1), sizeof(value)-1, 0); \
+        zend_hash_update(configuration_hash, name, sizeof(name), &tmp, sizeof(zval), NULL);
+
+void phpdbg_ini_defaults(HashTable *configuration_hash) /* {{{ */
+{
+       zval tmp;
+       INI_DEFAULT("report_zend_debug", "0");
+} /* }}} */
+
+static void phpdbg_welcome(zend_bool cleaning TSRMLS_DC) /* {{{ */
+{
+       /* print blurb */
+       if (!cleaning) {
+               phpdbg_notice("Welcome to phpdbg, the interactive PHP debugger, v%s",
+                               PHPDBG_VERSION);
+               phpdbg_writeln("To get help using phpdbg type \"help\" and press enter");
+               phpdbg_notice("Please report bugs to <%s>", PHPDBG_ISSUES);
+       } else {
+               phpdbg_notice("Clean Execution Environment");
+
+               phpdbg_writeln("Classes\t\t\t%d", zend_hash_num_elements(EG(class_table)));
+               phpdbg_writeln("Functions\t\t%d", zend_hash_num_elements(EG(function_table)));
+               phpdbg_writeln("Constants\t\t%d", zend_hash_num_elements(EG(zend_constants)));
+               phpdbg_writeln("Includes\t\t%d",  zend_hash_num_elements(&EG(included_files)));
+       }
+} /* }}} */
+
+static inline void phpdbg_sigint_handler(int signo) /* {{{ */
+{
+       TSRMLS_FETCH();
+
+       if (EG(in_execution)) {
+               /* set signalled only when not interactive */
+               if (!(PHPDBG_G(flags) & PHPDBG_IS_INTERACTIVE)) {
+                       PHPDBG_G(flags) |= PHPDBG_IS_SIGNALED;
+               }
+       } else {
+               PHPDBG_G(flags) |= PHPDBG_IS_QUITTING;
+               zend_bailout();
+       }
+} /* }}} */
+
+#ifndef _WIN32
+int phpdbg_open_socket(const char *interface, short port) /* {{{ */
+{
+       int fd = socket(AF_INET, SOCK_STREAM, 0);
+       
+       switch (fd) {
+               case -1:
+                       return -1;
+                       
+               default: {
+                       int reuse = 1;
+
+                       switch (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*) &reuse, sizeof(reuse))) {
+                               case -1:
+                                       close(fd);
+                                       return -2;
+                               
+                               default: {
+                                       struct sockaddr_in address;
+                                       
+                                       memset(&address, 0, sizeof(address));
+                                       
+                                       address.sin_port = htons(port);
+                                       address.sin_family = AF_INET;
+                                       
+                                       if ((*interface == '*')) {
+                                               address.sin_addr.s_addr = htonl(INADDR_ANY);                                            
+                                       } else if (!inet_pton(AF_INET, interface, &address.sin_addr)) {
+                                               close(fd);
+                                               return -3;
+                                       }
+                                       
+                                       switch (bind(fd, (struct sockaddr *)&address, sizeof(address))) {
+                                               case -1:
+                                                       close(fd);
+                                                       return -4;
+                                                       
+                                               default: {
+                                                       listen(fd, 5);
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+       
+       return fd;
+} /* }}} */
+
+static inline void phpdbg_close_sockets(int (*socket)[2], FILE *streams[2]) /* {{{ */
+{      
+       if ((*socket)[0] >= 0) {
+               shutdown(
+                       (*socket)[0], SHUT_RDWR);
+               close((*socket)[0]);
+       }
+       
+       if (streams[0]) {
+               fclose(streams[0]);
+       }
+       
+       if ((*socket)[1] >= 0) {
+               shutdown(
+                       (*socket)[1], SHUT_RDWR);
+               close((*socket)[1]);
+       }
+       
+       if (streams[1]) {
+               fclose(streams[1]);
+       }
+} /* }}} */
+
+/* don't inline this, want to debug it easily, will inline when done */
+
+int phpdbg_open_sockets(char *address, int port[2], int (*listen)[2], int (*socket)[2], FILE* streams[2]) /* {{{ */
+{
+       if (((*listen)[0]) < 0 && ((*listen)[1]) < 0) {
+               ((*listen)[0]) = phpdbg_open_socket(address, (short)port[0]);
+               ((*listen)[1]) = phpdbg_open_socket(address, (short)port[1]);
+       }
+
+       streams[0] = NULL;
+       streams[1] = NULL;
+
+       if ((*listen)[0] < 0 || (*listen)[1] < 0) {
+               if ((*listen)[0] < 0) {
+                       phpdbg_rlog(stderr,
+                               "console failed to initialize (stdin) on %s:%d", address, port[0]);
+               }
+
+               if ((*listen)[1] < 0) {
+                       phpdbg_rlog(stderr,
+                               "console failed to initialize (stdout) on %s:%d", address, port[1]);
+               }
+
+               if ((*listen)[0] >= 0) {
+                       close((*listen)[0]);
+               }
+
+               if ((*listen)[1] >= 0) {
+                       close((*listen)[1]);
+               }
+
+               return FAILURE;
+       }
+
+       phpdbg_close_sockets(socket, streams);
+
+       phpdbg_rlog(stderr,
+               "accepting connections on %s:%d/%d", address, port[0], port[1]);
+       {
+               struct sockaddr_in address;
+               socklen_t size = sizeof(address);
+               char buffer[20] = {0};
+
+               {
+                       memset(&address, 0, size);
+                       (*socket)[0] = accept(
+                               (*listen)[0], (struct sockaddr *) &address, &size);
+                       inet_ntop(AF_INET, &address.sin_addr, buffer, sizeof(buffer));
+
+                       phpdbg_rlog(stderr, "connection (stdin) from %s", buffer);
+               }
+
+               {
+                       memset(&address, 0, size);
+                       (*socket)[1] = accept(
+                               (*listen)[1], (struct sockaddr *) &address, &size);
+                   inet_ntop(AF_INET, &address.sin_addr, buffer, sizeof(buffer));
+
+                       phpdbg_rlog(stderr, "connection (stdout) from %s", buffer);
+               }
+       }
+
+       dup2((*socket)[0], fileno(stdin));
+       dup2((*socket)[1], fileno(stdout));
+       
+       setbuf(stdout, NULL);
+
+       streams[0] = fdopen((*socket)[0], "r");
+       streams[1] = fdopen((*socket)[1], "w");
+       
+       return SUCCESS;
+} /* }}} */
+#endif
+
+int main(int argc, char **argv) /* {{{ */
+{
+       sapi_module_struct *phpdbg = &phpdbg_sapi_module;
+       char *sapi_name;
+       char *ini_entries;
+       int   ini_entries_len;
+       char **zend_extensions = NULL;
+       zend_ulong zend_extensions_len = 0L;
+       zend_bool ini_ignore;
+       char *ini_override;
+       char *exec;
+       size_t exec_len;
+       char *init_file;
+       size_t init_file_len;
+       zend_bool init_file_default;
+       char *oplog_file;
+       size_t oplog_file_len;
+       zend_ulong flags;
+       char *php_optarg;
+       int php_optind, opt, show_banner = 1;
+       long cleaning = 0;
+       zend_bool remote = 0;
+       int run = 0;
+       int step = 0;
+       char *bp_tmp_file;
+#ifndef _WIN32
+       char *address;
+       int listen[2];
+       int server[2];
+       int socket[2];
+       FILE* streams[2] = {NULL, NULL};
+#endif
+
+#ifdef ZTS
+       void ***tsrm_ls;
+#endif
+
+#ifndef _WIN32
+       address = strdup("127.0.0.1");
+       socket[0] = -1;
+       socket[1] = -1;
+       listen[0] = -1;
+       listen[1] = -1;
+       server[0] = -1;
+       server[1] = -1;
+       streams[0] = NULL;
+       streams[1] = NULL;
+#endif
+
+#ifdef PHP_WIN32
+       _fmode = _O_BINARY;                 /* sets default for file streams to binary */
+       setmode(_fileno(stdin), O_BINARY);  /* make the stdio mode be binary */
+       setmode(_fileno(stdout), O_BINARY); /* make the stdio mode be binary */
+       setmode(_fileno(stderr), O_BINARY); /* make the stdio mode be binary */
+#endif
+
+#ifdef ZTS
+       tsrm_startup(1, 1, 0, NULL);
+
+       tsrm_ls = ts_resource(0);
+#endif
+
+phpdbg_main:
+       if (!cleaning) {
+               bp_tmp_file = malloc(L_tmpnam);
+               tmpnam(bp_tmp_file);
+               if (bp_tmp_file == NULL) {
+                       phpdbg_error("Unable to create temporary file");
+               }
+       }
+       ini_entries = NULL;
+       ini_entries_len = 0;
+       ini_ignore = 0;
+       ini_override = NULL;
+       zend_extensions = NULL;
+       zend_extensions_len = 0L;
+       exec = NULL;
+       exec_len = 0;
+       init_file = NULL;
+       init_file_len = 0;
+       init_file_default = 1;
+       oplog_file = NULL;
+       oplog_file_len = 0;
+       flags = PHPDBG_DEFAULT_FLAGS;
+       php_optarg = NULL;
+       php_optind = 1;
+       opt = 0;
+       run = 0;
+       step = 0;
+       sapi_name = NULL;
+       
+       
+       while ((opt = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {
+               switch (opt) {
+                       case 'r':
+                               run++;
+                               break;
+                       case 'n':
+                               ini_ignore = 1;
+                               break;
+                       case 'c':
+                               if (ini_override) {
+                                       free(ini_override);
+                               }
+                               ini_override = strdup(php_optarg);
+                               break;
+                       case 'd': {
+                               int len = strlen(php_optarg);
+                               char *val;
+
+                               if ((val = strchr(php_optarg, '='))) {
+                                 val++;
+                                 if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') {
+                                         ini_entries = realloc(ini_entries, ini_entries_len + len + sizeof("\"\"\n\0"));
+                                         memcpy(ini_entries + ini_entries_len, php_optarg, (val - php_optarg));
+                                         ini_entries_len += (val - php_optarg);
+                                         memcpy(ini_entries + ini_entries_len, "\"", 1);
+                                         ini_entries_len++;
+                                         memcpy(ini_entries + ini_entries_len, val, len - (val - php_optarg));
+                                         ini_entries_len += len - (val - php_optarg);
+                                         memcpy(ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0"));
+                                         ini_entries_len += sizeof("\n\0\"") - 2;
+                                 } else {
+                                         ini_entries = realloc(ini_entries, ini_entries_len + len + sizeof("\n\0"));
+                                         memcpy(ini_entries + ini_entries_len, php_optarg, len);
+                                         memcpy(ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0"));
+                                         ini_entries_len += len + sizeof("\n\0") - 2;
+                                 }
+                               } else {
+                                 ini_entries = realloc(ini_entries, ini_entries_len + len + sizeof("=1\n\0"));
+                                 memcpy(ini_entries + ini_entries_len, php_optarg, len);
+                                 memcpy(ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0"));
+                                 ini_entries_len += len + sizeof("=1\n\0") - 2;
+                               }
+                       } break;
+                       
+                       case 'z':
+                               zend_extensions_len++;
+                               if (zend_extensions) {
+                                       zend_extensions = realloc(zend_extensions, sizeof(char*) * zend_extensions_len);
+                               } else zend_extensions = malloc(sizeof(char*) * zend_extensions_len);
+                               zend_extensions[zend_extensions_len-1] = strdup(php_optarg);
+                       break;
+
+                       /* begin phpdbg options */
+
+                       case 'e': { /* set execution context */
+                               exec_len = strlen(php_optarg);
+                               if (exec_len) {
+                                       if (exec) {
+                                               free(exec);
+                                       }
+                                       exec = strdup(php_optarg);
+                               }
+                       } break;
+                       
+                       case 'S': { /* set SAPI name */
+                               if (sapi_name) {
+                                       free(sapi_name);
+                               }
+                               sapi_name = strdup(php_optarg);
+                       } break;
+
+                       case 'I': { /* ignore .phpdbginit */
+                               init_file_default = 0;
+                       } break;
+
+                       case 'i': { /* set init file */
+                               if (init_file) {
+                                       free(init_file);
+                               }
+                               
+                               init_file_len = strlen(php_optarg);
+                               if (init_file_len) {
+                                       init_file = strdup(php_optarg);
+                               }
+                       } break;
+
+                       case 'O': { /* set oplog output */
+                               oplog_file_len = strlen(php_optarg);
+                               if (oplog_file_len) {
+                                       oplog_file = strdup(php_optarg);
+                               }
+                       } break;
+
+                       case 'v': /* set quietness off */
+                               flags &= ~PHPDBG_IS_QUIET;
+                       break;
+
+                       case 's': /* set stepping on */
+                               step = 1;
+                       break;
+
+                       case 'E': /* stepping through eval on */
+                               flags |= PHPDBG_IS_STEPONEVAL;
+                       break;
+
+                       case 'b': /* set colours off */
+                               flags &= ~PHPDBG_IS_COLOURED;
+                       break;
+
+                       case 'q': /* hide banner */
+                               show_banner = 0;
+                       break;
+
+#ifndef _WIN32
+                       /* if you pass a listen port, we will accept input on listen port */
+                       /* and write output to listen port * 2 */
+                       
+                       case 'l': { /* set listen ports */
+                               if (sscanf(php_optarg, "%d/%d", &listen[0], &listen[1]) != 2) {
+                                       if (sscanf(php_optarg, "%d", &listen[0]) != 1) {
+                                               /* default to hardcoded ports */
+                                               listen[0] = 4000;
+                                               listen[1] = 8000;
+                                       } else {
+                                               listen[1] = (listen[0] * 2);
+                                       }
+                               }
+                       } break;
+                       
+                       case 'a': { /* set bind address */
+                               free(address);
+                               if (!php_optarg) {
+                                       address = strdup("*");
+                               } else address = strdup(php_optarg);
+                       } break;
+#endif
+
+                       case 'V': {
+                               sapi_startup(phpdbg);
+                               phpdbg->startup(phpdbg);
+                               printf(
+                                       "phpdbg %s (built: %s %s)\nCopyright (c) 2013 %s\nPHP %s, Copyright (c) 1997-2013 The PHP Group\n%s",
+                                       PHPDBG_VERSION,
+                                       __DATE__,
+                                       __TIME__,
+                                       PHPDBG_AUTHORS,
+                                       PHP_VERSION,
+                                       get_zend_version()
+                               );
+                               sapi_deactivate(TSRMLS_C);
+                               sapi_shutdown();
+                               return 0;
+                       } break;
+               }
+       }
+
+#ifndef _WIN32
+       /* setup remote server if necessary */
+       if (!cleaning &&
+               (listen[0] > 0 && listen[1] > 0)) {
+               if (phpdbg_open_sockets(address, listen, &server, &socket, streams) == FAILURE) {
+                       remote = 0;
+                       exit(0);
+               }
+               /* set remote flag to stop service shutting down upon quit */
+               remote = 1;
+       }
+#endif
+
+       if (sapi_name) {
+               phpdbg->name = sapi_name;
+       }
+       
+       phpdbg->ini_defaults = phpdbg_ini_defaults;
+       phpdbg->phpinfo_as_text = 1;
+       phpdbg->php_ini_ignore_cwd = 1;
+
+       sapi_startup(phpdbg);
+
+       phpdbg->executable_location = argv[0];
+       phpdbg->phpinfo_as_text = 1;
+       phpdbg->php_ini_ignore = ini_ignore;
+       phpdbg->php_ini_path_override = ini_override;
+
+       if (ini_entries) {
+               ini_entries = realloc(ini_entries, ini_entries_len + sizeof(phpdbg_ini_hardcoded));
+               memmove(ini_entries + sizeof(phpdbg_ini_hardcoded) - 2, ini_entries, ini_entries_len + 1);
+               memcpy(ini_entries, phpdbg_ini_hardcoded, sizeof(phpdbg_ini_hardcoded) - 2);
+       } else {
+               ini_entries = malloc(sizeof(phpdbg_ini_hardcoded));
+               memcpy(ini_entries, phpdbg_ini_hardcoded, sizeof(phpdbg_ini_hardcoded));
+       }
+       ini_entries_len += sizeof(phpdbg_ini_hardcoded) - 2;
+       
+       if (zend_extensions_len) {
+               zend_ulong zend_extension = 0L;
+               
+               while (zend_extension < zend_extensions_len) {
+                       const char *ze = zend_extensions[zend_extension];
+                       size_t ze_len = strlen(ze);
+                       
+                       ini_entries = realloc(
+                               ini_entries, ini_entries_len + (ze_len + (sizeof("zend_extension=\n"))));
+                       memcpy(&ini_entries[ini_entries_len], "zend_extension=", (sizeof("zend_extension=\n")-1));
+                       ini_entries_len += (sizeof("zend_extension=")-1);
+                       memcpy(&ini_entries[ini_entries_len], ze, ze_len);
+                       ini_entries_len += ze_len;
+                       memcpy(&ini_entries[ini_entries_len], "\n", (sizeof("\n") - 1));
+
+                       free(zend_extensions[zend_extension]);
+                       zend_extension++;
+               }
+               
+               free(zend_extensions);
+       }
+
+       phpdbg->ini_entries = ini_entries;
+               
+       if (phpdbg->startup(phpdbg) == SUCCESS) {
+               
+               zend_activate(TSRMLS_C);
+               
+               /* do not install sigint handlers for remote consoles */
+               /* sending SIGINT then provides a decent way of shutting down the server */
+#ifdef ZEND_SIGNALS
+# ifndef _WIN32
+               if (listen[0] < 0) {
+# endif
+                       zend_try {
+                               zend_signal_activate(TSRMLS_C);
+                               zend_signal(SIGINT, phpdbg_sigint_handler TSRMLS_CC);
+                       } zend_end_try();
+# ifndef _WIN32
+               }
+# endif
+#else
+# ifndef _WIN32
+               if (listen[0] < 0) {
+# endif
+                       signal(SIGINT, phpdbg_sigint_handler);
+#ifndef _WIN32
+               }
+#endif
+#endif
+
+               PG(modules_activated) = 0;
+               
+               /* set flags from command line */
+               PHPDBG_G(flags) = flags;
+
+#ifndef _WIN32
+               /* setup io here */
+               if (streams[0] && streams[1]) {
+                       PHPDBG_G(flags) |= PHPDBG_IS_REMOTE;
+
+                       signal(SIGPIPE, SIG_IGN);
+               }
+#endif
+
+               PHPDBG_G(io)[PHPDBG_STDIN] = stdin;
+               PHPDBG_G(io)[PHPDBG_STDOUT] = stdout;
+               PHPDBG_G(io)[PHPDBG_STDERR] = stderr;
+               
+               if (exec) { /* set execution context */
+                       PHPDBG_G(exec) = phpdbg_resolve_path(
+                                       exec TSRMLS_CC);
+                       PHPDBG_G(exec_len) = strlen(PHPDBG_G(exec));
+
+                       free(exec);
+               }
+
+               if (oplog_file) { /* open oplog */
+                       PHPDBG_G(oplog) = fopen(oplog_file, "w+");
+                       if (!PHPDBG_G(oplog)) {
+                               phpdbg_error(
+                                               "Failed to open oplog %s", oplog_file);
+                       }
+                       free(oplog_file);
+               }
+
+               /* set default colors */
+               phpdbg_set_color_ex(PHPDBG_COLOR_PROMPT,  PHPDBG_STRL("white-bold") TSRMLS_CC);
+               phpdbg_set_color_ex(PHPDBG_COLOR_ERROR,   PHPDBG_STRL("red-bold") TSRMLS_CC);
+               phpdbg_set_color_ex(PHPDBG_COLOR_NOTICE,  PHPDBG_STRL("green") TSRMLS_CC);
+
+               /* set default prompt */
+               phpdbg_set_prompt(PROMPT TSRMLS_CC);
+
+               zend_try {
+                       zend_activate_modules(TSRMLS_C);
+               } zend_end_try();
+
+               if (show_banner) {
+                       /* print blurb */
+                       phpdbg_welcome((cleaning > 0) TSRMLS_CC);
+               }
+
+               zend_try {
+                       /* activate globals, they can be overwritten */
+                       zend_activate_auto_globals(TSRMLS_C);
+               } zend_end_try();
+
+               /* initialize from file */
+               zend_try {
+                       PHPDBG_G(flags) |= PHPDBG_IS_INITIALIZING;
+                       phpdbg_init(init_file, init_file_len, init_file_default TSRMLS_CC);
+                       phpdbg_try_file_init(bp_tmp_file, strlen(bp_tmp_file), 0 TSRMLS_CC);
+                       PHPDBG_G(flags) &= ~PHPDBG_IS_INITIALIZING;
+               } zend_catch {
+                       PHPDBG_G(flags) &= ~PHPDBG_IS_INITIALIZING;
+                       if (PHPDBG_G(flags) & PHPDBG_IS_QUITTING) {
+                               goto phpdbg_out;
+                       }
+               } zend_end_try();
+
+               /* step from here, not through init */
+               if (step) {
+                       PHPDBG_G(flags) |= PHPDBG_IS_STEPPING;
+               }
+
+               if (run) {
+                       /* no need to try{}, run does it ... */
+                       PHPDBG_COMMAND_HANDLER(run)(NULL, NULL TSRMLS_CC);
+                       if (run > 1) {
+                               /* if -r is on the command line more than once just quit */
+                               goto phpdbg_out;
+                       }
+               }
+
+phpdbg_interact:
+               /* phpdbg main() */
+               do {
+                       zend_try {
+                               phpdbg_interactive(TSRMLS_C);
+                       } zend_catch {
+                               if ((PHPDBG_G(flags) & PHPDBG_IS_CLEANING)) {
+                                       FILE *bp_tmp_fp = fopen(bp_tmp_file, "w");
+                                       phpdbg_export_breakpoints(bp_tmp_fp TSRMLS_CC);
+                                       fclose(bp_tmp_fp);
+                                       cleaning = 1;
+                                       goto phpdbg_out;
+                               } else {
+                                       cleaning = 0;
+                               }
+#ifndef _WIN32
+                               /* remote client disconnected */
+                               if ((PHPDBG_G(flags) & PHPDBG_IS_DISCONNECTED)) {
+
+                                       /* renegociate connections */
+                                       phpdbg_open_sockets(
+                                               address, listen, &server, &socket, streams);
+                                       
+                                       /* set streams */
+                                       if (streams[0] && streams[1]) {
+                                               PHPDBG_G(flags) &= ~PHPDBG_IS_QUITTING;
+                                       }
+                                       
+                                       /* this must be forced */
+                                       CG(unclean_shutdown) = 0;
+                               }
+#endif
+                               if (PHPDBG_G(flags) & PHPDBG_IS_QUITTING) {
+                                       goto phpdbg_out;
+                               }
+                       } zend_end_try();
+               } while(!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING));
+               
+               /* this must be forced */
+               CG(unclean_shutdown) = 0;
+               
+phpdbg_out:
+#ifndef _WIN32
+               if (PHPDBG_G(flags) & PHPDBG_IS_DISCONNECTED) {
+                       PHPDBG_G(flags) &= ~PHPDBG_IS_DISCONNECTED;
+                       goto phpdbg_interact;
+               }
+#endif
+
+#ifndef ZTS
+               /* force cleanup of auto and core globals */
+               zend_hash_clean(CG(auto_globals));
+               memset(
+                       &core_globals, 0, sizeof(php_core_globals));
+#endif
+               
+               if (ini_entries) {
+                       free(ini_entries);
+               }
+
+               if (ini_override) {
+                       free(ini_override);
+               }
+
+               if (PG(modules_activated)) {
+                       zend_try {
+                               zend_deactivate_modules(TSRMLS_C);
+                       } zend_end_try();
+               }
+
+               zend_deactivate(TSRMLS_C);
+
+               zend_try {
+                       zend_post_deactivate_modules(TSRMLS_C);
+               } zend_end_try();
+
+#ifdef ZEND_SIGNALS
+               zend_try {
+                       zend_signal_deactivate(TSRMLS_C);
+               } zend_end_try();
+#endif
+
+               zend_try {
+                       php_module_shutdown(TSRMLS_C);
+               } zend_end_try();
+
+               sapi_shutdown();
+       }
+
+       if (cleaning || remote) {
+               goto phpdbg_main;
+       }
+
+#ifdef ZTS
+       /* bugggy */
+       /* tsrm_shutdown(); */
+#endif
+
+#ifndef _WIN32
+       if (address) {
+               free(address);  
+       }
+#endif
+
+       if (sapi_name) {
+               free(sapi_name);
+       }
+       
+       free(bp_tmp_file);
+
+       return 0;
+} /* }}} */
diff --git a/phpdbg.h b/phpdbg.h
new file mode 100644 (file)
index 0000000..f0a59ce
--- /dev/null
+++ b/phpdbg.h
@@ -0,0 +1,187 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_H
+#define PHPDBG_H
+
+#ifdef PHP_WIN32
+# define PHPDBG_API __declspec(dllexport)
+#elif defined(__GNUC__) && __GNUC__ >= 4
+# define PHPDBG_API __attribute__ ((visibility("default")))
+#else
+# define PHPDBG_API
+#endif
+
+#include "php.h"
+#include "php_globals.h"
+#include "php_variables.h"
+#include "php_getopt.h"
+#include "zend_builtin_functions.h"
+#include "zend_extensions.h"
+#include "zend_modules.h"
+#include "zend_globals.h"
+#include "zend_ini_scanner.h"
+#include "zend_stream.h"
+#include "SAPI.h"
+#include <fcntl.h>
+#include <sys/types.h>
+#if defined(_WIN32) && !defined(__MINGW32__)
+# include <windows.h>
+# include "config.w32.h"
+# undef  strcasecmp
+# undef  strncasecmp
+# define strcasecmp _stricmp 
+# define strncasecmp _strnicmp 
+#else
+# include "php_config.h"
+#endif
+#ifndef O_BINARY
+#      define O_BINARY 0
+#endif
+#include "php_main.h"
+
+#ifdef ZTS
+# include "TSRM.h"
+#endif
+
+#ifdef HAVE_LIBREADLINE
+#   include <readline/readline.h>
+#   include <readline/history.h>
+#endif
+
+#include "phpdbg_cmd.h"
+#include "phpdbg_utils.h"
+
+#ifdef ZTS
+# define PHPDBG_G(v) TSRMG(phpdbg_globals_id, zend_phpdbg_globals *, v)
+#else
+# define PHPDBG_G(v) (phpdbg_globals.v)
+#endif
+
+#define PHPDBG_NEXT   2
+#define PHPDBG_UNTIL  3
+#define PHPDBG_FINISH 4
+#define PHPDBG_LEAVE  5
+
+/*
+ BEGIN: DO NOT CHANGE DO NOT CHANGE DO NOT CHANGE
+*/
+
+/* {{{ tables */
+#define PHPDBG_BREAK_FILE            0
+#define PHPDBG_BREAK_SYM             1
+#define PHPDBG_BREAK_OPLINE          2
+#define PHPDBG_BREAK_METHOD          3
+#define PHPDBG_BREAK_COND            4
+#define PHPDBG_BREAK_OPCODE          5
+#define PHPDBG_BREAK_FUNCTION_OPLINE 6
+#define PHPDBG_BREAK_METHOD_OPLINE   7
+#define PHPDBG_BREAK_FILE_OPLINE     8
+#define PHPDBG_BREAK_MAP             9
+#define PHPDBG_BREAK_TABLES          10 /* }}} */
+
+/* {{{ flags */
+#define PHPDBG_HAS_FILE_BP            (1<<1)
+#define PHPDBG_HAS_SYM_BP             (1<<2)
+#define PHPDBG_HAS_OPLINE_BP          (1<<3)
+#define PHPDBG_HAS_METHOD_BP          (1<<4)
+#define PHPDBG_HAS_COND_BP            (1<<5)
+#define PHPDBG_HAS_OPCODE_BP          (1<<6)
+#define PHPDBG_HAS_FUNCTION_OPLINE_BP (1<<7)
+#define PHPDBG_HAS_METHOD_OPLINE_BP   (1<<8)
+#define PHPDBG_HAS_FILE_OPLINE_BP     (1<<9) /* }}} */
+
+/*
+ END: DO NOT CHANGE DO NOT CHANGE DO NOT CHANGE
+*/
+
+#define PHPDBG_IN_COND_BP             (1<<10)
+#define PHPDBG_IN_EVAL                (1<<11)
+
+#define PHPDBG_IS_STEPPING            (1<<12)
+#define PHPDBG_IS_QUIET               (1<<13)
+#define PHPDBG_IS_QUITTING            (1<<14)
+#define PHPDBG_IS_COLOURED            (1<<15)
+#define PHPDBG_IS_CLEANING            (1<<16)
+
+#define PHPDBG_IN_UNTIL               (1<<17)
+#define PHPDBG_IN_FINISH              (1<<18)
+#define PHPDBG_IN_LEAVE               (1<<19)
+
+#define PHPDBG_IS_REGISTERED          (1<<20)
+#define PHPDBG_IS_STEPONEVAL          (1<<21)
+#define PHPDBG_IS_INITIALIZING        (1<<22)
+#define PHPDBG_IS_SIGNALED            (1<<23)
+#define PHPDBG_IS_INTERACTIVE         (1<<24)
+#define PHPDBG_IS_BP_ENABLED          (1<<25)
+#define PHPDBG_IS_REMOTE              (1<<26)
+#define PHPDBG_IS_DISCONNECTED        (1<<27)
+
+#define PHPDBG_SEEK_MASK              (PHPDBG_IN_UNTIL|PHPDBG_IN_FINISH|PHPDBG_IN_LEAVE)
+#define PHPDBG_BP_RESOLVE_MASK           (PHPDBG_HAS_FUNCTION_OPLINE_BP|PHPDBG_HAS_METHOD_OPLINE_BP|PHPDBG_HAS_FILE_OPLINE_BP)
+#define PHPDBG_BP_MASK                (PHPDBG_HAS_FILE_BP|PHPDBG_HAS_SYM_BP|PHPDBG_HAS_METHOD_BP|PHPDBG_HAS_OPLINE_BP|PHPDBG_HAS_COND_BP|PHPDBG_HAS_OPCODE_BP|PHPDBG_HAS_FUNCTION_OPLINE_BP|PHPDBG_HAS_METHOD_OPLINE_BP|PHPDBG_HAS_FILE_OPLINE_BP)
+
+#ifndef _WIN32
+#      define PHPDBG_DEFAULT_FLAGS (PHPDBG_IS_QUIET|PHPDBG_IS_COLOURED|PHPDBG_IS_BP_ENABLED)
+#else
+#      define PHPDBG_DEFAULT_FLAGS (PHPDBG_IS_QUIET|PHPDBG_IS_BP_ENABLED)
+#endif /* }}} */
+
+/* {{{ strings */
+#define PHPDBG_NAME "phpdbg"
+#define PHPDBG_AUTHORS "Felipe Pena, Joe Watkins and Bob Weinand" /* Ordered by last name */
+#define PHPDBG_URL "http://phpdbg.com"
+#define PHPDBG_ISSUES "http://github.com/krakjoe/phpdbg/issues"
+#define PHPDBG_VERSION "0.3.0"
+#define PHPDBG_INIT_FILENAME ".phpdbginit"
+/* }}} */
+
+/* {{{ output descriptors */
+#define PHPDBG_STDIN                   0
+#define PHPDBG_STDOUT                  1
+#define PHPDBG_STDERR                  2
+#define PHPDBG_IO_FDS                  3 /* }}} */
+
+/* {{{ structs */
+ZEND_BEGIN_MODULE_GLOBALS(phpdbg)
+       HashTable bp[PHPDBG_BREAK_TABLES];           /* break points */
+       HashTable registered;                        /* registered */
+       HashTable seek;                              /* seek oplines */
+       phpdbg_frame_t frame;                        /* frame */
+
+       char *exec;                                  /* file to execute */
+       size_t exec_len;                             /* size of exec */
+       zend_op_array *ops;                          /* op_array */
+       zval *retval;                                /* return value */
+       int bp_count;                                /* breakpoint count */
+       int vmret;                                   /* return from last opcode handler execution */
+
+       FILE *oplog;                                 /* opline log */
+       FILE *io[PHPDBG_IO_FDS];                     /* io */
+
+       char *prompt[2];                             /* prompt */
+       const phpdbg_color_t *colors[PHPDBG_COLORS]; /* colors */
+
+       phpdbg_command_t *lcmd;                      /* last command */
+       phpdbg_param_t lparam;                       /* last param */
+
+       zend_ulong flags;                            /* phpdbg flags */
+ZEND_END_MODULE_GLOBALS(phpdbg) /* }}} */
+
+#endif /* PHPDBG_H */
diff --git a/phpdbg.init.d b/phpdbg.init.d
new file mode 100755 (executable)
index 0000000..99a1ab3
--- /dev/null
@@ -0,0 +1,122 @@
+################################################################
+# File:         /etc/init.d/phpdbg                             #
+# Author:       krakjoe                                        #
+# Purpose:      Daemonize phpdbg automatically on boot         #
+# chkconfig:    2345    07 09                                  #
+# description:  Starts, stops and restarts phpdbg daemon       #
+################################################################
+LOCKFILE=/var/lock/subsys/phpdbg
+PIDFILE=/var/run/phpdbg.pid
+STDIN=4000
+STDOUT=8000
+################################################################
+# Either set path to phpdbg here or rely on phpdbg in ENV/PATH #
+################################################################
+if [ "x${PHPDBG}" == "x" ]; then
+       PHPDBG=$(which phpdbg 2>/dev/null)
+fi
+################################################################
+# Options to pass to phpdbg upon boot                          #
+################################################################
+OPTIONS=
+LOGFILE=/var/log/phpdbg.log
+################################################################
+#     STOP EDITING STOP EDITING STOP EDITING STOP EDITING      #
+################################################################
+. /etc/rc.d/init.d/functions
+RETVAL=1
+################################################################
+insanity()
+{
+       if [ "x${PHPDBG}" == "x" ]; then
+               PHPDBG=$(which phpdbg 2>>/dev/null)
+               if [ $? != 0 ]; then
+                       echo -n $"Fatal: cannot find phpdbg ${PHPDBG}"
+                       echo_failure
+                       echo
+                       return 1
+               fi
+       else
+               if [ ! -x ${PHPDBG} ]; then
+                       echo -n $"Fatal: cannot execute phpdbg ${PHPDBG}"
+                       echo_failure
+                       echo
+                       return 1
+               fi
+       fi
+       
+       return 0
+}
+
+start()
+{
+       insanity
+
+       if [ $? -eq 1 ]; then
+               return $RETVAL
+       fi
+
+       echo -n $"Starting: phpdbg ${OPTIONS} on ${STDIN}/${STDOUT} "
+       nohup ${PHPDBG} -l${STDIN}/${STDOUT} ${OPTIONS} 2>>${LOGFILE} 1>/dev/null </dev/null &
+       PID=$!
+       RETVAL=$?
+       if [ $RETVAL -eq 0 ]; then
+               echo $PID > $PIDFILE
+               echo_success
+       else
+               echo_failure
+       fi
+       echo
+       [ $RETVAL = 0 ] && touch ${LOCKFILE}
+       return $RETVAL
+}
+
+stop()
+{
+       insanity
+
+       if [ $? -eq 1 ]; then
+               return $RETVAL
+       fi
+
+       if [ -f ${LOCKFILE} ] && [ -f ${PIDFILE} ]
+       then
+               echo -n $"Stopping: phpdbg ${OPTIONS} on ${STDIN}/${STDOUT} "
+               kill -s TERM $(cat $PIDFILE)
+               RETVAL=$?
+               if [ $RETVAL -eq 0 ]; then
+                       echo_success
+               else
+                       echo_failure
+               fi
+               echo
+               [ $RETVAL = 0 ] && rm -f ${LOCKFILE} ${PIDFILE}
+       else
+               echo -n $"Error: phpdbg not running"
+               echo_failure
+               echo
+               [ $RETVAL = 1 ]
+       fi
+       return $RETVAL
+}
+##################################################################
+case "$1" in
+       start)
+       start
+       ;;
+       stop)
+       stop
+       ;;
+       status)
+       status $PHPDBG
+       ;;
+       restart)
+       $0 stop
+       $0 start
+       ;;
+       *)
+       echo "usage: $0 start|stop|restart|status"
+       ;;
+esac
+###################################################################
+exit $RETVAL
diff --git a/phpdbg_bp.c b/phpdbg_bp.c
new file mode 100644 (file)
index 0000000..69e0fa7
--- /dev/null
@@ -0,0 +1,1661 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "zend.h"
+#include "zend_hash.h"
+#include "phpdbg.h"
+#include "phpdbg_bp.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_opcode.h"
+#include "zend_globals.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+/* {{{ private api functions */
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_file(zend_op_array* TSRMLS_DC);
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function* TSRMLS_DC);
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_method(zend_op_array* TSRMLS_DC);
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t TSRMLS_DC);
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opcode(zend_uchar TSRMLS_DC);
+static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(zend_execute_data *execute_data TSRMLS_DC); /* }}} */
+
+/*
+* Note:
+*      A break point must always set the correct id and type
+*      A set breakpoint function must always map new points
+*/
+static inline void _phpdbg_break_mapping(int id, HashTable *table TSRMLS_DC)
+{
+       zend_hash_index_update(
+               &PHPDBG_G(bp)[PHPDBG_BREAK_MAP], (id), (void**) &table, sizeof(void*), NULL);
+}
+
+#define PHPDBG_BREAK_MAPPING(id, table) _phpdbg_break_mapping(id, table TSRMLS_CC)
+#define PHPDBG_BREAK_UNMAPPING(id) \
+       zend_hash_index_del(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], (id))
+
+#define PHPDBG_BREAK_INIT(b, t) do {\
+       b.id = PHPDBG_G(bp_count)++; \
+       b.type = t; \
+       b.disabled = 0;\
+       b.hits = 0; \
+} while(0)
+
+static void phpdbg_file_breaks_dtor(void *data) /* {{{ */
+{
+       phpdbg_breakfile_t *bp = (phpdbg_breakfile_t*) data;
+
+       efree((char*)bp->filename);
+} /* }}} */
+
+static void phpdbg_class_breaks_dtor(void *data) /* {{{ */
+{
+       phpdbg_breakmethod_t *bp = (phpdbg_breakmethod_t*) data;
+
+       efree((char*)bp->class_name);
+       efree((char*)bp->func_name);
+} /* }}} */
+
+static void phpdbg_opline_class_breaks_dtor(void *data) /* {{{ */
+{
+       zend_hash_destroy((HashTable *)data);
+} /* }}} */
+
+static void phpdbg_opline_breaks_dtor(void *data) /* {{{ */
+{
+       phpdbg_breakopline_t *bp = (phpdbg_breakopline_t *) data;
+
+       if (bp->class_name) {
+               efree((char*)bp->class_name);
+       }
+       if (bp->func_name) {
+               efree((char*)bp->func_name);
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_reset_breakpoints(TSRMLS_D) /* {{{ */
+{
+       if (zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP])) {
+               HashPosition position[2];
+               HashTable **table = NULL;
+
+               for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], &position[0]);
+                       zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], (void**)&table, &position[0]) == SUCCESS;
+                       zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], &position[0])) {
+                       phpdbg_breakbase_t *brake;
+
+                       for (zend_hash_internal_pointer_reset_ex((*table), &position[1]);
+                               zend_hash_get_current_data_ex((*table), (void**)&brake, &position[1]) == SUCCESS;
+                               zend_hash_move_forward_ex((*table), &position[1])) {
+                               brake->hits = 0;
+                       }
+               }
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_export_breakpoints(FILE *handle TSRMLS_DC) /* {{{ */
+{
+       HashPosition position[2];
+       HashTable **table = NULL;
+       zend_ulong id = 0L;
+
+       if (zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP])) {
+               phpdbg_notice(
+                       "Exporting %d breakpoints",
+                       zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]));
+               /* this only looks like magic, it isn't */
+               for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], &position[0]);
+                       zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], (void**)&table, &position[0]) == SUCCESS;
+                       zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], &position[0])) {
+                       phpdbg_breakbase_t *brake;
+
+                       zend_hash_get_current_key_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], NULL, NULL, &id, 0, &position[0]);
+
+                       for (zend_hash_internal_pointer_reset_ex((*table), &position[1]);
+                               zend_hash_get_current_data_ex((*table), (void**)&brake, &position[1]) == SUCCESS;
+                               zend_hash_move_forward_ex((*table), &position[1])) {
+                               if (brake->id == id) {
+                                       switch (brake->type) {
+                                               case PHPDBG_BREAK_FILE: {
+                                                       fprintf(handle,
+                                                               "break file %s:%lu\n",
+                                                               ((phpdbg_breakfile_t*)brake)->filename,
+                                                               ((phpdbg_breakfile_t*)brake)->line);
+                                               } break;
+
+                                               case PHPDBG_BREAK_SYM: {
+                                                       fprintf(handle,
+                                                               "break func %s\n",
+                                                               ((phpdbg_breaksymbol_t*)brake)->symbol);
+                                               } break;
+
+                                               case PHPDBG_BREAK_METHOD: {
+                                                       fprintf(handle,
+                                                               "break method %s::%s\n",
+                                                               ((phpdbg_breakmethod_t*)brake)->class_name,
+                                                               ((phpdbg_breakmethod_t*)brake)->func_name);
+                                               } break;
+
+                                               case PHPDBG_BREAK_METHOD_OPLINE: {
+                                                       fprintf(handle,
+                                                               "break address %s::%s#%ld\n",
+                                                               ((phpdbg_breakopline_t*)brake)->class_name,
+                                                               ((phpdbg_breakopline_t*)brake)->func_name,
+                                                               ((phpdbg_breakopline_t*)brake)->opline_num);
+                                               } break;
+
+                                               case PHPDBG_BREAK_FUNCTION_OPLINE: {
+                                                       fprintf(handle,
+                                                               "break address %s#%ld\n",
+                                                               ((phpdbg_breakopline_t*)brake)->func_name,
+                                                               ((phpdbg_breakopline_t*)brake)->opline_num);
+                                               } break;
+
+                                               case PHPDBG_BREAK_FILE_OPLINE: {
+                                                       fprintf(handle,
+                                                               "break address %s:%ld\n",
+                                                               ((phpdbg_breakopline_t*)brake)->class_name,
+                                                               ((phpdbg_breakopline_t*)brake)->opline_num);
+                                               } break;
+
+                                               case PHPDBG_BREAK_OPCODE: {
+                                                       fprintf(handle,
+                                                               "break op %s\n",
+                                                               ((phpdbg_breakop_t*)brake)->name);
+                                               } break;
+
+                                               case PHPDBG_BREAK_COND: {
+                                                       phpdbg_breakcond_t *conditional = (phpdbg_breakcond_t*) brake;
+
+                                                       if (conditional->paramed) {
+                                                               switch (conditional->param.type) {
+                                                                       case STR_PARAM:
+                                                                               fprintf(handle,
+                                                                                       "break at %s if %s\n", conditional->param.str, conditional->code);
+                                                                       break;
+
+                                                                       case METHOD_PARAM:
+                                                                               fprintf(handle,
+                                                                                       "break at %s::%s if %s\n",
+                                                                                       conditional->param.method.class, conditional->param.method.name,
+                                                                                       conditional->code);
+                                                                       break;
+
+                                                                       case FILE_PARAM:
+                                                                               fprintf(handle,
+                                                                                       "break at %s:%lu if %s\n",
+                                                                                       conditional->param.file.name, conditional->param.file.line,
+                                                                                       conditional->code);
+                                                                       break;
+
+                                                                       default: { /* do nothing */ } break;
+                                                               }
+                                                       } else {
+                                                               fprintf(
+                                                                       handle, "break on %s\n", conditional->code);
+                                                       }
+                                               } break;
+                                       }
+                               }
+                       }
+               }
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_file(const char *path, long line_num TSRMLS_DC) /* {{{ */
+{
+       struct stat sb;
+
+       if (VCWD_STAT(path, &sb) != FAILURE) {
+               if (sb.st_mode & (S_IFREG|S_IFLNK)) {
+                       HashTable *broken;
+                       phpdbg_breakfile_t new_break;
+                       size_t path_len = strlen(path);
+
+                       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE],
+                               path, path_len, (void**)&broken) == FAILURE) {
+                               HashTable breaks;
+
+                               zend_hash_init(&breaks, 8, NULL, phpdbg_file_breaks_dtor, 0);
+
+                               zend_hash_update(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE],
+                                       path, path_len, &breaks, sizeof(HashTable),
+                                       (void**)&broken);
+                       }
+
+                       if (!zend_hash_index_exists(broken, line_num)) {
+                               PHPDBG_G(flags) |= PHPDBG_HAS_FILE_BP;
+
+                               PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_FILE);
+                               new_break.filename = estrndup(path, path_len);
+                               new_break.line = line_num;
+
+                               zend_hash_index_update(
+                                       broken, line_num, (void**)&new_break, sizeof(phpdbg_breakfile_t), NULL);
+
+                               phpdbg_notice("Breakpoint #%d added at %s:%ld",
+                                       new_break.id, new_break.filename, new_break.line);
+
+                               PHPDBG_BREAK_MAPPING(new_break.id, broken);
+                       } else {
+                               phpdbg_error("Breakpoint at %s:%ld exists", path, line_num);
+                       }
+
+               } else {
+                       phpdbg_error("Cannot set breakpoint in %s, it is not a regular file", path);
+               }
+       } else {
+               phpdbg_error("Cannot stat %s, it does not exist", path);
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_symbol(const char *name, size_t name_len TSRMLS_DC) /* {{{ */
+{
+       if (!zend_hash_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], name, name_len)) {
+               phpdbg_breaksymbol_t new_break;
+
+               PHPDBG_G(flags) |= PHPDBG_HAS_SYM_BP;
+
+               PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_SYM);
+               new_break.symbol = estrndup(name, name_len);
+
+               zend_hash_update(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], new_break.symbol,
+                       name_len, &new_break, sizeof(phpdbg_breaksymbol_t), NULL);
+
+               phpdbg_notice("Breakpoint #%d added at %s",
+                       new_break.id, new_break.symbol);
+
+               PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_SYM]);
+       } else {
+               phpdbg_notice("Breakpoint exists at %s", name);
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_method(const char *class_name, const char *func_name TSRMLS_DC) /* {{{ */
+{
+       HashTable class_breaks, *class_table;
+       size_t class_len = strlen(class_name);
+       size_t func_len = strlen(func_name);
+       char *lcname = zend_str_tolower_dup(func_name, func_len);
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], class_name,
+               class_len, (void**)&class_table) != SUCCESS) {
+               zend_hash_init(&class_breaks, 8, NULL, phpdbg_class_breaks_dtor, 0);
+               zend_hash_update(
+                       &PHPDBG_G(bp)[PHPDBG_BREAK_METHOD],
+                       class_name, class_len,
+                       (void**)&class_breaks, sizeof(HashTable), (void**)&class_table);
+       }
+
+       if (!zend_hash_exists(class_table, lcname, func_len)) {
+               phpdbg_breakmethod_t new_break;
+
+               PHPDBG_G(flags) |= PHPDBG_HAS_METHOD_BP;
+
+               PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_METHOD);
+               new_break.class_name = estrndup(class_name, class_len);
+               new_break.class_len = class_len;
+               new_break.func_name = estrndup(func_name, func_len);
+               new_break.func_len = func_len;
+
+               zend_hash_update(class_table, lcname, func_len,
+                       &new_break, sizeof(phpdbg_breakmethod_t), NULL);
+
+               phpdbg_notice("Breakpoint #%d added at %s::%s",
+                       new_break.id, class_name, func_name);
+
+               PHPDBG_BREAK_MAPPING(new_break.id, class_table);
+       } else {
+               phpdbg_notice("Breakpoint exists at %s::%s", class_name, func_name);
+    }
+
+    efree(lcname);
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_opline(zend_ulong opline TSRMLS_DC) /* {{{ */
+{
+       if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], opline)) {
+               phpdbg_breakline_t new_break;
+
+               PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP;
+
+               PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPLINE);
+               new_break.name = NULL;
+               new_break.opline = opline;
+               new_break.base = NULL;
+
+               zend_hash_index_update(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], opline,
+                       &new_break, sizeof(phpdbg_breakline_t), NULL);
+
+               phpdbg_notice("Breakpoint #%d added at %#lx",
+                       new_break.id, new_break.opline);
+               PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
+       } else {
+               phpdbg_notice("Breakpoint exists at %#lx", opline);
+       }
+} /* }}} */
+
+PHPDBG_API int phpdbg_resolve_op_array_break(phpdbg_breakopline_t *brake, zend_op_array *op_array TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakline_t opline_break;
+       if (op_array->last < brake->opline_num) {
+               if (brake->class_name == NULL) {
+                       phpdbg_error("There are only %d oplines in function %s (breaking at opline %ld impossible)", op_array->last, brake->func_name, brake->opline_num);
+               } else if (brake->func_name == NULL) {
+                       phpdbg_error("There are only %d oplines in file %s (breaking at opline %ld impossible)", op_array->last, brake->class_name, brake->opline_num);
+               } else {
+                       phpdbg_error("There are only %d oplines in method %s::%s (breaking at opline %ld impossible)", op_array->last, brake->class_name, brake->func_name, brake->opline_num);
+               }
+
+               return FAILURE;
+       }
+
+       opline_break.disabled = 0;
+       opline_break.hits = 0;
+       opline_break.id = brake->id;
+       opline_break.opline = brake->opline = (zend_ulong)(op_array->opcodes + brake->opline_num);
+       opline_break.name = NULL;
+       opline_break.base = brake;
+       if (op_array->scope) {
+               opline_break.type = PHPDBG_BREAK_METHOD_OPLINE;
+       } else if (op_array->function_name) {
+               opline_break.type = PHPDBG_BREAK_FUNCTION_OPLINE;
+       } else {
+               opline_break.type = PHPDBG_BREAK_FILE_OPLINE;
+       }
+
+       PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP;
+
+       zend_hash_index_update(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], opline_break.opline, &opline_break, sizeof(phpdbg_breakline_t), NULL);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_API void phpdbg_resolve_op_array_breaks(zend_op_array *op_array TSRMLS_DC) /* {{{ */
+{
+       HashTable *func_table = &PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE];
+       HashTable *oplines_table;
+       HashPosition position;
+       phpdbg_breakopline_t *brake;
+
+       if (op_array->scope != NULL &&
+           zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], op_array->scope->name, op_array->scope->name_length, (void **)&func_table) == FAILURE) {
+               return;
+       }
+
+       if (op_array->function_name == NULL) {
+               if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], op_array->filename, strlen(op_array->filename), (void **)&oplines_table) == FAILURE) {
+                       return;
+               }
+       } else if (zend_hash_find(func_table, op_array->function_name?op_array->function_name:"", op_array->function_name?strlen(op_array->function_name):0, (void **)&oplines_table) == FAILURE) {
+               return;
+       }
+
+       for (zend_hash_internal_pointer_reset_ex(oplines_table, &position);
+            zend_hash_get_current_data_ex(oplines_table, (void**) &brake, &position) == SUCCESS;
+            zend_hash_move_forward_ex(oplines_table, &position)) {
+               if (phpdbg_resolve_op_array_break(brake, op_array TSRMLS_CC) == SUCCESS) {
+                       phpdbg_breakline_t *opline_break;
+
+                       zend_hash_internal_pointer_end(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
+                       zend_hash_get_current_data(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (void **)&opline_break);
+
+                       phpdbg_notice("Breakpoint #%d resolved at %s%s%s#%ld (opline %#lx)",
+                               brake->id,
+                               brake->class_name?brake->class_name:"",
+                               brake->class_name&&brake->func_name?"::":"",
+                               brake->func_name?brake->func_name:"",
+                               brake->opline_num,
+                               brake->opline);
+               }
+       }
+} /* }}} */
+
+PHPDBG_API int phpdbg_resolve_opline_break(phpdbg_breakopline_t *new_break TSRMLS_DC) /* {{{ */
+{
+       HashTable *func_table = EG(function_table);
+       zend_function *func;
+
+       if (new_break->func_name == NULL) {
+               if (EG(current_execute_data) == NULL) {
+                       if (PHPDBG_G(ops) != NULL && !memcmp(PHPDBG_G(ops)->filename, new_break->class_name, new_break->class_len)) {
+                               if (phpdbg_resolve_op_array_break(new_break, PHPDBG_G(ops) TSRMLS_CC) == SUCCESS) {
+                                       return SUCCESS;
+                               } else {
+                                       return 2;
+                               }
+                       }
+                       return FAILURE;
+               } else {
+                       zend_execute_data *execute_data = EG(current_execute_data);
+                       do {
+                               if (execute_data->op_array->function_name == NULL && execute_data->op_array->scope == NULL && !memcmp(execute_data->op_array->filename, new_break->class_name, new_break->class_len)) {
+                                       if (phpdbg_resolve_op_array_break(new_break, execute_data->op_array TSRMLS_CC) == SUCCESS) {
+                                               return SUCCESS;
+                                       } else {
+                                               return 2;
+                                       }
+                               }
+                       } while ((execute_data = execute_data->prev_execute_data) != NULL);
+                       return FAILURE;
+               }
+       }
+
+       if (new_break->class_name != NULL) {
+               zend_class_entry **ce;
+               if (zend_hash_find(EG(class_table), zend_str_tolower_dup(new_break->class_name, new_break->class_len), new_break->class_len + 1, (void **)&ce) == FAILURE) {
+                       return FAILURE;
+               }
+               func_table = &(*ce)->function_table;
+       }
+
+       if (zend_hash_find(func_table, zend_str_tolower_dup(new_break->func_name, new_break->func_len), new_break->func_len + 1, (void **)&func) == FAILURE) {
+               if (new_break->class_name != NULL && new_break->func_name != NULL) {
+                       phpdbg_error("Method %s doesn't exist in class %s", new_break->func_name, new_break->class_name);
+                       return 2;
+               }
+               return FAILURE;
+       }
+
+       if (func->type != ZEND_USER_FUNCTION) {
+               if (new_break->class_name == NULL) {
+                       phpdbg_error("%s is not an user defined function, no oplines exist", new_break->func_name);
+               } else {
+                       phpdbg_error("%s::%s is not an user defined method, no oplines exist", new_break->class_name, new_break->func_name);
+               }
+               return 2;
+       }
+
+       if (phpdbg_resolve_op_array_break(new_break, &func->op_array TSRMLS_CC) == FAILURE) {
+               return 2;
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_method_opline(const char *class, const char *method, zend_ulong opline TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakopline_t new_break;
+       HashTable class_breaks, *class_table;
+       HashTable method_breaks, *method_table;
+
+       PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_METHOD_OPLINE);
+       new_break.func_len = strlen(method);
+       new_break.func_name = estrndup(method, new_break.func_len);
+       new_break.class_len = strlen(class);
+       new_break.class_name = estrndup(class, new_break.class_len);
+       new_break.opline_num = opline;
+       new_break.opline = 0;
+
+       switch (phpdbg_resolve_opline_break(&new_break TSRMLS_CC)) {
+               case FAILURE:
+                       phpdbg_notice("Pending breakpoint #%d at %s::%s#%ld", new_break.id, new_break.class_name, new_break.func_name, opline);
+                       break;
+
+               case SUCCESS:
+                       phpdbg_notice("Breakpoint #%d added at %s::%s#%ld", new_break.id, new_break.class_name, new_break.func_name, opline);
+                       break;
+
+               case 2:
+                       return;
+       }
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], new_break.class_name, new_break.class_len, (void **)&class_table) == FAILURE) {
+               zend_hash_init(&class_breaks, 8, NULL, phpdbg_opline_class_breaks_dtor, 0);
+               zend_hash_update(
+                       &PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE],
+                       new_break.class_name,
+                       new_break.class_len,
+                       (void **)&class_breaks, sizeof(HashTable), (void **)&class_table);
+       }
+
+       if (zend_hash_find(class_table, new_break.func_name, new_break.func_len, (void **)&method_table) == FAILURE) {
+               zend_hash_init(&method_breaks, 8, NULL, phpdbg_opline_breaks_dtor, 0);
+               zend_hash_update(
+                       class_table,
+                       new_break.func_name,
+                       new_break.func_len,
+                       (void **)&method_breaks, sizeof(HashTable), (void **)&method_table);
+       }
+
+       if (zend_hash_index_exists(method_table, opline)) {
+               phpdbg_notice("Breakpoint already exists for %s::%s#%ld", new_break.class_name, new_break.func_name, opline);
+               efree((char*)new_break.func_name);
+               efree((char*)new_break.class_name);
+               PHPDBG_G(bp_count)--;
+               return;
+       }
+
+       PHPDBG_G(flags) |= PHPDBG_HAS_METHOD_OPLINE_BP;
+
+       PHPDBG_BREAK_MAPPING(new_break.id, method_table);
+
+       zend_hash_index_update(method_table, opline, &new_break, sizeof(phpdbg_breakopline_t), NULL);
+}
+
+PHPDBG_API void phpdbg_set_breakpoint_function_opline(const char *function, zend_ulong opline TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakopline_t new_break;
+       HashTable func_breaks, *func_table;
+
+       PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_FUNCTION_OPLINE);
+       new_break.func_len = strlen(function);
+       new_break.func_name = estrndup(function, new_break.func_len);
+       new_break.class_len = 0;
+       new_break.class_name = NULL;
+       new_break.opline_num = opline;
+       new_break.opline = 0;
+
+       switch (phpdbg_resolve_opline_break(&new_break TSRMLS_CC)) {
+               case FAILURE:
+                       phpdbg_notice("Pending breakpoint #%d at %s#%ld", new_break.id, new_break.func_name, opline);
+                       break;
+
+               case SUCCESS:
+                       phpdbg_notice("Breakpoint #%d added at %s#%ld", new_break.id, new_break.func_name, opline);
+                       break;
+
+               case 2:
+                       return;
+       }
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], new_break.func_name, new_break.func_len, (void **)&func_table) == FAILURE) {
+               zend_hash_init(&func_breaks, 8, NULL, phpdbg_opline_breaks_dtor, 0);
+               zend_hash_update(
+                       &PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE],
+                       new_break.func_name,
+                       new_break.func_len,
+                       (void **)&func_breaks, sizeof(HashTable), (void **)&func_table);
+       }
+
+       if (zend_hash_index_exists(func_table, opline)) {
+               phpdbg_notice("Breakpoint already exists for %s#%ld", new_break.func_name, opline);
+               efree((char*)new_break.func_name);
+               PHPDBG_G(bp_count)--;
+               return;
+       }
+
+       PHPDBG_BREAK_MAPPING(new_break.id, func_table);
+
+       PHPDBG_G(flags) |= PHPDBG_HAS_FUNCTION_OPLINE_BP;
+
+       zend_hash_index_update(func_table, opline, &new_break, sizeof(phpdbg_breakopline_t), NULL);
+}
+
+PHPDBG_API void phpdbg_set_breakpoint_file_opline(const char *file, zend_ulong opline TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakopline_t new_break;
+       HashTable file_breaks, *file_table;
+
+       PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_FILE_OPLINE);
+       new_break.func_len = 0;
+       new_break.func_name = NULL;
+       new_break.class_len = strlen(file);
+       new_break.class_name = estrndup(file, new_break.class_len);
+       new_break.opline_num = opline;
+       new_break.opline = 0;
+
+       switch (phpdbg_resolve_opline_break(&new_break TSRMLS_CC)) {
+               case FAILURE:
+                       phpdbg_notice("Pending breakpoint #%d at %s:%ld", new_break.id, new_break.class_name, opline);
+                       break;
+
+               case SUCCESS:
+                       phpdbg_notice("Breakpoint #%d added at %s:%ld", new_break.id, new_break.class_name, opline);
+                       break;
+
+               case 2:
+                       return;
+       }
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], new_break.class_name, new_break.class_len, (void **)&file_table) == FAILURE) {
+               zend_hash_init(&file_breaks, 8, NULL, phpdbg_opline_breaks_dtor, 0);
+               zend_hash_update(
+                       &PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE],
+                       new_break.class_name,
+                       new_break.class_len,
+                       (void **)&file_breaks, sizeof(HashTable), (void **)&file_table);
+       }
+
+       if (zend_hash_index_exists(file_table, opline)) {
+               phpdbg_notice("Breakpoint already exists for %s:%ld", new_break.class_name, opline);
+               efree((char*)new_break.class_name);
+               PHPDBG_G(bp_count)--;
+               return;
+       }
+
+       PHPDBG_BREAK_MAPPING(new_break.id, file_table);
+
+       PHPDBG_G(flags) |= PHPDBG_HAS_FILE_OPLINE_BP;
+
+       zend_hash_index_update(file_table, opline, &new_break, sizeof(phpdbg_breakopline_t), NULL);
+}
+
+PHPDBG_API void phpdbg_set_breakpoint_opcode(const char *name, size_t name_len TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakop_t new_break;
+       zend_ulong hash = zend_hash_func(name, name_len);
+
+       if (zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], hash)) {
+               phpdbg_notice(
+                       "Breakpoint exists for %s", name);
+               return;
+       }
+
+       PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPCODE);
+       new_break.hash = hash;
+       new_break.name = estrndup(name, name_len);
+
+       zend_hash_index_update(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], hash,
+               &new_break, sizeof(phpdbg_breakop_t), NULL);
+
+       PHPDBG_G(flags) |= PHPDBG_HAS_OPCODE_BP;
+
+       phpdbg_notice("Breakpoint #%d added at %s", new_break.id, name);
+       PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE]);
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline TSRMLS_DC) /* {{{ */
+{
+       if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (zend_ulong) opline)) {
+               phpdbg_breakline_t new_break;
+
+               PHPDBG_G(flags) |= PHPDBG_HAS_OPLINE_BP;
+
+               PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_OPLINE);
+               new_break.opline = (zend_ulong) opline;
+
+               zend_hash_index_update(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE],
+                       (zend_ulong) opline, &new_break, sizeof(phpdbg_breakline_t), NULL);
+
+               phpdbg_notice("Breakpoint #%d added at %#lx",
+                       new_break.id, new_break.opline);
+               PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
+       }
+} /* }}} */
+
+static inline void phpdbg_create_conditional_break(phpdbg_breakcond_t *brake, const phpdbg_param_t *param, const char *expr, size_t expr_len, zend_ulong hash TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakcond_t new_break;
+       zend_uint cops = CG(compiler_options);
+       zval pv;
+
+       PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_COND);
+       new_break.hash = hash;
+
+       if (param) {
+               new_break.paramed = 1;
+               phpdbg_copy_param(
+                       param, &new_break.param TSRMLS_CC);
+       } else {
+               new_break.paramed = 0;
+       }
+
+       cops = CG(compiler_options);
+
+       CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL;
+
+       new_break.code = estrndup(expr, expr_len);
+       new_break.code_len = expr_len;
+
+       Z_STRLEN(pv) = expr_len + sizeof("return ;") - 1;
+       Z_STRVAL(pv) = emalloc(Z_STRLEN(pv) + 1);
+       memcpy(Z_STRVAL(pv), "return ", sizeof("return ") - 1);
+       memcpy(Z_STRVAL(pv) + sizeof("return ") - 1, expr, expr_len);
+       Z_STRVAL(pv)[Z_STRLEN(pv) - 1] = ';';
+       Z_STRVAL(pv)[Z_STRLEN(pv)] = '\0';
+       Z_TYPE(pv) = IS_STRING;
+
+       new_break.ops = zend_compile_string(
+               &pv, "Conditional Breakpoint Code" TSRMLS_CC);
+
+       zval_dtor(&pv);
+
+       if (new_break.ops) {
+               zend_hash_index_update(
+                       &PHPDBG_G(bp)[PHPDBG_BREAK_COND], hash, &new_break,
+                       sizeof(phpdbg_breakcond_t), (void**)&brake);
+
+               phpdbg_notice("Conditional breakpoint #%d added %s/%p",
+                       brake->id, brake->code, brake->ops);
+
+               PHPDBG_G(flags) |= PHPDBG_HAS_COND_BP;
+               PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
+       } else {
+                phpdbg_error(
+                       "Failed to compile code for expression %s", expr);
+                efree((char*)new_break.code);
+                PHPDBG_G(bp_count)--;
+       }
+       CG(compiler_options) = cops;
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_expression(const char *expr, size_t expr_len TSRMLS_DC) /* {{{ */
+{
+       zend_ulong expr_hash = zend_inline_hash_func(expr, expr_len);
+       phpdbg_breakcond_t new_break;
+
+       if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], expr_hash)) {
+               phpdbg_create_conditional_break(
+                       &new_break, NULL, expr, expr_len, expr_hash TSRMLS_CC);
+       } else {
+               phpdbg_notice("Conditional break %s exists", expr);
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_breakpoint_at(const phpdbg_param_t *param, const phpdbg_input_t *input TSRMLS_DC) /* {{{ */
+{
+       if (input->argc > 3 && phpdbg_argv_is(2, "if")) {
+               phpdbg_breakcond_t new_break;
+               phpdbg_param_t new_param;
+
+               zend_ulong expr_hash = 0L;
+               size_t expr_len;
+               const char *join = strstr(input->string, "if");
+               const char *expr = (join) + sizeof("if");
+
+               expr_len = strlen(expr);
+               expr = phpdbg_trim(expr, expr_len, &expr_len);
+               expr_hash = zend_inline_hash_func(expr, expr_len);
+
+               {
+                       /* get a clean parameter from input string */
+                       size_t sparam_len = 0L;
+                       char *sparam = input->string;
+
+                       sparam[
+                               strstr(input->string, " ") - input->string] = 0;
+                       sparam_len = strlen(sparam);
+
+                       switch (phpdbg_parse_param(sparam, sparam_len, &new_param TSRMLS_CC)) {
+                               case EMPTY_PARAM:
+                               case NUMERIC_PARAM:
+                                       phpdbg_clear_param(
+                                               &new_param TSRMLS_CC);
+                                       goto usage;
+
+                               default: { /* do nothing */ } break;
+                       }
+
+                       expr_hash += phpdbg_hash_param(&new_param TSRMLS_CC);
+               }
+
+               if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], expr_hash)) {
+                       phpdbg_create_conditional_break(
+                               &new_break, &new_param, expr, expr_len, expr_hash TSRMLS_CC);
+               } else {
+                       phpdbg_notice(
+                               "Conditional break %s exists at the specified location", expr);
+               }
+
+               phpdbg_clear_param(&new_param TSRMLS_CC);
+       } else {
+usage:
+               phpdbg_error("usage: break at <func|method|file:line|address> if <expression>");
+       }
+} /* }}} */
+
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_file(zend_op_array *op_array TSRMLS_DC) /* {{{ */
+{
+       HashTable *breaks;
+       phpdbg_breakbase_t *brake;
+       size_t name_len = strlen(op_array->filename);
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], op_array->filename,
+               name_len, (void**)&breaks) == FAILURE) {
+               return NULL;
+       }
+
+       if (zend_hash_index_find(breaks, (*EG(opline_ptr))->lineno, (void**)&brake) == SUCCESS) {
+               return brake;
+       }
+
+       return NULL;
+} /* }}} */
+
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function *fbc TSRMLS_DC) /* {{{ */
+{
+       const char *fname;
+       zend_op_array *ops;
+       phpdbg_breakbase_t *brake;
+
+       if (fbc->type != ZEND_USER_FUNCTION) {
+               return NULL;
+       }
+
+       ops = (zend_op_array*)fbc;
+
+       if (ops->scope) {
+               /* find method breaks here */
+               return phpdbg_find_breakpoint_method(ops TSRMLS_CC);
+       }
+
+       fname = ops->function_name;
+
+       if (!fname) {
+               fname = "main";
+       }
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], fname, strlen(fname), (void**)&brake) == SUCCESS) {
+               return brake;
+       }
+
+       return NULL;
+} /* }}} */
+
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_method(zend_op_array *ops TSRMLS_DC) /* {{{ */
+{
+       HashTable *class_table;
+       phpdbg_breakbase_t *brake;
+
+       if (zend_hash_find(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], ops->scope->name,
+               ops->scope->name_length, (void**)&class_table) == SUCCESS) {
+               char *lcname = zend_str_tolower_dup(ops->function_name, strlen(ops->function_name));
+               size_t lcname_len = strlen(lcname);
+
+               if (zend_hash_find(
+                       class_table,
+                       lcname,
+                       lcname_len, (void**)&brake) == SUCCESS) {
+                       efree(lcname);
+                       return brake;
+               }
+
+               efree(lcname);
+       }
+
+       return NULL;
+} /* }}} */
+
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t opline TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakline_t *brake;
+
+       if (zend_hash_index_find(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE],
+               (zend_ulong) opline, (void**)&brake) == SUCCESS) {
+               return (brake->base?(phpdbg_breakbase_t *)brake->base:(phpdbg_breakbase_t *)brake);
+       }
+
+       return NULL;
+} /* }}} */
+
+static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opcode(zend_uchar opcode TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakbase_t *brake;
+       const char *opname = phpdbg_decode_opcode(opcode);
+
+       if (memcmp(opname, PHPDBG_STRL("UNKNOWN")) == 0) {
+               return NULL;
+       }
+
+       if (zend_hash_index_find(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE],
+               zend_hash_func(opname, strlen(opname)), (void**)&brake) == SUCCESS) {
+               return brake;
+       }
+       return NULL;
+} /* }}} */
+
+static inline zend_bool phpdbg_find_breakpoint_param(phpdbg_param_t *param, zend_execute_data *execute_data TSRMLS_DC) /* {{{ */
+{
+       zend_function *function = (zend_function*) execute_data->function_state.function;
+
+       switch (param->type) {
+               case NUMERIC_FUNCTION_PARAM:
+               case STR_PARAM: {
+                       /* function breakpoint */
+
+                       if (function->type != ZEND_USER_FUNCTION) {
+                               return 0;
+                       }
+
+                       {
+                               const char *str = NULL;
+                               size_t len = 0L;
+                               zend_op_array *ops = (zend_op_array*)function;
+                               str = ops->function_name ? ops->function_name : "main";
+                               len = strlen(str);
+
+                               if (len == param->len && memcmp(param->str, str, len) == SUCCESS) {
+                                       return param->type == STR_PARAM || execute_data->opline - ops->opcodes == param->num;
+                               }
+                       }
+               } break;
+
+               case FILE_PARAM: {
+                       if (param->file.line == zend_get_executed_lineno(TSRMLS_C)) {
+                               const char *str = zend_get_executed_filename(TSRMLS_C);
+                               size_t lengths[2] = {strlen(param->file.name), strlen(str)};
+
+                               if (lengths[0] == lengths[1]) {
+                                       return (memcmp(
+                                               param->file.name, str, lengths[0]) == SUCCESS);
+                               }
+                       }
+               } break;
+
+               case NUMERIC_METHOD_PARAM:
+               case METHOD_PARAM: {
+                       if (function->type != ZEND_USER_FUNCTION) {
+                               return 0;
+                       }
+
+                       {
+                               zend_op_array *ops = (zend_op_array*) function;
+
+                               if (ops->scope) {
+                                       size_t lengths[2] = {strlen(param->method.class), ops->scope->name_length};
+                                       if (lengths[0] == lengths[1] && memcmp(param->method.class, ops->scope->name, lengths[0]) == SUCCESS) {
+                                               lengths[0] = strlen(param->method.name);
+                                               lengths[1] = strlen(ops->function_name);
+
+                                               if (lengths[0] == lengths[1] && memcmp(param->method.name, ops->function_name, lengths[0]) == SUCCESS) {
+                                                       return param->type == METHOD_PARAM || (execute_data->opline - ops->opcodes) == param->num;
+                                               }
+                                       }
+                               }
+                       }
+               } break;
+
+               case ADDR_PARAM: {
+                       return ((zend_ulong)(phpdbg_opline_ptr_t)execute_data->opline == param->addr);
+               } break;
+
+               default: {
+                       /* do nothing */
+               } break;
+       }
+       return 0;
+} /* }}} */
+
+static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(zend_execute_data *execute_data TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakcond_t *bp;
+       HashPosition position;
+       int breakpoint = FAILURE;
+
+       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], &position);
+            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], (void*)&bp, &position) == SUCCESS;
+             zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], &position)) {
+               zval *retval = NULL;
+               int orig_interactive = CG(interactive);
+               zval **orig_retval = EG(return_value_ptr_ptr);
+               zend_op_array *orig_ops = EG(active_op_array);
+               zend_op **orig_opline = EG(opline_ptr);
+
+               if (((phpdbg_breakbase_t*)bp)->disabled) {
+                       continue;
+               }
+
+               if (bp->paramed) {
+                       if (!phpdbg_find_breakpoint_param(&bp->param, execute_data TSRMLS_CC)) {
+                               continue;
+                       }
+               }
+
+               ALLOC_INIT_ZVAL(retval);
+
+               EG(return_value_ptr_ptr) = &retval;
+               EG(active_op_array) = bp->ops;
+               EG(no_extensions) = 1;
+
+               if (!EG(active_symbol_table)) {
+                       zend_rebuild_symbol_table(TSRMLS_C);
+               }
+
+               CG(interactive) = 0;
+
+               zend_try {
+                       PHPDBG_G(flags) |= PHPDBG_IN_COND_BP;
+                       zend_execute(EG(active_op_array) TSRMLS_CC);
+#if PHP_VERSION_ID >= 50700
+                       if (zend_is_true(retval TSRMLS_CC)) {
+#else
+                       if (zend_is_true(retval)) {
+#endif
+                               breakpoint = SUCCESS;
+                       }
+               } zend_catch {
+                       CG(interactive) = orig_interactive;
+
+                       EG(no_extensions)=1;
+                       EG(return_value_ptr_ptr) = orig_retval;
+                       EG(active_op_array) = orig_ops;
+                       EG(opline_ptr) = orig_opline;
+                       PHPDBG_G(flags) &= ~PHPDBG_IN_COND_BP;
+               } zend_end_try();
+
+               CG(interactive) = orig_interactive;
+
+               EG(no_extensions)=1;
+               EG(return_value_ptr_ptr) = orig_retval;
+               EG(active_op_array) = orig_ops;
+               EG(opline_ptr) = orig_opline;
+               PHPDBG_G(flags) &= ~PHPDBG_IN_COND_BP;
+
+               if (breakpoint == SUCCESS) {
+                       break;
+               }
+       }
+
+       return (breakpoint == SUCCESS) ? ((phpdbg_breakbase_t*)bp) : NULL;
+} /* }}} */
+
+PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakpoint(zend_execute_data* execute_data TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakbase_t *base = NULL;
+
+       if (!(PHPDBG_G(flags) & PHPDBG_IS_BP_ENABLED)) {
+               return NULL;
+       }
+
+       /* conditions cannot be executed by eval()'d code */
+       if (!(PHPDBG_G(flags) & PHPDBG_IN_EVAL) &&
+               (PHPDBG_G(flags) & PHPDBG_HAS_COND_BP) &&
+               (base = phpdbg_find_conditional_breakpoint(execute_data TSRMLS_CC))) {
+               goto result;
+       }
+
+       if ((PHPDBG_G(flags) & PHPDBG_HAS_FILE_BP) &&
+               (base = phpdbg_find_breakpoint_file(execute_data->op_array TSRMLS_CC))) {
+               goto result;
+       }
+
+       if (PHPDBG_G(flags) & (PHPDBG_HAS_METHOD_BP|PHPDBG_HAS_SYM_BP)) {
+               /* check we are at the beginning of the stack */
+               if (execute_data->opline == EG(active_op_array)->opcodes) {
+                       if ((base = phpdbg_find_breakpoint_symbol(
+                                       execute_data->function_state.function TSRMLS_CC))) {
+                               goto result;
+                       }
+               }
+       }
+
+       if ((PHPDBG_G(flags) & PHPDBG_HAS_OPLINE_BP) &&
+               (base = phpdbg_find_breakpoint_opline(execute_data->opline TSRMLS_CC))) {
+               goto result;
+       }
+
+       if ((PHPDBG_G(flags) & PHPDBG_HAS_OPCODE_BP) &&
+               (base = phpdbg_find_breakpoint_opcode(execute_data->opline->opcode TSRMLS_CC))) {
+               goto result;
+       }
+
+       return NULL;
+
+result:
+       /* we return nothing for disable breakpoints */
+       if (base->disabled) {
+               return NULL;
+       }
+
+       return base;
+} /* }}} */
+
+PHPDBG_API void phpdbg_delete_breakpoint(zend_ulong num TSRMLS_DC) /* {{{ */
+{
+       HashTable **table;
+       HashPosition position;
+       phpdbg_breakbase_t *brake;
+
+       if ((brake = phpdbg_find_breakbase_ex(num, &table, &position TSRMLS_CC))) {
+               char *key;
+               zend_uint klen;
+               zend_ulong idx;
+               int type = brake->type;
+               char *name = NULL;
+               size_t name_len = 0L;
+
+               switch (type) {
+                       case PHPDBG_BREAK_FILE:
+                       case PHPDBG_BREAK_METHOD:
+                               if (zend_hash_num_elements((*table)) == 1) {
+                                       name = estrdup(brake->name);
+                                       name_len = strlen(name);
+                                       if (zend_hash_num_elements(&PHPDBG_G(bp)[type]) == 1) {
+                                               PHPDBG_G(flags) &= ~(1<<(brake->type+1));
+                                       }
+                               }
+                       break;
+
+                       default: {
+                               if (zend_hash_num_elements((*table)) == 1) {
+                                       PHPDBG_G(flags) &= ~(1<<(brake->type+1));
+                               }
+                       }
+               }
+
+               switch (type) {
+                       case PHPDBG_BREAK_FILE_OPLINE:
+                       case PHPDBG_BREAK_FUNCTION_OPLINE:
+                       case PHPDBG_BREAK_METHOD_OPLINE:
+                               if (zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]) == 1) {
+                                       PHPDBG_G(flags) &= PHPDBG_HAS_OPLINE_BP;
+                               }
+                               zend_hash_index_del(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], ((phpdbg_breakopline_t*)brake)->opline);
+               }
+
+               switch (zend_hash_get_current_key_ex(
+                       (*table), &key, &klen, &idx, 0, &position)) {
+
+                       case HASH_KEY_IS_STRING:
+                               zend_hash_del((*table), key, klen);
+                       break;
+
+                       default:
+                               zend_hash_index_del((*table), idx);
+               }
+
+               switch (type) {
+                       case PHPDBG_BREAK_FILE:
+                       case PHPDBG_BREAK_METHOD:
+                               if (name) {
+                                       zend_hash_del(&PHPDBG_G(bp)[type], name, name_len);
+                                       efree(name);
+                               }
+                       break;
+               }
+
+               phpdbg_notice("Deleted breakpoint #%ld", num);
+               PHPDBG_BREAK_UNMAPPING(num);
+       } else {
+               phpdbg_error("Failed to find breakpoint #%ld", num);
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_clear_breakpoints(TSRMLS_D) /* {{{ */
+{
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]);
+       zend_hash_clean(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP]);
+
+       PHPDBG_G(flags) &= ~PHPDBG_BP_MASK;
+
+       PHPDBG_G(bp_count) = 0;
+} /* }}} */
+
+PHPDBG_API void phpdbg_hit_breakpoint(phpdbg_breakbase_t *brake, zend_bool output TSRMLS_DC) /* {{{ */
+{
+       brake->hits++;
+
+       if (output) {
+               phpdbg_print_breakpoint(brake TSRMLS_CC);
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_print_breakpoint(phpdbg_breakbase_t *brake TSRMLS_DC) /* {{{ */
+{
+       if (!brake)
+               goto unknown;
+
+       switch (brake->type) {
+               case PHPDBG_BREAK_FILE: {
+                       phpdbg_notice("Breakpoint #%d at %s:%ld, hits: %lu",
+                               ((phpdbg_breakfile_t*)brake)->id,
+                               ((phpdbg_breakfile_t*)brake)->filename,
+                               ((phpdbg_breakfile_t*)brake)->line,
+                               ((phpdbg_breakfile_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_SYM: {
+                       phpdbg_notice("Breakpoint #%d in %s() at %s:%u, hits: %lu",
+                               ((phpdbg_breaksymbol_t*)brake)->id,
+                               ((phpdbg_breaksymbol_t*)brake)->symbol,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakfile_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_OPLINE: {
+                       phpdbg_notice("Breakpoint #%d in %#lx at %s:%u, hits: %lu",
+                               ((phpdbg_breakline_t*)brake)->id,
+                               ((phpdbg_breakline_t*)brake)->opline,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakline_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_METHOD_OPLINE: {
+                        phpdbg_notice("Breakpoint #%d in %s::%s()#%lu at %s:%u, hits: %lu",
+                               ((phpdbg_breakopline_t*)brake)->id,
+                               ((phpdbg_breakopline_t*)brake)->class_name,
+                               ((phpdbg_breakopline_t*)brake)->func_name,
+                               ((phpdbg_breakopline_t*)brake)->opline_num,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakopline_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_FUNCTION_OPLINE: {
+                        phpdbg_notice("Breakpoint #%d in %s()#%lu at %s:%u, hits: %lu",
+                               ((phpdbg_breakopline_t*)brake)->id,
+                               ((phpdbg_breakopline_t*)brake)->func_name,
+                               ((phpdbg_breakopline_t*)brake)->opline_num,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakopline_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_FILE_OPLINE: {
+                        phpdbg_notice("Breakpoint #%d in %s:%lu at %s:%u, hits: %lu",
+                               ((phpdbg_breakopline_t*)brake)->id,
+                               ((phpdbg_breakopline_t*)brake)->class_name,
+                               ((phpdbg_breakopline_t*)brake)->opline_num,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakopline_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_OPCODE: {
+                        phpdbg_notice("Breakpoint #%d in %s at %s:%u, hits: %lu",
+                               ((phpdbg_breakop_t*)brake)->id,
+                               ((phpdbg_breakop_t*)brake)->name,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakop_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_METHOD: {
+                        phpdbg_notice("Breakpoint #%d in %s::%s() at %s:%u, hits: %lu",
+                               ((phpdbg_breakmethod_t*)brake)->id,
+                               ((phpdbg_breakmethod_t*)brake)->class_name,
+                               ((phpdbg_breakmethod_t*)brake)->func_name,
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C),
+                               ((phpdbg_breakmethod_t*)brake)->hits);
+               } break;
+
+               case PHPDBG_BREAK_COND: {
+                       if (((phpdbg_breakcond_t*)brake)->paramed) {
+                               char *param;
+                               phpdbg_notice("Conditional breakpoint #%d: at %s if %s %s:%u, hits: %lu",
+                                       ((phpdbg_breakcond_t*)brake)->id,
+                                       phpdbg_param_tostring(&((phpdbg_breakcond_t*)brake)->param, &param TSRMLS_CC),
+                                       ((phpdbg_breakcond_t*)brake)->code,
+                                       zend_get_executed_filename(TSRMLS_C),
+                                       zend_get_executed_lineno(TSRMLS_C),
+                                       ((phpdbg_breakcond_t*)brake)->hits);
+                               if (param)
+                                       free(param);
+                       } else {
+                               phpdbg_notice("Conditional breakpoint #%d: on %s == true %s:%u, hits: %lu",
+                                       ((phpdbg_breakcond_t*)brake)->id,
+                                       ((phpdbg_breakcond_t*)brake)->code,
+                                       zend_get_executed_filename(TSRMLS_C),
+                                       zend_get_executed_lineno(TSRMLS_C),
+                                       ((phpdbg_breakcond_t*)brake)->hits);
+                       }
+
+               } break;
+
+               default: {
+unknown:
+                       phpdbg_notice("Unknown breakpoint at %s:%u",
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C));
+               }
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_enable_breakpoint(zend_ulong id TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakbase_t *brake = phpdbg_find_breakbase(id TSRMLS_CC);
+
+       if (brake) {
+               brake->disabled = 0;
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_disable_breakpoint(zend_ulong id TSRMLS_DC) /* {{{ */
+{
+       phpdbg_breakbase_t *brake = phpdbg_find_breakbase(id TSRMLS_CC);
+
+       if (brake) {
+               brake->disabled = 1;
+       }
+} /* }}} */
+
+PHPDBG_API void phpdbg_enable_breakpoints(TSRMLS_D) /* {{{ */
+{
+       PHPDBG_G(flags) |= PHPDBG_IS_BP_ENABLED;
+} /* }}} */
+
+PHPDBG_API void phpdbg_disable_breakpoints(TSRMLS_D) { /* {{{ */
+       PHPDBG_G(flags) &= ~PHPDBG_IS_BP_ENABLED;
+} /* }}} */
+
+PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakbase(zend_ulong id TSRMLS_DC) /* {{{ */
+{
+       HashTable **table;
+       HashPosition position;
+
+       return phpdbg_find_breakbase_ex(id, &table, &position TSRMLS_CC);
+} /* }}} */
+
+PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakbase_ex(zend_ulong id, HashTable ***table, HashPosition *position TSRMLS_DC) /* {{{ */
+{
+       if (zend_hash_index_find(&PHPDBG_G(bp)[PHPDBG_BREAK_MAP], id, (void**)table) == SUCCESS) {
+               phpdbg_breakbase_t *brake;
+
+               for (zend_hash_internal_pointer_reset_ex((**table), position);
+                       zend_hash_get_current_data_ex((**table), (void**)&brake, position) == SUCCESS;
+                       zend_hash_move_forward_ex((**table), position)) {
+
+                       if (brake->id == id) {
+                               return brake;
+                       }
+               }
+       }
+       return NULL;
+} /* }}} */
+
+PHPDBG_API void phpdbg_print_breakpoints(zend_ulong type TSRMLS_DC) /* {{{ */
+{
+       switch (type) {
+               case PHPDBG_BREAK_SYM: if ((PHPDBG_G(flags) & PHPDBG_HAS_SYM_BP)) {
+                       HashPosition position;
+                       phpdbg_breaksymbol_t *brake;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Function Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], &position);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], (void**) &brake, &position) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM], &position)) {
+                               phpdbg_writeln("#%d\t\t%s%s",
+                                       brake->id, brake->symbol,
+                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                       }
+               } break;
+
+               case PHPDBG_BREAK_METHOD: if ((PHPDBG_G(flags) & PHPDBG_HAS_METHOD_BP)) {
+                       HashPosition position[2];
+                       HashTable *class_table;
+                       char *class_name = NULL;
+                       zend_uint class_len = 0;
+                       zend_ulong class_idx = 0L;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Method Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], &position[0]);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], (void**) &class_table, &position[0]) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD], &position[0])) {
+
+                               if (zend_hash_get_current_key_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD],
+                                       &class_name, &class_len, &class_idx, 0, &position[0]) == HASH_KEY_IS_STRING) {
+                                       phpdbg_breakmethod_t *brake;
+
+                                       for (zend_hash_internal_pointer_reset_ex(class_table, &position[1]);
+                                            zend_hash_get_current_data_ex(class_table, (void**)&brake, &position[1]) == SUCCESS;
+                                            zend_hash_move_forward_ex(class_table, &position[1])) {
+                                               phpdbg_writeln("#%d\t\t%s::%s%s",
+                                                       brake->id, brake->class_name, brake->func_name,
+                                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                       }
+                               }
+
+                       }
+               } break;
+
+               case PHPDBG_BREAK_FILE: if ((PHPDBG_G(flags) & PHPDBG_HAS_FILE_BP)) {
+                       HashPosition position[2];
+                       HashTable *points;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("File Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], &position[0]);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], (void**) &points, &position[0]) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE], &position[0])) {
+                               phpdbg_breakfile_t *brake;
+
+                               for (zend_hash_internal_pointer_reset_ex(points, &position[1]);
+                                    zend_hash_get_current_data_ex(points, (void**)&brake, &position[1]) == SUCCESS;
+                                    zend_hash_move_forward_ex(points, &position[1])) {
+                                       phpdbg_writeln("#%d\t\t%s:%lu%s",
+                                               brake->id, brake->filename, brake->line,
+                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                               }
+                       }
+
+               } break;
+
+               case PHPDBG_BREAK_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_OPLINE_BP)) {
+                       HashPosition position;
+                       phpdbg_breakline_t *brake;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Opline Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], &position);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], (void**) &brake, &position) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE], &position)) {
+                               switch (brake->type) {
+                                       case PHPDBG_BREAK_METHOD_OPLINE:
+                                       case PHPDBG_BREAK_FUNCTION_OPLINE:
+                                       case PHPDBG_BREAK_FILE_OPLINE:
+                                               phpdbg_writeln("#%d\t\t%#lx\t\t(%s breakpoint)%s", brake->id, brake->opline,
+                                                       brake->type == PHPDBG_BREAK_METHOD_OPLINE?"method":
+                                                               brake->type == PHPDBG_BREAK_FUNCTION_OPLINE?"function":
+                                                                       brake->type == PHPDBG_BREAK_FILE_OPLINE?"file":
+                                                                               "--- error ---",
+                                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                       default:
+                                               phpdbg_writeln("#%d\t\t%#lx", brake->id, brake->opline);
+                                               break;
+                               }
+                       }
+               } break;
+
+               case PHPDBG_BREAK_METHOD_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_METHOD_OPLINE_BP)) {
+                       HashPosition position[3];
+                       HashTable *class_table, *method_table;
+                       char *class_name = NULL, *method_name = NULL;
+                       zend_uint class_len = 0, method_len = 0;
+                       zend_ulong class_idx = 0L, method_idx = 0L;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Method opline Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], &position[0]);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], (void**) &class_table, &position[0]) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE], &position[0])) {
+
+                               if (zend_hash_get_current_key_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE],
+                                       &class_name, &class_len, &class_idx, 0, &position[0]) == HASH_KEY_IS_STRING) {
+
+                                       for (zend_hash_internal_pointer_reset_ex(class_table, &position[1]);
+                                            zend_hash_get_current_data_ex(class_table, (void**) &method_table, &position[1]) == SUCCESS;
+                                            zend_hash_move_forward_ex(class_table, &position[1])) {
+
+                                               if (zend_hash_get_current_key_ex(class_table,
+                                                       &method_name, &method_len, &method_idx, 0, &position[0]) == HASH_KEY_IS_STRING) {
+
+                                                       phpdbg_breakopline_t *brake;
+
+                                                       for (zend_hash_internal_pointer_reset_ex(method_table, &position[2]);
+                                                            zend_hash_get_current_data_ex(method_table, (void**)&brake, &position[2]) == SUCCESS;
+                                                            zend_hash_move_forward_ex(method_table, &position[2])) {
+                                                               phpdbg_writeln("#%d\t\t%s::%s opline %ld%s",
+                                                                       brake->id, brake->class_name, brake->func_name, brake->opline_num,
+                                                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                                       }
+                                               }
+                                       }
+                               }
+
+                       }
+               } break;
+
+               case PHPDBG_BREAK_FUNCTION_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_FUNCTION_OPLINE_BP)) {
+                       HashPosition position[2];
+                       HashTable *function_table;
+                       char *function_name = NULL;
+                       zend_uint function_len = 0;
+                       zend_ulong function_idx = 0L;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Function opline Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], &position[0]);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], (void**) &function_table, &position[0]) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE], &position[0])) {
+
+                               if (zend_hash_get_current_key_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE],
+                                       &function_name, &function_len, &function_idx, 0, &position[0]) == HASH_KEY_IS_STRING) {
+
+                                       phpdbg_breakopline_t *brake;
+
+                                       for (zend_hash_internal_pointer_reset_ex(function_table, &position[1]);
+                                            zend_hash_get_current_data_ex(function_table, (void**)&brake, &position[1]) == SUCCESS;
+                                            zend_hash_move_forward_ex(function_table, &position[1])) {
+                                               phpdbg_writeln("#%d\t\t%s opline %ld%s",
+                                                       brake->id, brake->func_name, brake->opline_num,
+                                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                       }
+                               }
+
+                       }
+               } break;
+
+               case PHPDBG_BREAK_FILE_OPLINE: if ((PHPDBG_G(flags) & PHPDBG_HAS_FILE_OPLINE_BP)) {
+                       HashPosition position[2];
+                       HashTable *file_table;
+                       char *file_name = NULL;
+                       zend_uint file_len = 0;
+                       zend_ulong file_idx = 0L;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("File opline Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], &position[0]);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], (void**) &file_table, &position[0]) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE], &position[0])) {
+
+                               if (zend_hash_get_current_key_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE],
+                                       &file_name, &file_len, &file_idx, 0, &position[0]) == HASH_KEY_IS_STRING) {
+
+                                       phpdbg_breakopline_t *brake;
+
+                                       for (zend_hash_internal_pointer_reset_ex(file_table, &position[1]);
+                                            zend_hash_get_current_data_ex(file_table, (void**)&brake, &position[1]) == SUCCESS;
+                                            zend_hash_move_forward_ex(file_table, &position[1])) {
+                                               phpdbg_writeln("#%d\t\t%s opline %ld%s",
+                                                       brake->id, brake->class_name, brake->opline_num,
+                                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                       }
+                               }
+                       }
+               } break;
+
+               case PHPDBG_BREAK_COND: if ((PHPDBG_G(flags) & PHPDBG_HAS_COND_BP)) {
+                       HashPosition position;
+                       phpdbg_breakcond_t *brake;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Conditional Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], &position);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], (void**) &brake, &position) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], &position)) {
+                               if (brake->paramed) {
+                                       switch (brake->param.type) {
+                                               case STR_PARAM:
+                                                       phpdbg_writeln("#%d\t\tat %s if %s%s",
+                                                               brake->id,
+                                                               brake->param.str,
+                                                               brake->code,
+                                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                               case NUMERIC_FUNCTION_PARAM:
+                                                       phpdbg_writeln("#%d\t\tat %s#%ld if %s%s",
+                                                               brake->id,
+                                                               brake->param.str,
+                                                               brake->param.num,
+                                                               brake->code,
+                                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                               case METHOD_PARAM:
+                                                       phpdbg_writeln("#%d\t\tat %s::%s if %s%s",
+                                                               brake->id,
+                                                               brake->param.method.class,
+                                                               brake->param.method.name,
+                                                               brake->code,
+                                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                               case NUMERIC_METHOD_PARAM:
+                                                       phpdbg_writeln("#%d\t\tat %s::%s#%ld if %s%s",
+                                                               brake->id,
+                                                               brake->param.method.class,
+                                                               brake->param.method.name,
+                                                               brake->param.num,
+                                                               brake->code,
+                                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                               case FILE_PARAM:
+                                                       phpdbg_writeln("#%d\t\tat %s:%lu if %s%s",
+                                                               brake->id,
+                                                               brake->param.file.name,
+                                                               brake->param.file.line,
+                                                               brake->code,
+                                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                               case ADDR_PARAM:
+                                                       phpdbg_writeln("#%d\t\tat #%lx if %s%s",
+                                                               brake->id,
+                                                               brake->param.addr,
+                                                               brake->code,
+                                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                                               break;
+
+                                               default:
+                                                       phpdbg_error("Invalid parameter type for conditional breakpoint");
+                                               return;
+                                       }
+                               } else {
+                                       phpdbg_writeln("#%d\t\tif %s%s",
+                                               brake->id, brake->code,
+                                               ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                               }
+                       }
+               } break;
+
+               case PHPDBG_BREAK_OPCODE: if (PHPDBG_G(flags) & PHPDBG_HAS_OPCODE_BP) {
+                       HashPosition position;
+                       phpdbg_breakop_t *brake;
+
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_writeln("Opcode Breakpoints:");
+                       for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], &position);
+                            zend_hash_get_current_data_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], (void**) &brake, &position) == SUCCESS;
+                            zend_hash_move_forward_ex(&PHPDBG_G(bp)[PHPDBG_BREAK_OPCODE], &position)) {
+                               phpdbg_writeln("#%d\t\t%s%s",
+                                       brake->id, brake->name,
+                                       ((phpdbg_breakbase_t*)brake)->disabled ? " [disabled]" : "");
+                       }
+               } break;
+       }
+} /* }}} */
diff --git a/phpdbg_bp.h b/phpdbg_bp.h
new file mode 100644 (file)
index 0000000..ed1b413
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_BP_H
+#define PHPDBG_BP_H
+
+/* {{{ */
+typedef struct _zend_op *phpdbg_opline_ptr_t; /* }}} */
+
+/* {{{ breakpoint base structure */
+#define phpdbg_breakbase(name) \
+       int         id; \
+       zend_uchar  type; \
+       zend_ulong  hits; \
+       zend_bool   disabled; \
+       const char *name /* }}} */
+
+/* {{{ breakpoint base */
+typedef struct _phpdbg_breakbase_t {
+       phpdbg_breakbase(name);
+} phpdbg_breakbase_t; /* }}} */
+
+/**
+ * Breakpoint file-based representation
+ */
+typedef struct _phpdbg_breakfile_t {
+       phpdbg_breakbase(filename);
+       long        line;
+} phpdbg_breakfile_t;
+
+/**
+ * Breakpoint symbol-based representation
+ */
+typedef struct _phpdbg_breaksymbol_t {
+       phpdbg_breakbase(symbol);
+} phpdbg_breaksymbol_t;
+
+/**
+ * Breakpoint method based representation
+ */
+typedef struct _phpdbg_breakmethod_t {
+       phpdbg_breakbase(class_name);
+       size_t      class_len;
+       const char *func_name;
+       size_t      func_len;
+} phpdbg_breakmethod_t;
+
+/**
+ * Breakpoint opline num based representation
+ */
+typedef struct _phpdbg_breakopline_t {
+       phpdbg_breakbase(func_name);
+       size_t      func_len;
+       const char *class_name;
+       size_t      class_len;
+       zend_ulong  opline_num;
+       zend_ulong  opline;
+} phpdbg_breakopline_t;
+
+/**
+ * Breakpoint opline based representation
+ */
+typedef struct _phpdbg_breakline_t {
+       phpdbg_breakbase(name);
+       zend_ulong opline;
+       phpdbg_breakopline_t *base;
+} phpdbg_breakline_t;
+
+/**
+ * Breakpoint opcode based representation
+ */
+typedef struct _phpdbg_breakop_t {
+       phpdbg_breakbase(name);
+       zend_ulong hash;
+} phpdbg_breakop_t;
+
+/**
+ * Breakpoint condition based representation
+ */
+typedef struct _phpdbg_breakcond_t {
+       phpdbg_breakbase(code);
+       size_t          code_len;
+       zend_bool       paramed;
+       phpdbg_param_t  param;
+       zend_ulong      hash;
+       zend_op_array  *ops;
+} phpdbg_breakcond_t;
+
+/* {{{ Opline breaks API */
+PHPDBG_API void phpdbg_resolve_op_array_breaks(zend_op_array *op_array TSRMLS_DC);
+PHPDBG_API int phpdbg_resolve_op_array_break(phpdbg_breakopline_t *brake, zend_op_array *op_array TSRMLS_DC);
+PHPDBG_API int phpdbg_resolve_opline_break(phpdbg_breakopline_t *new_break TSRMLS_DC); /* }}} */
+
+/* {{{ Breakpoint Creation API */
+PHPDBG_API void phpdbg_set_breakpoint_file(const char* filename, long lineno TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_symbol(const char* func_name, size_t func_name_len TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_method(const char* class_name, const char* func_name TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_opcode(const char* opname, size_t opname_len TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_opline(zend_ulong opline TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_method_opline(const char *class, const char *method, zend_ulong opline TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_function_opline(const char *function, zend_ulong opline TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_file_opline(const char *file, zend_ulong opline TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_expression(const char* expression, size_t expression_len TSRMLS_DC);
+PHPDBG_API void phpdbg_set_breakpoint_at(const phpdbg_param_t *param, const phpdbg_input_t *input TSRMLS_DC); /* }}} */
+
+/* {{{ Breakpoint Detection API */
+PHPDBG_API phpdbg_breakbase_t* phpdbg_find_breakpoint(zend_execute_data* TSRMLS_DC); /* }}} */
+
+/* {{{ Misc Breakpoint API */
+PHPDBG_API void phpdbg_hit_breakpoint(phpdbg_breakbase_t* brake, zend_bool output TSRMLS_DC);
+PHPDBG_API void phpdbg_print_breakpoints(zend_ulong type TSRMLS_DC);
+PHPDBG_API void phpdbg_print_breakpoint(phpdbg_breakbase_t* brake TSRMLS_DC);
+PHPDBG_API void phpdbg_reset_breakpoints(TSRMLS_D);
+PHPDBG_API void phpdbg_clear_breakpoints(TSRMLS_D);
+PHPDBG_API void phpdbg_delete_breakpoint(zend_ulong num TSRMLS_DC);
+PHPDBG_API void phpdbg_enable_breakpoints(TSRMLS_D);
+PHPDBG_API void phpdbg_enable_breakpoint(zend_ulong id TSRMLS_DC);
+PHPDBG_API void phpdbg_disable_breakpoint(zend_ulong id TSRMLS_DC);
+PHPDBG_API void phpdbg_disable_breakpoints(TSRMLS_D); /* }}} */
+
+/* {{{ Breakbase API */
+PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakbase(zend_ulong id TSRMLS_DC);
+PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakbase_ex(zend_ulong id, HashTable ***table, HashPosition *position TSRMLS_DC); /* }}} */
+
+/* {{{ Breakpoint Exportation API */
+PHPDBG_API void phpdbg_export_breakpoints(FILE *handle TSRMLS_DC); /* }}} */
+
+#endif /* PHPDBG_BP_H */
diff --git a/phpdbg_break.c b/phpdbg_break.c
new file mode 100644 (file)
index 0000000..1423b96
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "phpdbg.h"
+#include "phpdbg_print.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_opcode.h"
+#include "phpdbg_break.h"
+#include "phpdbg_bp.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_BREAK(file) /* {{{ */
+{
+       switch (param->type) {
+               case FILE_PARAM:
+                       phpdbg_set_breakpoint_file(param->file.name, param->file.line TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(method) /* {{{ */
+{
+       switch (param->type) {
+               case METHOD_PARAM:
+                       phpdbg_set_breakpoint_method(param->method.class, param->method.name TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+       
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(address) /* {{{ */
+{
+       switch (param->type) {
+               case ADDR_PARAM:
+                       phpdbg_set_breakpoint_opline(param->addr TSRMLS_CC);
+                       break;
+
+               case NUMERIC_METHOD_PARAM:
+                       phpdbg_set_breakpoint_method_opline(param->method.class, param->method.name, param->num TSRMLS_CC);
+                       break;
+
+               case NUMERIC_FUNCTION_PARAM:
+                       phpdbg_set_breakpoint_function_opline(param->str, param->num TSRMLS_CC);
+                       break;                  
+
+               case FILE_PARAM:
+                       phpdbg_set_breakpoint_file_opline(param->file.name, param->file.line TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(on) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM:
+                       phpdbg_set_breakpoint_expression(param->str, param->len TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(at) /* {{{ */
+{
+       phpdbg_set_breakpoint_at(param, input TSRMLS_CC);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(lineno) /* {{{ */
+{
+       switch (param->type) {
+               case NUMERIC_PARAM: {
+                       if (PHPDBG_G(exec)) {
+                               phpdbg_set_breakpoint_file(phpdbg_current_file(TSRMLS_C), param->num TSRMLS_CC);
+                       } else {
+                               phpdbg_error("Execution context not set!");
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(func) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM:
+                       phpdbg_set_breakpoint_symbol(param->str, param->len TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(op) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM:
+                       phpdbg_set_breakpoint_opcode(param->str, param->len TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_BREAK(del) /* {{{ */
+{
+       switch (param->type) {
+               case NUMERIC_PARAM: {
+                       phpdbg_delete_breakpoint(param->num TSRMLS_CC);
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
diff --git a/phpdbg_break.h b/phpdbg_break.h
new file mode 100644 (file)
index 0000000..04abeb6
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_BREAK_H
+#define PHPDBG_BREAK_H
+
+#include "TSRM.h"
+#include "phpdbg_cmd.h"
+
+#define PHPDBG_BREAK(name) PHPDBG_COMMAND(break_##name)
+
+/**
+ * Printer Forward Declarations
+ */
+PHPDBG_BREAK(file);
+PHPDBG_BREAK(func);
+PHPDBG_BREAK(method);
+PHPDBG_BREAK(address);
+PHPDBG_BREAK(at);
+PHPDBG_BREAK(op);
+PHPDBG_BREAK(on);
+PHPDBG_BREAK(lineno);
+PHPDBG_BREAK(del);
+
+/**
+ * Commands
+ */
+static const phpdbg_command_t phpdbg_break_commands[] = {
+       PHPDBG_COMMAND_D_EX(file,        "specify breakpoint by file:line",                        'F', break_file,    NULL, 1),
+       PHPDBG_COMMAND_D_EX(func,        "specify breakpoint by global function name",             'f', break_func,    NULL, 1),
+       PHPDBG_COMMAND_D_EX(method,      "specify breakpoint by class::method",                    'm', break_method,  NULL, 1),
+       PHPDBG_COMMAND_D_EX(address,     "specify breakpoint by address",                          'a', break_address, NULL, 1),
+       PHPDBG_COMMAND_D_EX(op,          "specify breakpoint by opcode",                           'O', break_op,      NULL, 1),
+       PHPDBG_COMMAND_D_EX(on,          "specify breakpoint by condition",                        'o', break_on,      NULL, 1),
+       PHPDBG_COMMAND_D_EX(at,          "specify breakpoint by location and condition",           'A', break_at,      NULL, 1),
+       PHPDBG_COMMAND_D_EX(lineno,      "specify breakpoint by line of currently executing file", 'l', break_lineno,  NULL, 1),
+       PHPDBG_COMMAND_D_EX(del,         "delete breakpoint by identifier number",                 'd', break_del,     NULL, 1),
+       PHPDBG_END_COMMAND
+};
+
+#endif /* PHPDBG_BREAK_H */
diff --git a/phpdbg_cmd.c b/phpdbg_cmd.c
new file mode 100644 (file)
index 0000000..9f052d6
--- /dev/null
@@ -0,0 +1,669 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "phpdbg.h"
+#include "phpdbg_cmd.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_set.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_API const char *phpdbg_get_param_type(const phpdbg_param_t *param TSRMLS_DC) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM:
+                       return "empty";
+               case ADDR_PARAM:
+                       return "address";
+               case NUMERIC_PARAM:
+                       return "numeric";
+               case METHOD_PARAM:
+                       return "method";
+               case NUMERIC_FUNCTION_PARAM:
+                       return "function opline";
+               case NUMERIC_METHOD_PARAM:
+                       return "method opline";
+               case FILE_PARAM:
+                       return "file or file opline";
+               case STR_PARAM:
+                       return "string";
+               default: /* this is bad */
+                       return "unknown";
+       }
+}
+
+PHPDBG_API phpdbg_param_type phpdbg_parse_param(const char *str, size_t len, phpdbg_param_t *param TSRMLS_DC) /* {{{ */
+{
+       char *class_name, *func_name;
+
+       if (len == 0) {
+               param->type = EMPTY_PARAM;
+               goto parsed;
+       }
+
+       if (phpdbg_is_addr(str)) {
+               param->addr = strtoul(str, 0, 16);
+               param->type = ADDR_PARAM;
+               goto parsed;
+
+       } else if (phpdbg_is_numeric(str)) {
+               param->num = strtol(str, NULL, 0);
+               param->type = NUMERIC_PARAM;
+               goto parsed;
+
+       } else if (phpdbg_is_class_method(str, len+1, &class_name, &func_name)) {
+               param->method.class = class_name;
+               param->method.name = func_name;
+               param->type = METHOD_PARAM;
+               goto parsed;
+       } else {
+               char *line_pos = strrchr(str, ':');
+
+               if (line_pos && phpdbg_is_numeric(line_pos+1)) {
+                       if (strchr(str, ':') == line_pos) {
+                               char path[MAXPATHLEN];
+
+                               memcpy(path, str, line_pos - str);
+                               path[line_pos - str] = 0;
+                               *line_pos = 0;
+                               param->file.name = phpdbg_resolve_path(path TSRMLS_CC);
+                               param->file.line = strtol(line_pos+1, NULL, 0);
+                               param->type = FILE_PARAM;
+
+                               goto parsed;
+                       }
+               }
+
+               line_pos = strrchr(str, '#');
+
+               if (line_pos && phpdbg_is_numeric(line_pos+1)) {
+                       if (strchr(str, '#') == line_pos) {
+                               param->num = strtol(line_pos + 1, NULL, 0);
+
+                               if (phpdbg_is_class_method(str, line_pos - str, &class_name, &func_name)) {
+                                       param->method.class = class_name;
+                                       param->method.name = func_name;
+                                       param->type = NUMERIC_METHOD_PARAM;
+                               } else {
+                                       param->len = line_pos - str;
+                                       param->str = estrndup(str, param->len);
+                                       param->type = NUMERIC_FUNCTION_PARAM;
+                               }
+
+                               goto parsed;
+                       }
+               }
+       }
+
+       param->str = estrndup(str, len);
+       param->len = len;
+       param->type = STR_PARAM;
+
+parsed:
+       phpdbg_debug("phpdbg_parse_param(\"%s\", %lu): %s",
+               str, len, phpdbg_get_param_type(param TSRMLS_CC));
+       return param->type;
+} /* }}} */
+
+PHPDBG_API void phpdbg_clear_param(phpdbg_param_t *param TSRMLS_DC) /* {{{ */
+{
+       if (param) {
+               switch (param->type) {
+                       case FILE_PARAM:
+                               efree(param->file.name);
+                               break;
+                       case METHOD_PARAM:
+                               efree(param->method.class);
+                               efree(param->method.name);
+                               break;
+                       case STR_PARAM:
+                               efree(param->str);
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+} /* }}} */
+
+PHPDBG_API char* phpdbg_param_tostring(const phpdbg_param_t *param, char **pointer TSRMLS_DC) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM:
+                       asprintf(pointer,
+                               "%s", param->str);
+               break;
+
+               case ADDR_PARAM:
+                       asprintf(pointer,
+                               "%#lx", param->addr);
+               break;
+
+               case NUMERIC_PARAM:
+                       asprintf(pointer,
+                               "%li",
+                               param->num);
+               break;
+
+               case METHOD_PARAM:
+                       asprintf(pointer,
+                               "%s::%s",
+                               param->method.class,
+                               param->method.name);
+               break;
+
+               case FILE_PARAM:
+                       if (param->num) {
+                               asprintf(pointer,
+                                       "%s:%lu#%lu",
+                                       param->file.name,
+                                       param->file.line,
+                                       param->num);
+                       } else {
+                               asprintf(pointer,
+                                       "%s:%lu",
+                                       param->file.name,
+                                       param->file.line);
+                       }
+               break;
+
+               case NUMERIC_FUNCTION_PARAM:
+                       asprintf(pointer,
+                               "%s#%lu", param->str, param->num);
+               break;
+
+               case NUMERIC_METHOD_PARAM:
+                       asprintf(pointer,
+                               "%s::%s#%lu",
+                               param->method.class,
+                               param->method.name,
+                               param->num);
+               break;
+
+               default:
+                       asprintf(pointer,
+                               "%s", "unknown");
+       }
+
+       return *pointer;
+} /* }}} */
+
+PHPDBG_API void phpdbg_copy_param(const phpdbg_param_t* src, phpdbg_param_t* dest TSRMLS_DC) /* {{{ */
+{
+       switch ((dest->type = src->type)) {
+               case STR_PARAM:
+                       dest->str = estrndup(src->str, src->len);
+                       dest->len = src->len;
+               break;
+
+               case ADDR_PARAM:
+                       dest->addr = src->addr;
+               break;
+
+               case NUMERIC_PARAM:
+                       dest->num = src->num;
+               break;
+
+               case METHOD_PARAM:
+                       dest->method.class = estrdup(src->method.class);
+                       dest->method.name = estrdup(src->method.name);
+               break;
+
+               case FILE_PARAM:
+                       dest->file.name = estrdup(src->file.name);
+                       dest->file.line = src->file.line;
+                       if (src->num)
+                               dest->num   = src->num;
+               break;
+
+               case NUMERIC_FUNCTION_PARAM:
+                       dest->str = estrndup(src->str, src->len);
+                       dest->num = src->num;
+                       dest->len = src->len;
+               break;
+
+               case NUMERIC_METHOD_PARAM:
+                       dest->method.class = estrdup(src->method.class);
+                       dest->method.name = estrdup(src->method.name);
+                       dest->num = src->num;
+               break;
+
+               case EMPTY_PARAM: { /* do nothing */ } break;
+       }
+} /* }}} */
+
+PHPDBG_API zend_ulong phpdbg_hash_param(const phpdbg_param_t *param TSRMLS_DC) /* {{{ */
+{
+       zend_ulong hash = param->type;
+
+       switch (param->type) {
+               case STR_PARAM:
+                       hash += zend_inline_hash_func(param->str, param->len);
+               break;
+
+               case METHOD_PARAM:
+                       hash += zend_inline_hash_func(param->method.class, strlen(param->method.class));
+                       hash += zend_inline_hash_func(param->method.name, strlen(param->method.name));
+               break;
+
+               case FILE_PARAM:
+                       hash += zend_inline_hash_func(param->file.name, strlen(param->file.name));
+                       hash += param->file.line;
+                       if (param->num)
+                               hash += param->num;
+               break;
+
+               case ADDR_PARAM:
+                       hash += param->addr;
+               break;
+
+               case NUMERIC_PARAM:
+                       hash += param->num;
+               break;
+
+               case NUMERIC_FUNCTION_PARAM:
+                       hash += zend_inline_hash_func(param->str, param->len);
+                       hash += param->num;
+               break;
+
+               case NUMERIC_METHOD_PARAM:
+                       hash += zend_inline_hash_func(param->method.class, strlen(param->method.class));
+                       hash += zend_inline_hash_func(param->method.name, strlen(param->method.name));
+                       if (param->num)
+                               hash+= param->num;
+               break;
+
+               case EMPTY_PARAM: { /* do nothing */ } break;
+       }
+
+       return hash;
+} /* }}} */
+
+PHPDBG_API zend_bool phpdbg_match_param(const phpdbg_param_t *l, const phpdbg_param_t *r TSRMLS_DC) /* {{{ */
+{
+       if (l && r) {
+               if (l->type == r->type) {
+                       switch (l->type) {
+
+                               case NUMERIC_FUNCTION_PARAM:
+                                       if (l->num != r->num) {
+                                               break;
+                                       }
+                               /* break intentionally omitted */
+
+                               case STR_PARAM:
+                                       return (l->len == r->len) &&
+                                                       (memcmp(l->str, r->str, l->len) == SUCCESS);
+
+                               case NUMERIC_PARAM:
+                                       return (l->num == r->num);
+
+                               case ADDR_PARAM:
+                                       return (l->addr == r->addr);
+
+                               case FILE_PARAM: {
+                                       if (l->file.line == r->file.line) {
+                                               size_t lengths[2] = {
+                                                       strlen(l->file.name), strlen(r->file.name)};
+
+                                               if (lengths[0] == lengths[1]) {
+                                                       if ((!l->num && !r->num) || (l->num == r->num)) {
+                                                               return (memcmp(
+                                                                       l->file.name, r->file.name, lengths[0]) == SUCCESS);
+                                                       }
+                                               }
+                                       }
+                               } break;
+
+                               case NUMERIC_METHOD_PARAM:
+                                       if (l->num != r->num) {
+                                               break;
+                                       }
+                               /* break intentionally omitted */
+
+                               case METHOD_PARAM: {
+                                       size_t lengths[2] = {
+                                               strlen(l->method.class), strlen(r->method.class)};
+                                       if (lengths[0] == lengths[1]) {
+                                               if (memcmp(l->method.class, r->method.class, lengths[0]) == SUCCESS) {
+                                                       lengths[0] = strlen(l->method.name);
+                                                       lengths[1] = strlen(r->method.name);
+
+                                                       if (lengths[0] == lengths[1]) {
+                                                               return (memcmp(
+                                                                       l->method.name, r->method.name, lengths[0]) == SUCCESS);
+                                                       }
+                                               }
+                                       }
+                               } break;
+
+                               case EMPTY_PARAM:
+                                       return 1;
+                       }
+               }
+       }
+       return 0;
+} /* }}} */
+
+PHPDBG_API phpdbg_input_t **phpdbg_read_argv(char *buffer, int *argc TSRMLS_DC) /* {{{ */
+{
+       char *p;
+       char b[PHPDBG_MAX_CMD];
+       int l=0;
+       enum states {
+               IN_BETWEEN,
+               IN_WORD,
+               IN_STRING
+       } state = IN_BETWEEN;
+       phpdbg_input_t **argv = NULL;
+
+       argv = (phpdbg_input_t**) emalloc(sizeof(phpdbg_input_t*));
+       (*argc) = 0;
+
+#define RESET_STATE() do { \
+       phpdbg_input_t *arg = emalloc(sizeof(phpdbg_input_t)); \
+       if (arg) { \
+               b[l]=0; \
+               arg->length = l; \
+               arg->string = estrndup(b, arg->length); \
+               arg->argv = NULL; \
+               arg->argc = 0; \
+               argv = (phpdbg_input_t**) erealloc(argv, sizeof(phpdbg_input_t*) * ((*argc)+1)); \
+               argv[(*argc)++] = arg; \
+               l = 0; \
+       } \
+       state = IN_BETWEEN; \
+} while (0)
+
+       for (p = buffer; *p != '\0'; p++) {
+               int c = (unsigned char) *p;
+               switch (state) {
+                       case IN_BETWEEN:
+                               if (isspace(c)) {
+                                       continue;
+                               }
+                               if (c == '"') {
+                                       state = IN_STRING;
+                                       continue;
+                               }
+                               state = IN_WORD;
+                               b[l++]=c;
+                               continue;
+
+                       case IN_STRING:
+                               if (c == '"') {
+                                       if (buffer[(p - buffer)-1] == '\\') {
+                                               b[l-1]=c;
+                                               continue;
+                                       }
+                                       RESET_STATE();
+                               } else {
+                                       b[l++]=c;
+                               }
+                               continue;
+
+                       case IN_WORD:
+                               if (isspace(c)) {
+                                       RESET_STATE();
+                               } else {
+                                       b[l++]=c;
+                               }
+                               continue;
+               }
+       }
+
+       switch (state) {
+               case IN_WORD: {
+                       RESET_STATE();
+               } break;
+
+               case IN_STRING:
+                       phpdbg_error(
+                               "Malformed command line (unclosed quote) @ %ld: %s!",
+                               (p - buffer)-1, &buffer[(p - buffer)-1]);
+               break;
+
+               case IN_BETWEEN:
+               break;
+       }
+
+       if ((*argc) == 0) {
+               /* not needed */
+               efree(argv);
+
+               /* to be sure */
+               return NULL;
+       }
+
+       return argv;
+} /* }}} */
+
+PHPDBG_API phpdbg_input_t *phpdbg_read_input(char *buffered TSRMLS_DC) /* {{{ */
+{
+       phpdbg_input_t *buffer = NULL;
+       char *cmd = NULL;
+
+       if (!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING)) {
+               if ((PHPDBG_G(flags) & PHPDBG_IS_REMOTE) &&
+                       (buffered == NULL)) {
+                       fflush(PHPDBG_G(io)[PHPDBG_STDOUT]);
+               }
+
+               if (buffered == NULL) {
+#ifndef HAVE_LIBREADLINE
+                       char buf[PHPDBG_MAX_CMD];
+                       if ((!(PHPDBG_G(flags) & PHPDBG_IS_REMOTE) && !phpdbg_write(phpdbg_get_prompt(TSRMLS_C))) ||
+                               !fgets(buf, PHPDBG_MAX_CMD, PHPDBG_G(io)[PHPDBG_STDIN])) {
+                               /* the user has gone away */
+                               phpdbg_error("Failed to read console!");
+                               PHPDBG_G(flags) |= (PHPDBG_IS_QUITTING|PHPDBG_IS_DISCONNECTED);
+                               zend_bailout();
+                               return NULL;
+                       }
+
+                       cmd = buf;
+#else
+                       if ((PHPDBG_G(flags) & PHPDBG_IS_REMOTE)) {
+                               char buf[PHPDBG_MAX_CMD];
+                               if (fgets(buf, PHPDBG_MAX_CMD, PHPDBG_G(io)[PHPDBG_STDIN])) {
+                                       cmd = buf;
+                               } else cmd = NULL;
+                       } else cmd = readline(phpdbg_get_prompt(TSRMLS_C));
+
+                       if (!cmd) {
+                               /* the user has gone away */
+                               phpdbg_error("Failed to read console!");
+                               PHPDBG_G(flags) |= (PHPDBG_IS_QUITTING|PHPDBG_IS_DISCONNECTED);
+                               zend_bailout();
+                               return NULL;
+                       }
+
+                       if (!(PHPDBG_G(flags) & PHPDBG_IS_REMOTE)) {
+                               add_history(cmd);
+                       }
+#endif
+               } else cmd = buffered;
+
+               /* allocate and sanitize buffer */
+               buffer = (phpdbg_input_t*) ecalloc(1, sizeof(phpdbg_input_t));
+               if (!buffer) {
+                       return NULL;
+               }
+
+               buffer->string = phpdbg_trim(cmd, strlen(cmd), &buffer->length);
+
+               /* store constant pointer to start of buffer */
+               buffer->start = (char* const*) buffer->string;
+
+               buffer->argv = phpdbg_read_argv(
+                       buffer->string, &buffer->argc TSRMLS_CC);
+
+#ifdef PHPDBG_DEBUG
+               if (buffer->argc) {
+                       int arg = 0;
+
+                       while (arg < buffer->argc) {
+                               phpdbg_debug(
+                                       "argv %d=%s", arg, buffer->argv[arg]->string);
+                               arg++;
+                       }
+               }
+#endif
+
+#ifdef HAVE_LIBREADLINE
+               if (!buffered && cmd &&
+                       !(PHPDBG_G(flags) & PHPDBG_IS_REMOTE)) {
+                       free(cmd);
+               }
+#endif
+
+               return buffer;
+       }
+
+       return NULL;
+} /* }}} */
+
+PHPDBG_API void phpdbg_destroy_argv(phpdbg_input_t **argv, int argc TSRMLS_DC) /* {{{ */
+{
+       if (argv) {
+               if (argc) {
+                       int arg;
+                       for (arg=0; arg<argc; arg++) {
+                               phpdbg_destroy_input(
+                                       &argv[arg] TSRMLS_CC);
+                       }
+               }
+               efree(argv);
+       }
+
+} /* }}} */
+
+PHPDBG_API void phpdbg_destroy_input(phpdbg_input_t **input TSRMLS_DC) /*{{{ */
+{
+       if (*input) {
+               if ((*input)->string) {
+                       efree((*input)->string);
+               }
+
+               phpdbg_destroy_argv(
+                       (*input)->argv, (*input)->argc TSRMLS_CC);
+
+               efree(*input);
+       }
+} /* }}} */
+
+PHPDBG_API int phpdbg_do_cmd(const phpdbg_command_t *command, phpdbg_input_t *input TSRMLS_DC) /* {{{ */
+{
+       int rc = FAILURE;
+
+       if (input->argc > 0) {
+               while (command && command->name && command->handler) {
+                       if (((command->name_len == input->argv[0]->length) &&
+                               (memcmp(command->name, input->argv[0]->string, command->name_len) == SUCCESS)) ||
+                               (command->alias &&
+                               (input->argv[0]->length == 1) &&
+                               (command->alias == *input->argv[0]->string))) {
+
+                               phpdbg_param_t param;
+
+                               param.type = EMPTY_PARAM;
+
+                               if (input->argc > 1) {
+                                       if (command->subs) {
+                                               phpdbg_input_t sub = *input;
+
+                                               sub.string += input->argv[0]->length;
+                                               sub.length -= input->argv[0]->length;
+
+                                               sub.string = phpdbg_trim(
+                                                       sub.string, sub.length, &sub.length);
+
+                                               sub.argc--;
+                                               sub.argv++;
+
+                                               phpdbg_debug(
+                                                       "trying sub commands in \"%s\" for \"%s\" with %d arguments",
+                                                       command->name, sub.argv[0]->string, sub.argc-1);
+
+                                               if (phpdbg_do_cmd(command->subs, &sub TSRMLS_CC) == SUCCESS) {
+                                                       efree(sub.string);
+                                                       return SUCCESS;
+                                               }
+
+                                               efree(sub.string);
+                                       }
+
+                                       /* no sub command found */
+                                       {
+                                               char *store = input->string;
+
+                                               input->string += input->argv[0]->length;
+                                               input->length -= input->argv[0]->length;
+
+                                               input->string = phpdbg_trim(
+                                                       input->string, input->length, &input->length);
+
+                                               efree(store);
+                                       }
+
+                                       /* pass parameter on */
+                                       phpdbg_parse_param(
+                                               input->string,
+                                               input->length,
+                                               &param TSRMLS_CC);
+                               }
+
+                               phpdbg_debug(
+                                       "found command %s for %s with %d arguments",
+                                       command->name, input->argv[0]->string, input->argc-1);
+                               {
+                                       int arg;
+                                       for (arg=1; arg<input->argc; arg++) {
+                                               phpdbg_debug(
+                                                       "\t#%d: [%s=%zu]",
+                                                       arg,
+                                                       input->argv[arg]->string,
+                                                       input->argv[arg]->length);
+                                       }
+                               }
+
+                               rc = command->handler(&param, input TSRMLS_CC);
+
+                               /* only set last command when it is worth it! */
+                               if ((rc != FAILURE) &&
+                                       !(PHPDBG_G(flags) & PHPDBG_IS_INITIALIZING)) {
+                                       PHPDBG_G(lcmd) = (phpdbg_command_t*) command;
+                                       phpdbg_clear_param(
+                                               &PHPDBG_G(lparam) TSRMLS_CC);
+                                       PHPDBG_G(lparam) = param;
+                               }
+                               break;
+                       }
+                       command++;
+               }
+       } else {
+               /* this should NEVER happen */
+               phpdbg_error(
+                       "No function executed!!");
+       }
+
+       return rc;
+} /* }}} */
+
diff --git a/phpdbg_cmd.h b/phpdbg_cmd.h
new file mode 100644 (file)
index 0000000..e779fd4
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_CMD_H
+#define PHPDBG_CMD_H
+
+#include "TSRM.h"
+
+typedef struct _phpdbg_command_t phpdbg_command_t;
+
+/* {{{ Command and Parameter */
+enum {
+       NO_ARG = 0,
+       REQUIRED_ARG,
+       OPTIONAL_ARG
+};
+
+typedef enum {
+       EMPTY_PARAM = 0,
+       ADDR_PARAM,
+       FILE_PARAM,
+       METHOD_PARAM,
+       STR_PARAM,
+       NUMERIC_PARAM,
+       NUMERIC_FUNCTION_PARAM,
+       NUMERIC_METHOD_PARAM
+} phpdbg_param_type;
+
+typedef struct _phpdbg_input_t phpdbg_input_t;
+
+struct _phpdbg_input_t {
+       char * const *start;
+       char *string;
+       size_t length;
+       phpdbg_input_t **argv;
+       int argc;
+};
+
+typedef struct _phpdbg_param {
+       phpdbg_param_type type;
+       long num;
+       zend_ulong addr;
+       struct {
+               char *name;
+               long line;
+       } file;
+       struct {
+               char *class;
+               char *name;
+       } method;
+       char *str;
+       size_t len;
+} phpdbg_param_t;
+
+typedef int (*phpdbg_command_handler_t)(const phpdbg_param_t*, const phpdbg_input_t* TSRMLS_DC);
+
+struct _phpdbg_command_t {
+       const char *name;                   /* Command name */
+       size_t name_len;                    /* Command name length */
+       const char *tip;                    /* Menu tip */
+       size_t tip_len;                     /* Menu tip length */
+       char alias;                         /* Alias */
+       phpdbg_command_handler_t handler;   /* Command handler */
+       const phpdbg_command_t *subs;       /* Sub Commands */
+       char arg_type;                      /* Accept args? */
+};
+/* }}} */
+
+/* {{{ misc */
+#define PHPDBG_STRL(s) s, sizeof(s)-1
+#define PHPDBG_MAX_CMD 500
+#define PHPDBG_FRAME(v) (PHPDBG_G(frame).v)
+#define PHPDBG_EX(v) (EG(current_execute_data)->v) 
+
+typedef struct {
+       int num;
+       zend_execute_data *execute_data;
+} phpdbg_frame_t;
+/* }}} */
+
+
+
+/*
+* Workflow:
+* 1) read input
+*      input takes the line from console, creates argc/argv
+* 2) parse parameters into suitable types based on arg_type
+*      takes input from 1) and arg_type and creates parameters
+* 3) do command
+*      executes commands
+* 4) destroy parameters
+*      cleans up what was allocated by creation of parameters
+* 5) destroy input
+*      cleans up what was allocated by creation of input
+*/
+
+/*
+* Input Management
+*/
+PHPDBG_API phpdbg_input_t* phpdbg_read_input(char *buffered TSRMLS_DC);
+PHPDBG_API void phpdbg_destroy_input(phpdbg_input_t** TSRMLS_DC);
+
+/*
+* Argument Management
+*/
+PHPDBG_API phpdbg_input_t** phpdbg_read_argv(char *buffer, int *argc TSRMLS_DC);
+PHPDBG_API void phpdbg_destroy_argv(phpdbg_input_t **argv, int argc TSRMLS_DC);
+#define phpdbg_argv_is(n, s) \
+       (memcmp(input->argv[n]->string, s, input->argv[n]->length) == SUCCESS)
+
+/*
+* Parameter Management
+*/
+PHPDBG_API phpdbg_param_type phpdbg_parse_param(const char*, size_t, phpdbg_param_t* TSRMLS_DC);
+PHPDBG_API void phpdbg_clear_param(phpdbg_param_t* TSRMLS_DC);
+PHPDBG_API void phpdbg_copy_param(const phpdbg_param_t*, phpdbg_param_t* TSRMLS_DC);
+PHPDBG_API zend_bool phpdbg_match_param(const phpdbg_param_t *, const phpdbg_param_t * TSRMLS_DC);
+PHPDBG_API zend_ulong phpdbg_hash_param(const phpdbg_param_t * TSRMLS_DC);
+PHPDBG_API const char* phpdbg_get_param_type(const phpdbg_param_t* TSRMLS_DC);
+PHPDBG_API char* phpdbg_param_tostring(const phpdbg_param_t *param, char **pointer TSRMLS_DC);
+
+/*
+* Command Executor
+*/
+PHPDBG_API int phpdbg_do_cmd(const phpdbg_command_t*, phpdbg_input_t* TSRMLS_DC);
+
+/**
+ * Command Declarators
+ */
+#define PHPDBG_COMMAND_HANDLER(name) phpdbg_do_##name
+
+#define PHPDBG_COMMAND_D_EX(name, tip, alias, handler, children, has_args) \
+       {PHPDBG_STRL(#name), tip, sizeof(tip)-1, alias, phpdbg_do_##handler, children, has_args}
+
+#define PHPDBG_COMMAND_D(name, tip, alias, children, has_args) \
+       {PHPDBG_STRL(#name), tip, sizeof(tip)-1, alias, phpdbg_do_##name, children, has_args}
+
+#define PHPDBG_COMMAND(name) int phpdbg_do_##name(const phpdbg_param_t *param, const phpdbg_input_t *input TSRMLS_DC)
+
+#define PHPDBG_COMMAND_ARGS param, input TSRMLS_CC
+
+#define PHPDBG_END_COMMAND {NULL, 0, NULL, 0, '\0', NULL, NULL, '\0'}
+
+/*
+* Default Switch Case
+*/
+#define phpdbg_default_switch_case() \
+       default: \
+               phpdbg_error("Unsupported parameter type (%s) for command", phpdbg_get_param_type(param TSRMLS_CC)); \
+       break
+
+#endif /* PHPDBG_CMD_H */
diff --git a/phpdbg_frame.c b/phpdbg_frame.c
new file mode 100644 (file)
index 0000000..24aff59
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "zend.h"
+#include "phpdbg.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_frame.h"
+#include "phpdbg_list.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+void phpdbg_restore_frame(TSRMLS_D) /* {{{ */
+{
+       if (PHPDBG_FRAME(num) == 0) {
+               return;
+       }
+
+       PHPDBG_FRAME(num) = 0;
+
+       /* move things back */
+       EG(current_execute_data) = PHPDBG_FRAME(execute_data);
+
+       EG(opline_ptr) = &PHPDBG_EX(opline);
+       EG(active_op_array) = PHPDBG_EX(op_array);
+       EG(return_value_ptr_ptr) = PHPDBG_EX(original_return_value);
+       EG(active_symbol_table) = PHPDBG_EX(symbol_table);
+       EG(This) = PHPDBG_EX(current_this);
+       EG(scope) = PHPDBG_EX(current_scope);
+       EG(called_scope) = PHPDBG_EX(current_called_scope);
+} /* }}} */
+
+void phpdbg_switch_frame(int frame TSRMLS_DC) /* {{{ */
+{
+       zend_execute_data *execute_data = PHPDBG_FRAME(num)?PHPDBG_FRAME(execute_data):EG(current_execute_data);
+       int i = 0;
+
+       if (PHPDBG_FRAME(num) == frame) {
+               phpdbg_notice("Already in frame #%d", frame);
+               return;
+       }
+
+       while (execute_data) {
+               if (i++ == frame) {
+                       break;
+               }
+
+               do {
+                       execute_data = execute_data->prev_execute_data;
+               } while (execute_data && execute_data->opline == NULL);
+       }
+
+       if (execute_data == NULL) {
+               phpdbg_error("No frame #%d", frame);
+               return;
+       }
+
+       phpdbg_restore_frame(TSRMLS_C);
+
+       if (frame > 0) {
+               PHPDBG_FRAME(num) = frame;
+
+               /* backup things and jump back */
+               PHPDBG_FRAME(execute_data) = EG(current_execute_data);
+               EG(current_execute_data) = execute_data;
+
+               EG(opline_ptr) = &PHPDBG_EX(opline);
+               EG(active_op_array) = PHPDBG_EX(op_array);
+               PHPDBG_FRAME(execute_data)->original_return_value = EG(return_value_ptr_ptr);
+               EG(return_value_ptr_ptr) = PHPDBG_EX(original_return_value);
+               EG(active_symbol_table) = PHPDBG_EX(symbol_table);
+               EG(This) = PHPDBG_EX(current_this);
+               EG(scope) = PHPDBG_EX(current_scope);
+               EG(called_scope) = PHPDBG_EX(current_called_scope);
+       }
+
+       phpdbg_notice("Switched to frame #%d", frame);
+       phpdbg_list_file(
+               zend_get_executed_filename(TSRMLS_C),
+               3,
+               zend_get_executed_lineno(TSRMLS_C)-1,
+               zend_get_executed_lineno(TSRMLS_C)
+               TSRMLS_CC
+       );
+} /* }}} */
+
+static void phpdbg_dump_prototype(zval **tmp TSRMLS_DC) /* {{{ */
+{
+       zval **funcname, **class, **type, **args, **argstmp;
+       char is_class;
+
+       zend_hash_find(Z_ARRVAL_PP(tmp), "function", sizeof("function"),
+               (void **)&funcname);
+
+       if ((is_class = zend_hash_find(Z_ARRVAL_PP(tmp),
+               "object", sizeof("object"), (void **)&class)) == FAILURE) {
+               is_class = zend_hash_find(Z_ARRVAL_PP(tmp), "class", sizeof("class"),
+                       (void **)&class);
+       } else {
+               zend_get_object_classname(*class, (const char **)&Z_STRVAL_PP(class),
+                       (zend_uint *)&Z_STRLEN_PP(class) TSRMLS_CC);
+       }
+
+       if (is_class == SUCCESS) {
+               zend_hash_find(Z_ARRVAL_PP(tmp), "type", sizeof("type"), (void **)&type);
+       }
+
+       phpdbg_write("%s%s%s(",
+               is_class == FAILURE?"":Z_STRVAL_PP(class),
+               is_class == FAILURE?"":Z_STRVAL_PP(type),
+               Z_STRVAL_PP(funcname)
+       );
+
+       if (zend_hash_find(Z_ARRVAL_PP(tmp), "args", sizeof("args"),
+               (void **)&args) == SUCCESS) {
+               HashPosition iterator;
+               const zend_function *func = phpdbg_get_function(
+                       Z_STRVAL_PP(funcname), is_class == FAILURE ? NULL : Z_STRVAL_PP(class) TSRMLS_CC);
+               const zend_arg_info *arginfo = func ? func->common.arg_info : NULL;
+               int j = 0, m = func ? func->common.num_args : 0;
+               zend_bool is_variadic = 0;
+
+               zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(args), &iterator);
+               while (zend_hash_get_current_data_ex(Z_ARRVAL_PP(args),
+                       (void **) &argstmp, &iterator) == SUCCESS) {
+                       if (j) {
+                               phpdbg_write(", ");
+                       }
+                       if (m && j < m) {
+#if PHP_VERSION_ID >= 50600
+                               is_variadic = arginfo[j].is_variadic;
+#endif
+                               phpdbg_write("%s=%s",
+                                       arginfo[j].name, is_variadic ? "[": "");
+                       }
+                       ++j;
+
+                       zend_print_flat_zval_r(*argstmp TSRMLS_CC);
+                       zend_hash_move_forward_ex(Z_ARRVAL_PP(args), &iterator);
+               }
+               if (is_variadic) {
+                       phpdbg_write("]");
+               }
+       }
+       phpdbg_write(")");
+}
+
+void phpdbg_dump_backtrace(size_t num TSRMLS_DC) /* {{{ */
+{
+       zval zbacktrace;
+       zval **tmp;
+       zval **file, **line;
+       HashPosition position;
+       int i = 1, limit = num;
+       int user_defined;
+
+       if (limit < 0) {
+               phpdbg_error("Invalid backtrace size %d", limit);
+       }
+
+       zend_fetch_debug_backtrace(
+               &zbacktrace, 0, 0, limit TSRMLS_CC);
+
+       zend_hash_internal_pointer_reset_ex(Z_ARRVAL(zbacktrace), &position);
+       zend_hash_get_current_data_ex(Z_ARRVAL(zbacktrace), (void**)&tmp, &position);
+       while (1) {
+               user_defined = zend_hash_find(Z_ARRVAL_PP(tmp), "file", sizeof("file"), (void **)&file);
+               zend_hash_find(Z_ARRVAL_PP(tmp), "line", sizeof("line"), (void **)&line);
+               zend_hash_move_forward_ex(Z_ARRVAL(zbacktrace), &position);
+
+               if (zend_hash_get_current_data_ex(Z_ARRVAL(zbacktrace),
+                       (void**)&tmp, &position) == FAILURE) {
+                       phpdbg_write("frame #0: {main} at %s:%ld", Z_STRVAL_PP(file), Z_LVAL_PP(line));
+                       break;
+               }
+
+               if (user_defined == SUCCESS) {
+                       phpdbg_write("frame #%d: ", i++);
+                       phpdbg_dump_prototype(tmp TSRMLS_CC);
+                       phpdbg_writeln(" at %s:%ld", Z_STRVAL_PP(file), Z_LVAL_PP(line));
+               } else {
+                       phpdbg_write(" => ");
+                       phpdbg_dump_prototype(tmp TSRMLS_CC);
+                       phpdbg_writeln(" (internal function)");
+               }
+       }
+
+       phpdbg_writeln(EMPTY);
+       zval_dtor(&zbacktrace);
+} /* }}} */
diff --git a/phpdbg_frame.h b/phpdbg_frame.h
new file mode 100644 (file)
index 0000000..fbccd54
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_FRAME_H
+#define PHPDBG_FRAME_H
+
+#include "TSRM.h"
+
+void phpdbg_restore_frame(TSRMLS_D);
+void phpdbg_switch_frame(int TSRMLS_DC);
+void phpdbg_dump_backtrace(size_t TSRMLS_DC);
+
+#endif /* PHPDBG_FRAME_H */
diff --git a/phpdbg_help.c b/phpdbg_help.c
new file mode 100644 (file)
index 0000000..edb1265
--- /dev/null
@@ -0,0 +1,603 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "phpdbg.h"
+#include "phpdbg_help.h"
+#include "phpdbg_print.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_break.h"
+#include "phpdbg_list.h"
+#include "phpdbg_info.h"
+#include "phpdbg_set.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_HELP(exec) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("\tWill attempt execution, if compilation has not yet taken place, it occurs now");
+       phpdbg_writeln("The execution context must be set before execution can take place");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(step) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("You can enable and disable stepping at any phpdbg prompt during execution");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sstepping 1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%ss 1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill enable stepping");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("While stepping is enabled you are presented with a prompt after the execution of each opcode");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(next) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_write("Step back into the vm and execute the next opcode");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%snext", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sn", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill cause control to be passed back to the vm, continuing execution");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: is only useful while executing");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(until) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Step back into the vm, skipping breakpoints until the next source line");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%suntil", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%su", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill cause control to be passed back to the vm, continuing execution");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: is only useful while executing");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(finish) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Step back into the vm, skipping breakpoints until past the end of the current stack");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sfinish", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sF", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill cause control to be passed back to the vm, continuing execution");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: this allows all breakpoints that would otherwise break execution in the current scope to be skipped");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(leave) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Step back into the vm, skipping breakpoints until the current stack is returning");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sleave", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sL", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill cause a break when instructed to leave the current context");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: this allows inspection of the return value before it is returned");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(compile) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Pre-compilation of the execution context provides the opportunity to inspect opcodes before execution");
+       phpdbg_writeln("The execution context must be set for compilation to succeed");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%scompile", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sc", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill compile the current execution context, populating class/function/constant/etc tables");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: It is a good idea to clean the environment between each compilation");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(print) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("By default, print will show information about the current execution context");
+       phpdbg_writeln("Other printing commands give access to instruction information");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sprint class \\my\\class", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp c \\my\\class", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instructions for the methods in \\my\\class");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sprint method \\my\\class::method", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp m \\my\\class::method", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instructions for \\my\\class::method");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sprint func .getSomething", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp f .getSomething", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instructions for ::getSomething in the active scope");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sprint func my_function", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp f my_function", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instructions for the global function my_function");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sprint opline", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp o", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instruction for the current opline");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sprint exec", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp e", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instructions for the execution context");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sprint stack", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sp s", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the instructions for the current stack");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Specific printers loaded are show below:");
+       phpdbg_notice("Commands");
+       {
+               const phpdbg_command_t *print_command = phpdbg_print_commands;
+
+               phpdbg_writeln("\tAlias\tCommand\t\tPurpose");
+               while (print_command && print_command->name) {
+                       if (print_command->alias) {
+                               phpdbg_writeln("\t[%c]\t%s\t\t%s", print_command->alias, print_command->name, print_command->tip);
+                       } else {
+                               phpdbg_writeln("\t[ ]\t%s\t\t%s", print_command->name, print_command->tip);
+                       }
+                       ++print_command;
+               }
+       }
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(run) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Execute the current context inside the phpdbg vm");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%srun", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sr", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill cause execution of the context, if it is set.");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: The execution context must be set, but not necessarily compiled before execution occurs");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(eval) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Access to eval() allows you to change the environment during execution, careful though!!");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%seval $variable", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sE $variable", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print_r($variable) on the console, if it is defined");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%seval $variable = \"Hello phpdbg :)\"", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sE $variable = \"Hello phpdbg :)\"", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill set $variable in the current scope");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: eval() will always show the result; do not prefix the code with \"return\"");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(break) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Setting a breakpoint stops execution at a specific stage");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sbreak [file] test.php:1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [F] test.php:1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break execution on line 1 of test.php");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak [func] my_function", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [f] my_function", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break execution on entry to my_function");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak [method] \\my\\class::method", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [m] \\my\\class::method", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break execution on entry to \\my\\class::method");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak [address] 0x7ff68f570e08", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [a] 0x7ff68f570e08", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at the opline with the address provided");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak [address] my_function#1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [a] my_function#1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at the opline number 1 of the function my_function");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak [address] \\my\\class::method#2", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [a] \\my\\class::method#2", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at the opline number 2 of the method \\my\\class::method");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak address test.php:3", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb a test.php:3", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at the opline number 3 of test.php");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak [lineno] 200", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb [l] 200", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at line 200 of the currently executing file");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak on ($expression == true)", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb on ($expression == true)", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break when the condition evaluates to true");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak at phpdbg::isGreat if ($expression == true)", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at every opcode in phpdbg::isGreat when the condition evaluates to true");
+       phpdbg_writeln("\t%sbreak at test.php:20 if ($expression == true)", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break at every opcode on line 20 of test.php when the condition evaluates to true");
+       phpdbg_write("\t");
+       phpdbg_notice("The location can be anything accepted by file, func, method, or address break commands");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak op ZEND_ADD", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb O ZEND_ADD", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill break on every occurence of the opcode provided");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%sbreak del 1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sb d 1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill remove the breakpoint with the given identifier");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: An address is only valid for the current compilation");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("The parameters enclosed by [] are usually optional, but help avoid ambigious commands");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Specific breakers loaded are show below:");
+       phpdbg_notice("Commands");
+       {
+               const phpdbg_command_t *break_command = phpdbg_break_commands;
+
+               phpdbg_writeln("\tAlias\tCommand\t\tPurpose");
+               while (break_command && break_command->name) {
+                       if (break_command->alias) {
+                               phpdbg_writeln("\t[%c]\t%s\t\t%s", break_command->alias, break_command->name, break_command->tip);
+                       } else {
+                               phpdbg_writeln("\t[ ]\t%s\t\t%s", break_command->name, break_command->tip);
+                       }
+                       ++break_command;
+               }
+       }
+       phpdbg_writeln("Note: Conditional breaks are costly, use them sparingly!");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(clean) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("While debugging you may experience errors because of attempts to redeclare classes, constants or functions");
+       phpdbg_writeln("Cleaning the environment cleans these tables, so that files can be recompiled without exiting phpdbg");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(clear) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Clearing breakpoints means you can once again run code without interruption");
+       phpdbg_writeln("Note: all breakpoints are lost; be sure debugging is complete before clearing");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(info) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("info commands provide quick access to various types of information about the PHP environment");
+       phpdbg_writeln("Specific info commands are show below:");
+       phpdbg_notice("Commands");
+       {
+               const phpdbg_command_t *info_command = phpdbg_info_commands;
+
+               phpdbg_writeln("\tAlias\tCommand\t\tPurpose");
+               while (info_command && info_command->name) {
+                       if (info_command->alias) {
+                               phpdbg_writeln("\t[%c]\t%s\t\t%s", info_command->alias, info_command->name, info_command->tip);
+                       } else {
+                               phpdbg_writeln("\t[ ]\t%s\t\t%s", info_command->name, info_command->tip);
+                       }
+                       ++info_command;
+               }
+       }
+
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(quiet) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Setting quietness on will stop the OPLINE output during execution");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%squiet 1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sQ 1", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill silence OPLINE output, while");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%squiet 0", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sQ 0", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill enable OPLINE output again");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: Quietness is disabled automatically while stepping");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(back) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("The backtrace is built with the default debug backtrace functionality");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sback 5", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%st 5", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill limit the number of frames to 5, the default is no limit");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: it is not necessary for an exception to be thrown to show a backtrace");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(frame) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("When viewing a backtrace, it is sometimes useful to jump to a frame in that trace");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sframe 2", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sf 2", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill go to frame 2, temporarily affecting scope and allowing access to the variables in that frame");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: the current frame is restored when execution continues");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(list) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("The list command displays source code for the given argument");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%slist [lines] 2", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sl [l] 2", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print next 2 lines from the current file");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%slist [func] my_function", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sl [f] my_function", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the source of the global function \"my_function\"");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%slist [func] .mine", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sl [f] .mine", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the source of the method \"mine\" from the active scope");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%slist [method] my::method", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sl [m] my::method", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the source of \"my::method\"");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%slist c myClass", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sl c myClass", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill print the source of \"myClass\"");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: before listing functions you must have a populated function table, try compile!!");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("The parameters enclosed by [] are usually optional, but help avoid ambigious commands");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Specific listers loaded are show below:");
+       phpdbg_notice("Commands");
+       {
+               const phpdbg_command_t *list_command = phpdbg_list_commands;
+
+               phpdbg_writeln("\tAlias\tCommand\t\tPurpose");
+               while (list_command && list_command->name) {
+                       if (list_command->alias) {
+                               phpdbg_writeln("\t[%c]\t%s\t\t%s", list_command->alias, list_command->name, list_command->tip);
+                       } else {
+                               phpdbg_writeln("\t[ ]\t%s\t\t%s", list_command->name, list_command->tip);
+                       }
+                       ++list_command;
+               }
+       }
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(oplog) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Even when quietness is enabled you may wish to save opline logs to a file");
+       phpdbg_writeln("Setting a new oplog closes the previously open log");
+       phpdbg_writeln("The log includes a high resolution timestamp on each entry");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%soplog /path/to/my.oplog", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sO /path/to/my.oplog", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill open the file /path/to/my.oplog for writing, creating it if it does not exist");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("\t%soplog 0", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sO 0", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill close the currently open log file, disabling oplog");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: upon failure to open a new oplog, the last oplog is held open");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(set) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Configure how phpdbg looks and behaves with the set command");
+       phpdbg_writeln("Specific set commands are show below:");
+       phpdbg_notice("Commands");
+       {
+               const phpdbg_command_t *set_command = phpdbg_set_commands;
+
+               phpdbg_writeln("\tAlias\tCommand\t\tPurpose");
+               while (set_command && set_command->name) {
+                       if (set_command->alias) {
+                               phpdbg_writeln("\t[%c]\t%s\t\t%s", set_command->alias, set_command->name, set_command->tip);
+                       } else {
+                               phpdbg_writeln("\t[ ]\t%s\t\t%s", set_command->name, set_command->tip);
+                       }
+                       ++set_command;
+               }
+       }
+#ifndef _WIN32
+       phpdbg_notice("Colors");
+       {
+               const phpdbg_color_t *color = phpdbg_get_colors(TSRMLS_C);
+               
+               if (PHPDBG_G(flags) & PHPDBG_IS_COLOURED) {
+                       phpdbg_writeln("\t%-20s\t\tExample", "Name");
+               } else {
+                       phpdbg_writeln("\tName");
+               }
+               
+               while (color && color->name) {
+                       if (PHPDBG_G(flags) & PHPDBG_IS_COLOURED) {
+                               phpdbg_writeln(
+                                       "\t%-20s\t\t\033[%smphpdbg rocks :)\033[0m", color->name, color->code);
+                       } else {
+                               phpdbg_writeln("\t%s", color->name);
+                       }
+                       ++color;
+               }
+       }
+       phpdbg_writeln("The <element> for set color can be \"prompt\", \"notice\", or \"error\"");
+#endif
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(register) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Register any global function for use as a command in phpdbg console");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sregister scandir", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%sR scandir", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill register the scandir function for use in phpdbg");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: arguments passed as strings, return (if present) print_r'd on console");
+       if (zend_hash_num_elements(&PHPDBG_G(registered))) {
+               HashPosition position;
+               char *name = NULL;
+               zend_uint name_len = 0;
+
+               phpdbg_notice("Registered Functions (%d)", zend_hash_num_elements(&PHPDBG_G(registered)));
+               for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(registered), &position);
+                       zend_hash_get_current_key_ex(&PHPDBG_G(registered), &name, &name_len, NULL, 1, &position) == HASH_KEY_IS_STRING;
+                       zend_hash_move_forward_ex(&PHPDBG_G(registered), &position)) {
+                       phpdbg_writeln("|-------> %s", name);
+                       efree(name);
+               }
+       }
+
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(source) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Sourcing a phpdbginit during your debugging session might save some time");
+       phpdbg_writeln("The source command can also be used to export breakpoints to a phpdbginit file");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%ssource /my/init", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%s. /my/init", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill execute the phpdbginit file at /my/init");
+       phpdbg_writeln("\t%ssource export /my/init", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%s. export /my/init", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill export breakpoints to /my/init in phpdbginit file format");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(shell) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Direct access to shell commands saves having to switch windows/consoles");
+       phpdbg_writeln(EMPTY);
+       phpdbg_notice("Examples");
+       phpdbg_writeln("\t%sshell ls /usr/src/php-src", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\t%s- ls /usr/src/php-src", phpdbg_get_prompt(TSRMLS_C));
+       phpdbg_writeln("\tWill execute ls /usr/src/php-src, displaying the output in the console");
+       phpdbg_writeln(EMPTY);
+       phpdbg_writeln("Note: read only commands please!");
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_HELP(options) /* {{{ */
+{
+       phpdbg_help_header();
+       phpdbg_writeln("Below are the command line options supported by phpdbg");
+       phpdbg_notice("Command Line Options and Flags");
+       phpdbg_writeln(" -c\t-c/my/php.ini\t\tSet php.ini file to load");
+       phpdbg_writeln(" -d\t-dmemory_limit=4G\tSet a php.ini directive");
+       phpdbg_writeln(" -n\tN/A\t\t\tDisable default php.ini");
+       phpdbg_writeln(" -q\tN/A\t\t\tSupress welcome banner");
+       phpdbg_writeln(" -e\t-emytest.php\t\tSet execution context");
+       phpdbg_writeln(" -v\tN/A\t\t\tEnable oplog output");
+       phpdbg_writeln(" -s\tN/A\t\t\tEnable stepping");
+       phpdbg_writeln(" -b\tN/A\t\t\tDisable colour");
+       phpdbg_writeln(" -i\t-imy.init\t\tSet .phpdbginit file");
+       phpdbg_writeln(" -I\tN/A\t\t\tIgnore default .phpdbginit");
+       phpdbg_writeln(" -O\t-Omy.oplog\t\tSets oplog output file");
+       phpdbg_writeln(" -r\tN/A\t\t\tRun execution context");
+       phpdbg_writeln(" -E\tN/A\t\t\tEnable step through eval, careful!");
+       phpdbg_writeln(" -S\t-Scli\t\t\tOverride SAPI name, careful!");
+#ifndef _WIN32
+       phpdbg_writeln(" -l\t-l4000\t\t\tSetup remote console ports");
+       phpdbg_writeln(" -a\t-a192.168.0.3\t\tSetup remote console bind address");
+#endif
+       phpdbg_writeln(" -V\tN/A\t\t\tVersion number");
+       phpdbg_notice("Passing -rr will quit automatically after execution");
+#ifndef _WIN32
+       phpdbg_writeln("Remote Console Mode");
+       phpdbg_notice("For security, phpdbg will bind only to the loopback interface by default");
+       phpdbg_writeln("-a without an argument implies all; phpdbg will bind to all available interfaces.");
+       phpdbg_writeln("specify both stdin and stdout with -lstdin/stdout; by default stdout is stdin * 2.");
+       phpdbg_notice("Steps should be taken to secure this service if bound to a public interface/port");
+#endif
+       phpdbg_help_footer();
+       return SUCCESS;
+} /* }}} */
diff --git a/phpdbg_help.h b/phpdbg_help.h
new file mode 100644 (file)
index 0000000..012a1b4
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_HELP_H
+#define PHPDBG_HELP_H
+
+#include "TSRM.h"
+#include "phpdbg.h"
+#include "phpdbg_cmd.h"
+
+#define PHPDBG_HELP(name) PHPDBG_COMMAND(help_##name)
+
+/**
+ * Helper Forward Declarations
+ */
+PHPDBG_HELP(exec);
+PHPDBG_HELP(compile);
+PHPDBG_HELP(step);
+PHPDBG_HELP(next);
+PHPDBG_HELP(run);
+PHPDBG_HELP(eval);
+PHPDBG_HELP(until);
+PHPDBG_HELP(finish);
+PHPDBG_HELP(leave);
+PHPDBG_HELP(print);
+PHPDBG_HELP(break);
+PHPDBG_HELP(clean);
+PHPDBG_HELP(clear);
+PHPDBG_HELP(info);
+PHPDBG_HELP(back);
+PHPDBG_HELP(frame);
+PHPDBG_HELP(quiet);
+PHPDBG_HELP(list);
+PHPDBG_HELP(set);
+PHPDBG_HELP(register);
+PHPDBG_HELP(options);
+PHPDBG_HELP(source);
+PHPDBG_HELP(shell);
+
+/**
+ * Commands
+ */
+static const phpdbg_command_t phpdbg_help_commands[] = {
+       PHPDBG_COMMAND_D_EX(exec,     "the execution context should be a valid path",                                    'e', help_exec,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(compile,  "allow inspection of code before execution",                                       'c', help_compile, NULL, 0),
+       PHPDBG_COMMAND_D_EX(step,     "step through execution to break at every opcode",                                 's', help_step,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(next,     "continue executing while stepping or after breaking",                             'n', help_next,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(run,      "execute inside the phpdbg vm",                                                    'r', help_run,     NULL, 0),
+       PHPDBG_COMMAND_D_EX(eval,     "access to eval() allows affecting the environment",                               'E', help_eval,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(until,    "continue until the current line is executed",                                     'u', help_until,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(finish,   "continue until the current function has returned",                                'F', help_finish,  NULL, 0),
+       PHPDBG_COMMAND_D_EX(leave,    "continue until the current function is returning",                                'L', help_leave,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(print,    "print context information or instructions",                                       'p', help_print,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(break,    "breakpoints allow execution interruption",                                        'b', help_break,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(clean,    "resetting the environment is useful while debugging",                             'X', help_clean,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(clear,    "reset breakpoints to execute without interruption",                               'c', help_clear,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(info,     "quick access to useful information on the console",                               'i', help_info,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(back,     "show debug backtrace information during execution",                               't', help_back,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(frame,    "switch to a frame in the current stack for inspection",                           'f', help_frame,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(quiet,    "be quiet during execution",                                                       'Q', help_quiet,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(list,     "list code gives you quick access to code",                                        'l', help_list,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(set,      "configure how phpdbg looks and behaves",                                          'S', help_set,     NULL, 0),
+       PHPDBG_COMMAND_D_EX(register, "register a function for use as a command",                                        'R', help_register,NULL, 0),
+       PHPDBG_COMMAND_D_EX(options,  "show information about command line options",                                     'o', help_options, NULL, 0),
+       PHPDBG_COMMAND_D_EX(source,   "load a phpdbginit file at the console",                                           '.', help_source,  NULL, 0),
+       PHPDBG_COMMAND_D_EX(shell,    "execute system commands with direct shell access",                                '-', help_shell,   NULL, 0),
+       PHPDBG_END_COMMAND
+};
+
+#define phpdbg_help_header() \
+       phpdbg_notice("Welcome to phpdbg, the interactive PHP debugger, v%s", PHPDBG_VERSION);
+#define phpdbg_help_footer() \
+       phpdbg_notice("Please report bugs to <%s>", PHPDBG_ISSUES);
+
+#endif /* PHPDBG_HELP_H */
diff --git a/phpdbg_info.c b/phpdbg_info.c
new file mode 100644 (file)
index 0000000..1744f59
--- /dev/null
@@ -0,0 +1,355 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "phpdbg.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_info.h"
+#include "phpdbg_bp.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_INFO(break) /* {{{ */
+{
+       phpdbg_print_breakpoints(PHPDBG_BREAK_FILE TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_SYM TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_METHOD TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_OPLINE TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_FILE_OPLINE TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_FUNCTION_OPLINE TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_METHOD_OPLINE TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_COND TSRMLS_CC);
+       phpdbg_print_breakpoints(PHPDBG_BREAK_OPCODE TSRMLS_CC);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_INFO(files) /* {{{ */
+{
+       HashPosition pos;
+       char *fname;
+
+       phpdbg_notice("Included files: %d",
+               zend_hash_num_elements(&EG(included_files)));
+
+       zend_hash_internal_pointer_reset_ex(&EG(included_files), &pos);
+       while (zend_hash_get_current_key_ex(&EG(included_files), &fname,
+               NULL, NULL, 0, &pos) == HASH_KEY_IS_STRING) {
+               phpdbg_writeln("File: %s", fname);
+               zend_hash_move_forward_ex(&EG(included_files), &pos);
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_INFO(error) /* {{{ */
+{
+       if (PG(last_error_message)) {
+               phpdbg_writeln("Last error: %s at %s line %d",
+                       PG(last_error_message), PG(last_error_file), PG(last_error_lineno));
+       } else {
+               phpdbg_notice("No error found!");
+       }
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_INFO(vars) /* {{{ */
+{
+       HashTable vars;
+       HashPosition pos;
+       char *var;
+       zval **data;
+
+       if (!EG(active_op_array)) {
+               phpdbg_error("No active op array!");
+               return SUCCESS;
+       }
+
+       if (!EG(active_symbol_table)) {
+               zend_rebuild_symbol_table(TSRMLS_C);
+
+               if (!EG(active_symbol_table)) {
+                       phpdbg_error("No active symbol table!");
+                       return SUCCESS;
+               }
+       }
+
+       zend_hash_init(&vars, 8, NULL, NULL, 0);
+
+       zend_hash_internal_pointer_reset_ex(EG(active_symbol_table), &pos);
+       while (zend_hash_get_current_key_ex(EG(active_symbol_table), &var,
+               NULL, NULL, 0, &pos) == HASH_KEY_IS_STRING) {
+               zend_hash_get_current_data_ex(EG(active_symbol_table), (void **)&data, &pos);
+               if (*var != '_') {
+                       zend_hash_update(
+                               &vars, var, strlen(var)+1, (void**)data, sizeof(zval*), NULL);
+               }
+               zend_hash_move_forward_ex(EG(active_symbol_table), &pos);
+       }
+
+       {
+               zend_op_array *ops = EG(active_op_array);
+               
+               if (ops->function_name) {
+                       if (ops->scope) {
+                               phpdbg_notice(
+                               "Variables in %s::%s() (%d)", ops->scope->name, ops->function_name, zend_hash_num_elements(&vars));
+                       } else {
+                               phpdbg_notice(
+                                       "Variables in %s() (%d)", ops->function_name, zend_hash_num_elements(&vars));
+                       }
+               } else {
+                       if (ops->filename) {
+                               phpdbg_notice(
+                               "Variables in %s (%d)", ops->filename, zend_hash_num_elements(&vars));
+                       } else {
+                               phpdbg_notice(
+                                       "Variables @ %p (%d)", ops, zend_hash_num_elements(&vars));
+                       }
+               }
+       }
+
+       if (zend_hash_num_elements(&vars)) {
+               phpdbg_writeln("Address\t\tRefs\tType\t\tVariable");
+               for (zend_hash_internal_pointer_reset_ex(&vars, &pos);
+                       zend_hash_get_current_data_ex(&vars, (void**) &data, &pos) == SUCCESS;
+                       zend_hash_move_forward_ex(&vars, &pos)) {
+                       char *var;
+
+                       zend_hash_get_current_key_ex(&vars, &var, NULL, NULL, 0, &pos);
+
+                       if (*data) {
+                               phpdbg_write(
+                               "%p\t%d\t",
+                                       *data,
+                                       Z_REFCOUNT_PP(data));
+
+                               switch (Z_TYPE_PP(data)) {
+                                       case IS_STRING:         phpdbg_write("(string)\t");     break;
+                                       case IS_LONG:           phpdbg_write("(integer)\t");    break;
+                                       case IS_DOUBLE:         phpdbg_write("(float)\t");              break;
+                                       case IS_RESOURCE:       phpdbg_write("(resource)\t");   break;
+                                       case IS_ARRAY:          phpdbg_write("(array)\t");              break;
+                                       case IS_OBJECT:         phpdbg_write("(object)\t");     break;
+                                       case IS_NULL:           phpdbg_write("(null)\t");               break;
+                               }
+
+                               if (Z_TYPE_PP(data) == IS_RESOURCE) {
+                                       int type;
+
+                                       phpdbg_writeln(
+                                               "%s$%s", Z_ISREF_PP(data) ? "&": "", var);
+                                       if (zend_list_find(Z_RESVAL_PP(data), &type)) {
+                                               phpdbg_write(
+                                                       "|-------(typeof)------> (%s)",
+                                                       zend_rsrc_list_get_rsrc_type(type TSRMLS_CC));
+                                       } else {
+                                               phpdbg_write(
+                                                       "|-------(typeof)------> (unknown)");
+                                       }
+                                       phpdbg_writeln(EMPTY);
+                               } else if (Z_TYPE_PP(data) == IS_OBJECT) {
+                                       phpdbg_writeln(
+                                               "%s$%s", Z_ISREF_PP(data) ? "&": "", var);
+                                       phpdbg_write(
+                                               "|-----(instanceof)----> (%s)", Z_OBJCE_PP(data)->name);
+                                       phpdbg_writeln(EMPTY);
+                               } else {
+                                       phpdbg_write(
+                                               "%s$%s", Z_ISREF_PP(data) ? "&": "", var);
+                               }
+                       } else {
+                               phpdbg_write(
+                                       "n/a\tn/a\tn/a\t$%s", var);
+                       }
+                       phpdbg_writeln(EMPTY);
+               }
+       }
+
+       zend_hash_destroy(&vars);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_INFO(literal) /* {{{ */
+{
+       if ((EG(in_execution) && EG(active_op_array)) || PHPDBG_G(ops)) {
+               zend_op_array *ops = EG(active_op_array) ? EG(active_op_array) : PHPDBG_G(ops);
+               int literal = 0, count = ops->last_literal-1;
+
+               if (ops->function_name) {
+                       if (ops->scope) {
+                               phpdbg_notice(
+                               "Literal Constants in %s::%s() (%d)", ops->scope->name, ops->function_name, count);
+                       } else {
+                               phpdbg_notice(
+                                       "Literal Constants in %s() (%d)", ops->function_name, count);
+                       }
+               } else {
+                       if (ops->filename) {
+                               phpdbg_notice(
+                               "Literal Constants in %s (%d)", ops->filename, count);
+                       } else {
+                               phpdbg_notice(
+                                       "Literal Constants @ %p (%d)", ops, count);
+                       }
+               }
+
+               while (literal < ops->last_literal) {
+                       if (Z_TYPE(ops->literals[literal].constant) != IS_NULL) {
+                               phpdbg_write("|-------- C%u -------> [", literal);
+                               zend_print_zval(
+                                       &ops->literals[literal].constant, 0);
+                               phpdbg_write("]");
+                               phpdbg_writeln(EMPTY);
+                       }
+                       literal++;
+               }
+       } else {
+               phpdbg_error("Not executing!");
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_INFO(memory) /* {{{ */
+{
+       if (is_zend_mm(TSRMLS_C)) {
+               phpdbg_notice("Memory Manager Information");
+               phpdbg_notice("Current");
+               phpdbg_writeln("|-------> Used:\t%.3f kB", 
+                       (float) (zend_memory_usage(0 TSRMLS_CC)/1024));
+               phpdbg_writeln("|-------> Real:\t%.3f kB", 
+                       (float) (zend_memory_usage(1 TSRMLS_CC)/1024));
+               phpdbg_notice("Peak");
+               phpdbg_writeln("|-------> Used:\t%.3f kB", 
+                       (float) (zend_memory_peak_usage(0 TSRMLS_CC)/1024));
+               phpdbg_writeln("|-------> Real:\t%.3f kB", 
+                       (float) (zend_memory_peak_usage(1 TSRMLS_CC)/1024));
+       } else {
+               phpdbg_error("Memory Manager Disabled!");
+       }
+       return SUCCESS;
+} /* }}} */
+
+static inline void phpdbg_print_class_name(zend_class_entry **ce TSRMLS_DC) /* {{{ */
+{
+       phpdbg_write(
+               "%s %s %s (%d)",
+               ((*ce)->type == ZEND_USER_CLASS) ?
+                       "User" : "Internal",
+               ((*ce)->ce_flags & ZEND_ACC_INTERFACE) ?
+                       "Interface" :
+                       ((*ce)->ce_flags & ZEND_ACC_ABSTRACT) ?
+                               "Abstract Class" :
+                                       "Class",
+               (*ce)->name, zend_hash_num_elements(&(*ce)->function_table));
+} /* }}} */
+
+PHPDBG_INFO(classes) /* {{{ */
+{
+       HashPosition position;
+       zend_class_entry **ce;
+       HashTable classes;
+
+       zend_hash_init(&classes, 8, NULL, NULL, 0);
+
+       for (zend_hash_internal_pointer_reset_ex(EG(class_table), &position);
+               zend_hash_get_current_data_ex(EG(class_table), (void**)&ce, &position) == SUCCESS;
+               zend_hash_move_forward_ex(EG(class_table), &position)) {
+
+               if ((*ce)->type == ZEND_USER_CLASS) {
+                       zend_hash_next_index_insert(
+                               &classes, ce, sizeof(ce), NULL);
+               }
+       }
+
+       phpdbg_notice("User Classes (%d)",
+               zend_hash_num_elements(&classes));
+
+       for (zend_hash_internal_pointer_reset_ex(&classes, &position);
+               zend_hash_get_current_data_ex(&classes, (void**)&ce, &position) == SUCCESS;
+               zend_hash_move_forward_ex(&classes, &position)) {
+
+               phpdbg_print_class_name(ce TSRMLS_CC);
+               phpdbg_writeln(EMPTY);
+
+               if ((*ce)->parent) {
+                       zend_class_entry *pce = (*ce)->parent;
+                       do {
+                               phpdbg_write("|-------- ");
+                               phpdbg_print_class_name(&pce TSRMLS_CC);
+                               phpdbg_writeln(EMPTY);
+                       } while ((pce = pce->parent));
+               }
+
+               if ((*ce)->info.user.filename) {
+                       phpdbg_writeln(
+                               "|---- in %s on line %u",
+                               (*ce)->info.user.filename,
+                               (*ce)->info.user.line_start);
+               } else {
+                       phpdbg_writeln("|---- no source code");
+               }
+               phpdbg_writeln(EMPTY);
+       }
+
+       zend_hash_destroy(&classes);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_INFO(funcs) /* {{{ */
+{
+       HashPosition position;
+       zend_function *zf, **pzf;
+       HashTable functions;
+
+       zend_hash_init(&functions, 8, NULL, NULL, 0);
+
+       for (zend_hash_internal_pointer_reset_ex(EG(function_table), &position);
+               zend_hash_get_current_data_ex(EG(function_table), (void**)&zf, &position) == SUCCESS;
+               zend_hash_move_forward_ex(EG(function_table), &position)) {
+
+               if (zf->type == ZEND_USER_FUNCTION) {
+                       zend_hash_next_index_insert(
+                               &functions, (void**) &zf, sizeof(zend_function), NULL);
+               }
+       }
+
+       phpdbg_notice("User Functions (%d)",
+               zend_hash_num_elements(&functions));
+
+       for (zend_hash_internal_pointer_reset_ex(&functions, &position);
+               zend_hash_get_current_data_ex(&functions, (void**)&pzf, &position) == SUCCESS;
+               zend_hash_move_forward_ex(&functions, &position)) {
+               zend_op_array *op_array = &((*pzf)->op_array);
+
+               phpdbg_writeln(
+                       "|-------- %s in %s on line %d",
+                       op_array->function_name ? op_array->function_name : "{main}",
+                       op_array->filename ? op_array->filename : "(no source code)",
+                       op_array->line_start);
+       }
+
+       zend_hash_destroy(&functions);
+
+       return SUCCESS;
+} /* }}} */
diff --git a/phpdbg_info.h b/phpdbg_info.h
new file mode 100644 (file)
index 0000000..5d53812
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_INFO_H
+#define PHPDBG_INFO_H
+
+#include "phpdbg_cmd.h"
+
+#define PHPDBG_INFO(name) PHPDBG_COMMAND(info_##name)
+
+PHPDBG_INFO(files);
+PHPDBG_INFO(break);
+PHPDBG_INFO(classes);
+PHPDBG_INFO(funcs);
+PHPDBG_INFO(error);
+PHPDBG_INFO(vars);
+PHPDBG_INFO(literal);
+PHPDBG_INFO(memory);
+
+static const phpdbg_command_t phpdbg_info_commands[] = {
+       PHPDBG_COMMAND_D_EX(break,    "show breakpoints",              'b', info_break,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(files,    "show included files",           'F', info_files,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(classes,  "show loaded classes",           'c', info_classes, NULL, 0),
+       PHPDBG_COMMAND_D_EX(funcs,    "show loaded classes",           'f', info_funcs,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(error,    "show last error",               'e', info_error,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(vars,     "show active variables",         'v', info_vars,    NULL, 0),
+       PHPDBG_COMMAND_D_EX(literal,  "show active literal constants", 'l', info_literal, NULL, 0),
+       PHPDBG_COMMAND_D_EX(memory,   "show memory manager stats",     'm', info_memory,  NULL, 0),
+       PHPDBG_END_COMMAND
+};
+
+#endif /* PHPDBG_INFO_H */
diff --git a/phpdbg_list.c b/phpdbg_list.c
new file mode 100644 (file)
index 0000000..b49be85
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#ifndef _WIN32
+#      include <sys/mman.h>
+#      include <unistd.h>
+#endif
+#include <fcntl.h>
+#include "phpdbg.h"
+#include "phpdbg_list.h"
+#include "phpdbg_utils.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_LIST(lines) /* {{{ */
+{
+       if (!PHPDBG_G(exec) && !zend_is_executing(TSRMLS_C)) {
+               phpdbg_error("Not executing, and execution context not set");
+               return SUCCESS;
+       }
+
+       switch (param->type) {
+               case NUMERIC_PARAM:
+               case EMPTY_PARAM:
+                       phpdbg_list_file(phpdbg_current_file(TSRMLS_C),
+                               param->type == EMPTY_PARAM ? 0 : (param->num < 0 ? 1 - param->num : param->num),
+                               (param->type != EMPTY_PARAM && param->num < 0 ? param->num : 0) + zend_get_executed_lineno(TSRMLS_C),
+                               0 TSRMLS_CC);
+                       break;
+               case FILE_PARAM:
+                       phpdbg_list_file(param->file.name, param->file.line, 0, 0 TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_LIST(func) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM:
+                       phpdbg_list_function_byname(
+                               param->str, param->len TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_LIST(method) /* {{{ */
+{
+       switch (param->type) {
+               case METHOD_PARAM: {
+                       zend_class_entry **ce;
+
+                       if (zend_lookup_class(param->method.class, strlen(param->method.class), &ce TSRMLS_CC) == SUCCESS) {
+                               zend_function *function;
+                               char *lcname = zend_str_tolower_dup(param->method.name, strlen(param->method.name));
+
+                               if (zend_hash_find(&(*ce)->function_table, lcname, strlen(lcname)+1, (void**) &function) == SUCCESS) {
+                                       phpdbg_list_function(function TSRMLS_CC);
+                               } else {
+                                       phpdbg_error("Could not find %s::%s", param->method.class, param->method.name);
+                               }
+
+                               efree(lcname);
+                       } else {
+                               phpdbg_error("Could not find the class %s", param->method.class);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_LIST(class) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM: {
+                       zend_class_entry **ce;
+
+                       if (zend_lookup_class(param->str, param->len, &ce TSRMLS_CC) == SUCCESS) {
+                               if ((*ce)->type == ZEND_USER_CLASS) {
+                                       if ((*ce)->info.user.filename) {
+                                               phpdbg_list_file(
+                                                       (*ce)->info.user.filename,
+                                                       (*ce)->info.user.line_end - (*ce)->info.user.line_start + 1,
+                                                       (*ce)->info.user.line_start, 0 TSRMLS_CC
+                                               );
+                                       } else {
+                                               phpdbg_error("The source of the requested class (%s) cannot be found", (*ce)->name);
+                                       }
+                               } else {
+                                       phpdbg_error("The class requested (%s) is not user defined", (*ce)->name);
+                               }
+                       } else {
+                               phpdbg_error("The requested class (%s) could not be found", param->str);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+void phpdbg_list_file(const char *filename, long count, long offset, int highlight TSRMLS_DC) /* {{{ */
+{
+       unsigned char *mem, *pos, *last_pos, *end_pos;
+       struct stat st;
+#ifndef _WIN32
+       int fd;
+#else
+       HANDLE fd, map;
+#endif
+       int all_content = (count == 0);
+       int line = 0, displayed = 0;
+
+       if (VCWD_STAT(filename, &st) == FAILURE) {
+               phpdbg_error("Failed to stat file %s", filename);
+               return;
+       }
+
+#ifndef _WIN32
+       if ((fd = VCWD_OPEN(filename, O_RDONLY)) == FAILURE) {
+               phpdbg_error("Failed to open file %s to list", filename);
+               return;
+       }
+
+       pos = last_pos = mem = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+       end_pos = mem + st.st_size;
+#else
+
+       fd = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ,  NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+       if (fd == INVALID_HANDLE_VALUE) {
+               phpdbg_error("Failed to open file!");
+               return;
+       }
+
+       map = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, 0, NULL);
+       if (map == NULL) {
+               phpdbg_error("Failed to map file!");
+               CloseHandle(fd);
+               return;
+       }
+
+       pos = last_pos = mem = (char*) MapViewOfFile(map, FILE_MAP_READ, 0, 0, 0);
+       if (mem == NULL) {
+               phpdbg_error("Failed to map file in memory");
+               CloseHandle(map);
+               CloseHandle(fd);
+               return;
+       }
+       end_pos = mem + st.st_size;
+#endif
+       while (1) {
+               if (pos == end_pos) {
+                       break;
+               }
+
+               pos = memchr(last_pos, '\n', end_pos - last_pos);
+
+               if (!pos) {
+                       /* No more line breaks */
+                       pos = end_pos;
+               }
+
+               ++line;
+
+               if (!offset || offset <= line) {
+                       /* Without offset, or offset reached */
+                       if (!highlight) {
+                               phpdbg_writeln("%05u: %.*s", line, (int)(pos - last_pos), last_pos);
+                       } else {
+                               if (highlight != line) {
+                                       phpdbg_writeln(" %05u: %.*s", line, (int)(pos - last_pos), last_pos);
+                               } else {
+                                       phpdbg_writeln(">%05u: %.*s", line, (int)(pos - last_pos), last_pos);
+                               }
+                       }
+                       ++displayed;
+               }
+
+               last_pos = pos + 1;
+
+               if (!all_content && displayed == count) {
+                       /* Reached max line to display */
+                       break;
+               }
+       }
+
+#ifndef _WIN32
+       munmap(mem, st.st_size);
+       close(fd);
+#else
+       UnmapViewOfFile(mem);
+       CloseHandle(map);
+       CloseHandle(fd);
+#endif
+} /* }}} */
+
+void phpdbg_list_function(const zend_function *fbc TSRMLS_DC) /* {{{ */
+{
+       const zend_op_array *ops;
+
+       if (fbc->type != ZEND_USER_FUNCTION) {
+               phpdbg_error("The function requested (%s) is not user defined", fbc->common.function_name);
+               return;
+       }
+
+       ops = (zend_op_array*)fbc;
+
+       phpdbg_list_file(ops->filename,
+               ops->line_end - ops->line_start + 1, ops->line_start, 0 TSRMLS_CC);
+} /* }}} */
+
+void phpdbg_list_function_byname(const char *str, size_t len TSRMLS_DC) /* {{{ */
+{
+       HashTable *func_table = EG(function_table);
+       zend_function* fbc;
+       char *func_name = (char*) str;
+       size_t func_name_len = len;
+
+       /* search active scope if begins with period */
+       if (func_name[0] == '.') {
+               if (EG(scope)) {
+                       func_name++;
+                       func_name_len--;
+
+                       func_table = &EG(scope)->function_table;
+               } else {
+                       phpdbg_error("No active class");
+                       return;
+               }
+       } else if (!EG(function_table)) {
+               phpdbg_error("No function table loaded");
+               return;
+       } else {
+               func_table = EG(function_table);
+       }
+
+       /* use lowercase names, case insensitive */
+       func_name = zend_str_tolower_dup(func_name, func_name_len);
+
+       if (zend_hash_find(func_table, func_name, func_name_len+1, (void**)&fbc) == SUCCESS) {
+               phpdbg_list_function(fbc TSRMLS_CC);
+       } else {
+               phpdbg_error("Function %s not found", func_name);
+       }
+
+       efree(func_name);
+} /* }}} */
+
diff --git a/phpdbg_list.h b/phpdbg_list.h
new file mode 100644 (file)
index 0000000..d618027
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_LIST_H
+#define PHPDBG_LIST_H
+
+#include "TSRM.h"
+#include "phpdbg_cmd.h"
+
+#define PHPDBG_LIST(name)         PHPDBG_COMMAND(list_##name)
+#define PHPDBG_LIST_HANDLER(name) PHPDBG_COMMAND_HANDLER(list_##name)
+
+PHPDBG_LIST(lines);
+PHPDBG_LIST(class);
+PHPDBG_LIST(method);
+PHPDBG_LIST(func);
+
+void phpdbg_list_function_byname(const char *, size_t TSRMLS_DC);
+void phpdbg_list_function(const zend_function* TSRMLS_DC);
+void phpdbg_list_file(const char*, long, long, int TSRMLS_DC);
+
+static const phpdbg_command_t phpdbg_list_commands[] = {
+       PHPDBG_COMMAND_D_EX(lines,     "lists the specified lines",    'l', list_lines,  NULL, 1),
+       PHPDBG_COMMAND_D_EX(class,     "lists the specified class",    'c', list_class,  NULL, 1),
+       PHPDBG_COMMAND_D_EX(method,    "lists the specified method",   'm', list_method, NULL, 1),
+       PHPDBG_COMMAND_D_EX(func,      "lists the specified function", 'f', list_func,   NULL, 1),
+       PHPDBG_END_COMMAND
+};
+
+#endif /* PHPDBG_LIST_H */
diff --git a/phpdbg_opcode.c b/phpdbg_opcode.c
new file mode 100644 (file)
index 0000000..025d57a
--- /dev/null
@@ -0,0 +1,361 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "phpdbg.h"
+#include "zend_vm_opcodes.h"
+#include "zend_compile.h"
+#include "phpdbg_opcode.h"
+#include "phpdbg_utils.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+static inline zend_uint phpdbg_decode_literal(zend_op_array *ops, zend_literal *literal TSRMLS_DC) /* {{{ */
+{
+       int iter = 0;
+
+       while (iter < ops->last_literal) {
+               if (literal == &ops->literals[iter]) {
+                       return iter;
+               }
+               iter++;
+       }
+
+       return 0;
+} /* }}} */
+
+static inline char *phpdbg_decode_op(zend_op_array *ops, znode_op *op, zend_uint type, HashTable *vars TSRMLS_DC) /* {{{ */
+{
+       char *decode = NULL;
+
+       switch (type &~ EXT_TYPE_UNUSED) {
+               case IS_CV:
+                       asprintf(&decode, "$%s", ops->vars[op->var].name);
+               break;
+
+               case IS_VAR:
+               case IS_TMP_VAR: {
+                       zend_ulong id = 0, *pid = NULL;
+                       if (zend_hash_index_find(vars, (zend_ulong) ops->vars - op->var, (void**) &pid) != SUCCESS) {
+                               id = zend_hash_num_elements(vars);
+                               zend_hash_index_update(
+                                       vars, (zend_ulong) ops->vars - op->var,
+                                       (void**) &id,
+                                       sizeof(zend_ulong), NULL);
+                       } else id = *pid;
+                       asprintf(&decode, "@%lu", id);
+               } break;
+
+               case IS_CONST:
+                       asprintf(&decode, "C%u", phpdbg_decode_literal(ops, op->literal TSRMLS_CC));
+               break;
+
+               case IS_UNUSED:
+                       asprintf(&decode, "<unused>");
+               break;
+       }
+       return decode;
+} /* }}} */
+
+char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op, HashTable *vars TSRMLS_DC) /*{{{ */
+{
+       char *decode[4] = {NULL, NULL, NULL, NULL};
+
+       switch (op->opcode) {
+       case ZEND_JMP:
+#ifdef ZEND_GOTO
+       case ZEND_GOTO:
+#endif
+#ifdef ZEND_FAST_CALL
+       case ZEND_FAST_CALL:
+#endif
+                       asprintf(&decode[1], "J%ld", op->op1.jmp_addr - ops->opcodes);
+               goto format;
+
+       case ZEND_JMPZNZ:
+                       decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars TSRMLS_CC);
+                       asprintf(
+                               &decode[2], "J%u or J%lu", op->op2.opline_num, op->extended_value);
+               goto result;
+
+       case ZEND_JMPZ:
+       case ZEND_JMPNZ:
+       case ZEND_JMPZ_EX:
+       case ZEND_JMPNZ_EX:
+
+#ifdef ZEND_JMP_SET
+       case ZEND_JMP_SET:
+#endif
+#ifdef ZEND_JMP_SET_VAR
+       case ZEND_JMP_SET_VAR:
+#endif
+               decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars TSRMLS_CC);
+               asprintf(
+                       &decode[2], "J%ld", op->op2.jmp_addr - ops->opcodes);
+       goto result;
+
+       case ZEND_RECV_INIT:
+               goto result;
+
+               default: {
+                       decode[1] = phpdbg_decode_op(ops, &op->op1, op->op1_type, vars TSRMLS_CC);
+                       decode[2] = phpdbg_decode_op(ops, &op->op2, op->op2_type, vars TSRMLS_CC);
+result:
+                       decode[3] = phpdbg_decode_op(ops, &op->result, op->result_type, vars TSRMLS_CC);
+format:
+                       asprintf(
+                               &decode[0],
+                               "%-20s %-20s %-20s",
+                               decode[1] ? decode[1] : "",
+                               decode[2] ? decode[2] : "",
+                               decode[3] ? decode[3] : "");
+               }
+       }
+
+       if (decode[1])
+               free(decode[1]);
+       if (decode[2])
+               free(decode[2]);
+       if (decode[3])
+               free(decode[3]);
+
+       return decode[0];
+} /* }}} */
+
+void phpdbg_print_opline_ex(zend_execute_data *execute_data, HashTable *vars, zend_bool ignore_flags TSRMLS_DC) /* {{{ */
+{
+       /* force out a line while stepping so the user knows what is happening */
+       if (ignore_flags ||
+               (!(PHPDBG_G(flags) & PHPDBG_IS_QUIET) ||
+               (PHPDBG_G(flags) & PHPDBG_IS_STEPPING) ||
+               (PHPDBG_G(oplog)))) {
+
+               zend_op *opline = execute_data->opline;
+               char *decode = phpdbg_decode_opline(execute_data->op_array, opline, vars TSRMLS_CC);
+
+               if (ignore_flags || (!(PHPDBG_G(flags) & PHPDBG_IS_QUIET) || (PHPDBG_G(flags) & PHPDBG_IS_STEPPING))) {
+                       /* output line info */
+                       phpdbg_notice("L%-5u %16p %-30s %s %s",
+                          opline->lineno,
+                          opline,
+                          phpdbg_decode_opcode(opline->opcode),
+                          decode,
+                          execute_data->op_array->filename ? execute_data->op_array->filename : "unknown");
+               }
+
+               if (!ignore_flags && PHPDBG_G(oplog)) {
+                       phpdbg_log_ex(PHPDBG_G(oplog), "L%-5u %16p %-30s %s %s",
+                               opline->lineno,
+                               opline,
+                               phpdbg_decode_opcode(opline->opcode),
+                               decode,
+                               execute_data->op_array->filename ? execute_data->op_array->filename : "unknown");
+               }
+
+               if (decode) {
+                       free(decode);
+               }
+       }
+} /* }}} */
+
+void phpdbg_print_opline(zend_execute_data *execute_data, zend_bool ignore_flags TSRMLS_DC) /* {{{ */
+{
+       phpdbg_print_opline_ex(execute_data, NULL, ignore_flags TSRMLS_CC);
+} /* }}} */
+
+const char *phpdbg_decode_opcode(zend_uchar opcode) /* {{{ */
+{
+#define CASE(s) case s: return #s
+       switch (opcode) {
+               CASE(ZEND_NOP);
+               CASE(ZEND_ADD);
+               CASE(ZEND_SUB);
+               CASE(ZEND_MUL);
+               CASE(ZEND_DIV);
+               CASE(ZEND_MOD);
+               CASE(ZEND_SL);
+               CASE(ZEND_SR);
+               CASE(ZEND_CONCAT);
+               CASE(ZEND_BW_OR);
+               CASE(ZEND_BW_AND);
+               CASE(ZEND_BW_XOR);
+               CASE(ZEND_BW_NOT);
+               CASE(ZEND_BOOL_NOT);
+               CASE(ZEND_BOOL_XOR);
+               CASE(ZEND_IS_IDENTICAL);
+               CASE(ZEND_IS_NOT_IDENTICAL);
+               CASE(ZEND_IS_EQUAL);
+               CASE(ZEND_IS_NOT_EQUAL);
+               CASE(ZEND_IS_SMALLER);
+               CASE(ZEND_IS_SMALLER_OR_EQUAL);
+               CASE(ZEND_CAST);
+               CASE(ZEND_QM_ASSIGN);
+               CASE(ZEND_ASSIGN_ADD);
+               CASE(ZEND_ASSIGN_SUB);
+               CASE(ZEND_ASSIGN_MUL);
+               CASE(ZEND_ASSIGN_DIV);
+               CASE(ZEND_ASSIGN_MOD);
+               CASE(ZEND_ASSIGN_SL);
+               CASE(ZEND_ASSIGN_SR);
+               CASE(ZEND_ASSIGN_CONCAT);
+               CASE(ZEND_ASSIGN_BW_OR);
+               CASE(ZEND_ASSIGN_BW_AND);
+               CASE(ZEND_ASSIGN_BW_XOR);
+               CASE(ZEND_PRE_INC);
+               CASE(ZEND_PRE_DEC);
+               CASE(ZEND_POST_INC);
+               CASE(ZEND_POST_DEC);
+               CASE(ZEND_ASSIGN);
+               CASE(ZEND_ASSIGN_REF);
+               CASE(ZEND_ECHO);
+               CASE(ZEND_PRINT);
+               CASE(ZEND_JMP);
+               CASE(ZEND_JMPZ);
+               CASE(ZEND_JMPNZ);
+               CASE(ZEND_JMPZNZ);
+               CASE(ZEND_JMPZ_EX);
+               CASE(ZEND_JMPNZ_EX);
+               CASE(ZEND_CASE);
+               CASE(ZEND_SWITCH_FREE);
+               CASE(ZEND_BRK);
+               CASE(ZEND_CONT);
+               CASE(ZEND_BOOL);
+               CASE(ZEND_INIT_STRING);
+               CASE(ZEND_ADD_CHAR);
+               CASE(ZEND_ADD_STRING);
+               CASE(ZEND_ADD_VAR);
+               CASE(ZEND_BEGIN_SILENCE);
+               CASE(ZEND_END_SILENCE);
+               CASE(ZEND_INIT_FCALL_BY_NAME);
+               CASE(ZEND_DO_FCALL);
+               CASE(ZEND_DO_FCALL_BY_NAME);
+               CASE(ZEND_RETURN);
+               CASE(ZEND_RECV);
+               CASE(ZEND_RECV_INIT);
+               CASE(ZEND_SEND_VAL);
+               CASE(ZEND_SEND_VAR);
+               CASE(ZEND_SEND_REF);
+               CASE(ZEND_NEW);
+               CASE(ZEND_INIT_NS_FCALL_BY_NAME);
+               CASE(ZEND_FREE);
+               CASE(ZEND_INIT_ARRAY);
+               CASE(ZEND_ADD_ARRAY_ELEMENT);
+               CASE(ZEND_INCLUDE_OR_EVAL);
+               CASE(ZEND_UNSET_VAR);
+               CASE(ZEND_UNSET_DIM);
+               CASE(ZEND_UNSET_OBJ);
+               CASE(ZEND_FE_RESET);
+               CASE(ZEND_FE_FETCH);
+               CASE(ZEND_EXIT);
+               CASE(ZEND_FETCH_R);
+               CASE(ZEND_FETCH_DIM_R);
+               CASE(ZEND_FETCH_OBJ_R);
+               CASE(ZEND_FETCH_W);
+               CASE(ZEND_FETCH_DIM_W);
+               CASE(ZEND_FETCH_OBJ_W);
+               CASE(ZEND_FETCH_RW);
+               CASE(ZEND_FETCH_DIM_RW);
+               CASE(ZEND_FETCH_OBJ_RW);
+               CASE(ZEND_FETCH_IS);
+               CASE(ZEND_FETCH_DIM_IS);
+               CASE(ZEND_FETCH_OBJ_IS);
+               CASE(ZEND_FETCH_FUNC_ARG);
+               CASE(ZEND_FETCH_DIM_FUNC_ARG);
+               CASE(ZEND_FETCH_OBJ_FUNC_ARG);
+               CASE(ZEND_FETCH_UNSET);
+               CASE(ZEND_FETCH_DIM_UNSET);
+               CASE(ZEND_FETCH_OBJ_UNSET);
+               CASE(ZEND_FETCH_DIM_TMP_VAR);
+               CASE(ZEND_FETCH_CONSTANT);
+               CASE(ZEND_GOTO);
+               CASE(ZEND_EXT_STMT);
+               CASE(ZEND_EXT_FCALL_BEGIN);
+               CASE(ZEND_EXT_FCALL_END);
+               CASE(ZEND_EXT_NOP);
+               CASE(ZEND_TICKS);
+               CASE(ZEND_SEND_VAR_NO_REF);
+               CASE(ZEND_CATCH);
+               CASE(ZEND_THROW);
+               CASE(ZEND_FETCH_CLASS);
+               CASE(ZEND_CLONE);
+               CASE(ZEND_RETURN_BY_REF);
+               CASE(ZEND_INIT_METHOD_CALL);
+               CASE(ZEND_INIT_STATIC_METHOD_CALL);
+               CASE(ZEND_ISSET_ISEMPTY_VAR);
+               CASE(ZEND_ISSET_ISEMPTY_DIM_OBJ);
+               CASE(ZEND_PRE_INC_OBJ);
+               CASE(ZEND_PRE_DEC_OBJ);
+               CASE(ZEND_POST_INC_OBJ);
+               CASE(ZEND_POST_DEC_OBJ);
+               CASE(ZEND_ASSIGN_OBJ);
+               CASE(ZEND_INSTANCEOF);
+               CASE(ZEND_DECLARE_CLASS);
+               CASE(ZEND_DECLARE_INHERITED_CLASS);
+               CASE(ZEND_DECLARE_FUNCTION);
+               CASE(ZEND_RAISE_ABSTRACT_ERROR);
+               CASE(ZEND_DECLARE_CONST);
+               CASE(ZEND_ADD_INTERFACE);
+               CASE(ZEND_DECLARE_INHERITED_CLASS_DELAYED);
+               CASE(ZEND_VERIFY_ABSTRACT_CLASS);
+               CASE(ZEND_ASSIGN_DIM);
+               CASE(ZEND_ISSET_ISEMPTY_PROP_OBJ);
+               CASE(ZEND_HANDLE_EXCEPTION);
+               CASE(ZEND_USER_OPCODE);
+#ifdef ZEND_JMP_SET
+               CASE(ZEND_JMP_SET);
+#endif
+               CASE(ZEND_DECLARE_LAMBDA_FUNCTION);
+#ifdef ZEND_ADD_TRAIT
+               CASE(ZEND_ADD_TRAIT);
+#endif
+#ifdef ZEND_BIND_TRAITS
+               CASE(ZEND_BIND_TRAITS);
+#endif
+#ifdef ZEND_SEPARATE
+               CASE(ZEND_SEPARATE);
+#endif
+#ifdef ZEND_QM_ASSIGN_VAR
+               CASE(ZEND_QM_ASSIGN_VAR);
+#endif
+#ifdef ZEND_JMP_SET_VAR
+               CASE(ZEND_JMP_SET_VAR);
+#endif
+#ifdef ZEND_DISCARD_EXCEPTION
+               CASE(ZEND_DISCARD_EXCEPTION);
+#endif
+#ifdef ZEND_YIELD
+               CASE(ZEND_YIELD);
+#endif
+#ifdef ZEND_GENERATOR_RETURN
+               CASE(ZEND_GENERATOR_RETURN);
+#endif
+#ifdef ZEND_FAST_CALL
+               CASE(ZEND_FAST_CALL);
+#endif
+#ifdef ZEND_FAST_RET
+               CASE(ZEND_FAST_RET);
+#endif
+#ifdef ZEND_RECV_VARIADIC
+               CASE(ZEND_RECV_VARIADIC);
+#endif
+               CASE(ZEND_OP_DATA);
+               default:
+                       return "UNKNOWN";
+       }
+} /* }}} */
diff --git a/phpdbg_opcode.h b/phpdbg_opcode.h
new file mode 100644 (file)
index 0000000..5771488
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_OPCODE_H
+#define PHPDBG_OPCODE_H
+
+#include "zend_types.h"
+
+const char *phpdbg_decode_opcode(zend_uchar);
+char *phpdbg_decode_opline(zend_op_array *ops, zend_op *op, HashTable *vars TSRMLS_DC);
+void phpdbg_print_opline(zend_execute_data *execute_data, zend_bool ignore_flags TSRMLS_DC);
+void phpdbg_print_opline_ex(zend_execute_data *execute_data, HashTable *vars, zend_bool ignore_flags TSRMLS_DC);
+
+#endif /* PHPDBG_OPCODE_H */
diff --git a/phpdbg_print.c b/phpdbg_print.c
new file mode 100644 (file)
index 0000000..51edcfb
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "phpdbg.h"
+#include "phpdbg_print.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_opcode.h"
+#include "phpdbg_prompt.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_PRINT(opline) /* {{{ */
+{
+       if (EG(in_execution) && EG(current_execute_data)) {
+               phpdbg_print_opline(EG(current_execute_data), 1 TSRMLS_CC);
+       } else {
+               phpdbg_error("Not Executing!");
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+static inline void phpdbg_print_function_helper(zend_function *method TSRMLS_DC) /* {{{ */
+{
+       switch (method->type) {
+               case ZEND_USER_FUNCTION: {
+                       zend_op_array* op_array = &(method->op_array);
+                       HashTable vars;
+                       
+                       if (op_array) {
+                               zend_op *opline = &(op_array->opcodes[0]);
+                               zend_uint opcode = 0,
+                               end = op_array->last-1;
+
+                               if (method->common.scope) {
+                                       phpdbg_writeln("\tL%d-%d %s::%s() %s",
+                                               op_array->line_start, op_array->line_end,
+                                               method->common.scope->name,
+                                               method->common.function_name,
+                                               op_array->filename ? op_array->filename : "unknown");
+                               } else {
+                                       phpdbg_writeln("\tL%d-%d %s() %s",
+                                               method->common.function_name ? op_array->line_start : 0, 
+                                               method->common.function_name ? op_array->line_end : 0,
+                                               method->common.function_name ? method->common.function_name : "{main}",
+                                               op_array->filename ? op_array->filename : "unknown");
+                               }
+
+                               zend_hash_init(&vars, op_array->last, NULL, NULL, 0);
+                               do {
+                                       char *decode = phpdbg_decode_opline(op_array, opline, &vars TSRMLS_CC);
+                                       if (decode != NULL) {
+                                               phpdbg_writeln("\t\tL%u\t%p %-30s %s", 
+                                                       opline->lineno,
+                                                       opline, 
+                                                       phpdbg_decode_opcode(opline->opcode),
+                                                       decode);
+                                               free(decode);
+                                       } else {
+                                               phpdbg_error("\tFailed to decode opline %16p", opline);
+                                       }
+                                       opline++;
+                               } while (++opcode < end);
+                               zend_hash_destroy(&vars);
+                       }
+               } break;
+
+               default: {
+                       if (method->common.scope) {
+                               phpdbg_writeln("\tInternal %s::%s()", method->common.scope->name, method->common.function_name);
+                       } else {
+                               phpdbg_writeln("\tInternal %s()", method->common.function_name);
+                       }
+               }
+       }
+} /* }}} */
+
+PHPDBG_PRINT(exec) /* {{{ */
+{
+       if (PHPDBG_G(exec)) {
+               if (!PHPDBG_G(ops)) {
+                       phpdbg_compile(TSRMLS_C);
+               }
+
+               if (PHPDBG_G(ops)) {
+                       phpdbg_notice("Context %s", PHPDBG_G(exec));
+
+                       phpdbg_print_function_helper((zend_function*) PHPDBG_G(ops) TSRMLS_CC);
+               }
+       } else {
+               phpdbg_error("No execution context set");
+       }
+
+return SUCCESS;
+} /* }}} */
+
+PHPDBG_PRINT(stack) /* {{{ */
+{
+       zend_op_array *ops = EG(active_op_array);
+       
+       if (EG(in_execution) && ops) {
+               if (ops->function_name) {
+                       if (ops->scope) {
+                               phpdbg_notice("Stack in %s::%s()", ops->scope->name, ops->function_name);
+                       } else {
+                               phpdbg_notice("Stack in %s()", ops->function_name);
+                       }
+               } else {
+                       if (ops->filename) {
+                               phpdbg_notice("Stack in %s", ops->filename);
+                       } else {
+                               phpdbg_notice("Stack @ %p", ops);
+                       }
+               }
+               phpdbg_print_function_helper((zend_function*) ops TSRMLS_CC);
+       } else {
+               phpdbg_error("Not Executing!");
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_PRINT(class) /* {{{ */
+{
+       zend_class_entry **ce;
+
+       switch (param->type) {
+               case STR_PARAM: {
+                       if (zend_lookup_class(param->str, param->len, &ce TSRMLS_CC) == SUCCESS) {
+                               phpdbg_notice("%s %s: %s",
+                                       ((*ce)->type == ZEND_USER_CLASS) ?
+                                               "User" : "Internal",
+                                       ((*ce)->ce_flags & ZEND_ACC_INTERFACE) ?
+                                               "Interface" :
+                                               ((*ce)->ce_flags & ZEND_ACC_ABSTRACT) ?
+                                                       "Abstract Class" :
+                                                       "Class",
+                                       (*ce)->name);
+
+                               phpdbg_writeln("Methods (%d):", zend_hash_num_elements(&(*ce)->function_table));
+                               if (zend_hash_num_elements(&(*ce)->function_table)) {
+                                       HashPosition position;
+                                       zend_function *method;
+
+                                       for (zend_hash_internal_pointer_reset_ex(&(*ce)->function_table, &position);
+                                            zend_hash_get_current_data_ex(&(*ce)->function_table, (void**) &method, &position) == SUCCESS;
+                                            zend_hash_move_forward_ex(&(*ce)->function_table, &position)) {
+                                               phpdbg_print_function_helper(method TSRMLS_CC);
+                                       }
+                               }
+                       } else {
+                               phpdbg_error("The class %s could not be found", param->str);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_PRINT(method) /* {{{ */
+{
+       switch (param->type) {
+               case METHOD_PARAM: {
+                       zend_class_entry **ce;
+
+                       if (zend_lookup_class(param->method.class, strlen(param->method.class), &ce TSRMLS_CC) == SUCCESS) {
+                               zend_function *fbc;
+                               char *lcname = zend_str_tolower_dup(param->method.name, strlen(param->method.name));
+
+                               if (zend_hash_find(&(*ce)->function_table, lcname, strlen(lcname)+1, (void**)&fbc) == SUCCESS) {
+                                       phpdbg_notice("%s Method %s",
+                                               (fbc->type == ZEND_USER_FUNCTION) ? "User" : "Internal",
+                                               fbc->common.function_name);
+
+                                       phpdbg_print_function_helper(fbc TSRMLS_CC);
+                               } else {
+                                       phpdbg_error("The method %s could not be found", param->method.name);
+                               }
+
+                               efree(lcname);
+                       } else {
+                               phpdbg_error("The class %s could not be found", param->method.class);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_PRINT(func) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM: {
+                       HashTable *func_table = EG(function_table);
+                       zend_function* fbc;
+                       const char *func_name = param->str;
+                       size_t func_name_len = param->len;
+                       char *lcname;
+                       /* search active scope if begins with period */
+                       if (func_name[0] == '.') {
+                               if (EG(scope)) {
+                                       func_name++;
+                                       func_name_len--;
+
+                                       func_table = &EG(scope)->function_table;
+                               } else {
+                                       phpdbg_error("No active class");
+                                       return SUCCESS;
+                               }
+                       } else if (!EG(function_table)) {
+                               phpdbg_error("No function table loaded");
+                               return SUCCESS;
+                       } else {
+                               func_table = EG(function_table);
+                       }
+
+                       lcname  = zend_str_tolower_dup(func_name, func_name_len);
+
+                       if (zend_hash_find(func_table, lcname, strlen(lcname)+1, (void**)&fbc) == SUCCESS) {
+                               phpdbg_notice("%s %s %s",
+                                       (fbc->type == ZEND_USER_FUNCTION) ? "User" : "Internal",
+                                       (fbc->common.scope) ? "Method" : "Function",
+                                       fbc->common.function_name);
+
+                               phpdbg_print_function_helper(fbc TSRMLS_CC);
+                       } else {
+                               phpdbg_error("The function %s could not be found", func_name);
+                       }
+
+                       efree(lcname);
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
diff --git a/phpdbg_print.h b/phpdbg_print.h
new file mode 100644 (file)
index 0000000..1232f54
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_PRINT_H
+#define PHPDBG_PRINT_H
+
+#include "phpdbg_cmd.h"
+
+#define PHPDBG_PRINT(name) PHPDBG_COMMAND(print_##name)
+
+/**
+ * Printer Forward Declarations
+ */
+PHPDBG_PRINT(exec);
+PHPDBG_PRINT(opline);
+PHPDBG_PRINT(class);
+PHPDBG_PRINT(method);
+PHPDBG_PRINT(func);
+PHPDBG_PRINT(stack);
+
+/**
+ * Commands
+ */
+static const phpdbg_command_t phpdbg_print_commands[] = {
+       PHPDBG_COMMAND_D_EX(exec,       "print out the instructions in the execution context",  'e', print_exec,   NULL, 0),
+       PHPDBG_COMMAND_D_EX(opline,     "print out the instruction in the current opline",      'o', print_opline, NULL, 0),
+       PHPDBG_COMMAND_D_EX(class,      "print out the instructions in the specified class",    'c', print_class,  NULL, 1),
+       PHPDBG_COMMAND_D_EX(method,     "print out the instructions in the specified method",   'm', print_method, NULL, 1),
+       PHPDBG_COMMAND_D_EX(func,       "print out the instructions in the specified function", 'f', print_func,   NULL, 1),
+       PHPDBG_COMMAND_D_EX(stack,      "print out the instructions in the current stack",      's', print_stack,  NULL, 0),
+       PHPDBG_END_COMMAND
+};
+
+#endif /* PHPDBG_PRINT_H */
diff --git a/phpdbg_prompt.c b/phpdbg_prompt.c
new file mode 100644 (file)
index 0000000..f2f482b
--- /dev/null
@@ -0,0 +1,1328 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include "zend.h"
+#include "zend_compile.h"
+#include "phpdbg.h"
+#include "phpdbg_help.h"
+#include "phpdbg_print.h"
+#include "phpdbg_info.h"
+#include "phpdbg_break.h"
+#include "phpdbg_bp.h"
+#include "phpdbg_opcode.h"
+#include "phpdbg_list.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_prompt.h"
+#include "phpdbg_cmd.h"
+#include "phpdbg_set.h"
+#include "phpdbg_frame.h"
+
+/* {{{ command declarations */
+const phpdbg_command_t phpdbg_prompt_commands[] = {
+       PHPDBG_COMMAND_D(exec,    "set execution context",                    'e', NULL, 1),
+       PHPDBG_COMMAND_D(compile, "attempt compilation",                      'c', NULL, 0),
+       PHPDBG_COMMAND_D(step,    "step through execution",                   's', NULL, 1),
+       PHPDBG_COMMAND_D(next,    "continue execution",                       'n', NULL, 0),
+       PHPDBG_COMMAND_D(run,     "attempt execution",                        'r', NULL, 0),
+       PHPDBG_COMMAND_D(eval,    "evaluate some code",                       'E', NULL, 1),
+       PHPDBG_COMMAND_D(until,   "continue past the current line",           'u', NULL, 0),
+       PHPDBG_COMMAND_D(finish,  "continue past the end of the stack",       'F', NULL, 0),
+       PHPDBG_COMMAND_D(leave,   "continue until the end of the stack",      'L', NULL, 0),
+       PHPDBG_COMMAND_D(print,   "print something",                          'p', phpdbg_print_commands, 2),
+       PHPDBG_COMMAND_D(break,   "set breakpoint",                           'b', phpdbg_break_commands, 1),
+       PHPDBG_COMMAND_D(back,    "show trace",                               't', NULL, 0),
+       PHPDBG_COMMAND_D(frame,   "switch to a frame",                        'f', NULL, 1),
+       PHPDBG_COMMAND_D(list,    "lists some code",                          'l', phpdbg_list_commands, 2),
+       PHPDBG_COMMAND_D(info,    "displays some informations",               'i', phpdbg_info_commands, 1),
+       PHPDBG_COMMAND_D(clean,   "clean the execution environment",          'X', NULL, 0),
+       PHPDBG_COMMAND_D(clear,   "clear breakpoints",                        'C', NULL, 0),
+       PHPDBG_COMMAND_D(help,    "show help menu",                           'h', phpdbg_help_commands, 2),
+       PHPDBG_COMMAND_D(quiet,   "silence some output",                      'Q', NULL, 1),
+       PHPDBG_COMMAND_D(aliases, "show alias list",                          'a', NULL, 0),
+       PHPDBG_COMMAND_D(set,     "set phpdbg configuration",                 'S', phpdbg_set_commands,   1),
+       PHPDBG_COMMAND_D(register,"register a function",                      'R', NULL, 1),
+       PHPDBG_COMMAND_D(source,  "execute a phpdbginit",                     '.', NULL, 1),
+       PHPDBG_COMMAND_D(shell,   "shell a command",                          '-', NULL, 1),
+       PHPDBG_COMMAND_D(quit,    "exit phpdbg",                              'q', NULL, 0),
+       PHPDBG_END_COMMAND
+}; /* }}} */
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+static inline int phpdbg_call_register(phpdbg_input_t *input TSRMLS_DC) /* {{{ */
+{
+       phpdbg_input_t *function = input->argv[0];
+
+       if (zend_hash_exists(
+               &PHPDBG_G(registered), function->string, function->length+1)) {
+
+               zval fname, *fretval;
+               zend_fcall_info fci;
+
+               ZVAL_STRINGL(&fname, function->string, function->length, 1);
+
+               memset(&fci, 0, sizeof(zend_fcall_info));
+
+               fci.size = sizeof(zend_fcall_info);
+               fci.function_table = &PHPDBG_G(registered);
+               fci.function_name = &fname;
+               fci.symbol_table = EG(active_symbol_table);
+               fci.object_ptr = NULL;
+               fci.retval_ptr_ptr = &fretval;
+               fci.no_separation = 1;
+
+               if (input->argc > 1) {
+                       int param;
+                       zval params;
+
+                       array_init(&params);
+
+                       for (param = 0; param < (input->argc-1); param++) {
+                               add_next_index_stringl(
+                                       &params,
+                                       input->argv[param+1]->string,
+                                       input->argv[param+1]->length, 1);
+
+                               phpdbg_debug(
+                                       "created param[%d] from argv[%d]: %s",
+                                       param, param+1, input->argv[param+1]->string);
+                       }
+
+                       zend_fcall_info_args(&fci, &params TSRMLS_CC);
+               } else {
+                       fci.params = NULL;
+                       fci.param_count = 0;
+               }
+
+               phpdbg_debug(
+                       "created %d params from %d arguments",
+                       fci.param_count, input->argc);
+
+               zend_call_function(&fci, NULL TSRMLS_CC);
+
+               if (fretval) {
+                       zend_print_zval_r(
+                               fretval, 0 TSRMLS_CC);
+                       phpdbg_writeln(EMPTY);
+               }
+
+               zval_dtor(&fname);
+
+               return SUCCESS;
+       }
+
+       return FAILURE;
+} /* }}} */
+
+void phpdbg_try_file_init(char *init_file, size_t init_file_len, zend_bool free_init TSRMLS_DC) /* {{{ */
+{
+       struct stat sb;
+
+       if (init_file && VCWD_STAT(init_file, &sb) != -1) {
+               FILE *fp = fopen(init_file, "r");
+               if (fp) {
+                       int line = 1;
+
+                       char cmd[PHPDBG_MAX_CMD];
+                       size_t cmd_len = 0L;
+                       char *code = NULL;
+                       size_t code_len = 0L;
+                       zend_bool in_code = 0;
+
+                       while (fgets(cmd, PHPDBG_MAX_CMD, fp) != NULL) {
+                               cmd_len = strlen(cmd)-1;
+
+                               while (cmd_len > 0L && isspace(cmd[cmd_len-1]))
+                                       cmd_len--;
+
+                               cmd[cmd_len] = '\0';
+
+                               if (*cmd && cmd_len > 0L && cmd[0] != '#') {
+                                       if (cmd_len == 2) {
+                                               if (memcmp(cmd, "<:", sizeof("<:")-1) == SUCCESS) {
+                                                       in_code = 1;
+                                                       goto next_line;
+                                               } else {
+                                                       if (memcmp(cmd, ":>", sizeof(":>")-1) == SUCCESS) {
+                                                               in_code = 0;
+                                                               code[code_len] = '\0';
+                                                               {
+                                                                       zend_eval_stringl(
+                                                                               code, code_len, NULL, "phpdbginit code" TSRMLS_CC);
+                                                               }
+                                                               free(code);
+                                                               code = NULL;
+                                                               goto next_line;
+                                                       }
+                                               }
+                                       }
+
+                                       if (in_code) {
+                                               if (code == NULL) {
+                                                       code = malloc(cmd_len + 1);
+                                               } else code = realloc(code, code_len + cmd_len + 1);
+
+                                               if (code) {
+                                                       memcpy(
+                                                               &code[code_len], cmd, cmd_len);
+                                                       code_len += cmd_len;
+                                               }
+                                               goto next_line;
+                                       }
+
+                                       {
+                                               phpdbg_input_t *input = phpdbg_read_input(cmd TSRMLS_CC);
+                                               switch (phpdbg_do_cmd(phpdbg_prompt_commands, input TSRMLS_CC)) {
+                                                       case FAILURE:
+                                                               if (!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING)) {
+                                                                       if (phpdbg_call_register(input TSRMLS_CC) == FAILURE) {
+                                                                               phpdbg_error("Unrecognized command in %s:%d: %s!",  init_file, line, input->string);
+                                                                       }
+                                                               }
+                                                       break;
+                                               }
+                                               phpdbg_destroy_input(&input TSRMLS_CC);
+                                       }
+
+                               }
+next_line:
+                               line++;
+                       }
+
+                       if (code) {
+                               free(code);
+                       }
+
+                       fclose(fp);
+               } else {
+                       phpdbg_error(
+                               "Failed to open %s for initialization", init_file);
+               }
+
+               if (free_init) {
+                       free(init_file);
+               }
+       }
+} /* }}} */
+
+void phpdbg_init(char *init_file, size_t init_file_len, zend_bool use_default TSRMLS_DC) /* {{{ */
+{
+       if (!init_file && use_default) {
+               char *scan_dir = getenv("PHP_INI_SCAN_DIR");
+               int i;
+
+               phpdbg_try_file_init(PHPDBG_STRL(PHP_CONFIG_FILE_PATH "/" PHPDBG_INIT_FILENAME), 0 TSRMLS_CC);
+
+               if (!scan_dir) {
+                       scan_dir = PHP_CONFIG_FILE_SCAN_DIR;
+               }
+               while (*scan_dir != 0) {
+                       i = 0;
+                       while (scan_dir[i] != ':') {
+                               if (scan_dir[i++] == 0) {
+                                       i = -1;
+                                       break;
+                               }
+                       }
+                       if (i != -1) {
+                               scan_dir[i] = 0;
+                       }
+
+                       asprintf(
+                               &init_file, "%s/%s", scan_dir, PHPDBG_INIT_FILENAME);
+                       phpdbg_try_file_init(init_file, strlen(init_file), 1 TSRMLS_CC);
+                       if (i == -1) {
+                               break;
+                       }
+                       scan_dir += i + 1;
+               }
+
+               phpdbg_try_file_init(PHPDBG_STRL(PHPDBG_INIT_FILENAME), 0 TSRMLS_CC);
+       } else {
+               phpdbg_try_file_init(init_file, init_file_len, 1 TSRMLS_CC);
+       }
+}
+
+PHPDBG_COMMAND(exec) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM: {
+                       struct stat sb;
+
+                       if (VCWD_STAT(param->str, &sb) != FAILURE) {
+                               if (sb.st_mode & (S_IFREG|S_IFLNK)) {
+                                       char *res = phpdbg_resolve_path(param->str TSRMLS_CC);
+                                       size_t res_len = strlen(res);
+
+                                       if ((res_len != PHPDBG_G(exec_len)) || (memcmp(res, PHPDBG_G(exec), res_len) != SUCCESS)) {
+
+                                               if (PHPDBG_G(exec)) {
+                                                       phpdbg_notice("Unsetting old execution context: %s", PHPDBG_G(exec));
+                                                       efree(PHPDBG_G(exec));
+                                                       PHPDBG_G(exec) = NULL;
+                                                       PHPDBG_G(exec_len) = 0L;
+                                               }
+
+                                               if (PHPDBG_G(ops)) {
+                                                       phpdbg_notice("Destroying compiled opcodes");
+                                                       phpdbg_clean(0 TSRMLS_CC);
+                                               }
+
+                                               PHPDBG_G(exec) = res;
+                                               PHPDBG_G(exec_len) = res_len;
+
+                                               phpdbg_notice("Set execution context: %s", PHPDBG_G(exec));
+                                       } else {
+                                               phpdbg_notice("Execution context not changed");
+                                       }
+                               } else {
+                                       phpdbg_error("Cannot use %s as execution context, not a valid file or symlink", param->str);
+                               }
+                       } else {
+                               phpdbg_error("Cannot stat %s, ensure the file exists", param->str);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+int phpdbg_compile(TSRMLS_D) /* {{{ */
+{
+       zend_file_handle fh;
+
+       if (EG(in_execution)) {
+               phpdbg_error("Cannot compile while in execution");
+               return FAILURE;
+       }
+
+       phpdbg_notice("Attempting compilation of %s", PHPDBG_G(exec));
+
+       if (php_stream_open_for_zend_ex(PHPDBG_G(exec), &fh,
+               USE_PATH|STREAM_OPEN_FOR_INCLUDE TSRMLS_CC) == SUCCESS) {
+
+               PHPDBG_G(ops) = zend_compile_file(&fh, ZEND_INCLUDE TSRMLS_CC);
+               zend_destroy_file_handle(&fh TSRMLS_CC);
+
+               phpdbg_notice("Success");
+               return SUCCESS;
+       } else {
+               phpdbg_error("Could not open file %s", PHPDBG_G(exec));
+       }
+
+       return FAILURE;
+} /* }}} */
+
+PHPDBG_COMMAND(compile) /* {{{ */
+{
+       if (!PHPDBG_G(exec)) {
+               phpdbg_error("No execution context");
+               return SUCCESS;
+       }
+
+       if (!EG(in_execution)) {
+               if (PHPDBG_G(ops)) {
+                       phpdbg_error("Destroying previously compiled opcodes");
+                       phpdbg_clean(0 TSRMLS_CC);
+               }
+       }
+
+       phpdbg_compile(TSRMLS_C);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(step) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM:
+               case NUMERIC_PARAM: {
+                       if (param->type == NUMERIC_PARAM && param->num) {
+                               PHPDBG_G(flags) |= PHPDBG_IS_STEPPING;
+                       } else {
+                               PHPDBG_G(flags) &= ~PHPDBG_IS_STEPPING;
+                       }
+
+                       phpdbg_notice("Stepping %s",
+                               (PHPDBG_G(flags) & PHPDBG_IS_STEPPING) ? "on" : "off");
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(next) /* {{{ */
+{
+       return PHPDBG_NEXT;
+} /* }}} */
+
+PHPDBG_COMMAND(until) /* {{{ */
+{
+       if (!EG(in_execution)) {
+               phpdbg_error("Not executing");
+               return SUCCESS;
+       }
+
+       PHPDBG_G(flags) |= PHPDBG_IN_UNTIL;
+       {
+               zend_uint next = 0,
+                                 self = (EG(current_execute_data)->opline - EG(active_op_array)->opcodes);
+               zend_op  *opline = &EG(active_op_array)->opcodes[self];
+
+               for (next = self; next < EG(active_op_array)->last; next++) {
+                       if (EG(active_op_array)->opcodes[next].lineno != opline->lineno) {
+                               zend_hash_index_update(
+                                       &PHPDBG_G(seek),
+                                       (zend_ulong) &EG(active_op_array)->opcodes[next],
+                                       &EG(active_op_array)->opcodes[next],
+                                       sizeof(zend_op), NULL);
+                               break;
+                       }
+               }
+       }
+
+       return PHPDBG_UNTIL;
+} /* }}} */
+
+PHPDBG_COMMAND(finish) /* {{{ */
+{
+       if (!EG(in_execution)) {
+               phpdbg_error("Not executing");
+               return SUCCESS;
+       }
+
+       PHPDBG_G(flags) |= PHPDBG_IN_FINISH;
+       {
+               zend_uint next = 0,
+                                 self = (EG(current_execute_data)->opline - EG(active_op_array)->opcodes);
+
+               for (next = self; next < EG(active_op_array)->last; next++) {
+                       switch (EG(active_op_array)->opcodes[next].opcode) {
+                               case ZEND_RETURN:
+                               case ZEND_THROW:
+                               case ZEND_EXIT:
+#ifdef ZEND_YIELD
+                               case ZEND_YIELD:
+#endif
+                                       zend_hash_index_update(
+                                               &PHPDBG_G(seek),
+                                               (zend_ulong) &EG(active_op_array)->opcodes[next],
+                                               &EG(active_op_array)->opcodes[next],
+                                               sizeof(zend_op), NULL);
+                               break;
+                       }
+               }
+       }
+
+       return PHPDBG_FINISH;
+} /* }}} */
+
+PHPDBG_COMMAND(leave) /* {{{ */
+{
+       if (!EG(in_execution)) {
+               phpdbg_error("Not executing");
+               return SUCCESS;
+       }
+
+       PHPDBG_G(flags) |= PHPDBG_IN_LEAVE;
+       {
+               zend_uint next = 0,
+                                 self = (EG(current_execute_data)->opline - EG(active_op_array)->opcodes);
+
+               for (next = self; next < EG(active_op_array)->last; next++) {
+                       switch (EG(active_op_array)->opcodes[next].opcode) {
+                               case ZEND_RETURN:
+                               case ZEND_THROW:
+                               case ZEND_EXIT:
+#ifdef ZEND_YIELD
+                               case ZEND_YIELD:
+#endif
+                                       zend_hash_index_update(
+                                               &PHPDBG_G(seek),
+                                               (zend_ulong) &EG(active_op_array)->opcodes[next],
+                                               &EG(active_op_array)->opcodes[next],
+                                               sizeof(zend_op), NULL);
+                               break;
+                       }
+               }
+       }
+
+       return PHPDBG_LEAVE;
+} /* }}} */
+
+PHPDBG_COMMAND(frame) /* {{{ */
+{
+       switch (param->type) {
+               case NUMERIC_PARAM:
+                       phpdbg_switch_frame(param->num TSRMLS_CC);
+                       break;
+
+               case EMPTY_PARAM:
+                       phpdbg_notice("Currently in frame #%d", PHPDBG_G(frame).num);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+static inline void phpdbg_handle_exception(TSRMLS_D) /* }}} */
+{
+       zend_fcall_info fci;
+
+       zval fname,
+                *trace,
+                exception;
+
+       /* get filename and linenumber before unsetting exception */
+       const char *filename = zend_get_executed_filename(TSRMLS_C);
+       zend_uint lineno = zend_get_executed_lineno(TSRMLS_C);
+
+       /* copy exception */
+       exception = *EG(exception);
+       zval_copy_ctor(&exception);
+       EG(exception) = NULL;
+
+       phpdbg_error(
+               "Uncaught %s!",
+               Z_OBJCE(exception)->name);
+
+       /* call __toString */
+       ZVAL_STRINGL(&fname, "__tostring", sizeof("__tostring")-1, 1);
+       fci.size = sizeof(fci);
+       fci.function_table = &Z_OBJCE(exception)->function_table;
+       fci.function_name = &fname;
+       fci.symbol_table = NULL;
+       fci.object_ptr = &exception;
+       fci.retval_ptr_ptr = &trace;
+       fci.param_count = 0;
+       fci.params = NULL;
+       fci.no_separation = 1;
+       zend_call_function(&fci, NULL TSRMLS_CC);
+
+       if (trace) {
+               phpdbg_writeln(
+                       "Uncaught %s", Z_STRVAL_P(trace));
+               /* remember to dtor trace */
+               zval_ptr_dtor(&trace);
+       }
+
+       /* output useful information about address */
+       phpdbg_writeln(
+               "Stacked entered at %p in %s on line %u",
+               EG(active_op_array)->opcodes, filename, lineno);
+
+       zval_dtor(&fname);
+       zval_dtor(&exception);
+} /* }}} */
+
+PHPDBG_COMMAND(run) /* {{{ */
+{
+       if (EG(in_execution)) {
+               phpdbg_error("Cannot start another execution while one is in progress");
+               return SUCCESS;
+       }
+
+       if (PHPDBG_G(ops) || PHPDBG_G(exec)) {
+               zend_op **orig_opline = EG(opline_ptr);
+               zend_op_array *orig_op_array = EG(active_op_array);
+               zval **orig_retval_ptr = EG(return_value_ptr_ptr);
+
+               if (!PHPDBG_G(ops)) {
+                       if (phpdbg_compile(TSRMLS_C) == FAILURE) {
+                               phpdbg_error("Failed to compile %s, cannot run", PHPDBG_G(exec));
+                               goto out;
+                       }
+               }
+
+               EG(active_op_array) = PHPDBG_G(ops);
+               EG(return_value_ptr_ptr) = &PHPDBG_G(retval);
+               if (!EG(active_symbol_table)) {
+                       zend_rebuild_symbol_table(TSRMLS_C);
+               }
+
+               /* clean seek state */
+               PHPDBG_G(flags) &= ~PHPDBG_SEEK_MASK;
+               zend_hash_clean(
+                       &PHPDBG_G(seek));
+
+               /* reset hit counters */
+               phpdbg_reset_breakpoints(TSRMLS_C);
+
+               zend_try {
+                       php_output_activate(TSRMLS_C);
+                       PHPDBG_G(flags) ^= PHPDBG_IS_INTERACTIVE;
+                       zend_execute(EG(active_op_array) TSRMLS_CC);
+                       PHPDBG_G(flags) ^= PHPDBG_IS_INTERACTIVE;
+                       php_output_deactivate(TSRMLS_C);
+               } zend_catch {
+                       EG(active_op_array) = orig_op_array;
+                       EG(opline_ptr) = orig_opline;
+                       EG(return_value_ptr_ptr) = orig_retval_ptr;
+
+                       if (!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING)) {
+                               phpdbg_error("Caught exit/error from VM");
+                               goto out;
+                       }
+               } zend_end_try();
+
+               if (EG(exception)) {
+                       phpdbg_handle_exception(TSRMLS_C);
+               }
+
+               EG(active_op_array) = orig_op_array;
+               EG(opline_ptr) = orig_opline;
+               EG(return_value_ptr_ptr) = orig_retval_ptr;
+
+       } else {
+               phpdbg_error("Nothing to execute!");
+       }
+
+out:
+       PHPDBG_FRAME(num) = 0;
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(eval) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM: {
+                       zend_bool stepping = ((PHPDBG_G(flags) & PHPDBG_IS_STEPPING)==PHPDBG_IS_STEPPING);
+                       zval retval;
+
+                       if (!(PHPDBG_G(flags) & PHPDBG_IS_STEPONEVAL)) {
+                               PHPDBG_G(flags) &= ~ PHPDBG_IS_STEPPING;
+                       }
+
+                       /* disable stepping while eval() in progress */
+                       PHPDBG_G(flags) |= PHPDBG_IN_EVAL;
+                       zend_try {
+                               if (zend_eval_stringl(param->str, param->len,
+                                       &retval, "eval()'d code" TSRMLS_CC) == SUCCESS) {
+                                       zend_print_zval_r(
+                                               &retval, 0 TSRMLS_CC);
+                                       phpdbg_writeln(EMPTY);
+                                       zval_dtor(&retval);
+                               }
+                       } zend_end_try();
+                       PHPDBG_G(flags) &= ~PHPDBG_IN_EVAL;
+
+                       /* switch stepping back on */
+                       if (stepping &&
+                               !(PHPDBG_G(flags) & PHPDBG_IS_STEPONEVAL)) {
+                               PHPDBG_G(flags) |= PHPDBG_IS_STEPPING;
+                       }
+
+                       CG(unclean_shutdown) = 0;
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(back) /* {{{ */
+{
+       if (!EG(in_execution)) {
+               phpdbg_error("Not executing!");
+               return SUCCESS;
+       }
+
+       switch (param->type) {
+               case EMPTY_PARAM:
+               case NUMERIC_PARAM:
+                       phpdbg_dump_backtrace(
+                               (param->type == NUMERIC_PARAM) ? param->num : 0 TSRMLS_CC);
+               break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(print) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM: {
+                       phpdbg_writeln(SEPARATE);
+                       phpdbg_notice("Execution Context Information");
+#ifdef HAVE_LIBREADLINE
+                       phpdbg_writeln("Readline\tyes");
+#else
+                       phpdbg_writeln("Readline\tno");
+#endif
+
+                       phpdbg_writeln("Exec\t\t%s", PHPDBG_G(exec) ? PHPDBG_G(exec) : "none");
+                       phpdbg_writeln("Compiled\t%s", PHPDBG_G(ops) ? "yes" : "no");
+                       phpdbg_writeln("Stepping\t%s", (PHPDBG_G(flags) & PHPDBG_IS_STEPPING) ? "on" : "off");
+                       phpdbg_writeln("Quietness\t%s", (PHPDBG_G(flags) & PHPDBG_IS_QUIET) ? "on" : "off");
+                       phpdbg_writeln("Oplog\t\t%s", PHPDBG_G(oplog) ? "on" : "off");
+
+                       if (PHPDBG_G(ops)) {
+                               phpdbg_writeln("Opcodes\t\t%d", PHPDBG_G(ops)->last);
+
+                               if (PHPDBG_G(ops)->last_var) {
+                                       phpdbg_writeln("Variables\t%d", PHPDBG_G(ops)->last_var-1);
+                               } else {
+                                       phpdbg_writeln("Variables\tNone");
+                               }
+                       }
+
+                       phpdbg_writeln("Executing\t%s", EG(in_execution) ? "yes" : "no");
+                       if (EG(in_execution)) {
+                               phpdbg_writeln("VM Return\t%d", PHPDBG_G(vmret));
+                       }
+
+                       phpdbg_writeln("Classes\t\t%d", zend_hash_num_elements(EG(class_table)));
+                       phpdbg_writeln("Functions\t%d", zend_hash_num_elements(EG(function_table)));
+                       phpdbg_writeln("Constants\t%d", zend_hash_num_elements(EG(zend_constants)));
+                       phpdbg_writeln("Included\t%d", zend_hash_num_elements(&EG(included_files)));
+
+                       phpdbg_writeln(SEPARATE);
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(info) /* {{{ */
+{
+       phpdbg_error(
+               "No information command selected!");
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(set) /* {{{ */
+{
+       phpdbg_error(
+               "No information command selected!");
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(break) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM:
+                       phpdbg_set_breakpoint_file(
+                               zend_get_executed_filename(TSRMLS_C),
+                               zend_get_executed_lineno(TSRMLS_C) TSRMLS_CC);
+                       break;
+               case ADDR_PARAM:
+                       phpdbg_set_breakpoint_opline(param->addr TSRMLS_CC);
+                       break;
+               case NUMERIC_PARAM:
+                       if (PHPDBG_G(exec)) {
+                               phpdbg_set_breakpoint_file(phpdbg_current_file(TSRMLS_C), param->num TSRMLS_CC);
+                       } else {
+                               phpdbg_error("Execution context not set!");
+                       }
+                       break;
+               case METHOD_PARAM:
+                       phpdbg_set_breakpoint_method(param->method.class, param->method.name TSRMLS_CC);
+                       break;
+               case NUMERIC_METHOD_PARAM:
+                       phpdbg_set_breakpoint_method_opline(param->method.class, param->method.name, param->num TSRMLS_CC);
+                       break;
+               case NUMERIC_FUNCTION_PARAM:
+                       phpdbg_set_breakpoint_function_opline(param->str, param->num TSRMLS_CC);
+                       break;
+               case FILE_PARAM:
+                       phpdbg_set_breakpoint_file(param->file.name, param->file.line TSRMLS_CC);
+                       break;
+               case STR_PARAM:
+                       phpdbg_set_breakpoint_symbol(param->str, param->len TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(shell) /* {{{ */
+{
+       /* don't allow this to loop, ever ... */
+       switch (param->type) {
+               case STR_PARAM: {
+                       FILE *fd = NULL;
+                       if ((fd=VCWD_POPEN((char*)param->str, "w"))) {
+                               /* do something perhaps ?? do we want input ?? */
+                               fclose(fd);
+                       } else {
+                               phpdbg_error(
+                                       "Failed to execute %s", param->str);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(source) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM: {
+                       if (input->argc > 2) {
+                               if (phpdbg_argv_is(1, "export")) {
+                                       FILE *h = VCWD_FOPEN(input->argv[2]->string, "w+");
+                                       if (h) {
+                                               phpdbg_export_breakpoints(h TSRMLS_CC);
+                                               fclose(h);
+                                       } else phpdbg_error("Failed to open %s", input->argv[1]->string);
+                               } else {
+                                       phpdbg_error(
+                                               "Incorrect usage of source command, see help");
+                               }
+                       } else {
+                               struct stat sb;
+                               if (VCWD_STAT(param->str, &sb) != -1) {
+                                       phpdbg_try_file_init(param->str, param->len, 0 TSRMLS_CC);
+                               } else phpdbg_error("Cannot stat %s", param->str);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(register) /* {{{ */
+{
+       switch (param->type) {
+               case STR_PARAM: {
+                       zend_function *function;
+                       char *lcname = zend_str_tolower_dup(param->str, param->len);
+                       size_t lcname_len = strlen(lcname);
+
+                       if (!zend_hash_exists(&PHPDBG_G(registered), lcname, lcname_len+1)) {
+                               if (zend_hash_find(EG(function_table), lcname, lcname_len+1, (void**) &function) == SUCCESS) {
+                                       zend_hash_update(
+                                               &PHPDBG_G(registered), lcname, lcname_len+1, (void*)&function, sizeof(zend_function), NULL);
+                                       function_add_ref(function);
+
+                                       phpdbg_notice(
+                                               "Registered %s", lcname);
+                               } else {
+                                       phpdbg_error("The requested function (%s) could not be found", param->str);
+                               }
+                       } else {
+                               phpdbg_error(
+                                       "The requested name (%s) is already in use", lcname);
+                       }
+
+                       efree(lcname);
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(quit) /* {{{ */
+{
+       /* don't allow this to loop, ever ... */
+       if (!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING)) {
+
+               phpdbg_destroy_input((phpdbg_input_t**)&input TSRMLS_CC);
+
+               PHPDBG_G(flags) |= PHPDBG_IS_QUITTING;
+               zend_bailout();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(clean) /* {{{ */
+{
+       if (EG(in_execution)) {
+               phpdbg_error("Cannot clean environment while executing");
+               return SUCCESS;
+       }
+
+       phpdbg_notice("Cleaning Execution Environment");
+
+       phpdbg_writeln("Classes\t\t\t%d", zend_hash_num_elements(EG(class_table)));
+       phpdbg_writeln("Functions\t\t%d", zend_hash_num_elements(EG(function_table)));
+       phpdbg_writeln("Constants\t\t%d", zend_hash_num_elements(EG(zend_constants)));
+       phpdbg_writeln("Includes\t\t%d", zend_hash_num_elements(&EG(included_files)));
+
+       phpdbg_clean(1 TSRMLS_CC);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(clear) /* {{{ */
+{
+       phpdbg_notice("Clearing Breakpoints");
+
+       phpdbg_writeln("File\t\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE]));
+       phpdbg_writeln("Functions\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_SYM]));
+       phpdbg_writeln("Methods\t\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD]));
+       phpdbg_writeln("Oplines\t\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_OPLINE]));
+       phpdbg_writeln("File oplines\t\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_FILE_OPLINE]));
+       phpdbg_writeln("Function oplines\t\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_FUNCTION_OPLINE]));
+       phpdbg_writeln("Method oplines\t\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_METHOD_OPLINE]));
+       phpdbg_writeln("Conditionals\t\t%d", zend_hash_num_elements(&PHPDBG_G(bp)[PHPDBG_BREAK_COND]));
+
+       phpdbg_clear_breakpoints(TSRMLS_C);
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(aliases) /* {{{ */
+{
+       const phpdbg_command_t *prompt_command = phpdbg_prompt_commands;
+
+       phpdbg_help_header();
+       phpdbg_writeln("Below are the aliased, short versions of all supported commands");
+       while (prompt_command && prompt_command->name) {
+               if (prompt_command->alias) {
+                       if (prompt_command->subs) {
+                               const phpdbg_command_t *sub_command = prompt_command->subs;
+                               phpdbg_writeln(EMPTY);
+                               phpdbg_writeln(" %c -> %9s", prompt_command->alias, prompt_command->name);
+                               while (sub_command && sub_command->name) {
+                                       if (sub_command->alias) {
+                                               phpdbg_writeln(" |-------- %c -> %15s\t%s", sub_command->alias,
+                                                       sub_command->name, sub_command->tip);
+                                       }
+                                       ++sub_command;
+                               }
+                               phpdbg_writeln(EMPTY);
+                       } else {
+                               phpdbg_writeln(" %c -> %9s\t\t\t%s", prompt_command->alias,
+                                       prompt_command->name, prompt_command->tip);
+                       }
+               }
+
+               ++prompt_command;
+       }
+       phpdbg_help_footer();
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(help) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM: {
+                       const phpdbg_command_t *prompt_command = phpdbg_prompt_commands;
+                       const phpdbg_command_t *help_command = phpdbg_help_commands;
+
+                       phpdbg_help_header();
+                       phpdbg_writeln("To get help regarding a specific command type \"help command\"");
+
+                       phpdbg_notice("Commands");
+
+                       while (prompt_command && prompt_command->name) {
+                               phpdbg_writeln(
+                                       " %10s\t%s", prompt_command->name, prompt_command->tip);
+                               ++prompt_command;
+                       }
+
+                       phpdbg_notice("Help Commands");
+
+                       while (help_command && help_command->name) {
+                               phpdbg_writeln(" %10s\t%s", help_command->name, help_command->tip);
+                               ++help_command;
+                       }
+
+                       phpdbg_help_footer();
+               } break;
+
+               default: {
+                       phpdbg_error(
+                               "No help can be found for the subject \"%s\"", param->str);
+               }
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(quiet) /* {{{ */
+{
+       switch (param->type) {
+               case NUMERIC_PARAM: {
+                       if (param->num) {
+                               PHPDBG_G(flags) |= PHPDBG_IS_QUIET;
+                       } else {
+                               PHPDBG_G(flags) &= ~PHPDBG_IS_QUIET;
+                       }
+                       phpdbg_notice("Quietness %s",
+                               (PHPDBG_G(flags) & PHPDBG_IS_QUIET) ? "enabled" : "disabled");
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_COMMAND(list) /* {{{ */
+{
+       switch (param->type) {
+               case NUMERIC_PARAM:
+               case EMPTY_PARAM:
+                       return PHPDBG_LIST_HANDLER(lines)(PHPDBG_COMMAND_ARGS);
+
+               case FILE_PARAM:
+                       return PHPDBG_LIST_HANDLER(lines)(PHPDBG_COMMAND_ARGS);
+
+               case STR_PARAM:
+                       phpdbg_list_function_byname(param->str, param->len TSRMLS_CC);
+                       break;
+
+               case METHOD_PARAM:
+                       return PHPDBG_LIST_HANDLER(method)(PHPDBG_COMMAND_ARGS);
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+int phpdbg_interactive(TSRMLS_D) /* {{{ */
+{
+       int ret = SUCCESS;
+       phpdbg_input_t *input;
+
+       PHPDBG_G(flags) |= PHPDBG_IS_INTERACTIVE;
+
+       input = phpdbg_read_input(NULL TSRMLS_CC);
+
+       if (input && input->length > 0L) {
+               do {
+                       switch (ret = phpdbg_do_cmd(phpdbg_prompt_commands, input TSRMLS_CC)) {
+                               case FAILURE:
+                                       if (!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING)) {
+                                               if (phpdbg_call_register(input TSRMLS_CC) == FAILURE) {
+                                                       phpdbg_error("Failed to execute %s!", input->string);
+                                               }
+                                       }
+                               break;
+
+                               case PHPDBG_LEAVE:
+                               case PHPDBG_FINISH:
+                               case PHPDBG_UNTIL:
+                               case PHPDBG_NEXT: {
+                                       if (!EG(in_execution)) {
+                                               phpdbg_error("Not running");
+                                       }
+                                       goto out;
+                               }
+                       }
+
+                       phpdbg_destroy_input(&input TSRMLS_CC);
+               } while ((input = phpdbg_read_input(NULL TSRMLS_CC)) && (input->length > 0L));
+
+               if (input && !input->length)
+                       goto last;
+
+       } else {
+last:
+               if (PHPDBG_G(lcmd)) {
+                       ret = PHPDBG_G(lcmd)->handler(
+                                       &PHPDBG_G(lparam), input TSRMLS_CC);
+                       goto out;
+               }
+       }
+
+out:
+       phpdbg_destroy_input(&input TSRMLS_CC);
+
+       if (EG(in_execution)) {
+               phpdbg_restore_frame(TSRMLS_C);
+       }
+
+       PHPDBG_G(flags) &= ~PHPDBG_IS_INTERACTIVE;
+
+       return ret;
+} /* }}} */
+
+void phpdbg_clean(zend_bool full TSRMLS_DC) /* {{{ */
+{
+       /* this is implicitly required */
+       if (PHPDBG_G(ops)) {
+               destroy_op_array(PHPDBG_G(ops) TSRMLS_CC);
+               efree(PHPDBG_G(ops));
+               PHPDBG_G(ops) = NULL;
+       }
+
+       if (full) {
+               PHPDBG_G(flags) |= PHPDBG_IS_CLEANING;
+
+               zend_bailout();
+       }
+} /* }}} */
+
+static inline zend_execute_data *phpdbg_create_execute_data(zend_op_array *op_array, zend_bool nested TSRMLS_DC) /* {{{ */
+{
+#if PHP_VERSION_ID >= 50500
+       return zend_create_execute_data_from_op_array(op_array, nested TSRMLS_CC);
+#else
+
+#undef EX
+#define EX(element) execute_data->element
+#undef EX_CV
+#define EX_CV(var) EX(CVs)[var]
+#undef EX_CVs
+#define EX_CVs() EX(CVs)
+#undef EX_T
+#define EX_T(offset) (*(temp_variable *)((char *) EX(Ts) + offset))
+#undef EX_Ts
+#define EX_Ts() EX(Ts)
+
+       zend_execute_data *execute_data = (zend_execute_data *)zend_vm_stack_alloc(
+               ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)) +
+               ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)) +
+               ZEND_MM_ALIGNED_SIZE(sizeof(temp_variable)) * op_array->T TSRMLS_CC);
+
+       EX(CVs) = (zval***)((char*)execute_data + ZEND_MM_ALIGNED_SIZE(sizeof(zend_execute_data)));
+       memset(EX(CVs), 0, sizeof(zval**) * op_array->last_var);
+       EX(Ts) = (temp_variable *)(((char*)EX(CVs)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2)));
+       EX(fbc) = NULL;
+       EX(called_scope) = NULL;
+       EX(object) = NULL;
+       EX(old_error_reporting) = NULL;
+       EX(op_array) = op_array;
+       EX(symbol_table) = EG(active_symbol_table);
+       EX(prev_execute_data) = EG(current_execute_data);
+       EG(current_execute_data) = execute_data;
+       EX(nested) = nested;
+
+       if (!op_array->run_time_cache && op_array->last_cache_slot) {
+               op_array->run_time_cache = ecalloc(op_array->last_cache_slot, sizeof(void*));
+       }
+
+       if (op_array->this_var != -1 && EG(This)) {
+               Z_ADDREF_P(EG(This)); /* For $this pointer */
+               if (!EG(active_symbol_table)) {
+                       EX_CV(op_array->this_var) = (zval**)EX_CVs() + (op_array->last_var + op_array->this_var);
+                       *EX_CV(op_array->this_var) = EG(This);
+               } else {
+                       if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), (void**)&EX_CV(op_array->this_var))==FAILURE) {
+                               Z_DELREF_P(EG(This));
+                       }
+               }
+       }
+
+       EX(opline) = UNEXPECTED((op_array->fn_flags & ZEND_ACC_INTERACTIVE) != 0) && EG(start_op) ? EG(start_op) : op_array->opcodes;
+       EG(opline_ptr) = &EX(opline);
+
+       EX(function_state).function = (zend_function *) op_array;
+       EX(function_state).arguments = NULL;
+
+       return execute_data;
+#endif
+} /* }}} */
+
+#if PHP_VERSION_ID >= 50500
+void phpdbg_execute_ex(zend_execute_data *execute_data TSRMLS_DC) /* {{{ */
+{
+#else
+void phpdbg_execute_ex(zend_op_array *op_array TSRMLS_DC) /* {{{ */
+{
+       long long flags = 0;
+       zend_ulong address = 0L;
+       zend_execute_data *execute_data;
+       zend_bool nested = 0;
+#endif
+       zend_bool original_in_execution = EG(in_execution);
+       HashTable vars;
+
+#if PHP_VERSION_ID < 50500
+       if (EG(exception)) {
+               return;
+       }
+#endif
+
+       EG(in_execution) = 1;
+
+#if PHP_VERSION_ID >= 50500
+       if (0) {
+zend_vm_enter:
+               execute_data = phpdbg_create_execute_data(EG(active_op_array), 1 TSRMLS_CC);
+       }
+       zend_hash_init(&vars, EG(active_op_array)->last, NULL, NULL, 0);
+#else
+zend_vm_enter:
+       execute_data = phpdbg_create_execute_data(op_array, nested TSRMLS_CC);
+       nested = 1;
+       zend_hash_init(&vars, EG(active_op_array)->last, NULL, NULL, 0);
+#endif
+
+       while (1) {
+       
+               if ((PHPDBG_G(flags) & PHPDBG_BP_RESOLVE_MASK)) {
+                       /* resolve nth opline breakpoints */
+                       phpdbg_resolve_op_array_breaks(EG(active_op_array) TSRMLS_CC);
+               }
+               
+#ifdef ZEND_WIN32
+               if (EG(timed_out)) {
+                       zend_timeout(0);
+               }
+#endif
+
+#define DO_INTERACTIVE() do { \
+       if (!(PHPDBG_G(flags) & PHPDBG_IN_EVAL)) { \
+               phpdbg_list_file( \
+                       zend_get_executed_filename(TSRMLS_C), \
+                       3, \
+                       zend_get_executed_lineno(TSRMLS_C)-1, \
+                       zend_get_executed_lineno(TSRMLS_C) \
+                       TSRMLS_CC \
+               ); \
+       } \
+       \
+       do { \
+               switch (phpdbg_interactive(TSRMLS_C)) { \
+                       case PHPDBG_LEAVE: \
+                       case PHPDBG_FINISH: \
+                       case PHPDBG_UNTIL: \
+                       case PHPDBG_NEXT:{ \
+                               goto next; \
+                       } \
+               } \
+       } while (!(PHPDBG_G(flags) & PHPDBG_IS_QUITTING)); \
+} while (0)
+
+               /* allow conditional breakpoints and
+                       initialization to access the vm uninterrupted */
+               if ((PHPDBG_G(flags) & PHPDBG_IN_COND_BP) ||
+                       (PHPDBG_G(flags) & PHPDBG_IS_INITIALIZING)) {
+                       /* skip possible breakpoints */
+                       goto next;
+               }
+
+               /* perform seek operation */
+               if (PHPDBG_G(flags) & PHPDBG_SEEK_MASK) {
+                       /* current address */
+                       zend_ulong address = (zend_ulong) execute_data->opline;
+
+                       /* run to next line */
+                       if (PHPDBG_G(flags) & PHPDBG_IN_UNTIL) {
+                               if (zend_hash_index_exists(&PHPDBG_G(seek), address)) {
+                                       PHPDBG_G(flags) &= ~PHPDBG_IN_UNTIL;
+                                       zend_hash_clean(
+                                               &PHPDBG_G(seek));
+                               } else {
+                                       /* skip possible breakpoints */
+                                       goto next;
+                               }
+                       }
+
+                       /* run to finish */
+                       if (PHPDBG_G(flags) & PHPDBG_IN_FINISH) {
+                               if (zend_hash_index_exists(&PHPDBG_G(seek), address)) {
+                                       PHPDBG_G(flags) &= ~PHPDBG_IN_FINISH;
+                                       zend_hash_clean(
+                                               &PHPDBG_G(seek));
+                               }
+                               /* skip possible breakpoints */
+                               goto next;
+                       }
+
+                       /* break for leave */
+                       if (PHPDBG_G(flags) & PHPDBG_IN_LEAVE) {
+                               if (zend_hash_index_exists(&PHPDBG_G(seek), address)) {
+                                       PHPDBG_G(flags) &= ~PHPDBG_IN_LEAVE;
+                                       zend_hash_clean(
+                                               &PHPDBG_G(seek));
+                                       phpdbg_notice(
+                                               "Breaking for leave at %s:%u",
+                                               zend_get_executed_filename(TSRMLS_C),
+                                               zend_get_executed_lineno(TSRMLS_C)
+                                       );
+                                       DO_INTERACTIVE();
+                               } else {
+                                       /* skip possible breakpoints */
+                                       goto next;
+                               }
+                       }
+               }
+
+               /* not while in conditionals */
+               phpdbg_print_opline_ex(
+                       execute_data, &vars, 0 TSRMLS_CC);
+
+               /* search for breakpoints */
+               {
+                       phpdbg_breakbase_t *brake;
+
+                       if ((PHPDBG_G(flags) & PHPDBG_BP_MASK) &&
+                               (brake = phpdbg_find_breakpoint(execute_data TSRMLS_CC))) {
+                               phpdbg_hit_breakpoint(
+                                       brake, 1 TSRMLS_CC);
+                               DO_INTERACTIVE();
+                       }
+               }
+
+               if (PHPDBG_G(flags) & PHPDBG_IS_STEPPING) {
+                       DO_INTERACTIVE();
+               }
+
+next:
+               if (PHPDBG_G(flags) & PHPDBG_IS_SIGNALED) {
+                       phpdbg_writeln(EMPTY);
+                       phpdbg_notice("Program received signal SIGINT");
+                       PHPDBG_G(flags) &= ~PHPDBG_IS_SIGNALED;
+                       DO_INTERACTIVE();
+               }
+
+               PHPDBG_G(vmret) = execute_data->opline->handler(execute_data TSRMLS_CC);
+
+               if (PHPDBG_G(vmret) > 0) {
+                       switch (PHPDBG_G(vmret)) {
+                               case 1:
+                                       EG(in_execution) = original_in_execution;
+                                       zend_hash_destroy(&vars);
+                                       return;
+                               case 2:
+#if PHP_VERSION_ID < 50500
+                                       op_array = EG(active_op_array);
+#endif
+                                       zend_hash_destroy(&vars);
+                                       goto zend_vm_enter;
+                                       break;
+                               case 3:
+                                       execute_data = EG(current_execute_data);
+                                       break;
+                               default:
+                                       break;
+                       }
+               }
+       }
+       zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn't happen");
+} /* }}} */
diff --git a/phpdbg_prompt.h b/phpdbg_prompt.h
new file mode 100644 (file)
index 0000000..e6706c7
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_PROMPT_H
+#define PHPDBG_PROMPT_H
+
+/* {{{ */
+void phpdbg_init(char *init_file, size_t init_file_len, zend_bool use_default TSRMLS_DC);
+void phpdbg_try_file_init(char *init_file, size_t init_file_len, zend_bool free_init TSRMLS_DC);
+int phpdbg_interactive(TSRMLS_D);
+int phpdbg_compile(TSRMLS_D);
+void phpdbg_clean(zend_bool full TSRMLS_DC); /* }}} */
+
+/* {{{ phpdbg command handlers */
+PHPDBG_COMMAND(exec);
+PHPDBG_COMMAND(compile);
+PHPDBG_COMMAND(step);
+PHPDBG_COMMAND(next);
+PHPDBG_COMMAND(run);
+PHPDBG_COMMAND(eval);
+PHPDBG_COMMAND(until);
+PHPDBG_COMMAND(finish);
+PHPDBG_COMMAND(leave);
+PHPDBG_COMMAND(frame);
+PHPDBG_COMMAND(print);
+PHPDBG_COMMAND(break);
+PHPDBG_COMMAND(back);
+PHPDBG_COMMAND(list);
+PHPDBG_COMMAND(info);
+PHPDBG_COMMAND(clean);
+PHPDBG_COMMAND(clear);
+PHPDBG_COMMAND(help);
+PHPDBG_COMMAND(quiet);
+PHPDBG_COMMAND(aliases);
+PHPDBG_COMMAND(shell);
+PHPDBG_COMMAND(set);
+PHPDBG_COMMAND(source);
+PHPDBG_COMMAND(register);
+PHPDBG_COMMAND(quit); /* }}} */
+
+/* {{{ prompt commands */
+extern const phpdbg_command_t phpdbg_prompt_commands[]; /* }}} */
+
+/* {{{ */
+#if PHP_VERSION_ID >= 50500
+void phpdbg_execute_ex(zend_execute_data *execute_data TSRMLS_DC);
+#else
+void phpdbg_execute_ex(zend_op_array *op_array TSRMLS_DC);
+#endif /* }}} */
+
+#endif /* PHPDBG_PROMPT_H */
diff --git a/phpdbg_set.c b/phpdbg_set.c
new file mode 100644 (file)
index 0000000..2472e18
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include "phpdbg.h"
+#include "phpdbg_cmd.h"
+#include "phpdbg_set.h"
+#include "phpdbg_utils.h"
+#include "phpdbg_bp.h"
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+PHPDBG_SET(prompt) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM:
+                       phpdbg_writeln("%s", phpdbg_get_prompt(TSRMLS_C));
+                       break;
+
+               case STR_PARAM:
+                       phpdbg_set_prompt(param->str TSRMLS_CC);
+                       break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_SET(break) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM:
+                       phpdbg_writeln("%s",
+                               PHPDBG_G(flags) & PHPDBG_IS_BP_ENABLED ? "on" : "off");
+                       break;
+
+               case STR_PARAM:
+                       if (strncasecmp(param->str, PHPDBG_STRL("on")) == 0) {
+                               phpdbg_enable_breakpoints(TSRMLS_C);
+                       } else if (strncasecmp(param->str, PHPDBG_STRL("off")) == 0) {
+                               phpdbg_disable_breakpoints(TSRMLS_C);
+                       }
+                       break;
+                       
+               case NUMERIC_PARAM: {
+                       if (input->argc > 2) {
+                                       if (phpdbg_argv_is(2, "on")) {
+                                               phpdbg_enable_breakpoint(param->num TSRMLS_CC);
+                                       } else if (phpdbg_argv_is(2, "off")) {
+                                               phpdbg_disable_breakpoint(param->num TSRMLS_CC);
+                                       }
+                       } else {
+                               phpdbg_breakbase_t *brake = phpdbg_find_breakbase(param->num TSRMLS_CC);
+                               if (brake) {
+                                       phpdbg_writeln(
+                                               "%s", brake->disabled ? "off" : "on");
+                               } else {
+                                       phpdbg_error("Failed to find breakpoint #%lx", param->num);
+                               }
+                       }
+               } break;
+
+               default:
+                       phpdbg_error(
+                               "set break used incorrectly: set break [id] <on|off>");
+       }
+
+       return SUCCESS;
+} /* }}} */
+
+#ifndef _WIN32
+PHPDBG_SET(color) /* {{{ */
+{
+       if ((param->type == STR_PARAM) && (input->argc == 3)) {
+               const phpdbg_color_t *color = phpdbg_get_color(
+                       input->argv[2]->string, input->argv[2]->length TSRMLS_CC);
+               int element = PHPDBG_COLOR_INVALID;
+
+               /* @TODO(anyone) make this consistent with other set commands */
+               if (color) {
+                       if (phpdbg_argv_is(1, "prompt")) {
+                               phpdbg_notice(
+                                       "setting prompt color to %s (%s)", color->name, color->code);
+                               element = PHPDBG_COLOR_PROMPT;
+                               if (PHPDBG_G(prompt)[1]) {
+                                       free(PHPDBG_G(prompt)[1]);
+                                       PHPDBG_G(prompt)[1]=NULL;
+                               }
+                       } else if (phpdbg_argv_is(1, "error")) {
+                               phpdbg_notice(
+                                       "setting error color to %s (%s)", color->name, color->code);
+                               element = PHPDBG_COLOR_ERROR;
+
+                       } else if (phpdbg_argv_is(1, "notice")) {
+                               phpdbg_notice(
+                                       "setting notice color to %s (%s)", color->name, color->code);
+                               element = PHPDBG_COLOR_NOTICE;
+
+                       } else goto usage;
+
+                       /* set color for element */
+                       phpdbg_set_color(element, color TSRMLS_CC);
+               } else {
+                       phpdbg_error(
+                               "Failed to find the requested color (%s)", input->argv[2]->string);
+               }
+       } else {
+usage:
+               phpdbg_error(
+                       "set color used incorrectly: set color <prompt|error|notice> <color>");
+       }
+       return SUCCESS;
+} /* }}} */
+
+PHPDBG_SET(colors) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM: {
+                       phpdbg_writeln(
+                               "%s", PHPDBG_G(flags) & PHPDBG_IS_COLOURED ? "on" : "off");
+                       goto done;
+               }
+               
+               case STR_PARAM: {
+                       if (strncasecmp(param->str, PHPDBG_STRL("on")) == 0) {
+                               PHPDBG_G(flags) |= PHPDBG_IS_COLOURED;
+                               goto done;
+                       } else if (strncasecmp(param->str, PHPDBG_STRL("off")) == 0) {
+                               PHPDBG_G(flags) &= ~PHPDBG_IS_COLOURED;
+                               goto done;
+                       }
+               }
+               
+               default:
+                       phpdbg_error(
+                               "set colors used incorrectly: set colors <on|off>");
+       }
+
+done:
+       return SUCCESS;
+} /* }}} */
+#endif
+
+PHPDBG_SET(oplog) /* {{{ */
+{
+       switch (param->type) {
+               case EMPTY_PARAM:
+                       phpdbg_notice(
+                               "Oplog %s", PHPDBG_G(oplog) ? "enabled" : "disabled");
+               break;
+
+               case NUMERIC_PARAM: switch (param->num) {
+                       case 1:
+                               phpdbg_error(
+                                       "An output file must be provided to enable oplog");
+                       break;
+
+                       case 0: {
+                               if (PHPDBG_G(oplog)) {
+                                       phpdbg_notice("Disabling oplog");
+                                       fclose(
+                                               PHPDBG_G(oplog));
+                               } else {
+                                       phpdbg_error("Oplog is not enabled!");
+                               }
+                       } break;
+               } break;
+
+               case STR_PARAM: {
+                       /* open oplog */
+                       FILE *old = PHPDBG_G(oplog);
+
+                       PHPDBG_G(oplog) = fopen(param->str, "w+");
+                       if (!PHPDBG_G(oplog)) {
+                               phpdbg_error("Failed to open %s for oplog", param->str);
+                               PHPDBG_G(oplog) = old;
+                       } else {
+                               if (old) {
+                                       phpdbg_notice("Closing previously open oplog");
+                                       fclose(old);
+                               }
+                               phpdbg_notice("Successfully opened oplog %s", param->str);
+                       }
+               } break;
+
+               phpdbg_default_switch_case();
+       }
+
+       return SUCCESS;
+} /* }}} */
+
diff --git a/phpdbg_set.h b/phpdbg_set.h
new file mode 100644 (file)
index 0000000..1c48786
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_SET_H
+#define PHPDBG_SET_H
+
+#include "phpdbg_cmd.h"
+
+#define PHPDBG_SET(name) PHPDBG_COMMAND(set_##name)
+
+PHPDBG_SET(prompt);
+#ifndef _WIN32
+PHPDBG_SET(color);
+PHPDBG_SET(colors);
+#endif
+PHPDBG_SET(oplog);
+PHPDBG_SET(break);
+
+static const phpdbg_command_t phpdbg_set_commands[] = {
+       PHPDBG_COMMAND_D_EX(prompt,       "usage: set prompt <string>",          'p', set_prompt,       NULL, 0),
+#ifndef _WIN32
+       PHPDBG_COMMAND_D_EX(color,        "usage: set color  <element> <color>", 'c', set_color,        NULL, 1),
+       PHPDBG_COMMAND_D_EX(colors,       "usage: set colors <on|off>",                  'C', set_colors,       NULL, 1),
+#endif
+       PHPDBG_COMMAND_D_EX(oplog,        "usage: set oplog  <output>",          'O', set_oplog,        NULL, 0),
+       PHPDBG_COMMAND_D_EX(break,        "usage: set break [id] <on|off>",      'b', set_break,        NULL, 0),
+       PHPDBG_END_COMMAND
+};
+
+#endif /* PHPDBG_SET_H */
diff --git a/phpdbg_utils.c b/phpdbg_utils.c
new file mode 100644 (file)
index 0000000..86c17a7
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include "zend.h"
+#include "php.h"
+#include "spprintf.h"
+#include "phpdbg.h"
+#include "phpdbg_opcode.h"
+#include "phpdbg_utils.h"
+
+#ifdef _WIN32
+#      include "win32/time.h"
+#endif
+
+ZEND_EXTERN_MODULE_GLOBALS(phpdbg);
+
+/* {{{ color structures */
+const static phpdbg_color_t colors[] = {
+       PHPDBG_COLOR_D("none",             "0;0"),
+
+       PHPDBG_COLOR_D("white",            "0;64"),
+       PHPDBG_COLOR_D("white-bold",       "1;64"),
+       PHPDBG_COLOR_D("white-underline",  "4;64"),
+       PHPDBG_COLOR_D("red",              "0;31"),
+       PHPDBG_COLOR_D("red-bold",         "1;31"),
+       PHPDBG_COLOR_D("red-underline",    "4;31"),
+       PHPDBG_COLOR_D("green",            "0;32"),
+       PHPDBG_COLOR_D("green-bold",       "1;32"),
+       PHPDBG_COLOR_D("green-underline",  "4;32"),
+       PHPDBG_COLOR_D("yellow",           "0;33"),
+       PHPDBG_COLOR_D("yellow-bold",      "1;33"),
+       PHPDBG_COLOR_D("yellow-underline", "4;33"),
+       PHPDBG_COLOR_D("blue",             "0;34"),
+       PHPDBG_COLOR_D("blue-bold",        "1;34"),
+       PHPDBG_COLOR_D("blue-underline",   "4;34"),
+       PHPDBG_COLOR_D("purple",           "0;35"),
+       PHPDBG_COLOR_D("purple-bold",      "1;35"),
+       PHPDBG_COLOR_D("purple-underline", "4;35"),
+       PHPDBG_COLOR_D("cyan",             "0;36"),
+       PHPDBG_COLOR_D("cyan-bold",        "1;36"),
+       PHPDBG_COLOR_D("cyan-underline",   "4;36"),
+       PHPDBG_COLOR_D("black",            "0;30"),
+       PHPDBG_COLOR_D("black-bold",       "1;30"),
+       PHPDBG_COLOR_D("black-underline",  "4;30"),
+       PHPDBG_COLOR_END
+}; /* }}} */
+
+PHPDBG_API int phpdbg_is_numeric(const char *str) /* {{{ */
+{
+       if (!str)
+               return 0;
+
+       for (; *str; str++) {
+               if (isspace(*str) || *str == '-') {
+                       continue;
+               }
+               return isdigit(*str);
+       }
+       return 0;
+} /* }}} */
+
+PHPDBG_API int phpdbg_is_empty(const char *str) /* {{{ */
+{
+       if (!str)
+               return 1;
+
+       for (; *str; str++) {
+               if (isspace(*str)) {
+                       continue;
+               }
+               return 0;
+       }
+       return 1;
+} /* }}} */
+
+PHPDBG_API int phpdbg_is_addr(const char *str) /* {{{ */
+{
+       return str[0] && str[1] && memcmp(str, "0x", 2) == 0;
+} /* }}} */
+
+PHPDBG_API int phpdbg_is_class_method(const char *str, size_t len, char **class, char **method) /* {{{ */
+{
+       char *sep = NULL;
+
+       if (strstr(str, "#") != NULL)
+               return 0;
+
+       if (strstr(str, " ") != NULL)
+               return 0;
+
+       sep = strstr(str, "::");
+
+       if (!sep || sep == str || sep+2 == str+len-1) {
+               return 0;
+       }
+
+       if (class != NULL) {
+       
+               if (str[0] == '\\') {
+                       str++;
+                       len--;
+               }
+               
+               *class = estrndup(str, sep - str);
+               (*class)[sep - str] = 0;
+       }
+
+       if (method != NULL) {
+               *method = estrndup(sep+2, str + len - (sep + 2));
+       }
+
+       return 1;
+} /* }}} */
+
+PHPDBG_API char *phpdbg_resolve_path(const char *path TSRMLS_DC) /* {{{ */
+{
+       char resolved_name[MAXPATHLEN];
+
+       if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL) {
+               return NULL;
+       }
+
+       return estrdup(resolved_name);
+} /* }}} */
+
+PHPDBG_API const char *phpdbg_current_file(TSRMLS_D) /* {{{ */
+{
+       const char *file = zend_get_executed_filename(TSRMLS_C);
+
+       if (memcmp(file, "[no active file]", sizeof("[no active file]")) == 0) {
+               return PHPDBG_G(exec);
+       }
+
+       return file;
+} /* }}} */
+
+PHPDBG_API const zend_function *phpdbg_get_function(const char *fname, const char *cname TSRMLS_DC) /* {{{ */
+{
+       zend_function *func = NULL;
+       size_t fname_len = strlen(fname);
+       char *lcname = zend_str_tolower_dup(fname, fname_len);
+
+       if (cname) {
+               zend_class_entry **ce;
+               size_t cname_len = strlen(cname);
+               char *lc_cname = zend_str_tolower_dup(cname, cname_len);
+               int ret = zend_lookup_class(lc_cname, cname_len, &ce TSRMLS_CC);
+
+               efree(lc_cname);
+
+               if (ret == SUCCESS) {
+                       zend_hash_find(&(*ce)->function_table, lcname, fname_len+1,
+                               (void**)&func);
+               }
+       } else {
+               zend_hash_find(EG(function_table), lcname, fname_len+1,
+                       (void**)&func);
+       }
+
+       efree(lcname);
+       return func;
+} /* }}} */
+
+PHPDBG_API char *phpdbg_trim(const char *str, size_t len, size_t *new_len) /* {{{ */
+{
+       const char *p = str;
+       char *new = NULL;
+
+       while (p && isspace(*p)) {
+               ++p;
+               --len;
+       }
+
+       while (*p && isspace(*(p + len -1))) {
+               --len;
+       }
+
+       if (len == 0) {
+               new = estrndup("", sizeof(""));
+               *new_len = 0;
+       } else {
+               new = estrndup(p, len);
+               *(new + len) = '\0';
+
+               if (new_len) {
+                       *new_len = len;
+               }
+       }
+
+       return new;
+
+} /* }}} */
+
+PHPDBG_API int phpdbg_print(int type TSRMLS_DC, FILE *fp, const char *format, ...) /* {{{ */
+{
+       int rc = 0;
+       char *buffer = NULL;
+       va_list args;
+
+       if (format != NULL && strlen(format) > 0L) {
+               va_start(args, format);
+               vspprintf(&buffer, 0, format, args);
+               va_end(args);
+       }
+
+       /* TODO(anyone) colours */
+
+       switch (type) {
+               case P_ERROR:
+                       if (PHPDBG_G(flags) & PHPDBG_IS_COLOURED) {
+                               rc = fprintf(fp,
+                                               "\033[%sm[%s]\033[0m\n",
+                                               PHPDBG_G(colors)[PHPDBG_COLOR_ERROR]->code, buffer);
+                       } else {
+                               rc = fprintf(fp, "[%s]\n", buffer);
+                       }
+               break;
+
+               case P_NOTICE:
+                       if (PHPDBG_G(flags) & PHPDBG_IS_COLOURED) {
+                               rc = fprintf(fp,
+                                               "\033[%sm[%s]\033[0m\n",
+                                               PHPDBG_G(colors)[PHPDBG_COLOR_NOTICE]->code, buffer);
+                       } else {
+                               rc = fprintf(fp, "[%s]\n", buffer);
+                       }
+               break;
+
+               case P_WRITELN: {
+                       if (buffer) {
+                               rc = fprintf(fp, "%s\n", buffer);
+                       } else {
+                               rc = fprintf(fp, "\n");
+                       }
+               } break;
+
+               case P_WRITE:
+                       if (buffer) {
+                               rc = fprintf(fp, "%s", buffer);
+                       }
+               break;
+
+               /* no formatting on logging output */
+               case P_LOG:
+                       if (buffer) {
+                               struct timeval tp;
+                               if (gettimeofday(&tp, NULL) == SUCCESS) {
+                                       rc = fprintf(fp, "[%ld %.8F]: %s\n", tp.tv_sec, tp.tv_usec / 1000000.00, buffer);
+                               } else {
+                                       rc = FAILURE;
+                               }
+                       }
+                       break;
+       }
+
+       if (buffer) {
+               efree(buffer);
+       }
+
+       return rc;
+} /* }}} */
+
+PHPDBG_API int phpdbg_rlog(FILE *fp, const char *fmt, ...) { /* {{{ */
+       int rc = 0;
+
+       va_list args;
+       struct timeval tp;
+
+       va_start(args, fmt);
+       if (gettimeofday(&tp, NULL) == SUCCESS) {
+               char friendly[100];
+               char *format = NULL, *buffer = NULL;
+
+               strftime(friendly, 100, "%a %b %d %T.%%04d %Y", localtime(&tp.tv_sec));
+               asprintf(
+                       &buffer, friendly, tp.tv_usec/1000);
+               asprintf(
+                       &format, "[%s]: %s\n", buffer, fmt);
+               rc = vfprintf(
+                       fp, format, args);
+
+               free(format);
+               free(buffer);
+       }
+       va_end(args);
+
+       return rc;
+} /* }}} */
+
+PHPDBG_API const phpdbg_color_t *phpdbg_get_color(const char *name, size_t name_length TSRMLS_DC) /* {{{ */
+{
+       const phpdbg_color_t *color = colors;
+
+       while (color && color->name) {
+               if (name_length == color->name_length &&
+                       memcmp(name, color->name, name_length) == SUCCESS) {
+                       phpdbg_debug(
+                               "phpdbg_get_color(%s, %lu): %s", name, name_length, color->code);
+                       return color;
+               }
+               ++color;
+       }
+
+       phpdbg_debug(
+               "phpdbg_get_color(%s, %lu): failed", name, name_length);
+
+       return NULL;
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_color(int element, const phpdbg_color_t *color TSRMLS_DC) /* {{{ */
+{
+       PHPDBG_G(colors)[element] = color;
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_color_ex(int element, const char *name, size_t name_length TSRMLS_DC) /* {{{ */
+{
+       const phpdbg_color_t *color = phpdbg_get_color(name, name_length TSRMLS_CC);
+
+       if (color) {
+               phpdbg_set_color(element, color TSRMLS_CC);
+       } else PHPDBG_G(colors)[element] = colors;
+} /* }}} */
+
+PHPDBG_API const phpdbg_color_t* phpdbg_get_colors(TSRMLS_D) /* {{{ */
+{
+       return colors;
+} /* }}} */
+
+PHPDBG_API void phpdbg_set_prompt(const char *prompt TSRMLS_DC) /* {{{ */
+{
+       /* free formatted prompt */
+       if (PHPDBG_G(prompt)[1]) {
+               free(PHPDBG_G(prompt)[1]);
+               PHPDBG_G(prompt)[1] = NULL;
+       }
+       /* free old prompt */
+       if (PHPDBG_G(prompt)[0]) {
+               free(PHPDBG_G(prompt)[0]);
+               PHPDBG_G(prompt)[0] = NULL;
+       }
+
+       /* copy new prompt */
+       PHPDBG_G(prompt)[0] = strdup(prompt);
+} /* }}} */
+
+PHPDBG_API const char *phpdbg_get_prompt(TSRMLS_D) /* {{{ */
+{
+       /* find cached prompt */
+       if (PHPDBG_G(prompt)[1]) {
+               return PHPDBG_G(prompt)[1];
+       }
+
+       /* create cached prompt */
+       if ((PHPDBG_G(flags) & PHPDBG_IS_COLOURED)) {
+               asprintf(
+                       &PHPDBG_G(prompt)[1], "\033[%sm%s\033[0m ",
+                       PHPDBG_G(colors)[PHPDBG_COLOR_PROMPT]->code,
+                       PHPDBG_G(prompt)[0]);
+       } else {
+               asprintf(
+                       &PHPDBG_G(prompt)[1], "%s ",
+                       PHPDBG_G(prompt)[0]);
+       }
+
+       return PHPDBG_G(prompt)[1];
+} /* }}} */
diff --git a/phpdbg_utils.h b/phpdbg_utils.h
new file mode 100644 (file)
index 0000000..fbc17b7
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2013 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Authors: Felipe Pena <felipe@php.net>                                |
+   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
+   | Authors: Bob Weinand <bwoebi@php.net>                                |
+   +----------------------------------------------------------------------+
+*/
+
+#ifndef PHPDBG_UTILS_H
+#define PHPDBG_UTILS_H
+
+/**
+ * Input scan functions
+ */
+PHPDBG_API int phpdbg_is_numeric(const char*);
+PHPDBG_API int phpdbg_is_empty(const char*);
+PHPDBG_API int phpdbg_is_addr(const char*);
+PHPDBG_API int phpdbg_is_class_method(const char*, size_t, char**, char**);
+PHPDBG_API const char *phpdbg_current_file(TSRMLS_D);
+PHPDBG_API char *phpdbg_resolve_path(const char* TSRMLS_DC);
+PHPDBG_API char *phpdbg_trim(const char*, size_t, size_t*);
+PHPDBG_API const zend_function *phpdbg_get_function(const char*, const char* TSRMLS_DC);
+
+/**
+ * Error/notice/formatting helpers
+ */
+enum {
+       P_ERROR  = 1,
+       P_NOTICE,
+       P_WRITELN,
+       P_WRITE,
+       P_LOG
+};
+
+#ifdef ZTS
+PHPDBG_API int phpdbg_print(int TSRMLS_DC, FILE*, const char*, ...) PHP_ATTRIBUTE_FORMAT(printf, 4, 5);
+#else
+PHPDBG_API int phpdbg_print(int TSRMLS_DC, FILE*, const char*, ...) PHP_ATTRIBUTE_FORMAT(printf, 3, 4);
+#endif
+
+PHPDBG_API int phpdbg_rlog(FILE *stream, const char *fmt, ...);
+
+#define phpdbg_error(fmt, ...)              phpdbg_print(P_ERROR   TSRMLS_CC, PHPDBG_G(io)[PHPDBG_STDOUT], fmt, ##__VA_ARGS__)
+#define phpdbg_notice(fmt, ...)             phpdbg_print(P_NOTICE  TSRMLS_CC, PHPDBG_G(io)[PHPDBG_STDOUT], fmt, ##__VA_ARGS__)
+#define phpdbg_writeln(fmt, ...)            phpdbg_print(P_WRITELN TSRMLS_CC, PHPDBG_G(io)[PHPDBG_STDOUT], fmt, ##__VA_ARGS__)
+#define phpdbg_write(fmt, ...)              phpdbg_print(P_WRITE   TSRMLS_CC, PHPDBG_G(io)[PHPDBG_STDOUT], fmt, ##__VA_ARGS__)
+#define phpdbg_log(fmt, ...)                phpdbg_print(P_LOG     TSRMLS_CC, PHPDBG_G(io)[PHPDBG_STDOUT], fmt, ##__VA_ARGS__)
+
+#define phpdbg_error_ex(out, fmt, ...)      phpdbg_print(P_ERROR   TSRMLS_CC, out, fmt, ##__VA_ARGS__)
+#define phpdbg_notice_ex(out, fmt, ...)     phpdbg_print(P_NOTICE  TSRMLS_CC, out, fmt, ##__VA_ARGS__)
+#define phpdbg_writeln_ex(out, fmt, ...)    phpdbg_print(P_WRITELN TSRMLS_CC, out, fmt, ##__VA_ARGS__)
+#define phpdbg_write_ex(out, fmt, ...)      phpdbg_print(P_WRITE   TSRMLS_CC, out, fmt, ##__VA_ARGS__)
+#define phpdbg_log_ex(out, fmt, ...)        phpdbg_print(P_LOG     TSRMLS_CC, out, fmt, ##__VA_ARGS__)
+
+#if PHPDBG_DEBUG
+#      define phpdbg_debug(fmt, ...) phpdbg_print(P_LOG   TSRMLS_CC, PHPDBG_G(io)[PHPDBG_STDERR], fmt, ##__VA_ARGS__)
+#else
+#      define phpdbg_debug(fmt, ...)
+#endif
+
+/* {{{ For writing blank lines */
+#define EMPTY NULL /* }}} */
+
+/* {{{ For prompt lines */
+#define PROMPT "phpdbg>" /* }}} */
+
+/* {{{ For separation */
+#define SEPARATE "------------------------------------------------" /* }}} */
+
+/* {{{ Color Management */
+#define PHPDBG_COLOR_LEN 12
+#define PHPDBG_COLOR_D(color, code) \
+       {color, sizeof(color)-1, code}
+#define PHPDBG_COLOR_END \
+       {NULL, 0L, {0}}
+
+#define PHPDBG_COLOR_INVALID   -1
+#define PHPDBG_COLOR_PROMPT    0
+#define PHPDBG_COLOR_ERROR             1
+#define PHPDBG_COLOR_NOTICE            2
+#define PHPDBG_COLORS                  3
+
+typedef struct _phpdbg_color_t {
+       char       *name;
+       size_t      name_length;
+       const char  code[PHPDBG_COLOR_LEN];
+} phpdbg_color_t;
+
+PHPDBG_API const phpdbg_color_t *phpdbg_get_color(const char *name, size_t name_length TSRMLS_DC);
+PHPDBG_API void phpdbg_set_color(int element, const phpdbg_color_t *color TSRMLS_DC);
+PHPDBG_API void phpdbg_set_color_ex(int element, const char *name, size_t name_length TSRMLS_DC);
+PHPDBG_API const phpdbg_color_t* phpdbg_get_colors(TSRMLS_D); /* }}} */
+
+/* {{{ Prompt Management */
+PHPDBG_API void phpdbg_set_prompt(const char* TSRMLS_DC);
+PHPDBG_API const char *phpdbg_get_prompt(TSRMLS_D); /* }}} */
+
+#endif /* PHPDBG_UTILS_H */
diff --git a/test.php b/test.php
new file mode 100644 (file)
index 0000000..5fdbcbe
--- /dev/null
+++ b/test.php
@@ -0,0 +1,51 @@
+<?php
+if (isset($include)) {
+       include (sprintf("%s/web-bootstrap.php", dirname(__FILE__)));
+}
+
+$stdout = fopen("php://stdout", "w+");
+
+class phpdbg {
+    public function isGreat($greeting = null) {
+        printf(
+            "%s: %s\n", __METHOD__, $greeting);
+        return $this;
+    }
+}
+
+function test($x, $y = 0) {
+       $var = $x + 1;
+       $var += 2;
+       $var <<= 3;
+
+       $foo = function () {
+               echo "bar!\n";
+       };
+
+       $foo();
+
+       yield $var;
+}
+
+$dbg = new phpdbg();
+
+var_dump(
+    $dbg->isGreat("PHP Rocks!!"));
+
+foreach (test(1,2) as $gen)
+       continue;
+
+echo "it works!\n";
+
+if (isset($dump))
+       var_dump($_SERVER);
+
+function phpdbg_test_ob()
+{
+       echo 'Start';
+       ob_start();
+       echo 'Hello';
+       $b = ob_get_clean();
+       echo 'End';
+       echo $b;
+}
diff --git a/tests/commands/0001_basic.test b/tests/commands/0001_basic.test
new file mode 100644 (file)
index 0000000..08aa9ab
--- /dev/null
@@ -0,0 +1,8 @@
+#######################################################
+# name: basic
+# purpose: check basic functionality of phpdbg console
+# expect: TEST::EXACT
+# options: -rr
+#######################################################
+# [Nothing to execute!]
+#######################################################
diff --git a/tests/commands/0002_set.test b/tests/commands/0002_set.test
new file mode 100644 (file)
index 0000000..7720f94
--- /dev/null
@@ -0,0 +1,23 @@
+#################################################
+# name: set
+# purpose: tests for set commands
+# expect: TEST::CISTRING
+# options: -rr
+#################################################
+# setting prompt color
+# setting error color
+# setting notice color
+# Failed to find breakpoint #0
+# oplog disabled
+# not enabled
+# opened oplog test.log
+# nothing
+#################################################
+set color prompt none
+set color error none
+set color notice none
+set prompt promot>
+set break 0
+set oplog
+set oplog 0
+set oplog test.log
diff --git a/tests/commands/0101_info.test b/tests/commands/0101_info.test
new file mode 100644 (file)
index 0000000..397a45c
--- /dev/null
@@ -0,0 +1,19 @@
+#################################################
+# name: info
+# purpose: test info commands
+# expect: TEST::FORMAT
+# options: -rr
+#################################################
+#[User Classes (%d)]
+#User Class test (3)
+#|---- in phpdbginit code on line %d
+#################################################
+<:
+class test {
+        public function testMethod(){}
+        private function testPrivateMethod(){}
+        protected function testProtectedMethod(){}
+}
+:>
+info classes
+q
diff --git a/tests/commands/0102_print.test b/tests/commands/0102_print.test
new file mode 100644 (file)
index 0000000..de4acb7
--- /dev/null
@@ -0,0 +1,28 @@
+#################################################
+# name: print
+# purpose: test print commands
+# expect: TEST::FORMAT
+# options: -rr
+#################################################
+#[User Class: test]
+#Methods (3):
+#L%d-%d test::testMethod() %s
+#              L%d     %s ZEND_RETURN                    C%d                   <unused>             <unused>            
+#      L%d-%d test::testPrivateMethod() %s
+#              L%d     %s ZEND_RETURN                    C%d                   <unused>             <unused>            
+#      L%d-%d test::testProtectedMethod() %s
+#              L%d     %s ZEND_RETURN                    C%d                   <unused>             <unused>            
+#[User Method testMethod]
+#      L%d-%d test::testMethod() %s
+#              L%d     %s ZEND_RETURN                    C%d                   <unused>             <unused>   
+#################################################
+<:
+class test {
+        public function testMethod(){}
+        private function testPrivateMethod(){}
+        protected function testProtectedMethod(){}
+}
+:>
+print class test
+print method test::testMethod
+q
diff --git a/tests/commands/0103_register.test b/tests/commands/0103_register.test
new file mode 100644 (file)
index 0000000..3884159
--- /dev/null
@@ -0,0 +1,28 @@
+#################################################
+# name: register
+# purpose: test registration functions
+# expect: TEST::FORMAT
+# options: -rr
+#################################################
+#[Registered test_function]
+#array(5) {
+#  [0]=>
+#  string(1) "1"
+#  [1]=>
+#  string(1) "2"
+#  [2]=>
+#  string(1) "3"
+#  [3]=>
+#  string(1) "4"
+#  [4]=>
+#  string(1) "5"
+#}
+#################################################
+<:
+function test_function() {
+       var_dump(func_get_args());
+}
+:>
+R test_function
+test_function 1 2 3 4 5
+q
diff --git a/tests/commands/0104_clean.test b/tests/commands/0104_clean.test
new file mode 100644 (file)
index 0000000..c7a579b
--- /dev/null
@@ -0,0 +1,15 @@
+#################################################
+# name: clean
+# purpose: test cleaning environment
+# expect: TEST::FORMAT
+# options: -rr
+#################################################
+#[Cleaning Execution Environment]
+#Classes                       %d
+#Functions             %d
+#Constants             %d
+#Includes              %d
+#[Nothing to execute!]
+#################################################
+clean
+quit
diff --git a/tests/commands/0105_clear.test b/tests/commands/0105_clear.test
new file mode 100644 (file)
index 0000000..b547b0d
--- /dev/null
@@ -0,0 +1,18 @@
+#################################################
+# name: clear
+# purpose: test clearing breakpoints
+# expect: TEST::FORMAT
+# options: -rr
+#################################################
+#[Clearing Breakpoints]
+#File%w%d
+#Functions%w%d
+#Methods%w%d
+#Oplines%w%d
+#File oplines%w%d
+#Function oplines%w%d
+#Method oplines%w%d
+#Conditionals%w%d
+#################################################
+clear
+quit
diff --git a/tests/commands/0106_compile.test b/tests/commands/0106_compile.test
new file mode 100644 (file)
index 0000000..d79211d
--- /dev/null
@@ -0,0 +1,19 @@
+#################################################
+# name: compile
+# purpose: test compiling code
+# expect: TEST::FORMAT
+# options: -rr
+#################################################
+#[Attempting compilation of %s]
+#[Success]
+#Hello World
+#################################################
+<:
+define('OUT', 
+       tempnam(null, "phpdbg"));
+file_put_contents(OUT, "<?php echo \"Hello World\"; ?>");
+phpdbg_exec(OUT);
+:>
+compile
+run
+quit
diff --git a/tests/run-tests.php b/tests/run-tests.php
new file mode 100644 (file)
index 0000000..1fb6fa1
--- /dev/null
@@ -0,0 +1,583 @@
+<?php
+namespace phpdbg\testing {
+
+       /* 
+       * Workaround ...
+       */
+       if (!defined('DIR_SEP'))
+               define('DIR_SEP', '\\' . DIRECTORY_SEPARATOR);
+               
+       /**
+       * TestConfigurationExceptions are thrown 
+       * when the configuration prohibits tests executing
+       *
+       * @package phpdbg
+       * @subpackage testing
+       */
+       class TestConfigurationException extends \Exception {
+               
+               /** 
+               *
+               * @param array Tests confguration
+               * @param message Exception message
+               * @param ... formatting parameters
+               */
+               public function __construct() {
+                       $argv = func_get_args();
+                       
+                       if (count($argv)) {
+                       
+                               $this->config = array_shift($argv);
+                               $this->message = vsprintf(
+                                       array_shift($argv), $argv);
+                       }
+               }
+       }
+       
+       /**
+       *
+       * @package phpdbg
+       * @subpackage testing
+       */
+       class TestsConfiguration implements \ArrayAccess {
+       
+               /**
+               *
+               * @param array basic configuration
+               * @param array argv
+               */
+               public function __construct($config, $cmd) {
+                       $this->options = $config;
+                       while (($key = array_shift($cmd))) {
+                               switch (substr($key, 0, 1)) {
+                                       case '-': switch(substr($key, 1, 1)) {
+                                               case '-': {
+                                                       $arg = substr($key, 2);
+                                                       if (($e=strpos($arg, '=')) !== false) {
+                                                               $key = substr($arg, 0, $e);
+                                                               $value = substr($arg, $e+1);
+                                                       } else {
+                                                               $key = $arg;
+                                                               $value = array_shift($cmd);
+                                                       }
+                                       
+                                                       if (isset($key) && isset($value)) {
+                                                               switch ($key) {
+                                                                       case 'phpdbg':
+                                                                       case 'width':
+                                                                               $this->options[$key] = $value;
+                                                                       break;
+                                                                       
+                                                                       default: {
+                                                                               if (isset($config[$key])) {
+                                                                                       if (is_array($config[$key])) {
+                                                                                               $this->options[$key][] = $value;
+                                                                                       } else {
+                                                                                               $this->options[$key] = array($config[$key], $value);
+                                                                                       }
+                                                                               } else {
+                                                                                       $this->options[$key] = $value;
+                                                                               }
+                                                                       }       
+                                                               }
+                                                               
+                                                       }
+                                               } break;
+                               
+                                               default:
+                                                       $this->flags[] = substr($key, 1);
+                                       } break;
+                               }
+                       }
+                       
+                       if (!is_executable($this->options['phpdbg'])) {
+                               throw new TestConfigurationException(
+                                       $this->options, 'phpdbg could not be found at the specified path (%s)', $this->options['phpdbg']);
+                       } else $this->options['phpdbg'] = realpath($this->options['phpdbg']);
+                       
+                       $this->options['width'] = (integer) $this->options['width'];
+                       
+                       /* display properly, all the time */
+                       if ($this->options['width'] < 50) {
+                               $this->options['width'] = 50;
+                       }
+                       
+                       /* calculate column widths */
+                       $this->options['lwidth'] = ceil($this->options['width'] / 3);
+                       $this->options['rwidth'] = ceil($this->options['width'] - $this->options['lwidth']) - 5;
+               }
+               
+               public function hasFlag($flag) { 
+                       return in_array(
+                               $flag, $this->flags); 
+               }
+               
+               public function offsetExists($offset)           { return isset($this->options[$offset]); }
+               public function offsetGet($offset)                      { return $this->options[$offset]; }
+               public function offsetUnset($offset)            { unset($this->options[$offset]); }
+               public function offsetSet($offset, $data)       { $this->options[$offset] = $data; }
+               
+               protected $options = array();
+               protected $flags = array();
+       }
+       
+       /**
+       * Tests is the console programming API for the test suite
+       *
+       * @package phpdbg
+       * @subpackage testing
+       */
+       class Tests {
+       
+               /**
+               * Construct the console object
+               *
+               * @param array basic configuration 
+               * @param array command line
+               */
+               public function __construct(TestsConfiguration &$config) {
+                       $this->config = &$config;
+                       
+                       if ($this->config->hasFlag('help') ||
+                               $this->config->hasFlag('h')) {
+                               $this->showUsage();
+                               exit;
+                       }
+               }
+               
+               /**
+               * Find valid paths as specified by configuration 
+               *
+               */
+               public function findPaths($in = null) {
+                       $paths = array();
+                       $where = ($in != null) ? array($in) : $this->config['path'];
+                       
+                       foreach ($where as &$path) {
+                               if ($path) {
+                                       if (is_dir($path)) {
+                                               $paths[] = $path;
+                                               foreach (scandir($path) as $child) {
+                                                       if ($child != '.' && $child != '..') {
+                                                               $paths = array_merge(
+                                                                       $paths, $this->findPaths("$path/$child"));
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       
+                       return $paths;
+               }
+               
+               /**
+               *
+               * @param string the path to log
+               */
+               public function logPath($path) {
+                       printf(
+                               '%s [%s]%s', 
+                               str_repeat(
+                                       '-', $this->config['width'] - strlen($path)), 
+                               $path, PHP_EOL);
+               }
+               
+               /**
+               *
+               * @param string the path to log
+               */
+               public function logPathStats($path) {
+                       if (!isset($this->stats[$path])) {
+                               return;
+                       }
+                       
+                       $total = array_sum($this->stats[$path]);
+                       
+                       if ($total) {
+                               @$this->totals[true] += $this->stats[$path][true];
+                               @$this->totals[false] += $this->stats[$path][false];
+                       
+                               $stats = @sprintf(
+                                       "%d/%d %%%d", 
+                                       $this->stats[$path][true],
+                                       $this->stats[$path][false],
+                                       (100 / $total) * $this->stats[$path][true]);
+                       
+                               printf(
+                                       '%s [%s]%s',
+                                       str_repeat(
+                                               ' ', $this->config['width'] - strlen($stats)), 
+                                       $stats, PHP_EOL);
+                       
+                               printf("%s%s", str_repeat('-', $this->config['width']+3), PHP_EOL);
+                               printf("%s", PHP_EOL);
+                       }
+               }
+               
+               /**
+               *
+               */
+               public function logStats() {
+                       $total = array_sum($this->totals);
+                       $stats = @sprintf(
+                               "%d/%d %%%d",
+                               $this->totals[true],
+                               $this->totals[false],
+                               (100 / $total) * $this->totals[true]);
+                       printf(
+                               '%s [%s]%s',
+                               str_repeat(
+                                       ' ', $this->config['width'] - strlen($stats)), 
+                               $stats, PHP_EOL);
+                       
+               }
+               
+               /**
+               *
+               */
+               protected function showUsage() {
+                       printf('usage: php %s [flags] [options]%s', $this->config['exec'], PHP_EOL);
+                       printf('[options]:%s', PHP_EOL);
+                       printf("\t--path\t\tadd a path to scan outside of tests directory%s", PHP_EOL);
+                       printf("\t--width\t\tset line width%s", PHP_EOL);
+                       printf("\t--options\toptions to pass to phpdbg%s", PHP_EOL);
+                       printf("\t--phpdbg\tpath to phpdbg binary%s", PHP_EOL);
+                       printf('[flags]:%s', PHP_EOL);
+                       printf("\t-nodiff\t\tdo not write diffs on failure%s", PHP_EOL);
+                       printf("\t-nolog\t\tdo not write logs on failure%s", PHP_EOL);
+                       printf('[examples]:%s', PHP_EOL);
+                       printf("\tphp %s --phpdbg=/usr/local/bin/phpdbg --path=/usr/src/phpdbg/tests --options -n%s", 
+                               $this->config['exec'], PHP_EOL);
+                       
+               }
+               
+               /**
+               * Find valid tests at the specified path (assumed valid)
+               *
+               * @param string a valid path
+               */
+               public function findTests($path) {
+                       $tests = array();
+                       
+                       foreach (scandir($path) as $file) {
+                               if ($file == '.' || $file == '..') 
+                                       continue;
+                       
+                               $test = sprintf('%s/%s', $path, $file);
+
+                               if (preg_match('~\.test$~', $test)) {
+                                       yield new Test($this->config, $test);
+                               }
+                       }
+               }
+               
+               /**
+               *
+               * @param Test the test to log
+               */
+               public function logTest($path, Test $test) {
+                       @$this->stats[$path][($result=$test->getResult())]++;
+                       
+                       printf(
+                               "%-{$this->config['lwidth']}s %-{$this->config['rwidth']}s [%s]%s",
+                               $test->name, 
+                               $test->purpose, 
+                               $result ? "PASS" : "FAIL",
+                               PHP_EOL);
+               }
+               
+               protected $config;
+       }
+       
+       class Test {
+               /*
+               * Expect exact line for line match
+               */
+               const EXACT =           0x00000001;
+               
+               /*
+               * Expect strpos() !== false
+               */
+               const STRING =          0x00000010;
+               
+               /*
+               * Expect stripos() !== false
+               */
+               const CISTRING =        0x00000100;
+               
+               /*
+               * Formatted output
+               */
+               const FORMAT =          0x00001000;
+               
+               /**
+               * Format specifiers
+               */
+               private static $format = array(
+                       'search' => array(
+                               '%e',
+                               '%s',
+                               '%S',
+                               '%a',
+                               '%A',
+                               '%w',
+                               '%i',
+                               '%d',
+                               '%x',
+                               '%f',
+                               '%c',
+                               '%t',
+                               '%T'
+                       ),
+                       'replace' => array(
+                               DIR_SEP,
+                               '[^\r\n]+',
+                               '[^\r\n]*',
+                               '.+',
+                               '.*',
+                               '\s*',
+                               '[+-]?\d+',
+                               '\d+',
+                               '[0-9a-fA-F]+',
+                               '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?',
+                               '.',
+                               '\t',
+                               '\t+'
+                       )
+               );
+               
+               /**
+               * Constructs a new Test object given a specilized phpdbginit file
+               *
+               * @param array configuration
+               * @param string file
+               */
+               public function __construct(TestsConfiguration &$config, &$file) {
+                       if (($handle = fopen($file, 'r'))) {
+                               while (($line = fgets($handle))) {
+                                       $trim = trim($line);
+                                       
+                                       switch (substr($trim, 0, 1)) {
+                                               case '#': if (($chunks = array_map('trim', preg_split('~:~', substr($trim, 1), 2)))) {
+                                                       if (property_exists($this, $chunks[0])) {
+                                                               switch ($chunks[0]) {
+                                                                       case 'expect': {
+                                                                               if ($chunks[1]) {
+                                                                                       switch (strtoupper($chunks[1])) {
+                                                                                               case 'TEST::EXACT':
+                                                                                               case 'EXACT': { $this->expect = TEST::EXACT; } break;
+                                                                                               
+                                                                                               case 'TEST::STRING':
+                                                                                               case 'STRING': { $this->expect = TEST::STRING; } break;
+                                                                                               
+                                                                                               case 'TEST::CISTRING':
+                                                                                               case 'CISTRING': { $this->expect = TEST::CISTRING; } break;
+                                                                                               
+                                                                                               case 'TEST::FORMAT':
+                                                                                               case 'FORMAT': { $this->expect = TEST::FORMAT; } break;
+                                                                                               
+                                                                                               default: 
+                                                                                                       throw new TestConfigurationException(
+                                                                                                               $this->config, "unknown type of expectation (%s)", $chunks[1]);
+                                                                                       }
+                                                                               }
+                                                                       } break;
+                                                                       
+                                                                       default: {
+                                                                               $this->$chunks[0] = $chunks[1];
+                                                                       }       
+                                                               }
+                                                       } else switch(substr($trim, 1, 1)) {
+                                                               case '#': { /* do nothing */ } break;
+                                                               
+                                                               default: {
+                                                                       $line = preg_replace(
+                                                                               "~(\r\n)~", "\n", substr($trim, 1));
+                                                                       
+                                                                       $line = trim($line);
+                                                                       
+                                                                       switch ($this->expect) {
+                                                                               case TEST::FORMAT:
+                                                                                       $this->match[] = str_replace(
+                                                                                               self::$format['search'], 
+                                                                                               self::$format['replace'], preg_quote($line));
+                                                                               break;
+                                                                               
+                                                                               default: $this->match[] = $line;
+                                                                       }
+                                                               }
+                                                       }
+                                               } break;
+
+                                               default:
+                                                       break 2;
+                                       }
+                               }
+                               fclose($handle);
+                               
+                               $this->config = &$config;
+                               $this->file = &$file;
+                       }
+               }
+               
+               /**
+               * Obvious!! 
+               * 
+               */
+               public function getResult() {
+                       $options = sprintf(
+                               '-i%s -qb', $this->file);
+                       
+                       if ($this->options) {
+                               $options = sprintf(
+                                       '%s %s %s',
+                                       $options, 
+                                       $this->config['options'], 
+                                       $this->options
+                               );
+                       } else {
+                               $options = sprintf(
+                                       '%s %s', $options, $this->config['options']
+                               );
+                       }
+
+                       $result = `{$this->config['phpdbg']} {$options}`;
+
+                       if ($result) {
+                               foreach (preg_split('~(\r|\n)~', $result) as $num => $line) {
+                                       if (!$line && !isset($this->match[$num]))
+                                               continue;
+                                       
+                                       switch ($this->expect) {
+                                               case TEST::EXACT: {
+                                                       if (strcmp($line, $this->match[$num]) !== 0) {
+                                                               $this->diff['wants'][$num] = &$this->match[$num];
+                                                               $this->diff['gets'][$num] = $line;
+                                                       }
+                                               } continue 2;
+
+                                               case TEST::STRING: {
+                                                       if (strpos($line, $this->match[$num]) === false) {
+                                                               $this->diff['wants'][$num] = &$this->match[$num];
+                                                               $this->diff['gets'][$num] = $line;
+                                                       }
+                                               } continue 2;
+                                               
+                                               case TEST::CISTRING: {
+                                                       if (stripos($line, $this->match[$num]) === false) {
+                                                               $this->diff['wants'][$num] = &$this->match[$num];
+                                                               $this->diff['gets'][$num] = $line;
+                                                       }
+                                               } continue 2;
+                                               
+                                               case TEST::FORMAT: {
+                                                       $line = trim($line);
+                                                       if (!preg_match("/^{$this->match[$num]}\$/s", $line)) {
+                                                               $this->diff['wants'][$num] = &$this->match[$num];
+                                                               $this->diff['gets'][$num] = $line;
+                                                       }
+                                               } continue 2;
+                                       }
+                               }
+                       }
+                       
+                       $this->writeLog($result);
+                       $this->writeDiff();
+                       
+                       return (count($this->diff) == 0);
+               }
+               
+               /**
+               * Write diff to disk if configuration allows it
+               *
+               */
+               protected function writeDiff() {
+                       $diff = sprintf(
+                               '%s/%s.diff',
+                               dirname($this->file), basename($this->file));
+                               
+                       if (count($this->diff['wants'])) {
+                               if (!in_array('nodiff', $this->config['flags'])) {
+                                       if (($diff = fopen($diff, 'w+'))) {
+
+                                               foreach ($this->diff['wants'] as $line => $want) {
+                                                       $got = $this->diff['gets'][$line];
+                                               
+                                                       fprintf(
+                                                               $diff, '(%d) -%s%s', $line+1, $want, PHP_EOL);
+                                                       fprintf(
+                                                               $diff, '(%d) +%s%s', $line+1, $got, PHP_EOL);
+                                               }
+
+                                               fclose($diff);
+                                       }
+                               }
+                       } else unlink($diff);
+               }
+               
+               /**
+               * Write log to disk if configuration allows it
+               *
+               */
+               protected function writeLog(&$result = null) {
+                       $log = sprintf(
+                               '%s/%s.log',
+                               dirname($this->file), basename($this->file));
+
+                       if (count($this->diff) && $result) {
+                               if (!in_array('nolog', $this->config['flags'])) {
+                                       @file_put_contents(
+                                               $log, $result);
+                               }
+                       } else unlink($log);
+               }
+               
+               public $name;
+               public $purpose;
+               public $file;
+               public $options;
+               public $expect;
+               
+               protected $match;
+               protected $diff;
+               protected $stats;
+               protected $totals;
+       }
+}
+
+namespace {
+       use \phpdbg\Testing\Test;
+       use \phpdbg\Testing\Tests;
+       use \phpdbg\Testing\TestsConfiguration;
+
+       $cwd = dirname(__FILE__);
+       $cmd = $_SERVER['argv'];
+       {
+               $config = new TestsConfiguration(array(
+                       'exec' => realpath(array_shift($cmd)),
+                       'phpdbg' => realpath(sprintf(
+                               '%s/../phpdbg', $cwd
+                       )),
+                       'path' => array(
+                               realpath(dirname(__FILE__))
+                       ),
+                       'flags' => array(),
+                       'width' => 75
+               ), $cmd);
+
+               $tests = new Tests($config);
+
+               foreach ($tests->findPaths() as $path) {        
+                       $tests->logPath($path);
+
+                       foreach ($tests->findTests($path) as $test) {
+                               $tests->logTest($path, $test);
+                       }
+               
+                       $tests->logPathStats($path);
+               }
+               
+               $tests->logStats();
+       }
+}
+?>
diff --git a/travis/ci.sh b/travis/ci.sh
new file mode 100755 (executable)
index 0000000..44d56a0
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env sh
+git clone https://github.com/php/php-src
+cd php-src/sapi
+git clone https://github.com/krakjoe/phpdbg.git
+cd ../
+./buildconf --force
+./configure --disable-all --enable-phpdbg --enable-maintainer-zts
+make
+make test-phpdbg
diff --git a/web-bootstrap.php b/web-bootstrap.php
new file mode 100644 (file)
index 0000000..7b8c5d3
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * The following file shows how to bootstrap phpdbg so that you can mock specific server environments
+ * 
+ * eval include("web-bootstrap.php")
+ * exec index.php
+ * compile
+ * break ...
+ * run
+ */
+if (!defined('PHPDBG_BOOTSTRAPPED')) 
+{
+    /* define these once */
+    define("PHPDBG_BOOTPATH", "/opt/php-zts/htdocs");
+    define("PHPDBG_BOOTSTRAP", "index.php");
+    define("PHPDBG_BOOTSTRAPPED", sprintf("/%s", PHPDBG_BOOTSTRAP)); 
+}
+
+/*
+ * Superglobals are JIT, phpdbg will not over-write whatever you set during bootstrap
+ */
+
+$_SERVER = array 
+(
+  'HTTP_HOST' => 'localhost',
+  'HTTP_CONNECTION' => 'keep-alive',
+  'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+  'HTTP_USER_AGENT' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.65 Safari/537.36',
+  'HTTP_ACCEPT_ENCODING' => 'gzip,deflate,sdch',
+  'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8',
+  'HTTP_COOKIE' => 'tz=Europe%2FLondon; __utma=1.347100075.1384196523.1384196523.1384196523.1; __utmc=1; __utmz=1.1384196523.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)',
+  'PATH' => '/usr/local/bin:/usr/bin:/bin',
+  'SERVER_SIGNATURE' => '<address>Apache/2.4.6 (Ubuntu) Server at phpdbg.com Port 80</address>',
+  'SERVER_SOFTWARE' => 'Apache/2.4.6 (Ubuntu)',
+  'SERVER_NAME' => 'localhost',
+  'SERVER_ADDR' => '127.0.0.1',
+  'SERVER_PORT' => '80',
+  'REMOTE_ADDR' => '127.0.0.1',
+  'DOCUMENT_ROOT' => PHPDBG_BOOTPATH,
+  'REQUEST_SCHEME' => 'http',
+  'CONTEXT_PREFIX' => '',
+  'CONTEXT_DOCUMENT_ROOT' => PHPDBG_BOOTPATH,
+  'SERVER_ADMIN' => '[no address given]',
+  'SCRIPT_FILENAME' => sprintf(
+    '%s/%s', PHPDBG_BOOTPATH, PHPDBG_BOOTSTRAP
+  ),
+  'REMOTE_PORT' => '47931',
+  'GATEWAY_INTERFACE' => 'CGI/1.1',
+  'SERVER_PROTOCOL' => 'HTTP/1.1',
+  'REQUEST_METHOD' => 'GET',
+  'QUERY_STRING' => '',
+  'REQUEST_URI' => PHPDBG_BOOTSTRAPPED,
+  'SCRIPT_NAME' => PHPDBG_BOOTSTRAPPED,
+  'PHP_SELF' => PHPDBG_BOOTSTRAPPED,
+  'REQUEST_TIME' => time(),
+);
+
+$_GET = array();
+$_REQUEST = array();
+$_POST = array();
+$_COOKIE = array();
+$_FILES = array();
+
+chdir(PHPDBG_BOOTPATH);