root/sapi/phpdbg/phpdbg_watch.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. phpdbg_check_for_watchpoint
  2. phpdbg_change_watchpoint_access
  3. phpdbg_activate_watchpoint
  4. phpdbg_deactivate_watchpoint
  5. phpdbg_store_watchpoint
  6. phpdbg_remove_watchpoint
  7. phpdbg_create_addr_watchpoint
  8. phpdbg_create_zval_watchpoint
  9. phpdbg_create_ht_watchpoint
  10. phpdbg_create_watchpoint
  11. phpdbg_create_array_watchpoint
  12. phpdbg_get_property_key
  13. phpdbg_create_recursive_watchpoint
  14. phpdbg_delete_watchpoint_recursive
  15. phpdbg_delete_watchpoint
  16. phpdbg_watchpoint_parse_input
  17. phpdbg_watchpoint_parse_symtables
  18. PHPDBG_WATCH
  19. PHPDBG_WATCH
  20. PHPDBG_WATCH
  21. phpdbg_watch_HashTable_dtor
  22. phpdbg_create_var_watchpoint
  23. phpdbg_delete_var_watchpoint
  24. phpdbg_watchpoint_segfault_handler
  25. phpdbg_watchpoint_segfault_handler
  26. phpdbg_watchpoints_clean
  27. phpdbg_watch_dtor
  28. phpdbg_watch_mem_dtor
  29. phpdbg_setup_watchpoints
  30. phpdbg_print_changed_zval
  31. phpdbg_print_changed_zvals
  32. phpdbg_list_watchpoints
  33. phpdbg_watch_efree

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2015 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 <felipe@php.net>                                |
   | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
   | Authors: Bob Weinand <bwoebi@php.net>                                |
   +----------------------------------------------------------------------+
*/

#include "zend.h"
#include "phpdbg.h"
#include "phpdbg_btree.h"
#include "phpdbg_watch.h"
#include "phpdbg_utils.h"
#ifndef _WIN32
# include <unistd.h>
# include <sys/mman.h>
#endif

ZEND_EXTERN_MODULE_GLOBALS(phpdbg);


typedef struct {
        void *page;
        size_t size;
        char reenable_writing;
        /* data must be last element */
        void *data;
} phpdbg_watch_memdump;

#define MEMDUMP_SIZE(size) (sizeof(phpdbg_watch_memdump) - sizeof(void *) + (size))


static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr TSRMLS_DC) {
        phpdbg_watchpoint_t *watch;
        phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);

        if (result == NULL) {
                return NULL;
        }

        watch = result->ptr;

        /* check if that addr is in a mprotect()'ed memory area */
        if ((char *)phpdbg_get_page_boundary(watch->addr.ptr) > (char *)addr || (char *)phpdbg_get_page_boundary(watch->addr.ptr) + phpdbg_get_total_page_size(watch->addr.ptr, watch->size) < (char *)addr) {
                /* failure */
                return NULL;
        }

        return watch;
}

static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access TSRMLS_DC) {
        int m;

        /* pagesize is assumed to be in the range of 2^x */
        m = mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
}

static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        phpdbg_change_watchpoint_access(watch, PROT_READ TSRMLS_CC);
}

static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE TSRMLS_CC);
}

static inline void phpdbg_store_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong)watch->addr.ptr, watch);
}

static inline void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong)watch->addr.ptr);
}

void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
        watch->addr.ptr = addr;
        watch->size = size;
}

void phpdbg_create_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
        phpdbg_create_addr_watchpoint(zv, sizeof(zval), watch);
        watch->type = WATCH_ON_ZVAL;
}

void phpdbg_create_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
        phpdbg_create_addr_watchpoint(ht, sizeof(HashTable), watch);
        watch->type = WATCH_ON_HASHTABLE;
}

void phpdbg_watch_HashTable_dtor(zval **ptr);

static int phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        watch->flags |= PHPDBG_WATCH_SIMPLE;

        phpdbg_store_watchpoint(watch TSRMLS_CC);
        zend_hash_add(&PHPDBG_G(watchpoints), watch->str, watch->str_len, &watch, sizeof(phpdbg_watchpoint_t *), NULL);

        if (watch->type == WATCH_ON_ZVAL) {
                phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong)watch->parent_container, watch->parent_container->pDestructor);
                watch->parent_container->pDestructor = (dtor_func_t)phpdbg_watch_HashTable_dtor;
        }

        phpdbg_activate_watchpoint(watch TSRMLS_CC);

        return SUCCESS;
}

