From: krakjoe Date: Tue, 3 Dec 2013 00:15:33 +0000 (+0000) Subject: extended conditional breakpoints X-Git-Tag: php-5.6.0alpha1~110^2~30^2~6 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=fd9026b94895e487db56658b2e690e99d27a9c84;p=php extended conditional breakpoints --- diff --git a/Changelog.md b/Changelog.md index aed299cab1..be151c819b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ 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" Version 0.2.0 2013-11-31 ------------------------ diff --git a/phpdbg_bp.c b/phpdbg_bp.c index caf3c8ac0a..5ea78293f3 100644 --- a/phpdbg_bp.c +++ b/phpdbg_bp.c @@ -33,7 +33,7 @@ static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_symbol(zend_function* T 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(TSRMLS_D); /* }}} */ +static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(zend_execute_data *execute_data TSRMLS_DC); /* }}} */ /* * Note: @@ -328,54 +328,122 @@ PHPDBG_API void phpdbg_set_breakpoint_opline_ex(phpdbg_opline_ptr_t opline TSRML } } /* }}} */ -PHPDBG_API void phpdbg_set_breakpoint_expression(const char *expr, size_t expr_len TSRMLS_DC) /* {{{ */ +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) /* {{{ */ { - zend_ulong hash = zend_inline_hash_func(expr, expr_len); - - if (!zend_hash_index_exists(&PHPDBG_G(bp)[PHPDBG_BREAK_COND], hash)) { - 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; - - cops = CG(compiler_options); + phpdbg_breakcond_t new_break; + zend_uint cops = CG(compiler_options); + zval pv; - CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL; + 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); - new_break.code_len = Z_STRLEN(pv) = expr_len + sizeof("return ;") - 1; - new_break.code = 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; + CG(compiler_options) = ZEND_COMPILE_DEFAULT_FOR_EVAL; - new_break.ops = zend_compile_string( - &pv, "Conditional Breakpoint Code" TSRMLS_CC); + 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); - if (new_break.ops) { - phpdbg_breakcond_t *brake; + phpdbg_notice("Conditional breakpoint #%d added %s/%p", + brake->id, brake->code, brake->ops); - zend_hash_index_update( - &PHPDBG_G(bp)[PHPDBG_BREAK_COND], hash, &new_break, - sizeof(phpdbg_breakcond_t), (void**)&brake); + 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_notice("Conditional breakpoint #%d added %s/%p", - brake->id, brake->code, brake->ops); +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_G(flags) |= PHPDBG_HAS_COND_BP; - PHPDBG_BREAK_MAPPING(new_break.id, &PHPDBG_G(bp)[PHPDBG_BREAK_COND]); +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; + } + + 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_error( - "Failed to compile code for expression %s", expr); - efree((char*)new_break.code); - PHPDBG_G(bp_count)--; + phpdbg_notice( + "Conditional break %s exists at the specified location", expr); } - CG(compiler_options) = cops; + + phpdbg_clear_param(&new_param TSRMLS_CC); } else { - phpdbg_notice("Conditional break %s exists", expr); +usage: + phpdbg_error("usage: break at if "); } } /* }}} */ @@ -479,7 +547,82 @@ static inline phpdbg_breakbase_t *phpdbg_find_breakpoint_opcode(zend_uchar opcod return NULL; } /* }}} */ -static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(TSRMLS_D) /* {{{ */ +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 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) { + return (memcmp(param->str, str, len) == SUCCESS); + } + } + } 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 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]) { + if (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]) { + return (memcmp(param->method.name, + ops->function_name, lengths[0]) == SUCCESS); + } + } + } + } + } + } break; + + case ADDR_PARAM: { + return ((phpdbg_opline_ptr_t)execute_data->opline == param->addr); + } break; + + case NUMERIC_PARAM: + case EMPTY_PARAM: { + /* 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; @@ -493,6 +636,16 @@ static inline phpdbg_breakbase_t *phpdbg_find_conditional_breakpoint(TSRMLS_D) / 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); @@ -549,7 +702,7 @@ PHPDBG_API phpdbg_breakbase_t *phpdbg_find_breakpoint(zend_execute_data* execute /* 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(TSRMLS_C))) { + (base = phpdbg_find_conditional_breakpoint(execute_data TSRMLS_CC))) { goto result; } diff --git a/phpdbg_bp.h b/phpdbg_bp.h index d9ed37dbaa..3838a68d18 100644 --- a/phpdbg_bp.h +++ b/phpdbg_bp.h @@ -83,6 +83,8 @@ typedef struct _phpdbg_breakop_t { 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; @@ -94,7 +96,8 @@ PHPDBG_API void phpdbg_set_breakpoint_method(const char* class_name, const char* 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_expression(const char* expression, size_t expression_len 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); /* }}} */ diff --git a/phpdbg_break.c b/phpdbg_break.c index a8a5677e80..916211233a 100644 --- a/phpdbg_break.c +++ b/phpdbg_break.c @@ -78,6 +78,13 @@ PHPDBG_BREAK(on) /* {{{ */ return SUCCESS; } /* }}} */ +PHPDBG_BREAK(at) /* {{{ */ +{ + phpdbg_set_breakpoint_at(param, input TSRMLS_CC); + + return SUCCESS; +} /* }}} */ + PHPDBG_BREAK(lineno) /* {{{ */ { switch (param->type) { diff --git a/phpdbg_break.h b/phpdbg_break.h index 1d6f594bc8..cad801ced6 100644 --- a/phpdbg_break.h +++ b/phpdbg_break.h @@ -29,12 +29,13 @@ * 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(func); PHPDBG_BREAK(del); /** @@ -42,12 +43,13 @@ PHPDBG_BREAK(del); */ 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 expression", 'o', break_on, 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(func, "specify breakpoint by global function name", 'f', break_func, NULL, 1), PHPDBG_COMMAND_D_EX(del, "delete breakpoint by identifier number", 'd', break_del, NULL, 1), PHPDBG_END_COMMAND }; diff --git a/phpdbg_cmd.c b/phpdbg_cmd.c index 42a84e3b9f..d5db5f0a2f 100644 --- a/phpdbg_cmd.c +++ b/phpdbg_cmd.c @@ -119,6 +119,120 @@ PHPDBG_API void phpdbg_clear_param(phpdbg_param_t *param TSRMLS_DC) /* {{{ */ } /* }}} */ +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; + 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; + break; + + case ADDR_PARAM: + hash += param->addr; + break; + + case NUMERIC_PARAM: + 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 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]) { + return (memcmp( + l->file.name, r->file.name, lengths[0]) == SUCCESS); + } + } + } break; + + 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; diff --git a/phpdbg_cmd.h b/phpdbg_cmd.h index fcb96879c5..f3d38d58ea 100644 --- a/phpdbg_cmd.h +++ b/phpdbg_cmd.h @@ -120,13 +120,16 @@ PHPDBG_API void phpdbg_destroy_input(phpdbg_input_t** TSRMLS_DC); 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-1) == SUCCESS) + (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); /* diff --git a/phpdbg_help.c b/phpdbg_help.c index 964f7624fa..8d9915e59b 100644 --- a/phpdbg_help.c +++ b/phpdbg_help.c @@ -246,6 +246,13 @@ PHPDBG_HELP(break) /* {{{ */ 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");