diff options
author | Bob Weinand <bobwei9@hotmail.com> | 2013-12-20 14:27:51 +0100 |
---|---|---|
committer | Bob Weinand <bobwei9@hotmail.com> | 2013-12-20 14:27:51 +0100 |
commit | cee72c13d7defa8a51c33e2b6ed2c73d85fd4c2a (patch) | |
tree | c64561a3e7385a22ac6d416fb3252208b3a1392c | |
parent | 436ca2d019ed3ec3183129f9a3a7e329446077e9 (diff) | |
parent | ef3e0119b8437c855049ee3a67b0317dce055669 (diff) | |
download | php-git-cee72c13d7defa8a51c33e2b6ed2c73d85fd4c2a.tar.gz |
Merge branch 'master' of sapi/phpdbg into PHP-5.6
Including phpdbg.
48 files changed, 10215 insertions, 0 deletions
diff --git a/sapi/phpdbg/.gdbinit b/sapi/phpdbg/.gdbinit new file mode 100644 index 0000000000..401a4bb88c --- /dev/null +++ b/sapi/phpdbg/.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/sapi/phpdbg/.gitignore b/sapi/phpdbg/.gitignore new file mode 100644 index 0000000000..297efcbc42 --- /dev/null +++ b/sapi/phpdbg/.gitignore @@ -0,0 +1,5 @@ +.libs/ +./phpdbg +*.lo +*.o +build diff --git a/sapi/phpdbg/.phpdbginit b/sapi/phpdbg/.phpdbginit new file mode 100644 index 0000000000..1ad35218ed --- /dev/null +++ b/sapi/phpdbg/.phpdbginit @@ -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/sapi/phpdbg/.travis.yml b/sapi/phpdbg/.travis.yml new file mode 100644 index 0000000000..353402858e --- /dev/null +++ b/sapi/phpdbg/.travis.yml @@ -0,0 +1,3 @@ +language: c + +script: ./travis/ci.sh diff --git a/sapi/phpdbg/Changelog.md b/sapi/phpdbg/Changelog.md new file mode 100644 index 0000000000..c5d8b51514 --- /dev/null +++ b/sapi/phpdbg/Changelog.md @@ -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/sapi/phpdbg/Makefile.frag b/sapi/phpdbg/Makefile.frag new file mode 100644 index 0000000000..5be6d5b00f --- /dev/null +++ b/sapi/phpdbg/Makefile.frag @@ -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/sapi/phpdbg/README.md b/sapi/phpdbg/README.md new file mode 100644 index 0000000000..e7e5c731a8 --- /dev/null +++ b/sapi/phpdbg/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+ + +[](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/sapi/phpdbg/config.m4 b/sapi/phpdbg/config.m4 new file mode 100644 index 0000000000..274e6409d0 --- /dev/null +++ b/sapi/phpdbg/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/sapi/phpdbg/config.w32 b/sapi/phpdbg/config.w32 new file mode 100644 index 0000000000..29031507b3 --- /dev/null +++ b/sapi/phpdbg/config.w32 @@ -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/sapi/phpdbg/phpdbg.c b/sapi/phpdbg/phpdbg.c new file mode 100644 index 0000000000..17193ed244 --- /dev/null +++ b/sapi/phpdbg/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, ¶m TSRMLS_CC); + + switch (type) { + case METHOD_PARAM: + phpdbg_do_break_method(¶m, NULL TSRMLS_CC); + break; + + case FILE_PARAM: + phpdbg_do_break_file(¶m, NULL TSRMLS_CC); + break; + + case NUMERIC_PARAM: + phpdbg_do_break_lineno(¶m, NULL TSRMLS_CC); + break; + + case STR_PARAM: + phpdbg_do_break_func(¶m, NULL TSRMLS_CC); + break; + + default: zend_error( + E_WARNING, "unrecognized parameter type %ld", type); + } + + phpdbg_clear_param(¶m 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/sapi/phpdbg/phpdbg.h b/sapi/phpdbg/phpdbg.h new file mode 100644 index 0000000000..f0a59ce473 --- /dev/null +++ b/sapi/phpdbg/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/sapi/phpdbg/phpdbg.init.d b/sapi/phpdbg/phpdbg.init.d new file mode 100755 index 0000000000..99a1ab328b --- /dev/null +++ b/sapi/phpdbg/phpdbg.init.d @@ -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/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c new file mode 100644 index 0000000000..69e0fa7086 --- /dev/null +++ b/sapi/phpdbg/phpdbg_bp.c @@ -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, ¶m 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/sapi/phpdbg/phpdbg_bp.h b/sapi/phpdbg/phpdbg_bp.h new file mode 100644 index 0000000000..ed1b413f6d --- /dev/null +++ b/sapi/phpdbg/phpdbg_bp.h @@ -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/sapi/phpdbg/phpdbg_break.c b/sapi/phpdbg/phpdbg_break.c new file mode 100644 index 0000000000..1423b960e6 --- /dev/null +++ b/sapi/phpdbg/phpdbg_break.c @@ -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/sapi/phpdbg/phpdbg_break.h b/sapi/phpdbg/phpdbg_break.h new file mode 100644 index 0000000000..04abeb6805 --- /dev/null +++ b/sapi/phpdbg/phpdbg_break.h @@ -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/sapi/phpdbg/phpdbg_cmd.c b/sapi/phpdbg/phpdbg_cmd.c new file mode 100644 index 0000000000..9f052d6f6f --- /dev/null +++ b/sapi/phpdbg/phpdbg_cmd.c @@ -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, + ¶m 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(¶m, 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/sapi/phpdbg/phpdbg_cmd.h b/sapi/phpdbg/phpdbg_cmd.h new file mode 100644 index 0000000000..e779fd4b55 --- /dev/null +++ b/sapi/phpdbg/phpdbg_cmd.h @@ -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/sapi/phpdbg/phpdbg_frame.c b/sapi/phpdbg/phpdbg_frame.c new file mode 100644 index 0000000000..24aff59dd9 --- /dev/null +++ b/sapi/phpdbg/phpdbg_frame.c @@ -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/sapi/phpdbg/phpdbg_frame.h b/sapi/phpdbg/phpdbg_frame.h new file mode 100644 index 0000000000..fbccd5404f --- /dev/null +++ b/sapi/phpdbg/phpdbg_frame.h @@ -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/sapi/phpdbg/phpdbg_help.c b/sapi/phpdbg/phpdbg_help.c new file mode 100644 index 0000000000..edb1265955 --- /dev/null +++ b/sapi/phpdbg/phpdbg_help.c @@ -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/sapi/phpdbg/phpdbg_help.h b/sapi/phpdbg/phpdbg_help.h new file mode 100644 index 0000000000..012a1b49e7 --- /dev/null +++ b/sapi/phpdbg/phpdbg_help.h @@ -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/sapi/phpdbg/phpdbg_info.c b/sapi/phpdbg/phpdbg_info.c new file mode 100644 index 0000000000..1744f59215 --- /dev/null +++ b/sapi/phpdbg/phpdbg_info.c @@ -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/sapi/phpdbg/phpdbg_info.h b/sapi/phpdbg/phpdbg_info.h new file mode 100644 index 0000000000..5d53812370 --- /dev/null +++ b/sapi/phpdbg/phpdbg_info.h @@ -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/sapi/phpdbg/phpdbg_list.c b/sapi/phpdbg/phpdbg_list.c new file mode 100644 index 0000000000..b49be857ef --- /dev/null +++ b/sapi/phpdbg/phpdbg_list.c @@ -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/sapi/phpdbg/phpdbg_list.h b/sapi/phpdbg/phpdbg_list.h new file mode 100644 index 0000000000..d618027158 --- /dev/null +++ b/sapi/phpdbg/phpdbg_list.h @@ -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/sapi/phpdbg/phpdbg_opcode.c b/sapi/phpdbg/phpdbg_opcode.c new file mode 100644 index 0000000000..025d57a08d --- /dev/null +++ b/sapi/phpdbg/phpdbg_opcode.c @@ -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/sapi/phpdbg/phpdbg_opcode.h b/sapi/phpdbg/phpdbg_opcode.h new file mode 100644 index 0000000000..5771488e70 --- /dev/null +++ b/sapi/phpdbg/phpdbg_opcode.h @@ -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/sapi/phpdbg/phpdbg_print.c b/sapi/phpdbg/phpdbg_print.c new file mode 100644 index 0000000000..51edcfbf8d --- /dev/null +++ b/sapi/phpdbg/phpdbg_print.c @@ -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/sapi/phpdbg/phpdbg_print.h b/sapi/phpdbg/phpdbg_print.h new file mode 100644 index 0000000000..1232f544d2 --- /dev/null +++ b/sapi/phpdbg/phpdbg_print.h @@ -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/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c new file mode 100644 index 0000000000..f2f482b7ec --- /dev/null +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -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(¶ms); + + for (param = 0; param < (input->argc-1); param++) { + add_next_index_stringl( + ¶ms, + 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, ¶ms 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/sapi/phpdbg/phpdbg_prompt.h b/sapi/phpdbg/phpdbg_prompt.h new file mode 100644 index 0000000000..e6706c7d5b --- /dev/null +++ b/sapi/phpdbg/phpdbg_prompt.h @@ -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/sapi/phpdbg/phpdbg_set.c b/sapi/phpdbg/phpdbg_set.c new file mode 100644 index 0000000000..2472e1868c --- /dev/null +++ b/sapi/phpdbg/phpdbg_set.c @@ -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/sapi/phpdbg/phpdbg_set.h b/sapi/phpdbg/phpdbg_set.h new file mode 100644 index 0000000000..1c48786c66 --- /dev/null +++ b/sapi/phpdbg/phpdbg_set.h @@ -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/sapi/phpdbg/phpdbg_utils.c b/sapi/phpdbg/phpdbg_utils.c new file mode 100644 index 0000000000..86c17a71be --- /dev/null +++ b/sapi/phpdbg/phpdbg_utils.c @@ -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/sapi/phpdbg/phpdbg_utils.h b/sapi/phpdbg/phpdbg_utils.h new file mode 100644 index 0000000000..fbc17b78dd --- /dev/null +++ b/sapi/phpdbg/phpdbg_utils.h @@ -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/sapi/phpdbg/test.php b/sapi/phpdbg/test.php new file mode 100644 index 0000000000..5fdbcbe1a4 --- /dev/null +++ b/sapi/phpdbg/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/sapi/phpdbg/tests/commands/0001_basic.test b/sapi/phpdbg/tests/commands/0001_basic.test new file mode 100644 index 0000000000..08aa9ab664 --- /dev/null +++ b/sapi/phpdbg/tests/commands/0001_basic.test @@ -0,0 +1,8 @@ +####################################################### +# name: basic +# purpose: check basic functionality of phpdbg console +# expect: TEST::EXACT +# options: -rr +####################################################### +# [Nothing to execute!] +####################################################### diff --git a/sapi/phpdbg/tests/commands/0002_set.test b/sapi/phpdbg/tests/commands/0002_set.test new file mode 100644 index 0000000000..7720f94fff --- /dev/null +++ b/sapi/phpdbg/tests/commands/0002_set.test @@ -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/sapi/phpdbg/tests/commands/0101_info.test b/sapi/phpdbg/tests/commands/0101_info.test new file mode 100644 index 0000000000..397a45c992 --- /dev/null +++ b/sapi/phpdbg/tests/commands/0101_info.test @@ -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/sapi/phpdbg/tests/commands/0102_print.test b/sapi/phpdbg/tests/commands/0102_print.test new file mode 100644 index 0000000000..de4acb7651 --- /dev/null +++ b/sapi/phpdbg/tests/commands/0102_print.test @@ -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/sapi/phpdbg/tests/commands/0103_register.test b/sapi/phpdbg/tests/commands/0103_register.test new file mode 100644 index 0000000000..38841591ca --- /dev/null +++ b/sapi/phpdbg/tests/commands/0103_register.test @@ -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/sapi/phpdbg/tests/commands/0104_clean.test b/sapi/phpdbg/tests/commands/0104_clean.test new file mode 100644 index 0000000000..c7a579be17 --- /dev/null +++ b/sapi/phpdbg/tests/commands/0104_clean.test @@ -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/sapi/phpdbg/tests/commands/0105_clear.test b/sapi/phpdbg/tests/commands/0105_clear.test new file mode 100644 index 0000000000..b547b0d6ba --- /dev/null +++ b/sapi/phpdbg/tests/commands/0105_clear.test @@ -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/sapi/phpdbg/tests/commands/0106_compile.test b/sapi/phpdbg/tests/commands/0106_compile.test new file mode 100644 index 0000000000..d79211ddf7 --- /dev/null +++ b/sapi/phpdbg/tests/commands/0106_compile.test @@ -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/sapi/phpdbg/tests/run-tests.php b/sapi/phpdbg/tests/run-tests.php new file mode 100644 index 0000000000..1fb6fa1224 --- /dev/null +++ b/sapi/phpdbg/tests/run-tests.php @@ -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/sapi/phpdbg/travis/ci.sh b/sapi/phpdbg/travis/ci.sh new file mode 100755 index 0000000000..44d56a01ff --- /dev/null +++ b/sapi/phpdbg/travis/ci.sh @@ -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/sapi/phpdbg/web-bootstrap.php b/sapi/phpdbg/web-bootstrap.php new file mode 100644 index 0000000000..7b8c5d30de --- /dev/null +++ b/sapi/phpdbg/web-bootstrap.php @@ -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); |