static int phpdbg_create_array_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        HashTable *ht;

        switch (Z_TYPE_P(watch->addr.zv)) {
                case IS_ARRAY:
                        ht = Z_ARRVAL_P(watch->addr.zv);
                        break;
                case IS_OBJECT:
                        ht = Z_OBJPROP_P(watch->addr.zv);
                        break;
                default:
                        return FAILURE;
        }

        phpdbg_create_ht_watchpoint(ht, watch);

        phpdbg_create_watchpoint(watch TSRMLS_CC);

        return SUCCESS;
}

static char *phpdbg_get_property_key(char *key) {
        if (*key != 0) {
                return key;
        }
        return strchr(key + 1, 0) + 1;
}

static int phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t *watch TSRMLS_DC) {
        HashTable *ht;

        if (watch->type != WATCH_ON_ZVAL) {
                return FAILURE;
        }

        watch->flags |= PHPDBG_WATCH_RECURSIVE;
        phpdbg_create_watchpoint(watch TSRMLS_CC);

        switch (Z_TYPE_P(watch->addr.zv)) {
                case IS_ARRAY:
                        ht = Z_ARRVAL_P(watch->addr.zv);
                        break;
                case IS_OBJECT:
                        ht = Z_OBJPROP_P(watch->addr.zv);
                        break;
                default:
                        return SUCCESS;
        }

        {
                HashPosition position;
                zval **zv;
                zval key;

                for (zend_hash_internal_pointer_reset_ex(ht, &position);
                     zend_hash_get_current_data_ex(ht, (void **)&zv, &position) == SUCCESS;
                     zend_hash_move_forward_ex(ht, &position)) {
                        phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));

                        new_watch->flags = PHPDBG_WATCH_RECURSIVE;
                        new_watch->parent = watch;
                        new_watch->parent_container = ht;

                        zend_hash_get_current_key_zval_ex(ht, &key, &position);
                        if (Z_TYPE(key) == IS_STRING) {
                                new_watch->name_in_parent = zend_strndup(Z_STRVAL(key), Z_STRLEN(key));
                                new_watch->name_in_parent_len = Z_STRLEN(key);
                        } else {
                                new_watch->name_in_parent = NULL;
                                new_watch->name_in_parent_len = asprintf(&new_watch->name_in_parent, "%ld", Z_LVAL(key));
                        }

                        new_watch->str = NULL;
                        new_watch->str_len = asprintf(&new_watch->str, "%.*s%s%s%s", (int)watch->str_len, watch->str, Z_TYPE_P(watch->addr.zv) == IS_ARRAY?"[":"->", phpdbg_get_property_key(new_watch->name_in_parent), Z_TYPE_P(watch->addr.zv) == IS_ARRAY?"]":"");

                        phpdbg_create_zval_watchpoint(*zv, new_watch);
                        phpdbg_create_recursive_watchpoint(new_watch TSRMLS_CC);
                }
        }

        {
                phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));

                new_watch->parent = watch;
                new_watch->parent_container = watch->parent_container;
                new_watch->name_in_parent = zend_strndup(watch->name_in_parent, watch->name_in_parent_len);
                new_watch->name_in_parent_len = watch->name_in_parent_len;
                new_watch->str = NULL;
                new_watch->str_len = asprintf(&new_watch->str, "%.*s[]", (int)watch->str_len, watch->str);
                new_watch->flags = PHPDBG_WATCH_RECURSIVE;

                phpdbg_create_ht_watchpoint(ht, new_watch);
                phpdbg_create_watchpoint(new_watch TSRMLS_CC);
        }

        return SUCCESS;
}

