]> granicus.if.org Git - php/commitdiff
First semi-working version of a single watchpoint
authorBob Weinand <bobwei9@hotmail.com>
Sat, 14 Dec 2013 10:39:09 +0000 (05:39 -0500)
committerBob Weinand <bobwei9@hotmail.com>
Sat, 14 Dec 2013 10:39:09 +0000 (05:39 -0500)
phpdbg.c
phpdbg.h
phpdbg_watch.c

index 147a66e55a34087f833b6589f9c6fb4f121aa0f9..fc817694f392c0ac423b5ee4782d6ab2c4316892 100644 (file)
--- 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);
 
index f11176325b22b8d59e6bec3165c17969e5168234..5014976795898dc197a154ca598175a2c3523eb3 100644 (file)
--- 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 */
index 66721ee8e65317f5d1307190939ef0f994565e44..00c83b5717b3310925f34880e50918cdc5855c4a 100644 (file)
@@ -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;
        }