pg->has_file_bp = 0;
pg->has_sym_bp = 0;
pg->has_opline_bp = 0;
+ pg->has_method_bp = 0;
} /* }}} */
static PHP_MINIT_FUNCTION(phpdbg) /* {{{ */
free((char*)((phpdbg_breakline_t*)brake)->name);
} /* }}} */
+static void php_phpdbg_destroy_bp_methods(void *brake) /* {{{ */
+{
+ zend_hash_destroy((HashTable*)brake);
+} /* }}} */
+
static PHP_RINIT_FUNCTION(phpdbg) /* {{{ */
{
zend_hash_init(&PHPDBG_G(bp_files), 8, NULL, php_phpdbg_destroy_bp_file, 0);
zend_hash_init(&PHPDBG_G(bp_symbols), 8, NULL, php_phpdbg_destroy_bp_symbol, 0);
zend_hash_init(&PHPDBG_G(bp_oplines), 8, NULL, php_phpdbg_destroy_bp_opline, 0);
+ zend_hash_init(&PHPDBG_G(bp_methods), 8, NULL, php_phpdbg_destroy_bp_methods, 0);
return SUCCESS;
} /* }}} */
zend_hash_destroy(&PHPDBG_G(bp_files));
zend_hash_destroy(&PHPDBG_G(bp_symbols));
zend_hash_destroy(&PHPDBG_G(bp_oplines));
+ zend_hash_destroy(&PHPDBG_G(bp_methods));
if (PHPDBG_G(exec)) {
efree(PHPDBG_G(exec));
zend_hash_clean(&PHPDBG_G(bp_files));
zend_hash_clean(&PHPDBG_G(bp_symbols));
zend_hash_clean(&PHPDBG_G(bp_oplines));
+ zend_hash_clean(&PHPDBG_G(bp_methods));
} /* }}} */
zend_function_entry phpdbg_user_functions[] = {
typedef struct _phpdbg_command_t phpdbg_command_t;
ZEND_BEGIN_MODULE_GLOBALS(phpdbg)
- HashTable bp_files;
- HashTable bp_symbols;
- HashTable bp_oplines;
+ HashTable bp_files; /* file breakpoints */
+ HashTable bp_symbols; /* symbol breakpoints */
+ HashTable bp_oplines; /* opline breakpoints */
+ HashTable bp_methods; /* method breakpoints */
char *exec; /* file to execute */
size_t exec_len; /* size of exec */
zend_op_array *ops; /* op_array */
zend_bool has_file_bp; /* file-based breakpoint has been set */
zend_bool has_sym_bp; /* symbol-based breakpoint has been set */
zend_bool has_opline_bp; /* opline-based breakpoint has been set */
+ zend_bool has_method_bp; /* method-based breakpoint has been set */
zend_bool quitting; /* quitting flag */
int quiet; /* quiet */
phpdbg_command_t *last; /* last command */
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);
+} /* }}} */
+
void phpdbg_set_breakpoint_file(const char *path, long line_num TSRMLS_DC) /* {{{ */
{
phpdbg_breakfile_t new_break;
}
} /* }}} */
+void phpdbg_set_breakpoint_method(const char* class_name,
+ size_t class_len,
+ const char* func_name,
+ size_t func_len TSRMLS_DC) /* {{{ */
+{
+ HashTable class_breaks, *class_table;
+
+ if (zend_hash_find(&PHPDBG_G(bp_methods), 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_methods),
+ class_name, class_len,
+ (void**)&class_breaks, sizeof(HashTable), (void**)&class_table);
+ }
+
+ if (!zend_hash_exists(class_table, func_name, func_len)) {
+ phpdbg_breakmethod_t new_break;
+
+ PHPDBG_G(has_method_bp) = 1;
+
+ new_break.class_name = class_name;
+ new_break.class_len = class_len;
+ new_break.func_name = func_name;
+ new_break.func_len = func_len;
+ new_break.id = PHPDBG_G(bp_count)++;
+
+ zend_hash_update(class_table, func_name, func_len, &new_break, sizeof(phpdbg_breakmethod_t), NULL);
+ printf(
+ "[Breakpoint #%d added at %s::%s]\n", new_break.id, class_name, func_name);
+ } else {
+ printf(
+ "[Breakpoint exists at %s::%s]\n", class_name, func_name);
+ }
+} /* }}} */
+
void phpdbg_set_breakpoint_opline(const char *name TSRMLS_DC) /* {{{ */
{
zend_ulong opline = strtoul(name, 0, 16);
return FAILURE;
} /* }}} */
+int phpdbg_find_breakpoint_method(zend_function *fbc TSRMLS_DC) /* {{{ */
+{
+ HashTable *class_table;
+ phpdbg_breakmethod_t *bp;
+ zend_op_array *ops = NULL;
+
+ if (fbc->type != ZEND_USER_FUNCTION) {
+ return FAILURE;
+ }
+
+ ops = ((zend_op_array*)fbc);
+
+ if (!ops->scope) {
+ return FAILURE;
+ }
+
+ if (zend_hash_find(&PHPDBG_G(bp_methods), ops->scope->name, ops->scope->name_length,
+ (void**)&class_table) == SUCCESS) {
+ if (zend_hash_find(
+ class_table,
+ ops->function_name,
+ strlen(ops->function_name), (void**)&bp) == SUCCESS) {
+
+ printf(
+ "[Breakpoint #%d in %s::%s() at %s:%u]\n", bp->id, bp->class_name, bp->func_name,
+ zend_get_executed_filename(TSRMLS_C),
+ zend_get_executed_lineno(TSRMLS_C));
+ return SUCCESS;
+ }
+ }
+
+ return FAILURE;
+} /* }}} */
+
int phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t opline TSRMLS_DC) /* {{{ */
{
phpdbg_breakline_t *bp;
zend_hash_clean(&PHPDBG_G(bp_files));
zend_hash_clean(&PHPDBG_G(bp_symbols));
zend_hash_clean(&PHPDBG_G(bp_oplines));
-
+ zend_hash_clean(&PHPDBG_G(bp_methods));
+
PHPDBG_G(has_file_bp) = 0;
PHPDBG_G(has_sym_bp) = 0;
PHPDBG_G(has_opline_bp) = 0;
+ PHPDBG_G(has_method_bp) = 0;
PHPDBG_G(bp_count) = 0;
} /* }}} */
int id;
} phpdbg_breaksymbol_t;
+/**
+ * Breakpoint method based representation
+ */
+typedef struct _phpdbg_breakmethod_t {
+ const char *class_name;
+ size_t class_len;
+ const char *func_name;
+ size_t func_len;
+ int id;
+} phpdbg_breakmethod_t;
+
/**
* Breakpoint opline based representation
*/
void phpdbg_set_breakpoint_file(const char*, long TSRMLS_DC);
void phpdbg_set_breakpoint_symbol(const char* TSRMLS_DC);
+void phpdbg_set_breakpoint_method(const char*, size_t, const char*, size_t TSRMLS_DC);
void phpdbg_set_breakpoint_opline(const char* TSRMLS_DC);
void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t TSRMLS_DC);
int phpdbg_find_breakpoint_file(zend_op_array* TSRMLS_DC);
int phpdbg_find_breakpoint_symbol(zend_function* TSRMLS_DC);
+int phpdbg_find_breakpoint_method(zend_function* TSRMLS_DC);
int phpdbg_find_breakpoint_opline(phpdbg_opline_ptr_t TSRMLS_DC);
void phpdbg_clear_breakpoints(TSRMLS_D);
printf("Setting a breakpoint stops execution at a specific stage, the syntax is:\n");
printf("\tfile:line\n");
printf("\tfunction\n");
+ printf("\t\\my\\class::method\n");
printf("\t0x16\n");
printf("For example:\n");
printf("\tphpdbg> break test.php:1\n");
printf("Will break execution on line 1 of test.php\n");
printf("\tphpdbg> break my_function\n");
printf("Will break execution on entry to my_function\n");
+ printf("\tphpdbg> break \\my\\class::method\n");
+ printf("Will break execution on entry to \\my\\class::method\n");
printf("\tphpdbg> break 0x7ff68f570e08\n");
printf("Will break at the opline with the address provided (addresses are shown during execution)\n");
printf("It is important to note, an address is only valid for the current compiled representation of the script\n");
if (expr_len) {
if (zend_eval_stringl((char*)expr, expr_len-1,
&retval, "eval()'d code" TSRMLS_CC) == SUCCESS) {
- zend_print_zval_r(&retval, 0 TSRMLS_CC);
+ zend_print_zval_r(
+ &retval, 0 TSRMLS_CC);
printf("\n");
- zval_dtor(&retval);
}
} else {
printf("[No expression provided !]\n");
static PHPDBG_COMMAND(break) /* {{{ */
{
- const char *line_pos = zend_memrchr(expr, ':', expr_len);
-
+ char *line_pos = NULL;
+ char *func_pos = NULL;
+
+ if (!expr_len) {
+ printf(
+ "[No expression found]\n");
+ return FAILURE;
+ }
+
+ line_pos = strchr(expr, ':');
+
if (line_pos) {
- char path[MAXPATHLEN], resolved_name[MAXPATHLEN];
- long line_num = strtol(line_pos+1, NULL, 0);
-
- if (line_num) {
- memcpy(path, expr, line_pos - expr);
- path[line_pos - expr] = 0;
-
- if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL) {
- printf("[Failed to expand path %s]\n", path);
- return FAILURE;
+ if (!(func_pos=strchr(line_pos+1, ':'))) {
+ char path[MAXPATHLEN], resolved_name[MAXPATHLEN];
+ long line_num = strtol(line_pos+1, NULL, 0);
+
+ if (line_num) {
+ memcpy(path, expr, line_pos - expr);
+ path[line_pos - expr] = 0;
+
+ if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL) {
+ printf("[Failed to expand path %s]\n", path);
+ return FAILURE;
+ }
+
+ phpdbg_set_breakpoint_file(resolved_name, line_num TSRMLS_CC);
+ } else {
+ printf("[No line specified in expression %s]\n", expr);
+ return FAILURE;
+ }
+ } else {
+ char *class;
+ char *func;
+
+ size_t func_len = strlen(func_pos+1),
+ class_len = (line_pos - expr);
+
+ if (func_len) {
+ class = emalloc(class_len+1);
+ func = emalloc(func_len+1);
+
+ memcpy(class, expr, class_len);
+ class[class_len]='\0';
+ memcpy(func, func_pos+1, func_len);
+ func[func_len]='\0';
+
+ phpdbg_set_breakpoint_method(class, class_len, func, func_len TSRMLS_CC);
+ } else {
+ printf("[No function found in method expression %s]\n", expr);
+ return FAILURE;
}
-
- phpdbg_set_breakpoint_file(resolved_name, line_num TSRMLS_CC);
- } else {
- printf("[No line specified in expression %s]\n", expr);
- return FAILURE;
}
} else {
if (expr_len > 2 && expr[0] == '0' && expr[1] == 'x') {
char name[200];
size_t name_len = strlen(expr);
- name_len = MIN(name_len, 200);
- memcpy(name, expr, name_len);
- name[name_len] = 0;
+ if (name_len) {
+ name_len = MIN(name_len, 200);
+ memcpy(name, expr, name_len);
+ name[name_len] = 0;
- phpdbg_set_breakpoint_symbol(name TSRMLS_CC);
+ phpdbg_set_breakpoint_symbol(name TSRMLS_CC);
+ } else {
+ printf("[Malformed break command found]\n");
+ return FAILURE;
+ }
}
}
static PHPDBG_COMMAND(clean) /* {{{ */
{
- printf("[Cleaning Environment:]\n");
- printf("[\tClasses: %d]\n", zend_hash_num_elements(EG(class_table)));
- printf("[\tFunctions: %d]\n", zend_hash_num_elements(EG(function_table)));
- printf("[\tConstants: %d]\n", zend_hash_num_elements(EG(zend_constants)));
- printf("[\tIncluded: %d]\n", zend_hash_num_elements(&EG(included_files)));
-
- zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC);
- zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC);
- zend_hash_reverse_apply(EG(zend_constants), (apply_func_t) clean_non_persistent_constant_full TSRMLS_CC);
- zend_hash_clean(&EG(included_files));
-
- printf("[Clean Environment:]\n");
- printf("[\tClasses: %d]\n", zend_hash_num_elements(EG(class_table)));
- printf("[\tFunctions: %d]\n", zend_hash_num_elements(EG(function_table)));
- printf("[\tConstants: %d]\n", zend_hash_num_elements(EG(zend_constants)));
- printf("[\tIncluded: %d]\n", zend_hash_num_elements(&EG(included_files)));
+ if (!EG(in_execution)) {
+ printf("[Cleaning Environment:]\n");
+ printf("[\tClasses: %d]\n", zend_hash_num_elements(EG(class_table)));
+ printf("[\tFunctions: %d]\n", zend_hash_num_elements(EG(function_table)));
+ printf("[\tConstants: %d]\n", zend_hash_num_elements(EG(zend_constants)));
+ printf("[\tIncluded: %d]\n", zend_hash_num_elements(&EG(included_files)));
+
+ zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC);
+ zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC);
+ zend_hash_reverse_apply(EG(zend_constants), (apply_func_t) clean_non_persistent_constant_full TSRMLS_CC);
+ zend_hash_clean(&EG(included_files));
+
+ printf("[Clean Environment:]\n");
+ printf("[\tClasses: %d]\n", zend_hash_num_elements(EG(class_table)));
+ printf("[\tFunctions: %d]\n", zend_hash_num_elements(EG(function_table)));
+ printf("[\tConstants: %d]\n", zend_hash_num_elements(EG(zend_constants)));
+ printf("[\tIncluded: %d]\n", zend_hash_num_elements(&EG(included_files)));
+ } else {
+ printf(
+ "[Cannot clean environment while executing]\n");
+ return FAILURE;
+ }
return SUCCESS;
} /* }}} */
printf("[\tFile\t%d]\n", zend_hash_num_elements(&PHPDBG_G(bp_files)));
printf("[\tSymbols\t%d]\n", zend_hash_num_elements(&PHPDBG_G(bp_symbols)));
printf("[\tOplines\t%d]\n", zend_hash_num_elements(&PHPDBG_G(bp_oplines)));
-
+ printf("[\tMethods\t%d]\n", zend_hash_num_elements(&PHPDBG_G(bp_methods)));
+
phpdbg_clear_breakpoints(TSRMLS_C);
return SUCCESS;
}
}
- if (PHPDBG_G(has_sym_bp) && execute_data->opline->opcode != ZEND_RETURN) {
+ if ((PHPDBG_G(has_sym_bp)||PHPDBG_G(has_method_bp))) {
zend_execute_data *previous = execute_data->prev_execute_data;
if (previous && previous != execute_data && previous->opline) {
- if (previous->opline->opcode == ZEND_DO_FCALL
- || previous->opline->opcode == ZEND_DO_FCALL_BY_NAME) {
- if (phpdbg_find_breakpoint_symbol(
- previous->function_state.function TSRMLS_CC) == SUCCESS) {
- while (phpdbg_interactive(TSRMLS_C) != PHPDBG_NEXT) {
- if (!PHPDBG_G(quitting)) {
- continue;
+ /* check we are the beginning of a function entry */
+ if (execute_data->opline == EG(active_op_array)->opcodes) {
+ switch (previous->opline->opcode) {
+ case ZEND_DO_FCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ case ZEND_INIT_STATIC_METHOD_CALL: {
+ if (phpdbg_find_breakpoint_symbol(previous->function_state.function TSRMLS_CC) == SUCCESS ||
+ phpdbg_find_breakpoint_method(previous->function_state.function TSRMLS_CC) == SUCCESS) {
+ while (phpdbg_interactive(TSRMLS_C) != PHPDBG_NEXT) {
+ if (!PHPDBG_G(quitting)) {
+ continue;
+ }
+ }
}
- }
+ } break;
}
}
}
}
-
+
if (PHPDBG_G(has_opline_bp)
&& phpdbg_find_breakpoint_opline(execute_data->opline TSRMLS_CC) == SUCCESS) {
while (phpdbg_interactive(TSRMLS_C) != PHPDBG_NEXT) {
<?php
-phpdbg_clear();
-function test() {
- echo "Hello World\n";
- $hidden = "variable";
- phpdbg_break();
+class my {
+ public function method() {
+ return $this;
+ }
}
function test2() {
echo "Hello World 2\n";
}
-if (!isset($greeting)) {
- echo test();
-}
-phpdbg_break();
+$my = new my();
+var_dump($my->method());
-test2();
return true;
?>