static int phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t *watch, zend_bool user_request TSRMLS_DC) {
        if (watch->type == WATCH_ON_HASHTABLE || (watch->type == WATCH_ON_ZVAL && (Z_TYPE_P(watch->addr.zv) == IS_ARRAY || Z_TYPE_P(watch->addr.zv) == IS_OBJECT))) {
                HashTable *ht;
                phpdbg_btree_result *result;

                if (watch->type == WATCH_ON_HASHTABLE && user_request) {
                        HashPosition position;
                        zval **zv;
                        zval key;
                        char *str;
                        int str_len;
                        phpdbg_watchpoint_t **watchpoint;

                        ht = watch->addr.ht;

                        for (zend_hash_internal_pointer_reset_ex(ht, &position);
                             zend_hash_get_current_data_ex(ht, (void **)&zv, &position) == SUCCESS;
                             zend_hash_move_forward_ex(ht, &position)) {
                                zend_hash_get_current_key_zval_ex(ht, &key, &position);
                                str = NULL;
                                if (Z_TYPE(key) == IS_STRING) {
                                        str_len = asprintf(&str, "%.*s%s%s%s", (int)watch->parent->str_len, watch->parent->str, Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"[":"->", phpdbg_get_property_key(Z_STRVAL(key)), Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"]":"");
                                } else {
                                        str_len = asprintf(&str, "%.*s%s%li%s", (int)watch->parent->str_len, watch->parent->str, Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"[":"->", Z_LVAL(key), Z_TYPE_P(watch->parent->addr.zv) == IS_ARRAY?"]":"");
                                }

                                if (zend_hash_find(&PHPDBG_G(watchpoints), str, str_len, (void **) &watchpoint) == SUCCESS) {
                                        phpdbg_delete_watchpoint_recursive(*watchpoint, 1 TSRMLS_CC);
                                }
                        }
                } else {
                        switch (Z_TYPE_P(watch->addr.zv)) {
                                case IS_ARRAY:
                                        ht = Z_ARRVAL_P(watch->addr.zv);
                                        break;
                                case IS_OBJECT:
                                        ht = Z_OBJPROP_P(watch->addr.zv);
                                        break;
                        }

                        if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ht))) {
                                phpdbg_delete_watchpoint_recursive((phpdbg_watchpoint_t *) result->ptr, user_request TSRMLS_CC);
                        }
                }
        }

        return zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
}

static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch TSRMLS_DC) {
        int ret;
        phpdbg_watchpoint_t *watch;
        phpdbg_btree_result *result;

        if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong)tmp_watch->addr.ptr)) == NULL) {
                return FAILURE;
        }

        watch = result->ptr;

        if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
                ret = phpdbg_delete_watchpoint_recursive(watch, 1 TSRMLS_CC);
        } else {
                ret = zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
        }

        free(tmp_watch->str);
        efree(tmp_watch);

        return ret;
}

