/* +----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2014 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 | | Authors: Joe Watkins | | Authors: Bob Weinand | +----------------------------------------------------------------------+ */ #include "phpdbg.h" #include "phpdbg_cmd.h" #include "phpdbg_utils.h" #include "phpdbg_set.h" #include "phpdbg_parser.h" #include "phpdbg_lexer.h" int phpdbg_stack_execute(phpdbg_param_t *stack, char **why); void phpdbg_stack_free(phpdbg_param_t *stack); int yyparse(phpdbg_param_t *stack, yyscan_t scanner); ZEND_EXTERN_MODULE_GLOBALS(phpdbg); PHPDBG_API const char *phpdbg_get_param_type(const phpdbg_param_t *param TSRMLS_DC) /* {{{ */ { switch (param->type) { case STACK_PARAM: return "stack"; 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 STACK_PARAM: /* nope */ break; 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 STACK_PARAM: /* nope */ break; 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 STACK_PARAM: /* nope, or yep */ return 1; break; 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; #ifndef HAVE_LIBREADLINE char buf[PHPDBG_MAX_CMD]; #endif 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) { disconnect: if (0) { PHPDBG_G(flags) |= (PHPDBG_IS_QUITTING|PHPDBG_IS_DISCONNECTED); zend_bailout(); return NULL; } #ifndef HAVE_LIBREADLINE if (!(PHPDBG_G(flags) & PHPDBG_IS_REMOTE)) { if (!phpdbg_write("%s", phpdbg_get_prompt(TSRMLS_C))) { goto disconnect; } } /* note: EOF is ignored */ readline: if (!fgets(buf, PHPDBG_MAX_CMD, PHPDBG_G(io)[PHPDBG_STDIN])) { /* the user has gone away */ if ((PHPDBG_G(flags) & PHPDBG_IS_REMOTE)) { goto disconnect; } else goto readline; } cmd = buf; #else /* note: EOF makes readline write prompt again in local console mode */ readline: 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 goto disconnect; } else cmd = readline(phpdbg_get_prompt(TSRMLS_C)); if (!cmd) { goto readline; } 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); { yyscan_t scanner; YY_BUFFER_STATE state; phpdbg_param_t stack; phpdbg_init_param(&stack, STACK_PARAM); if (yylex_init(&scanner)) { fprintf(stderr, "could not initialize scanner\n"); return buffer; } state = yy_scan_string(buffer->string, scanner); if (yyparse(&stack, scanner) <= 0) { char *why = NULL; if (phpdbg_stack_execute(&stack, &why) != SUCCESS) { fprintf(stderr, "Execution Error: %s\n", why ? why : "for no particular reason"); } if (why) { free(why); } } yy_delete_buffer(state, scanner); yylex_destroy(scanner); phpdbg_stack_free(&stack); } #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; argstring) { 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; phpdbg_command_t *initial_last_cmd; phpdbg_param_t initial_last_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; argargc; arg++) { phpdbg_debug( "\t#%d: [%s=%zu]", arg, input->argv[arg]->string, input->argv[arg]->length); } } initial_last_param = PHPDBG_G(lparam); initial_last_cmd = (phpdbg_command_t *)PHPDBG_G(lcmd); PHPDBG_G(lparam) = param; PHPDBG_G(lcmd) = (phpdbg_command_t *)command; 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_clear_param(&initial_last_param TSRMLS_CC); } else if (PHPDBG_G(lcmd) == command && !memcmp(&PHPDBG_G(lparam),& initial_last_param, sizeof(phpdbg_param_t))) { PHPDBG_G(lparam) = initial_last_param; PHPDBG_G(lcmd) = initial_last_cmd; phpdbg_clear_param(¶m TSRMLS_CC); } break; } command++; } } else { /* this should NEVER happen */ phpdbg_error( "No function executed!!"); } return rc; } /* }}} */