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_free_watch
  11. phpdbg_create_refcounted_watchpoint
  12. phpdbg_add_watch_collision
  13. phpdbg_remove_watch_collision
  14. phpdbg_create_reference_watch
  15. phpdbg_get_refcount_watch
  16. phpdbg_create_watchpoint
  17. phpdbg_create_simple_watchpoint
  18. phpdbg_create_array_watchpoint
  19. phpdbg_create_recursive_watchpoint
  20. phpdbg_create_recursive_ht_watch
  21. phpdbg_create_recursive_zval_watch
  22. phpdbg_delete_implicit_parents
  23. phpdbg_delete_watchpoint_recursive
  24. phpdbg_delete_ht_watchpoints_recursive
  25. phpdbg_delete_zval_watchpoints_recursive
  26. phpdbg_delete_watchpoints_recursive
  27. phpdbg_delete_watchpoint
  28. phpdbg_watchpoint_parse_wrapper
  29. phpdbg_watchpoint_parse_input
  30. phpdbg_watchpoint_parse_step
  31. phpdbg_watchpoint_parse_symtables
  32. PHPDBG_WATCH
  33. PHPDBG_WATCH
  34. PHPDBG_WATCH
  35. phpdbg_watch_HashTable_dtor
  36. phpdbg_create_var_watchpoint
  37. phpdbg_delete_var_watchpoint
  38. phpdbg_watchpoint_segfault_handler
  39. phpdbg_watchpoint_segfault_handler
  40. phpdbg_watchpoints_clean
  41. phpdbg_watch_dtor
  42. phpdbg_watch_mem_dtor
  43. phpdbg_watch_free_ptr_dtor
  44. phpdbg_setup_watchpoints
  45. phpdbg_print_changed_zval
  46. phpdbg_print_changed_zvals
  47. phpdbg_list_watchpoints
  48. phpdbg_watch_efree

   1 /*
   2    +----------------------------------------------------------------------+
   3    | PHP Version 7                                                        |
   4    +----------------------------------------------------------------------+
   5    | Copyright (c) 1997-2016 The PHP Group                                |
   6    +----------------------------------------------------------------------+
   7    | This source file is subject to version 3.01 of the PHP license,      |
   8    | that is bundled with this package in the file LICENSE, and is        |
   9    | available through the world-wide-web at the following url:           |
  10    | http://www.php.net/license/3_01.txt                                  |
  11    | If you did not receive a copy of the PHP license and are unable to   |
  12    | obtain it through the world-wide-web, please send a note to          |
  13    | license@php.net so we can mail you a copy immediately.               |
  14    +----------------------------------------------------------------------+
  15    | Authors: Felipe Pena <felipe@php.net>                                |
  16    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
  17    | Authors: Bob Weinand <bwoebi@php.net>                                |
  18    +----------------------------------------------------------------------+
  19 */
  20 
  21 /* Some information for the reader...
  22  *
  23  * Watchpoints are either simple, recursive or implicit (PHPDBG_WATCH_* flags)
  24  * Simple means that a particular watchpoint was explicitely defined
  25  * Recursive watchpoints are created recursively and substitute simple watchpoints
  26  * Implicit watchpoints are implicitely created on all ancestors of simple or recursive watchpoints
  27  * Recursive and (simple or implicit) watchpoints are mutually exclusive
  28  *
  29  * PHPDBG_G(watchpoint_tree) contains all watchpoints identified by the watch target address
  30  * PHPDBG_G(watch_HashTables) contains the dtors of the HashTables to call in our custom dtor (we substitute the dtor of HashTables containing watched zvals by our own dtor)
  31  * PHPDBG_G(watchpoints) contains all watchpoints (except the ones managed by watch collision)
  32  * PHPDBG_G(watch_collisions) is indexed by a zend_reference * pointer. It stores information about collisions (everything which contains a zend_reference * may be referenced by multiple watches)
  33  *
  34  * Creating a watchpoint:
  35  * * Create watchpoints with PHPDBG_WATCH_IMPLICIT set on each zval and HashTable in hierarchy except the last zval or HashTable fetch. (if already existing PHPDBG_WATCH_IMPLICIT flag is added)
  36  * * Create a PHPDBG_WATCH_SIMPLE watch for simple watches or a PHPDBG_WATCH_RECURSIVE watch for recursive watches
  37  * * When the target zval is an IS_REFERENCE, create a watchpoint on it too
  38  * * Each time a watchpoints parent is a zval and it is Z_REFCOUNTED(), put a watchpoint (WATCH_ON_REFCOUNTED) on it and add a watchpoint collision
  39  * * When in recursive mode and encountering a not-refcounted PHPDBG_WATCH_SIMPLE, remove it and recreate it with a PHPDBG_WATCH_RECURSIVE (handled via watch collision)
  40  * * Make attention to not add something twice or iterate over it twice
  41  *
  42  * Deleting a watchpoint:
  43  * * Only allow deletion of recursive watches at their root and simple watches
  44  * * Go to referenced variable. And remove watch collision on *parent* (if there is a parent)
  45  * * If it is Z_REFCOUNTED(), remove that watch collision
  46  *
  47  * Watch collisions:
  48  * * hold a counter for recursive, if it is incremented from 0 to 1, create recursive watchpoint
  49  * * holds a HashTable for normal (not implicit) watchpoints ... it is used to get the fetch type of the HashTable (depending on whether it is empty or not)
  50  * * holds a HashTable for implicit watchpoints ... (some sort of a refcounter, but ensure that there are no duplicates)
  51  * * if normal and implicit watchpoints are empty, drop that watch collision and remove WATCH_ON_REFCOUNT alongside with watchpoint on an eventual reference
  52  *
  53  * Watching on addresses:
  54  * * Address and size are transformed into memory page aligned address and size
  55  * * mprotect() enables or disables them (depending on flags) - Windows has a transparent compatibility layer in phpdbg_win.c
  56  * * segfault handler dumps watched memory segment and deactivates watchpoint
  57  * * later watches inside these memory segments are compared against their current value and eventually reactivated (or deleted)
  58  *
  59  * A watched zval was removed:
  60  * * trigger a memory copy (in segv handler) and an automatic deactivation
  61  * * change a type flag _zval_struct.u1.v.type_flags (add PHPDBG_DESTRUCTED_ZVAL flag) in memory dump
  62  *
  63  * A watched zval was changed:
  64  * * check if parent container has a different reference for referenced zval - recursively update all watches and drop them if necessary
  65  * * if _zval_struct.u1.v.type_flags & PHPDBG_DESTRUCTED_ZVAL, add it to a list of zvals to be handled at the end (if location was not changed, remove it finally)
  66  * * display changes if watch->flags & PHPDBG_WATCH_NORMAL (means: not implicit)
  67  * * handle case where Z_RECOUNTED() or Z_PTR() changed (remove/add collison(s))
  68  * * if necessary ... on zvals: handle references, if recursive also objects and arrays ... on arrays: if recursive, add new elements
  69  * * drop destructed zval watchpoints which were not updated
  70  */
  71 
  72 #include "zend.h"
  73 #include "phpdbg.h"
  74 #include "phpdbg_btree.h"
  75 #include "phpdbg_watch.h"
  76 #include "phpdbg_utils.h"
  77 #include "phpdbg_prompt.h"
  78 #ifndef _WIN32
  79 # include <unistd.h>
  80 # include <sys/mman.h>
  81 #endif
  82 
  83 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
  84 
  85 const phpdbg_command_t phpdbg_watch_commands[] = {
  86         PHPDBG_COMMAND_D_EX(array,      "create watchpoint on an array", 'a', watch_array,     &phpdbg_prompt_commands[24], "s", 0),
  87         PHPDBG_COMMAND_D_EX(delete,     "delete watchpoint",             'd', watch_delete,    &phpdbg_prompt_commands[24], "s", 0),
  88         PHPDBG_COMMAND_D_EX(recursive,  "create recursive watchpoints",  'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
  89         PHPDBG_END_COMMAND
  90 };
  91 
  92 //#define HT_FROM_WATCH(watch) (watch->type == WATCH_ON_OBJECT ? watch->addr.obj->handlers->get_properties(watch->parent_container.zv) : watch->type == WATCH_ON_ARRAY ? &watch->addr.arr->ht : NULL)
  93 #define HT_FROM_ZVP(zvp) (Z_TYPE_P(zvp) == IS_OBJECT ? Z_OBJPROP_P(zvp) : Z_TYPE_P(zvp) == IS_ARRAY ? Z_ARRVAL_P(zvp) : NULL)
  94 
  95 #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t)) /* we are not interested in gc and flags */
  96 #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
  97 #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
  98 
  99 typedef struct {
 100         void *page;
 101         size_t size;
 102         char reenable_writing;
 103         /* data must be last element */
 104         void *data;
 105 } phpdbg_watch_memdump;
 106 
 107 #define MEMDUMP_SIZE(size) (sizeof(phpdbg_watch_memdump) - sizeof(void *) + (size))
 108 
 109 
 110 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
 111         phpdbg_watchpoint_t *watch;
 112         phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
 113 
 114         if (result == NULL) {
 115                 return NULL;
 116         }
 117 
 118         watch = result->ptr;
 119 
 120         /* check if that addr is in a mprotect()'ed memory area */
 121         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) {
 122                 /* failure */
 123                 return NULL;
 124         }
 125 
 126         return watch;
 127 }
 128 
 129 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
 130         /* pagesize is assumed to be in the range of 2^x */
 131         mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
 132 }
 133 
 134 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
 135         phpdbg_change_watchpoint_access(watch, PROT_READ);
 136 }
 137 
 138 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
 139         phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
 140 }
 141 
 142 static inline void phpdbg_store_watchpoint(phpdbg_watchpoint_t *watch) {
 143         phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
 144 }
 145 
 146 static inline void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
 147         phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
 148 }
 149 
 150 void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
 151         watch->addr.ptr = addr;
 152         watch->size = size;
 153 }
 154 
 155 void phpdbg_create_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
 156         phpdbg_create_addr_watchpoint(zv, sizeof(zval), watch);
 157         watch->type = WATCH_ON_ZVAL;
 158 }
 159 
 160 void phpdbg_create_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
 161         phpdbg_create_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
 162         watch->type = WATCH_ON_HASHTABLE;
 163         watch->implicit_ht_count = 0;
 164 }
 165 
 166 static int phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t *watch);
 167 static int phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t *watch);
 168 
 169 void phpdbg_watch_HashTable_dtor(zval *ptr);
 170 
 171 static void phpdbg_free_watch(phpdbg_watchpoint_t *watch) {
 172         zend_string_release(watch->str);
 173         zend_string_release(watch->name_in_parent);
 174 }
 175 
 176 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch);
 177 static void phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t *watch);
 178 static void phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t *watch);
 179 static void phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t *watch);
 180 
 181 /* Store all the possible watches the refcounted may refer to (for displaying & deleting by identifier) [collision] */
 182 static phpdbg_watchpoint_t *phpdbg_create_refcounted_watchpoint(phpdbg_watchpoint_t *parent, zend_refcounted *ref) {
 183         phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
 184         watch->flags = parent->flags;
 185         watch->parent = parent;
 186         watch->str = parent->str;
 187         ++GC_REFCOUNT(parent->str);
 188         phpdbg_create_addr_watchpoint(&GC_REFCOUNT(ref), sizeof(uint32_t), watch);
 189         watch->type = WATCH_ON_REFCOUNTED;
 190 
 191         return watch;
 192 }
 193 
 194 /* Must prevent duplicates ... if there are duplicates, replace new by old! */
 195 static void phpdbg_add_watch_collision(phpdbg_watchpoint_t *watch) {
 196         phpdbg_watch_collision *cur;
 197 
 198         /* Check for either recursive or (simple and/or implicit) */
 199         ZEND_ASSERT(((watch->flags & PHPDBG_WATCH_RECURSIVE) == 0) ^ ((watch->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_SIMPLE)) == 0));
 200 
 201         if ((cur = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->addr.ref))) {
 202                 phpdbg_watchpoint_t *old;
 203                 int flags = 0;
 204                 if ((old = zend_hash_find_ptr(&cur->watches, watch->str)) || (old = zend_hash_find_ptr(&cur->implicit_watches, watch->str))) {
 205                         if (((old->flags ^ watch->flags) & (PHPDBG_WATCH_NORMAL|PHPDBG_WATCH_IMPLICIT)) == 0) {
 206                                 return; /* there was no change ... */
 207                         }
 208 
 209                         flags = old->flags;
 210 
 211                         if (flags & PHPDBG_WATCH_RECURSIVE) {
 212                                 if (!(watch->flags & PHPDBG_WATCH_RECURSIVE) && !--cur->refs) {
 213                                         phpdbg_delete_watchpoints_recursive(watch);
 214                                 }
 215                         }
 216                         if (flags & PHPDBG_WATCH_NORMAL) {
 217                                 zend_hash_del(&cur->watches, watch->str);
 218                                 if (zend_hash_num_elements(&cur->watches) > 0) {
 219                                         cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->watches, NULL));
 220                                 } else {
 221                                         cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->implicit_watches, NULL));
 222                                 }
 223                         }
 224                         if (flags & PHPDBG_WATCH_IMPLICIT) {
 225                                 zend_hash_del(&cur->implicit_watches, watch->str);
 226                         }
 227 
 228                         old->flags = watch->flags;
 229                         phpdbg_free_watch(watch);
 230                         efree(watch);
 231                         watch = old;
 232                 }
 233                 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
 234                         if (!(flags & PHPDBG_WATCH_RECURSIVE) && !cur->refs++) {
 235                                 phpdbg_create_recursive_zval_watch(watch->parent);
 236                         }
 237                 }
 238         } else {
 239                 phpdbg_watch_collision coll;
 240                 coll.refs = (watch->flags & PHPDBG_WATCH_RECURSIVE) != 0;
 241                 coll.watch = watch;
 242                 zend_hash_init(&coll.watches, 8, arghs, NULL, 0);
 243                 zend_hash_init(&coll.implicit_watches, 8, ..., NULL, 0);
 244                 cur = zend_hash_index_add_mem(&PHPDBG_G(watch_collisions), (zend_ulong) watch->addr.ref, &coll, sizeof(phpdbg_watch_collision));
 245                 phpdbg_store_watchpoint(cur->watch);
 246                 phpdbg_activate_watchpoint(cur->watch);
 247                 if (coll.refs) {
 248                         phpdbg_create_recursive_zval_watch(watch->parent);
 249                 }
 250         }
 251 
 252         if (watch->flags & PHPDBG_WATCH_NORMAL) {
 253                 cur->watch = watch;
 254                 zend_hash_add_ptr(&cur->watches, watch->str, watch->parent);
 255         }
 256         if (watch->flags & PHPDBG_WATCH_IMPLICIT) {
 257                 zend_hash_add_ptr(&cur->implicit_watches, watch->str, watch->parent);
 258         }
 259 }
 260 
 261 static void phpdbg_remove_watch_collision(phpdbg_watchpoint_t *watch) {
 262         phpdbg_watch_collision *cur;
 263         if ((cur = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) Z_COUNTED_P(watch->addr.zv)))) {
 264                 if (cur->refs && !--cur->refs) {
 265                         phpdbg_delete_watchpoints_recursive(watch);
 266                 }
 267 
 268                 zend_hash_del(&cur->watches, watch->str);
 269                 zend_hash_del(&cur->implicit_watches, watch->str);
 270 
 271                 if (zend_hash_num_elements(&cur->watches) > 0) {
 272                         cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->watches, NULL));
 273                 } else if (zend_hash_num_elements(&cur->implicit_watches) > 0) {
 274                         cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->implicit_watches, NULL));
 275                 } else {
 276                         phpdbg_deactivate_watchpoint(cur->watch);
 277                         phpdbg_remove_watchpoint(cur->watch);
 278 
 279                         zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) Z_COUNTED_P(watch->addr.zv));
 280                 }
 281         }
 282 }
 283 
 284 static phpdbg_watchpoint_t *phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch);
 285 
 286 static phpdbg_watchpoint_t *phpdbg_create_reference_watch(phpdbg_watchpoint_t *watch) {
 287         phpdbg_watchpoint_t *ref = emalloc(sizeof(phpdbg_watchpoint_t));
 288         watch->reference = ref;
 289         ref->flags = watch->flags;
 290         ref->str = watch->str;
 291         ++GC_REFCOUNT(ref->str);
 292         ref->parent = watch;
 293         ref->parent_container = NULL;
 294         phpdbg_create_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), ref);
 295 
 296         phpdbg_create_watchpoint(ref);
 297 
 298         return ref;
 299 }
 300 
 301 static phpdbg_watchpoint_t *phpdbg_get_refcount_watch(phpdbg_watchpoint_t *parent) {
 302         zend_refcounted *ref;
 303         phpdbg_btree_result *res;
 304 
 305         if (parent->type == WATCH_ON_HASHTABLE) {
 306                 parent = parent->parent;
 307                 if (!parent) {
 308                         return NULL;
 309                 }
 310         }
 311 
 312         ZEND_ASSERT(parent->type == WATCH_ON_ZVAL);
 313         ref = Z_COUNTED_P(parent->addr.zv);
 314 
 315         res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ref);
 316         if (res) {
 317                 return res->ptr;
 318         }
 319         return NULL;
 320 }
 321 
 322 static phpdbg_watchpoint_t *phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch) {
 323         phpdbg_watchpoint_t *ret = watch;
 324 
 325         if (watch->type == WATCH_ON_ZVAL) {
 326                 switch (Z_TYPE_P(watch->addr.zv)) {
 327                         case IS_NULL:
 328                         case IS_UNDEF:
 329                         case IS_TRUE:
 330                         case IS_FALSE:
 331                                 memset(watch->addr.zv, 0, sizeof(zend_value));
 332                 }
 333         }
 334 
 335         /* exclude references & refcounted */
 336         if (!watch->parent || watch->parent->type != WATCH_ON_ZVAL || watch->type == WATCH_ON_HASHTABLE) {
 337                 phpdbg_watchpoint_t *old_watch = zend_hash_find_ptr(&PHPDBG_G(watchpoints), watch->str);
 338 
 339                 if (old_watch) {
 340 #define return_and_free_watch(x) { \
 341         phpdbg_watchpoint_t *ref = phpdbg_get_refcount_watch(old_watch); \
 342         if (ref) { \
 343                 phpdbg_add_watch_collision(ref); \
 344         } \
 345         if (watch != old_watch) { \
 346                 phpdbg_free_watch(watch); \
 347                 efree(watch); \
 348         } \
 349         return (x); \
 350 }
 351                         if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
 352                                 if (old_watch->flags & PHPDBG_WATCH_RECURSIVE) {
 353                                         return_and_free_watch(NULL);
 354                                 } else {
 355                                         old_watch->flags &= ~(PHPDBG_WATCH_SIMPLE | PHPDBG_WATCH_IMPLICIT);
 356                                         old_watch->flags |= PHPDBG_WATCH_RECURSIVE;
 357                                         return_and_free_watch(old_watch);
 358                                 }
 359                         } else {
 360                                 if (!(old_watch->flags & PHPDBG_WATCH_RECURSIVE)) {
 361                                         old_watch->flags |= watch->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_SIMPLE);
 362                                 }
 363                                 return_and_free_watch(NULL);
 364                         }
 365                 } else {
 366                         if (watch->parent && watch->parent->type == WATCH_ON_HASHTABLE) {
 367                                 watch->parent->implicit_ht_count++;
 368                         }
 369                         zend_hash_add_ptr(&PHPDBG_G(watchpoints), watch->str, watch);
 370                 }
 371         }
 372 
 373         phpdbg_store_watchpoint(watch);
 374 
 375         if (watch->parent && watch->parent->type == WATCH_ON_ZVAL && Z_REFCOUNTED_P(watch->parent->addr.zv)) {
 376                 phpdbg_add_watch_collision(phpdbg_create_refcounted_watchpoint(watch, Z_COUNTED_P(watch->parent->addr.zv)));
 377         }
 378 
 379         if (watch->type == WATCH_ON_ZVAL) {
 380                 if (watch->parent_container) {
 381                         HashTable *ht_watches;
 382                         phpdbg_btree_result *find;
 383                         if (!(find = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container))) {
 384                                 phpdbg_watch_ht_info *hti = emalloc(sizeof(*hti));
 385                                 hti->dtor = watch->parent_container->pDestructor;
 386                                 ht_watches = &hti->watches;
 387                                 zend_hash_init(ht_watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
 388                                 phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container, hti);
 389                                 watch->parent_container->pDestructor = (dtor_func_t) phpdbg_watch_HashTable_dtor;
 390                         } else {
 391                                 ht_watches = &((phpdbg_watch_ht_info *) find->ptr)->watches;
 392                         }
 393                         zend_hash_add_ptr(ht_watches, watch->name_in_parent, watch);
 394                 }
 395 
 396                 if (Z_ISREF_P(watch->addr.zv)) {
 397                         ret = phpdbg_create_reference_watch(watch);
 398                 }
 399         }
 400 
 401         phpdbg_activate_watchpoint(watch);
 402 
 403         return ret;
 404 }
 405 
 406 static int phpdbg_create_simple_watchpoint(phpdbg_watchpoint_t *watch) {
 407         watch->flags |= PHPDBG_WATCH_SIMPLE;
 408 
 409         phpdbg_create_watchpoint(watch);
 410 
 411         return SUCCESS;
 412 }
 413 
 414 static int phpdbg_create_array_watchpoint(phpdbg_watchpoint_t *zv_watch) {
 415         zval *zv = zv_watch->addr.zv;
 416         phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
 417         HashTable *ht = HT_FROM_ZVP(zv);
 418 
 419         watch->parent = zv_watch;
 420 
 421         if (!ht) {
 422                 return FAILURE;
 423         }
 424 
 425         phpdbg_create_ht_watchpoint(ht, watch);
 426 
 427         if (phpdbg_create_watchpoint(watch) == NULL) {
 428                 return SUCCESS;
 429         }
 430 
 431         if (Z_TYPE_P(zv) == IS_ARRAY) {
 432                 watch->flags |= PHPDBG_WATCH_ARRAY;
 433         } else {
 434                 watch->flags |= PHPDBG_WATCH_OBJECT;
 435         }
 436 
 437         phpdbg_add_watch_collision(phpdbg_create_refcounted_watchpoint(watch, Z_COUNTED_P(zv)));
 438 
 439         return SUCCESS;
 440 }
 441 
 442 static int phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t *watch) {
 443         if (watch->type != WATCH_ON_ZVAL) {
 444                 return FAILURE;
 445         }
 446 
 447         watch->flags |= PHPDBG_WATCH_RECURSIVE;
 448         watch = phpdbg_create_watchpoint(watch);
 449 
 450         return SUCCESS;
 451 }
 452 
 453 static int phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t *watch) {
 454         zval *zv;
 455         zend_string *key;
 456         zend_long h;
 457 
 458         ZEND_ASSERT(watch->type == WATCH_ON_HASHTABLE);
 459 
 460         ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(watch), h, key, zv) {
 461                 phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
 462 
 463                 new_watch->flags = PHPDBG_WATCH_RECURSIVE;
 464                 new_watch->parent = watch;
 465                 new_watch->parent_container = HT_WATCH_HT(watch);
 466 
 467                 if (key) {
 468                         new_watch->name_in_parent = key;
 469                         ++GC_REFCOUNT(key);
 470                 } else {
 471                         new_watch->name_in_parent = strpprintf(0, ZEND_LONG_FMT, h);
 472                 }
 473 
 474                 new_watch->str = strpprintf(0, "%.*s%s%s%s", (int) ZSTR_LEN(watch->str) - 2, ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", phpdbg_get_property_key(ZSTR_VAL(new_watch->name_in_parent)), (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
 475 
 476                 while (Z_TYPE_P(zv) == IS_INDIRECT) {
 477                         zv = Z_INDIRECT_P(zv);
 478                 }
 479 
 480                 phpdbg_create_zval_watchpoint(zv, new_watch);
 481                 new_watch = phpdbg_create_watchpoint(new_watch);
 482                 phpdbg_create_recursive_zval_watch(new_watch);
 483         } ZEND_HASH_FOREACH_END();
 484 
 485         return SUCCESS;
 486 }
 487 
 488 static int phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t *watch) {
 489         HashTable *ht;
 490         zval *zvp;
 491 
 492         ZEND_ASSERT(watch->type == WATCH_ON_ZVAL);
 493 
 494         zvp = watch->addr.zv;
 495         ZVAL_DEREF(zvp);
 496 
 497         if ((ht = HT_FROM_ZVP(zvp))) {
 498                 phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
 499 
 500                 new_watch->flags = PHPDBG_WATCH_RECURSIVE;
 501                 new_watch->parent = watch;
 502                 new_watch->parent_container = watch->parent_container;
 503                 new_watch->name_in_parent = watch->name_in_parent;
 504                 ++GC_REFCOUNT(new_watch->name_in_parent);
 505                 new_watch->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
 506 
 507                 if (Z_TYPE_P(zvp) == IS_ARRAY) {
 508                         new_watch->flags |= PHPDBG_WATCH_ARRAY;
 509                 } else {
 510                         new_watch->flags |= PHPDBG_WATCH_OBJECT;
 511                 }
 512 
 513                 phpdbg_create_ht_watchpoint(ht, new_watch);
 514 
 515                 phpdbg_create_recursive_ht_watch(new_watch);
 516 
 517                 phpdbg_create_watchpoint(new_watch);
 518         }
 519 
 520         return SUCCESS;
 521 }
 522 
 523 static void phpdbg_delete_implicit_parents(phpdbg_watchpoint_t *watch) {
 524         phpdbg_watchpoint_t *parent = watch->parent;
 525         if (!parent) {
 526                 return;
 527         }
 528 
 529         ZEND_ASSERT(!(parent->flags & PHPDBG_WATCH_RECURSIVE));
 530         ZEND_ASSERT(parent->flags & PHPDBG_WATCH_IMPLICIT);
 531 
 532         if (parent->type == WATCH_ON_HASHTABLE && --parent->implicit_ht_count) {
 533                 return;
 534         }
 535 
 536         parent->flags &= ~PHPDBG_WATCH_IMPLICIT;
 537         if (!(parent->flags & PHPDBG_WATCH_SIMPLE)) {
 538                 if (parent->type == WATCH_ON_ZVAL && Z_REFCOUNTED_P(watch->addr.zv)) {
 539                         phpdbg_remove_watch_collision(parent);
 540                 }
 541                 zend_hash_del(&PHPDBG_G(watchpoints), parent->str);
 542         }
 543 }
 544 
 545 /* delete watchpoint, recursively (and inclusively) */
 546 static int phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t *watch, zend_bool user_request) {
 547         if (watch->type == WATCH_ON_HASHTABLE) {
 548                 if (user_request) {
 549                         phpdbg_delete_ht_watchpoints_recursive(watch);
 550                 } else {
 551                         HashTable *ht;
 552                         phpdbg_btree_result *result;
 553 
 554                         ht = HT_FROM_ZVP(watch->addr.zv);
 555 
 556                         if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ht))) {
 557                                 phpdbg_delete_watchpoint_recursive((phpdbg_watchpoint_t *) result->ptr, user_request);
 558                         }
 559                 }
 560         } else if (watch->type == WATCH_ON_ZVAL) {
 561                 phpdbg_delete_zval_watchpoints_recursive(watch);
 562         }
 563 
 564         return zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
 565 }
 566 
 567 static void phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
 568         zend_string *str, *strkey;
 569         zend_long numkey;
 570         phpdbg_watchpoint_t *watchpoint;
 571 
 572         ZEND_HASH_FOREACH_KEY(HT_WATCH_HT(watch), numkey, strkey) {
 573                 if (strkey) {
 574                         str = strpprintf(0, "%.*s%s%s%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", phpdbg_get_property_key(ZSTR_VAL(strkey)), (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
 575                 } else {
 576                         str = strpprintf(0, "%.*s%s" ZEND_LONG_FMT "%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", numkey, (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
 577                 }
 578 
 579                 if ((watchpoint = zend_hash_find_ptr(&PHPDBG_G(watchpoints), str))) {
 580                         phpdbg_delete_watchpoint_recursive(watchpoint, 1);
 581                 }
 582 
 583                 zend_string_release(str);
 584         } ZEND_HASH_FOREACH_END();
 585 }
 586 
 587 static void phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
 588         if (Z_REFCOUNTED_P(watch->addr.zv)) {
 589                 phpdbg_remove_watch_collision(watch);
 590         }
 591 }
 592 
 593 /* delete watchpoints recusively (exclusively!) */
 594 static void phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
 595         if (watch->type == WATCH_ON_ZVAL) {
 596                 phpdbg_delete_zval_watchpoints_recursive(watch);
 597         } else if (watch->type == WATCH_ON_HASHTABLE) {
 598                 phpdbg_delete_ht_watchpoints_recursive(watch);
 599         }
 600 }
 601 
 602 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch) {
 603         int ret;
 604         phpdbg_watchpoint_t *watch;
 605         phpdbg_btree_result *result;
 606 
 607         if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) tmp_watch->addr.ptr)) == NULL) {
 608                 return FAILURE;
 609         }
 610 
 611         watch = result->ptr;
 612 
 613         if (!(watch->flags & PHPDBG_WATCH_NORMAL) || (watch->parent && (watch->parent->flags & PHPDBG_WATCH_RECURSIVE))) {
 614                 return FAILURE; /* TODO: better error message for recursive (??) */
 615         }
 616 
 617         if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
 618                 ret = phpdbg_delete_watchpoint_recursive(watch, 1);
 619         } else {
 620                 ret = zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
 621         }
 622 
 623         phpdbg_free_watch(tmp_watch);
 624         efree(tmp_watch);
 625 
 626         return ret;
 627 }
 628 
 629 static int phpdbg_watchpoint_parse_wrapper(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, int (*callback)(phpdbg_watchpoint_t *)) {
 630         int ret;
 631         phpdbg_watchpoint_t *watch = ecalloc(1, sizeof(phpdbg_watchpoint_t));
 632         watch->str = zend_string_init(name, namelen, 0);
 633         watch->name_in_parent = zend_string_init(key, keylen, 0);
 634         watch->parent_container = parent;
 635         phpdbg_create_zval_watchpoint(zv, watch);
 636 
 637         ret = callback(watch);
 638 
 639         efree(name);
 640         efree(key);
 641 
 642         if (ret != SUCCESS) {
 643                 phpdbg_free_watch(watch);
 644                 efree(watch);
 645         }
 646 
 647         PHPDBG_G(watch_tmp) = NULL;
 648 
 649         return ret;
 650 }
 651 
 652 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, int (*callback)(phpdbg_watchpoint_t *), zend_bool silent) {
 653         return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, callback);
 654 }
 655 
 656 static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, int (*callback)(phpdbg_watchpoint_t *)) {
 657         phpdbg_watchpoint_t *watch;
 658 
 659         if ((watch = zend_hash_str_find_ptr(&PHPDBG_G(watchpoints), name, namelen))) {
 660                 watch->flags |= PHPDBG_WATCH_IMPLICIT;
 661                 PHPDBG_G(watch_tmp) = watch;
 662                 return SUCCESS;
 663         }
 664 
 665         watch = ecalloc(1, sizeof(phpdbg_watchpoint_t));
 666         watch->flags = PHPDBG_WATCH_IMPLICIT;
 667         watch->str = zend_string_init(name, namelen, 0);
 668         watch->name_in_parent = zend_string_init(key, keylen, 0);
 669         watch->parent_container = parent;
 670         watch->parent = PHPDBG_G(watch_tmp);
 671         phpdbg_create_zval_watchpoint(zv, watch);
 672 
 673         phpdbg_create_watchpoint(watch);
 674 
 675         efree(name);
 676         efree(key);
 677 
 678         PHPDBG_G(watch_tmp) = watch;
 679         return SUCCESS;
 680 }
 681 
 682 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(phpdbg_watchpoint_t *)) {
 683         if (EG(scope) && len >= 5 && !memcmp("$this", input, 5)) {
 684                 zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
 685         }
 686 
 687         if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, callback, 1) != FAILURE) {
 688                 return SUCCESS;
 689         }
 690 
 691         return phpdbg_parse_variable_with_arg(input, len, EG(current_execute_data)->symbol_table, 0, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_step, 0, callback);
 692 }
 693 
 694 PHPDBG_WATCH(delete) /* {{{ */
 695 {
 696         switch (param->type) {
 697                 case STR_PARAM:
 698                         if (phpdbg_delete_var_watchpoint(param->str, param->len) == FAILURE) {
 699                                 phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
 700                         } else {
 701                                 phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %.*s", (int) param->len, param->str);
 702                         }
 703                         break;
 704 
 705                 phpdbg_default_switch_case();
 706         }
 707 
 708         return SUCCESS;
 709 } /* }}} */
 710 
 711 PHPDBG_WATCH(recursive) /* {{{ */
 712 {
 713         if (phpdbg_rebuild_symtable() == FAILURE) {
 714                 return SUCCESS;
 715         }
 716 
 717         switch (param->type) {
 718                 case STR_PARAM:
 719                         if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint) != FAILURE) {
 720                                 phpdbg_notice("watchrecursive", "variable=\"%.*s\"", "Set recursive watchpoint on %.*s", (int)param->len, param->str);
 721                         }
 722                         break;
 723 
 724                 phpdbg_default_switch_case();
 725         }
 726 
 727         return SUCCESS;
 728 } /* }}} */
 729 
 730 PHPDBG_WATCH(array) /* {{{ */
 731 {
 732         if (phpdbg_rebuild_symtable() == FAILURE) {
 733                 return SUCCESS;
 734         }
 735 
 736         switch (param->type) {
 737                 case STR_PARAM:
 738                         if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint) != FAILURE) {
 739                                 phpdbg_notice("watcharray", "variable=\"%.*s\"", "Set array watchpoint on %.*s", (int)param->len, param->str);
 740                         }
 741                         break;
 742 
 743                 phpdbg_default_switch_case();
 744         }
 745 
 746         return SUCCESS;
 747 } /* }}} */
 748 
 749 void phpdbg_watch_HashTable_dtor(zval *zv) {
 750         phpdbg_btree_result *result;
 751         zval *orig_zv = zv;
 752 
 753         while (Z_TYPE_P(zv) == IS_INDIRECT) {
 754                 zv = Z_INDIRECT_P(zv);
 755         }
 756 
 757         if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) zv))) {
 758                 phpdbg_watchpoint_t *watch = result->ptr;
 759 
 760                 if (watch->flags & PHPDBG_WATCH_NORMAL) {
 761                         PHPDBG_G(watchpoint_hit) = 1;
 762 
 763                         phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "%.*s was removed, removing watchpoint%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_RECURSIVE) ? " recursively" : "");
 764                 }
 765 
 766                 if ((result = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container))) {
 767                         phpdbg_watch_ht_info *hti = result->ptr;
 768                         hti->dtor(orig_zv);
 769                         zend_hash_del(&hti->watches, watch->name_in_parent);
 770                         if (zend_hash_num_elements(&hti->watches) == 0) {
 771                                 watch->parent_container->pDestructor = hti->dtor;
 772                                 zend_hash_destroy(&hti->watches);
 773                                 phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container);
 774                                 efree(hti);
 775                         }
 776                 } else {
 777                         zval_ptr_dtor_wrapper(orig_zv);
 778                 }
 779 
 780                 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
 781                         phpdbg_delete_watchpoint_recursive(watch, 0);
 782                 } else {
 783                         zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
 784                 }
 785         }
 786 }
 787 
 788 int phpdbg_create_var_watchpoint(char *input, size_t len) {
 789         if (phpdbg_rebuild_symtable() == FAILURE) {
 790                 return FAILURE;
 791         }
 792 
 793         return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
 794 }
 795 
 796 int phpdbg_delete_var_watchpoint(char *input, size_t len) {
 797         if (phpdbg_rebuild_symtable() == FAILURE) {
 798                 return FAILURE;
 799         }
 800 
 801         return phpdbg_watchpoint_parse_input(input, len, EG(current_execute_data)->symbol_table, 0, phpdbg_delete_watchpoint, 0);
 802 }
 803 
 804 #ifdef _WIN32
 805 int phpdbg_watchpoint_segfault_handler(void *addr) {
 806 #else
 807 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
 808 #endif
 809         void *page;
 810         phpdbg_watch_memdump *dump;
 811         phpdbg_watchpoint_t *watch;
 812         size_t size;
 813 
 814         watch = phpdbg_check_for_watchpoint(
 815 #ifdef _WIN32
 816                 addr
 817 #else
 818                 info->si_addr
 819 #endif
 820                 );
 821 
 822         if (watch == NULL) {
 823                 return FAILURE;
 824         }
 825 
 826         page = phpdbg_get_page_boundary(watch->addr.ptr);
 827         size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);
 828 
 829         /* re-enable writing */
 830         mprotect(page, size, PROT_READ | PROT_WRITE);
 831 
 832         dump = malloc(MEMDUMP_SIZE(size));
 833         dump->page = page;
 834         dump->size = size;
 835         dump->reenable_writing = 0;
 836 
 837         memcpy(&dump->data, page, size);
 838 
 839         zend_llist_add_element(&PHPDBG_G(watchlist_mem), &dump);
 840 
 841         return SUCCESS;
 842 }
 843 
 844 void phpdbg_watchpoints_clean(void) {
 845         zend_hash_clean(&PHPDBG_G(watchpoints));
 846 }
 847 
 848 /* due to implicit delete... MUST BE DESTROYED MANUALLY */
 849 static void phpdbg_watch_dtor(zval *pDest) {
 850         phpdbg_watchpoint_t *watch = (phpdbg_watchpoint_t *) Z_PTR_P(pDest);
 851 
 852         if (watch->flags & PHPDBG_WATCH_IMPLICIT) {
 853                 watch->flags = PHPDBG_WATCH_SIMPLE; // tiny hack for delete_implicit_parents
 854 
 855                 if (watch->type == WATCH_ON_ZVAL) {
 856                         phpdbg_delete_zval_watchpoints_recursive(watch);
 857                 } else if (watch->type == WATCH_ON_HASHTABLE) {
 858                         phpdbg_watchpoint_t *watchpoint;
 859 
 860                         watch->implicit_ht_count++;
 861 
 862                         ZEND_HASH_FOREACH_PTR(&((phpdbg_watch_ht_info *) phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch))->ptr)->watches, watchpoint) {
 863                                 phpdbg_delete_watchpoint_recursive(watchpoint, 1);
 864                         } ZEND_HASH_FOREACH_END();
 865                 }
 866         }
 867 
 868         phpdbg_delete_implicit_parents(watch);
 869 
 870         phpdbg_deactivate_watchpoint(watch);
 871         phpdbg_remove_watchpoint(watch);
 872 
 873         phpdbg_free_watch(watch);
 874         efree(watch);
 875 }
 876 
 877 static void phpdbg_watch_mem_dtor(void *llist_data) {
 878         phpdbg_watch_memdump *dump = *(phpdbg_watch_memdump **) llist_data;
 879 
 880         /* Disble writing again */
 881         if (dump->reenable_writing) {
 882                 mprotect(dump->page, dump->size, PROT_READ);
 883         }
 884 
 885         free(dump);
 886 }
 887 
 888 static void phpdbg_watch_free_ptr_dtor(zval *ptr) {
 889         efree(Z_PTR_P(ptr));
 890 }
 891 
 892 void phpdbg_setup_watchpoints(void) {
 893 #if _SC_PAGE_SIZE
 894         phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
 895 #elif _SC_PAGESIZE
 896         phpdbg_pagesize = sysconf(_SC_PAGESIZE);
 897 #elif _SC_NUTC_OS_PAGESIZE
 898         phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
 899 #else
 900         phpdbg_pagesize = 4096; /* common pagesize */
 901 #endif
 902 
 903         zend_llist_init(&PHPDBG_G(watchlist_mem), sizeof(void *), phpdbg_watch_mem_dtor, 1);
 904         phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
 905         phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
 906         zend_hash_init(&PHPDBG_G(watchpoints), 8, NULL, phpdbg_watch_dtor, 0);
 907         zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, phpdbg_watch_free_ptr_dtor, 0);
 908 }
 909 
 910 static void phpdbg_print_changed_zval(phpdbg_watch_memdump *dump) {
 911         /* fetch all changes between dump->page and dump->page + dump->size */
 912         phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), (zend_ulong) dump->page, (zend_ulong) dump->page + dump->size);
 913         phpdbg_btree_result *result;
 914         int elementDiff;
 915         void *curTest;
 916 
 917         dump->reenable_writing = 0;
 918 
 919         while ((result = phpdbg_btree_next(&pos))) {
 920                 phpdbg_watchpoint_t *watch = result->ptr;
 921                 void *oldPtr = (char *) &dump->data + ((size_t) watch->addr.ptr - (size_t) dump->page);
 922                 char reenable = 1;
 923                 int removed = 0;
 924 
 925                 if ((size_t) watch->addr.ptr < (size_t) dump->page || (size_t) watch->addr.ptr + watch->size > (size_t) dump->page + dump->size) {
 926                         continue;
 927                 }
 928 
 929                 /* Test if the zval was separated or replaced and if necessary move the watchpoint */
 930                 if ((watch->type == WATCH_ON_HASHTABLE || watch->type == WATCH_ON_ZVAL) && watch->parent_container) {
 931                         if ((curTest = zend_symtable_find(watch->parent_container, watch->name_in_parent))) {
 932                                 while (Z_TYPE_P((zval *) curTest) == IS_INDIRECT) {
 933                                         curTest = Z_INDIRECT_P((zval *) curTest);
 934                                 }
 935 
 936                                 if (watch->type == WATCH_ON_HASHTABLE) {
 937                                         switch (Z_TYPE_P((zval *) curTest)) {
 938                                                 case IS_ARRAY:
 939                                                         curTest = (void *) Z_ARRVAL_P((zval *) curTest);
 940                                                         break;
 941                                                 case IS_OBJECT:
 942                                                         curTest = (void *) Z_OBJPROP_P((zval *) curTest);
 943                                                         break;
 944                                         }
 945                                 }
 946 
 947                                 if (curTest != watch->addr.ptr) {
 948                                         phpdbg_deactivate_watchpoint(watch);
 949                                         phpdbg_remove_watchpoint(watch);
 950                                         watch->addr.ptr = curTest;
 951                                         phpdbg_store_watchpoint(watch);
 952                                         phpdbg_activate_watchpoint(watch);
 953 
 954                                         reenable = 0;
 955                                 }
 956                         } else {
 957                                 removed = 1;
 958                         }
 959                 }
 960 
 961                 /* Show to the user what changed and delete watchpoint upon removal */
 962                 {
 963                         zend_bool do_break = 0;
 964 
 965                         switch (watch->type) {
 966                                 case WATCH_ON_ZVAL:
 967                                         do_break = memcmp(oldPtr, watch->addr.zv, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
 968                                         if (!do_break) {
 969                                                 goto end;
 970                                         }
 971                                         break;
 972                                 case WATCH_ON_HASHTABLE:
 973                                         do_break = zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_WATCH_HT(watch));
 974                                         if (!do_break) {
 975                                                 goto end;
 976                                         }
 977                                         break;
 978                                 case WATCH_ON_REFCOUNTED:
 979                                         do_break = memcmp(oldPtr, watch->addr.ref, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
 980                                         if (!do_break) {
 981                                                 goto end;
 982                                         }
 983                                         if (!(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
 984                                                 do_break = 0;
 985                                         }
 986                                         break;
 987                         }
 988 
 989                         if (!(watch->flags & PHPDBG_WATCH_NORMAL)) {
 990                                 do_break = 0;
 991                         }
 992 
 993                         if (do_break) {
 994                                 PHPDBG_G(watchpoint_hit) = 1;
 995 
 996                                 phpdbg_notice("watchhit", "variable=\"%s\"", "Breaking on watchpoint %.*s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
 997                                 phpdbg_xml("<watchdata %r>");
 998                         }
 999 
1000                         switch (watch->type) {
1001                                 case WATCH_ON_ZVAL: {
1002                                         zend_bool show_value = memcmp(oldPtr, watch->addr.zv, sizeof(zend_value) + sizeof(uint32_t) /* no metadata info */) != 0;
1003 
1004                                         if ((watch->flags & PHPDBG_WATCH_NORMAL) && (removed || show_value)) {
1005 /* TODO: Merge with refcounting watches, store if watched ref value is to be dropped etc. [for example: manually increment refcount transparently for displaying and drop it if it decrements to 1] */
1006                                                 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1007                                                         phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed");
1008                                                 } else {
1009                                                         phpdbg_out("Old value: ");
1010                                                         phpdbg_xml("<watchvalue %r type=\"old\">");
1011                                                         zend_print_flat_zval_r((zval *) oldPtr);
1012                                                         phpdbg_xml("</watchvalue>");
1013                                                         phpdbg_out("\n");
1014                                                 }
1015                                         }
1016 
1017                                         /* check if zval was removed */
1018                                         if (removed) {
1019                                                 if (watch->flags & PHPDBG_WATCH_NORMAL) {
1020                                                         phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Watchpoint %.*s was unset, removing watchpoint", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
1021                                                 }
1022                                                 zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
1023 
1024                                                 reenable = 0;
1025 
1026                                                 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1027                                                         phpdbg_remove_watch_collision(watch);
1028                                                 }
1029                                                 break;
1030                                         }
1031 
1032                                         if ((watch->flags & PHPDBG_WATCH_NORMAL) && show_value) {
1033                                                 phpdbg_out("New value%s: ", Z_ISREF_P(watch->addr.zv) ? " (reference)" : "");
1034                                                 phpdbg_xml("<watchvalue %r%s type=\"new\">", Z_ISREF_P(watch->addr.zv) ? " reference=\"reference\"" : "");
1035                                                 zend_print_flat_zval_r(watch->addr.zv);
1036                                                 phpdbg_xml("</watchvalue>");
1037                                                 phpdbg_out("\n");
1038                                         }
1039 
1040                                         /* add new watchpoints if necessary */
1041                                         if (Z_PTR_P(watch->addr.zv) != Z_PTR_P((zval *) oldPtr) || Z_TYPE_P(watch->addr.zv) != Z_TYPE_P((zval *) oldPtr)) {
1042                                                 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1043                                                         zval *new_zv = watch->addr.zv;
1044                                                         watch->addr.ptr = oldPtr;
1045                                                         phpdbg_remove_watch_collision(watch);
1046                                                         watch->addr.zv = new_zv;
1047                                                 }
1048                                                 if (Z_REFCOUNTED_P(watch->addr.zv)) {
1049                                                         if ((watch->flags & PHPDBG_WATCH_NORMAL) && (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1050                                                                 phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", Z_REFCOUNT_P(watch->addr.zv));
1051                                                         }
1052                                                         if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
1053                                                                 phpdbg_create_recursive_watchpoint(watch);
1054                                                         } else if (Z_ISREF_P(watch->addr.zv)) {
1055                                                                 phpdbg_create_reference_watch(watch);
1056                                                         }
1057                                                 }
1058                                         }
1059 
1060                                         break;
1061                                 }
1062                                 case WATCH_ON_HASHTABLE:
1063                                         /* We should be safely able to assume the HashTable to be consistent (inconsistent HashTables should have been caught by phpdbg_watch_efree() */
1064                                         elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_WATCH_HT(watch));
1065                                         if ((watch->flags & PHPDBG_WATCH_NORMAL) && elementDiff) {
1066                                                 if (elementDiff > 0) {
1067                                                         phpdbg_writeln("watchsize", "removed=\"%d\"", "%d elements were removed from the array", elementDiff);
1068                                                 } else {
1069                                                         phpdbg_writeln("watchsize", "added=\"%d\"", "%d elements were added to the array", -elementDiff);
1070                                                 }
1071                                         }
1072                                         /* add new watchpoints if necessary */
1073                                         if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
1074                                                 phpdbg_create_recursive_ht_watch(watch);
1075                                         }
1076                                         if ((watch->flags & PHPDBG_WATCH_NORMAL) && HT_WATCH_HT(watch)->nInternalPointer != HT_PTR_HT(oldPtr)->nInternalPointer) {
1077                                                 phpdbg_writeln("watcharrayptr", "", "Internal pointer of array was changed");
1078                                         }
1079                                         break;
1080                                 case WATCH_ON_REFCOUNTED: {
1081                                         if ((watch->flags & PHPDBG_WATCH_NORMAL) && (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1082                                                 phpdbg_writeln("watchrefcount", "type=\"old\" refcount=\"%d\"", "Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
1083                                                 phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", GC_REFCOUNT(watch->addr.ref));
1084                                         }
1085                                         break;
1086                                 }
1087                         }
1088 
1089                         if (do_break) {
1090                                 phpdbg_xml("</watchdata>");
1091                         }
1092                 } end:
1093 
1094                 dump->reenable_writing = dump->reenable_writing | reenable;
1095         }
1096 }
1097 
1098 int phpdbg_print_changed_zvals(void) {
1099         zend_llist_position pos;
1100         phpdbg_watch_memdump **dump;
1101         int ret;
1102 
1103         if (zend_llist_count(&PHPDBG_G(watchlist_mem)) == 0) {
1104                 return FAILURE;
1105         }
1106 
1107         dump = (phpdbg_watch_memdump **) zend_llist_get_last_ex(&PHPDBG_G(watchlist_mem), &pos);
1108 
1109         do {
1110                 phpdbg_print_changed_zval(*dump);
1111         } while ((dump = (phpdbg_watch_memdump **) zend_llist_get_prev_ex(&PHPDBG_G(watchlist_mem), &pos)));
1112 
1113         zend_llist_clean(&PHPDBG_G(watchlist_mem));
1114 
1115         ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1116         PHPDBG_G(watchpoint_hit) = 0;
1117 
1118         return ret;
1119 }
1120 
1121 void phpdbg_list_watchpoints(void) {
1122         phpdbg_watchpoint_t *watch;
1123 
1124         phpdbg_xml("<watchlist %r>");
1125 
1126         ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watchpoints), watch) {
1127                 if (watch->flags & PHPDBG_WATCH_NORMAL) {
1128                         phpdbg_writeln("watchvariable", "variable=\"%.*s\" on=\"%s\" type=\"%s\"", "%.*s (%s, %s)", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), watch->type == WATCH_ON_HASHTABLE ? "array" : watch->type == WATCH_ON_REFCOUNTED ? "refcount" : "variable", watch->flags == PHPDBG_WATCH_RECURSIVE ? "recursive" : "simple");
1129                 }
1130         } ZEND_HASH_FOREACH_END();
1131 
1132         phpdbg_xml("</watchlist>");
1133 }
1134 
1135 void phpdbg_watch_efree(void *ptr) {
1136         phpdbg_btree_result *result;
1137 
1138         result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr);
1139 
1140         if (result) {
1141                 phpdbg_watchpoint_t *watch = result->ptr;
1142 
1143                 if ((size_t) watch->addr.ptr + watch->size > (size_t) ptr) {
1144                         if (watch->type == WATCH_ON_REFCOUNTED) {
1145                                 /* remove watchpoint here from btree, zval watchpoint will remove it via remove_watch_collison */
1146                                 phpdbg_deactivate_watchpoint(watch);
1147                                 phpdbg_remove_watchpoint(watch);
1148                         } else {
1149                                 if (watch->type == WATCH_ON_ZVAL) {
1150                                         phpdbg_remove_watch_collision(watch);
1151                                 }
1152                                 if (watch->type == WATCH_ON_HASHTABLE && (watch->flags & PHPDBG_WATCH_SIMPLE)) {
1153                                         /* when a HashTable is freed, we can safely assume the other zvals all were dtor'ed */
1154                                         phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "Array %.*s was removed, removing watchpoint%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_RECURSIVE) ? " recursively" : "");
1155                                 }
1156                                 if (watch->type == WATCH_ON_HASHTABLE || watch->parent == NULL || watch->parent->type != WATCH_ON_ZVAL) { /* no references */
1157                                         zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
1158                                 }
1159                         }
1160                 }
1161         }
1162 
1163         if (PHPDBG_G(original_free_function)) {
1164                 PHPDBG_G(original_free_function)(ptr);
1165         }
1166 }

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