static int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, int (*callback)(phpdbg_watchpoint_t * TSRMLS_DC), zend_bool silent TSRMLS_DC) {
        int ret = FAILURE;
        zend_bool new_index = 1;
        char *last_index;
        int index_len = 0;
        zval **zv;

        if (len < 2 || *input != '$') {
                goto error;
        }

        while (i++ < len) {
                if (i == len) {
                        new_index = 1;
                } else {
                        switch (input[i]) {
                                case '[':
                                        new_index = 1;
                                        break;
                                case ']':
                                        break;
                                case '>':
                                        if (last_index[index_len - 1] == '-') {
                                                new_index = 1;
                                                index_len--;
                                        }
                                        break;

                                default:
                                        if (new_index) {
                                                last_index = input + i;
                                                new_index = 0;
                                        }
                                        if (input[i - 1] == ']') {
                                                goto error;
                                        }
                                        index_len++;
                        }
                }

                if (new_index && index_len == 0) {
                        HashPosition position;
                        for (zend_hash_internal_pointer_reset_ex(parent, &position);
                             zend_hash_get_current_data_ex(parent, (void **)&zv, &position) == SUCCESS;
                             zend_hash_move_forward_ex(parent, &position)) {
                                if (i == len || (i == len - 1 && input[len - 1] == ']')) {
                                        zval *key = emalloc(sizeof(zval));
                                        phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
                                        watch->flags = 0;
                                        zend_hash_get_current_key_zval_ex(parent, key, &position);
                                        convert_to_string(key);
                                        watch->str = malloc(i + Z_STRLEN_P(key) + 2);
                                        watch->str_len = sprintf(watch->str, "%.*s%s%s", (int)i, input, phpdbg_get_property_key(Z_STRVAL_P(key)), input[len - 1] == ']'?"]":"");
                                        efree(key);
                                        watch->name_in_parent = zend_strndup(last_index, index_len);
                                        watch->name_in_parent_len = index_len;
                                        watch->parent_container = parent;
                                        phpdbg_create_zval_watchpoint(*zv, watch);

                                        ret = callback(watch TSRMLS_CC) == SUCCESS || ret == SUCCESS?SUCCESS:FAILURE;
                                } else if (Z_TYPE_PP(zv) == IS_OBJECT) {
                                        phpdbg_watchpoint_parse_input(input, len, Z_OBJPROP_PP(zv), i, callback, silent TSRMLS_CC);
                                } else if (Z_TYPE_PP(zv) == IS_ARRAY) {
                                        phpdbg_watchpoint_parse_input(input, len, Z_ARRVAL_PP(zv), i, callback, silent TSRMLS_CC);
                                } else {
                                        /* Ignore silently */
                                }
                        }
                        return ret;
                } else if (new_index) {
                        char last_chr = last_index[index_len];
                        last_index[index_len] = 0;
                        if (zend_symtable_find(parent, last_index, index_len + 1, (void **)&zv) == FAILURE) {
                                if (!silent) {
                                        phpdbg_error("%.*s is undefined", (int)i, input);
                                }
                                return FAILURE;
                        }
                        last_index[index_len] = last_chr;
                        if (i == len) {
                                phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
                                watch->flags = 0;
                                watch->str = zend_strndup(input, len);
                                watch->str_len = len;
                                watch->name_in_parent = zend_strndup(last_index, index_len);
                                watch->name_in_parent_len = index_len;
                                watch->parent_container = parent;
                                phpdbg_create_zval_watchpoint(*zv, watch);

                                ret = callback(watch TSRMLS_CC) == SUCCESS || ret == SUCCESS?SUCCESS:FAILURE;
                        } else if (Z_TYPE_PP(zv) == IS_OBJECT) {
                                parent = Z_OBJPROP_PP(zv);
                        } else if (Z_TYPE_PP(zv) == IS_ARRAY) {
                                parent = Z_ARRVAL_PP(zv);
                        } else {
                                phpdbg_error("%.*s is nor an array nor an object", (int)i, input);
                                return FAILURE;
                        }
                        index_len = 0;
                }
        }

        return ret;
        error:
                phpdbg_error("Malformed input");
                return FAILURE;
}

static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(phpdbg_watchpoint_t * TSRMLS_DC) TSRMLS_DC) {
        if (EG(This) && len >= 5 && !memcmp("$this", input, 5)) {
                zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), NULL);
        }

        if (zend_is_auto_global(input, len TSRMLS_CC) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, callback, 1 TSRMLS_CC) != FAILURE) {
                return SUCCESS;
        }

        return phpdbg_watchpoint_parse_input(input, len, EG(active_symbol_table), 0, callback, 0 TSRMLS_CC);
}

PHPDBG_WATCH(delete) /* {{{ */
{
        switch (param->type) {
                case STR_PARAM:
                        if (phpdbg_delete_var_watchpoint(param->str, param->len TSRMLS_CC) == FAILURE) {
                                phpdbg_error("Nothing was deleted, no corresponding watchpoint found");
                        } else {
                                phpdbg_notice("Removed watchpoint %.*s", (int)param->len, param->str);
                        }
                        break;

                phpdbg_default_switch_case();
        }

        return SUCCESS;
} /* }}} */

PHPDBG_WATCH(recursive) /* {{{ */
{
        if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
                return SUCCESS;
        }

        switch (param->type) {
                case STR_PARAM:
                        if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint TSRMLS_CC) != FAILURE) {
                                phpdbg_notice("Set recursive watchpoint on %.*s", (int)param->len, param->str);
                        }
                        break;

                phpdbg_default_switch_case();
        }

        return SUCCESS;
} /* }}} */

PHPDBG_WATCH(array) /* {{{ */
{
        if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
                return SUCCESS;
        }

        switch (param->type) {
                case STR_PARAM:
                        if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint TSRMLS_CC) != FAILURE) {
                                phpdbg_notice("Set array watchpoint on %.*s", (int)param->len, param->str);
                        }
                        break;

                phpdbg_default_switch_case();
        }

        return SUCCESS;
} /* }}} */

