From: Bob Weinand Date: Sat, 14 Dec 2013 10:39:09 +0000 (-0500) Subject: First semi-working version of a single watchpoint X-Git-Tag: php-5.6.0beta2~1^2~37^2~51 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f312f6bad24770e0587c0d06723d22cac691056c;p=php First semi-working version of a single watchpoint --- diff --git a/phpdbg.c b/phpdbg.c index 147a66e55a..fc817694f3 100644 --- a/phpdbg.c +++ b/phpdbg.c @@ -154,6 +154,8 @@ static PHP_RINIT_FUNCTION(phpdbg) /* {{{ */ 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(watchpoints), 8, NULL, NULL, 0); /* TODO: dtor */ + zend_hash_init(&PHPDBG_G(seek), 8, NULL, NULL, 0); zend_hash_init(&PHPDBG_G(registered), 8, NULL, php_phpdbg_destroy_registered, 0); diff --git a/phpdbg.h b/phpdbg.h index f11176325b..5014976795 100644 --- a/phpdbg.h +++ b/phpdbg.h @@ -178,6 +178,7 @@ ZEND_BEGIN_MODULE_GLOBALS(phpdbg) phpdbg_btree *watchpoint_tree; /* tree with watchpoints */ HashTable watchpoints; /* watchpoints */ zend_llist watchlist_mem; /* triggered watchpoints */ + zend_bool watchpoint_hit; /* a watchpoint was hit */ char *exec; /* file to execute */ size_t exec_len; /* size of exec */ diff --git a/phpdbg_watch.c b/phpdbg_watch.c index 66721ee8e6..00c83b5717 100644 --- a/phpdbg_watch.c +++ b/phpdbg_watch.c @@ -37,8 +37,18 @@ typedef struct { #define MEMDUMP_SIZE(size) (sizeof(phpdbg_watch_memdump) - sizeof(void *) + (size)) +#define CHOOSE_BRANCH(n) \ + addr = (addr << 1) + !!(n); \ + branch = branch->branches[!!(n)]; + void phpdbg_watch_mem_dtor(void *llist_data) { - efree(llist_data); + void *page = (*(phpdbg_watch_memdump **)llist_data)->page; + size_t size = (*(phpdbg_watch_memdump **)llist_data)->size; + + efree(*(void **)llist_data); + + /* Disble writing again */ + mprotect(page, size, PROT_NONE | PROT_READ); } void phpdbg_setup_watchpoints(TSRMLS_D) { @@ -52,24 +62,64 @@ void phpdbg_setup_watchpoints(TSRMLS_D) { phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE); #endif - zend_llist_init(&PHPDBG_G(watchlist_mem), 0, phpdbg_watch_mem_dtor, 0); + zend_llist_init(&PHPDBG_G(watchlist_mem), sizeof(void *), phpdbg_watch_mem_dtor, 0); } void *phpdbg_get_page_boundary(void *addr) { - return (void *)((unsigned long)addr & ~(phpdbg_pagesize - 1)); + return (void *)((size_t)addr & ~(phpdbg_pagesize - 1)); } -size_t phpdbg_get_total_page_size(size_t size) { - return ((size - 1) & ~(phpdbg_pagesize - 1)) | phpdbg_pagesize; +size_t phpdbg_get_total_page_size(void *addr, size_t size) { + return (size_t)phpdbg_get_page_boundary(addr + size - 1) - (size_t)phpdbg_get_page_boundary(addr) + phpdbg_pagesize; } -phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) { +phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *watch_addr) { phpdbg_watchpoint_t *watch; + phpdbg_btree *branch = PHPDBG_G(watchpoint_tree); + int i = sizeof(void *) * 8 - 1, last_superior_i = -1; + size_t addr = 0; + size_t opline = (size_t)phpdbg_get_page_boundary(watch_addr) + phpdbg_pagesize - 1; /* find nearest watchpoint */ + do { + /* an impossible branch was found if: */ + if ((opline >> i) % 2 == 0 && !branch->branches[0]) { + /* there's no lower branch than opline */ + if (last_superior_i == -1) { + /* failure */ + return NULL; + } + /* reset state */ + branch = PHPDBG_G(watchpoint_tree); + addr = 0; + i = sizeof(void *) * 8 - 1; + /* follow branch according to bits in opline until the last lower branch before the impossible branch */ + do { + CHOOSE_BRANCH((opline >> i) % 2 == 1 && branch->branches[1]); + } while (--i > last_superior_i); + /* use now the lower branch of which we can be sure that it contains only branches lower than opline */ + CHOOSE_BRANCH(0); + /* and choose the highest possible branch in the branch containing only branches lower than opline */ + while (i--) { + CHOOSE_BRANCH(branch->branches[1]); + } + break; + } + /* follow branch according to bits in opline until having found an impossible branch */ + if ((opline >> i) % 2 == 1 && branch->branches[1]) { + if (branch->branches[0]) { + last_superior_i = i; + } + CHOOSE_BRANCH(1); + } else { + CHOOSE_BRANCH(0); + } + } while (i--); - /* check if that watchpoint includes addr */ - if (((char *)watch->addr.ptr) + watch->size > (char *)addr) { + watch = branch->watchpoint; + + /* check if that addr is in a mprotect()'ed memory area */ + if ((char *)phpdbg_get_page_boundary(watch->addr.ptr) + phpdbg_get_total_page_size(watch->addr.ptr, watch->size) < (char *)addr) { /* failure */ return NULL; } @@ -93,43 +143,97 @@ int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context TSRMLS_DC) } page = phpdbg_get_page_boundary(addr); - size = phpdbg_get_total_page_size(watch->size); + size = phpdbg_get_total_page_size(addr, watch->size); /* re-enable writing */ mprotect(page, size, PROT_NONE | PROT_READ | PROT_WRITE); dump = emalloc(MEMDUMP_SIZE(size)); + dump->page = page; + dump->size = size; memcpy(&dump->data, page, size); - zend_llist_add_element(&PHPDBG_G(watchlist_mem), dump); + zend_llist_add_element(&PHPDBG_G(watchlist_mem), &dump); return SUCCESS; } int phpdbg_print_changed_zval(void *llist_data) { - phpdbg_watch_memdump *dump = llist_data; - phpdbg_watchpoint_t *watch; - phpdbg_btree *tree = PHPDBG_G(watchpoint_tree); + phpdbg_watch_memdump *dump = *(phpdbg_watch_memdump **)llist_data; void *oldPtr; + size_t opline; TSRMLS_FETCH(); /* fetch all changes between dump->page and dump->page + dump->size */ - watch = tree->watchpoint; - oldPtr = (char *)dump->data + ((size_t)watch->addr.ptr - (size_t)dump->page); - if (memcmp(oldPtr, watch->addr.ptr, watch->size) == SUCCESS) { - phpdbg_notice("Breaking on watchpoint %s", watch->str); - switch (watch->type) { - case WATCH_ON_ZVAL: - phpdbg_write("Old value: "); - zend_print_flat_zval_r((zval *)oldPtr TSRMLS_CC); - phpdbg_write("New value: "); - zend_print_flat_zval_r(watch->addr.zv TSRMLS_CC); + opline = (size_t)dump->page + dump->size - 1; + + while (1) { + phpdbg_btree *branch = PHPDBG_G(watchpoint_tree); + phpdbg_watchpoint_t *watch; + int i = sizeof(void *) * 8 - 1, last_superior_i = -1; + size_t addr = 0; + + do { + /* an impossible branch was found if: */ + if ((opline >> i) % 2 == 0 && !branch->branches[0]) { + /* there's no lower branch than opline */ + if (last_superior_i == -1) { + return 1; + } + /* reset state */ + branch = PHPDBG_G(watchpoint_tree); + addr = 0; + i = sizeof(void *) * 8 - 1; + /* follow branch according to bits in opline until the last lower branch before the impossible branch */ + do { + CHOOSE_BRANCH((opline >> i) % 2 == 1 && branch->branches[1]); + } while (--i > last_superior_i); + /* use now the lower branch of which we can be sure that it contains only branches lower than opline */ + CHOOSE_BRANCH(0); + /* and choose the highest possible branch in the branch containing only branches lower than opline */ + while (i--) { + CHOOSE_BRANCH(branch->branches[1]); + } break; + } + /* follow branch according to bits in opline until having found an impossible branch */ + if ((opline >> i) % 2 == 1 && branch->branches[1]) { + if (branch->branches[0]) { + last_superior_i = i; + } + CHOOSE_BRANCH(1); + } else { + CHOOSE_BRANCH(0); + } + } while (i--); + + if (watch == branch->watchpoint) + return 1; /* TODO: there's sometime wrong with the breaking condition ... */ + + watch = branch->watchpoint; + oldPtr = (char *)&dump->data + ((size_t)watch->addr.ptr - (size_t)dump->page); + if (memcmp(oldPtr, watch->addr.ptr, watch->size) != SUCCESS) { + PHPDBG_G(watchpoint_hit) = 1; + + phpdbg_notice("Breaking on watchpoint %s", watch->str); + switch (watch->type) { + case WATCH_ON_ZVAL: + phpdbg_write("Old value: "); + zend_print_flat_zval_r((zval *)oldPtr TSRMLS_CC); + phpdbg_writeln(""); + phpdbg_write("New value: "); + zend_print_flat_zval_r(watch->addr.zv TSRMLS_CC); + phpdbg_writeln(""); + break; + } + } else { + break; } + + opline = (size_t)watch->addr.ptr - 1; } - return 1; } @@ -139,9 +243,33 @@ int phpdbg_print_changed_zvals(TSRMLS_D) { return FAILURE; } + PHPDBG_G(watchpoint_hit) = 0; zend_llist_apply_with_del(&PHPDBG_G(watchlist_mem), phpdbg_print_changed_zval); - return SUCCESS; + return PHPDBG_G(watchpoint_hit)?SUCCESS:FAILURE; +} + +void phpdbg_store_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) { + phpdbg_btree **branch = &PHPDBG_G(watchpoint_tree); + int i = sizeof(void *) * 8 - 1; + + do { + if (*branch == NULL) { + break; + } + branch = &(*branch)->branches[((size_t)watch->addr.ptr >> i) % 2]; + } while (i--); + + if (*branch == NULL) { + phpdbg_btree *memory = *branch = emalloc((i + 2) * sizeof(phpdbg_btree)); + do { + (*branch)->branches[!(((size_t)watch->addr.ptr >> i) % 2)] = NULL; + branch = &(*branch)->branches[((size_t)watch->addr.ptr >> i) % 2]; + *branch = ++memory; + } while (i--); + } + + (*branch)->watchpoint = watch; } void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) { @@ -152,7 +280,7 @@ void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t watch->type = WATCH_ON_PTR; /* pagesize is assumed to be in the range of 2^x */ - m = mprotect(phpdbg_get_page_boundary(addr), phpdbg_get_total_page_size(size), PROT_NONE | PROT_READ); + m = mprotect(phpdbg_get_page_boundary(addr), phpdbg_get_total_page_size(addr, size), PROT_NONE | PROT_READ); if (m == FAILURE) { phpdbg_error("Unable to set watchpoint (mprotect() failed)"); @@ -166,9 +294,8 @@ void phpdbg_create_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) { } int phpdbg_create_var_watchpoint(char *name, size_t len TSRMLS_DC) { - zval *zv; - phpdbg_watchpoint_t watch; - + zval **zv; + phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t)); if (!EG(active_op_array)) { phpdbg_error("No active op array!"); @@ -184,10 +311,13 @@ int phpdbg_create_var_watchpoint(char *name, size_t len TSRMLS_DC) { } } + watch->str = estrndup(name, len); + /* Lookup current symbol table */ if (zend_hash_find(EG(current_execute_data)->symbol_table, name, len + 1, (void **)&zv) == SUCCESS) { - phpdbg_create_zval_watchpoint(zv, &watch); - zend_hash_add(&PHPDBG_G(watchpoints), name, len, &watch, sizeof(phpdbg_watchpoint_t), NULL); + zend_hash_add(&PHPDBG_G(watchpoints), name, len, &watch, sizeof(phpdbg_watchpoint_t *), NULL); + phpdbg_store_watchpoint(watch TSRMLS_CC); + phpdbg_create_zval_watchpoint(*zv, watch); return SUCCESS; }