void phpdbg_watch_HashTable_dtor(zval **zv) {
        phpdbg_btree_result *result;
        TSRMLS_FETCH();

        zval_ptr_dtor_wrapper(zv);

        if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong)*zv))) {
                phpdbg_watchpoint_t *watch = result->ptr;

                PHPDBG_G(watchpoint_hit) = 1;

                phpdbg_notice("%.*s was removed, removing watchpoint%s", (int)watch->str_len, watch->str, (watch->flags & PHPDBG_WATCH_RECURSIVE)?" recursively":"");

                if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
                        phpdbg_delete_watchpoint_recursive(watch, 0 TSRMLS_CC);
                } else {
                        zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
                }
        }
}


int phpdbg_create_var_watchpoint(char *input, size_t len TSRMLS_DC) {
        if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
                return FAILURE;
        }

        return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_watchpoint TSRMLS_CC);
}

int phpdbg_delete_var_watchpoint(char *input, size_t len TSRMLS_DC) {
        if (phpdbg_rebuild_symtable(TSRMLS_C) == FAILURE) {
                return FAILURE;
        }

        return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_delete_watchpoint TSRMLS_CC);
}

#ifdef _WIN32
int phpdbg_watchpoint_segfault_handler(void *addr TSRMLS_DC) {
#else
int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context TSRMLS_DC) {
#endif
        void *page;
        phpdbg_watch_memdump *dump;
        phpdbg_watchpoint_t *watch;
        size_t size;

        watch = phpdbg_check_for_watchpoint(
#ifdef _WIN32
                addr
#else
                info->si_addr
#endif
                TSRMLS_CC);

        if (watch == NULL) {
                return FAILURE;
        }

        page = phpdbg_get_page_boundary(watch->addr.ptr);
        size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);

        /* re-enable writing */
        mprotect(page, size, PROT_READ | PROT_WRITE);

        dump = malloc(MEMDUMP_SIZE(size));
        dump->page = page;
        dump->size = size;

        memcpy(&dump->data, page, size);

        zend_llist_add_element(&PHPDBG_G(watchlist_mem), &dump);

        return SUCCESS;
}

void phpdbg_watchpoints_clean(TSRMLS_D) {
        zend_hash_clean(&PHPDBG_G(watchpoints));
}

static void phpdbg_watch_dtor(void *pDest) {
        phpdbg_watchpoint_t *watch = *(phpdbg_watchpoint_t **)pDest;
        TSRMLS_FETCH();

        phpdbg_deactivate_watchpoint(watch TSRMLS_CC);
        phpdbg_remove_watchpoint(watch TSRMLS_CC);

        free(watch->str);
        free(watch->name_in_parent);
        efree(watch);
}

static void phpdbg_watch_mem_dtor(void *llist_data) {
        phpdbg_watch_memdump *dump = *(phpdbg_watch_memdump **)llist_data;

        /* Disble writing again */
        if (dump->reenable_writing) {
                mprotect(dump->page, dump->size, PROT_READ);
        }

        free(*(void **)llist_data);
}

void phpdbg_setup_watchpoints(TSRMLS_D) {
#if _SC_PAGE_SIZE
        phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
#elif _SC_PAGESIZE
        phpdbg_pagesize = sysconf(_SC_PAGESIZE);
#elif _SC_NUTC_OS_PAGESIZE
        phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
#else
        phpdbg_pagesize = 4096; /* common pagesize */
#endif

        zend_llist_init(&PHPDBG_G(watchlist_mem), sizeof(void *), phpdbg_watch_mem_dtor, 1);
        phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
        phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
        zend_hash_init(&PHPDBG_G(watchpoints), 8, NULL, phpdbg_watch_dtor, 0 ZEND_FILE_LINE_CC);
}

static void phpdbg_print_changed_zval(phpdbg_watch_memdump *dump TSRMLS_DC) {
        /* fetch all changes between dump->page and dump->page + dump->size */
        phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), (zend_ulong)dump->page, (zend_ulong)dump->page + dump->size);
        phpdbg_btree_result *result, *htresult;
        int elementDiff;
        void *curTest;

        dump->reenable_writing = 0;

        while ((result = phpdbg_btree_next(&pos))) {
                phpdbg_watchpoint_t *watch = result->ptr, *htwatch;
                void *oldPtr = (char *)&dump->data + ((size_t)watch->addr.ptr - (size_t)dump->page);
                char reenable = 1;

                if ((size_t)watch->addr.ptr < (size_t)dump->page || (size_t)watch->addr.ptr + watch->size > (size_t)dump->page + dump->size) {
                        continue;
                }

                /* Test if the zval was separated and if necessary move the watchpoint */
                if (zend_hash_find(watch->parent_container, watch->name_in_parent, watch->name_in_parent_len + 1, &curTest) == SUCCESS) {
                        if (watch->type == WATCH_ON_HASHTABLE) {
                                switch (Z_TYPE_PP((zval **)curTest)) {
                                        case IS_ARRAY:
                                                curTest = (void *)Z_ARRVAL_PP((zval **)curTest);
                                                break;
                                        case IS_OBJECT:
                                                curTest = (void *)Z_OBJPROP_PP((zval **)curTest);
                                                break;
                                }
                        } else {
                                curTest = *(void **)curTest;
                        }

                        if (curTest != watch->addr.ptr) {
                                phpdbg_deactivate_watchpoint(watch TSRMLS_CC);
                                phpdbg_remove_watchpoint(watch TSRMLS_CC);
                                watch->addr.ptr = curTest;
                                phpdbg_store_watchpoint(watch TSRMLS_CC);
                                phpdbg_activate_watchpoint(watch TSRMLS_CC);

                                reenable = 0;
                        }
                }

                /* Show to the user what changed and delete watchpoint upon removal */
                if (memcmp(oldPtr, watch->addr.ptr, watch->size) != SUCCESS) {
                        if (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS || (watch->type == WATCH_ON_ZVAL && memcmp(oldPtr, watch->addr.zv, sizeof(zvalue_value))) || (watch->type == WATCH_ON_HASHTABLE
#if ZEND_DEBUG
                            && !watch->addr.ht->inconsistent
#endif
                            && zend_hash_num_elements((HashTable *)oldPtr) != zend_hash_num_elements(watch->addr.ht))) {
                                PHPDBG_G(watchpoint_hit) = 1;

                                phpdbg_notice("Breaking on watchpoint %s", watch->str);
                        }

                        switch (watch->type) {
                                case WATCH_ON_ZVAL: {
                                        int removed = ((zval *)oldPtr)->refcount__gc != watch->addr.zv->refcount__gc && !zend_symtable_exists(watch->parent_container, watch->name_in_parent, watch->name_in_parent_len + 1);
                                        int show_value = memcmp(oldPtr, watch->addr.zv, sizeof(zvalue_value));
                                        int show_ref = ((zval *)oldPtr)->refcount__gc != watch->addr.zv->refcount__gc || ((zval *)oldPtr)->is_ref__gc != watch->addr.zv->is_ref__gc;

                                        if (removed || show_value) {
                                                phpdbg_write("Old value: ");
                                                if ((Z_TYPE_P((zval *)oldPtr) == IS_ARRAY || Z_TYPE_P((zval *)oldPtr) == IS_OBJECT) && removed) {
                                                        phpdbg_writeln("Value inaccessible, HashTable already destroyed");
                                                } else {
                                                        zend_print_flat_zval_r((zval *)oldPtr TSRMLS_CC);
                                                        phpdbg_writeln("");
                                                }
                                        }
                                        if (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS && (removed || show_ref)) {
                                                phpdbg_writeln("Old refcount: %d; Old is_ref: %d", ((zval *)oldPtr)->refcount__gc, ((zval *)oldPtr)->is_ref__gc);
                                        }

                                        /* check if zval was removed */
                                        if (removed) {
                                                phpdbg_notice("Watchpoint %s was unset, removing watchpoint", watch->str);
                                                zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);

                                                reenable = 0;

                                                if (Z_TYPE_P((zval *)oldPtr) == IS_ARRAY || Z_TYPE_P((zval *)oldPtr) == IS_OBJECT) {
                                                        goto remove_ht_watch;
                                                }

                                                break;
                                        }

                                        if (show_value) {
                                                phpdbg_write("New value: ");
                                                zend_print_flat_zval_r(watch->addr.zv TSRMLS_CC);
                                                phpdbg_writeln("");
                                        }
                                        if (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS && show_ref) {
                                                phpdbg_writeln("New refcount: %d; New is_ref: %d", watch->addr.zv->refcount__gc, watch->addr.zv->is_ref__gc);
                                        }

                                        if ((Z_TYPE_P(watch->addr.zv) == IS_ARRAY && Z_ARRVAL_P(watch->addr.zv) != Z_ARRVAL_P((zval *)oldPtr)) || (Z_TYPE_P(watch->addr.zv) != IS_OBJECT && Z_OBJ_HANDLE_P(watch->addr.zv) == Z_OBJ_HANDLE_P((zval *)oldPtr))) {
                                                /* add new watchpoints if necessary */
                                                if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
                                                        phpdbg_create_recursive_watchpoint(watch TSRMLS_CC);
                                                }
                                        }

                                        if ((Z_TYPE_P((zval *)oldPtr) != IS_ARRAY || Z_ARRVAL_P(watch->addr.zv) == Z_ARRVAL_P((zval *)oldPtr)) && (Z_TYPE_P((zval *)oldPtr) != IS_OBJECT || Z_OBJ_HANDLE_P(watch->addr.zv) == Z_OBJ_HANDLE_P((zval *)oldPtr))) {
                                                break;
                                        }

remove_ht_watch:
                                        if ((htresult = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong)Z_ARRVAL_P((zval *)oldPtr)))) {
                                                htwatch = htresult->ptr;
                                                zend_hash_del(&PHPDBG_G(watchpoints), htwatch->str, htwatch->str_len);
                                        }

                                        break;
                                }
                                case WATCH_ON_HASHTABLE:

#if ZEND_DEBUG
                                        if (watch->addr.ht->inconsistent) {
                                                phpdbg_notice("Watchpoint %s was unset, removing watchpoint", watch->str);
                                                zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);

                                                reenable = 0;

                                                break;
                                        }
#endif

                                        elementDiff = zend_hash_num_elements((HashTable *)oldPtr) - zend_hash_num_elements(watch->addr.ht);
                                        if (elementDiff) {
                                                if (elementDiff > 0) {
                                                        phpdbg_writeln("%d elements were removed from the array", elementDiff);
                                                } else {
                                                        phpdbg_writeln("%d elements were added to the array", -elementDiff);

                                                        /* add new watchpoints if necessary */
                                                        if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
                                                                phpdbg_create_recursive_watchpoint(watch TSRMLS_CC);
                                                        }
                                                }
                                        }
                                        if (((HashTable *)oldPtr)->pInternalPointer != watch->addr.ht->pInternalPointer) {
                                                phpdbg_writeln("Internal pointer of array was changed");
                                        }
                                        break;
                        }
                }

                dump->reenable_writing = dump->reenable_writing | reenable;
        }
}

int phpdbg_print_changed_zvals(TSRMLS_D) {
        zend_llist_position pos;
        phpdbg_watch_memdump **dump;
        int ret;

        if (zend_llist_count(&PHPDBG_G(watchlist_mem)) == 0) {
                return FAILURE;
        }

        dump = (phpdbg_watch_memdump **)zend_llist_get_last_ex(&PHPDBG_G(watchlist_mem), &pos);

        do {
                phpdbg_print_changed_zval(*dump TSRMLS_CC);
        } while ((dump = (phpdbg_watch_memdump **)zend_llist_get_prev_ex(&PHPDBG_G(watchlist_mem), &pos)));

        zend_llist_clean(&PHPDBG_G(watchlist_mem));

        ret = PHPDBG_G(watchpoint_hit)?SUCCESS:FAILURE;
        PHPDBG_G(watchpoint_hit) = 0;

        return ret;
}

void phpdbg_list_watchpoints(TSRMLS_D) {
        HashPosition position;
        phpdbg_watchpoint_t **watch;

        for (zend_hash_internal_pointer_reset_ex(&PHPDBG_G(watchpoints), &position);
             zend_hash_get_current_data_ex(&PHPDBG_G(watchpoints), (void**) &watch, &position) == SUCCESS;
             zend_hash_move_forward_ex(&PHPDBG_G(watchpoints), &position)) {
                phpdbg_writeln("%.*s", (int)(*watch)->str_len, (*watch)->str);
        }
}

void phpdbg_watch_efree(void *ptr) {
        phpdbg_btree_result *result;
        TSRMLS_FETCH();

        result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)ptr);

        if (result) {
                phpdbg_watchpoint_t *watch = result->ptr;

                if ((size_t)watch->addr.ptr + watch->size > (size_t)ptr) {
                        zend_hash_del(&PHPDBG_G(watchpoints), watch->str, watch->str_len);
                }
        }

        PHPDBG_G(original_free_function)(ptr);
}

/* [<][>][^][v][top][bottom][index][help] */