1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | http://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Authors: Felipe Pena <felipe@php.net> |
14 | Authors: Joe Watkins <joe.watkins@live.co.uk> |
15 | Authors: Bob Weinand <bwoebi@php.net> |
16 +----------------------------------------------------------------------+
17 */
18
19 /* Some information for the reader...
20 *
21 * The main structure managing the direct observations is the watchpoint (phpdbg_watchpoint_t). There are several types of watchpoints currently:
22 * WATCH_ON_BUCKET: a watchpoint on a Bucket element, used to monitor values inside HashTables (largely handled equivalently to WATCH_ON_ZVAL, it just monitors also for IS_UNDEF and key changes)
23 * WATCH_ON_ZVAL: a watchpoint on a bare zval (&zend_reference.val, zval.value.indirect)
24 * WATCH_ON_STR: a watchpoint on a zend_string (put on &ZSTR_LEN() in order to not watch refcount/hash)
25 * WATCH_ON_HASHTABLE: a watchpoint on a HashTable (currently only used to observe size changes, put after flags in order to not watch refcount)
26 * WATCH_ON_REFCOUNTED: a watchpoint on a zend_refcounted, observes the refcount and serves as reference pointer in the custom efree handler
27 * WATCH_ON_HASHDATA: special watchpoint to watch for HT_GET_DATA_ADDR(ht) being efree()'d to be able to properly relocate Bucket watches
28 *
29 * Watch elements are either simple, recursive or implicit (PHPDBG_WATCH_* flags)
30 * Simple means that a particular watchpoint was explicitly defined
31 * Recursive watch elements are created recursively (recursive root flag is to distinguish the root element easily from its children recursive elements)
32 * Implicit watch elements are implicitly created on all ancestors of simple or recursive watch elements
33 * Recursive and (simple or implicit) watch elements are mutually exclusive
34 * Array/Object to distinguish watch elements on arrays
35 *
36 * Watch elements all contain a reference to a watchpoint (except if scheduled for recreation); a "watch" is a watch element created by the user with a specific id
37 * Each watch has its independent structure of watch elements, watchpoints are responsible for managing collisions and preventing pointers being watched multiple times
38 *
39 * PHPDBG_G(watchpoint_tree) contains all watchpoints identified by the watch target address
40 * PHPDBG_G(watch_HashTables) contains the addresses of parent_containers of watch elements
41 * PHPDBG_G(watch_elements) contains all directly defined watch elements (i.e. those which have an individual id)
42 * PHPDBG_G(watch_collisions) is indexed by a zend_refcounted * pointer (phpdbg_watchpoint_t.ref). It stores information about collisions (everything which contains a zend_refcounted * may be referenced by multiple watches)
43 * PHPDBG_G(watch_free) is a set of pointers to watch for being freed (like HashTables referenced by phpdbg_watch_element.parent_container)
44 * PHPDBG_G(watch_recreation) is the list of watch elements whose watchpoint has been removed (via efree() for example) and needs to be recreated
45 * PHPDBG_G(watchlist_mem) is the list of unprotected memory pages; used to watch which pages need their PROT_WRITE attribute removed after checking
46 *
47 * Watching on addresses:
48 * * Address and size are transformed into memory page aligned address and size
49 * * mprotect() enables or disables them (depending on flags) - Windows has a transparent compatibility layer in phpdbg_win.c
50 * * segfault handler stores the address of the page and marks it again as writable
51 * * later watchpoints pointing inside these pages are compared against their current value and eventually reactivated (or deleted)
52 *
53 * Creating a watch:
54 * * Implicit watch elements for each element in the hierarchy (starting from base, which typically is current symbol table) except the last one
55 * * Create a watch element with either simple flag or recursive [+ root] flags
56 * * If the element has recursive flag, create elements recursively for every referenced HashTable and zval
57 *
58 * Creating a watch element:
59 * * For each watch element a related watchpoint is created, if there's none yet; add itself then into the list of parents of that watchpoint
60 * * If the watch has a parent_container, add itself also into a phpdbg_watch_ht_info (inside PHPDBG_G(watch_HashTables)) [and creates it if not yet existing]
61 *
62 * Creation of watchpoints:
63 * * Watchpoints create a watch collision for each refcounted or indirect on the zval (if type is WATCH_ON_BUCKET or WATCH_ON_ZVAL)
64 * * Backs the current value of the watched pointer up
65 * * Installs the watchpoint in PHPDBG_G(watchpoint_tree) and activates it (activation of a watchpoint = remove PROT_WRITE from the pages the watched pointer resides on)
66 *
67 * Watch collisions:
68 * * Manages a watchpoint on the refcount (WATCH_ON_REFCOUNTED) or indirect zval (WATCH_ON_ZVAL)
69 * * Guarantees that every pointer is watched at most once (by having a pointer to collision mapping in PHPDBG_G(watch_collisions), which have the unique watchpoints for the respective collision)
70 * * Contains a list of parents, i.e. which watchpoints reference it (via watch->ref)
71 * * If no watchpoint is referencing it anymore, the watch collision and its associated watchpoints (phpdbg_watch_collision.ref/reference) are removed
72 *
73 * Deleting a watch:
74 * * Watches are stored by an id in PHPDBG_G(watch_elements); the associated watch element is then deleted
75 * * Deletes all parent and children implicit watch elements
76 *
77 * Deleting a watch element:
78 * * Removes itself from the parent list of the associated watchpoints; if that parent list is empty, also delete the watchpoint
79 * * Removes itself from the related phpdbg_watch_ht_info if it has a parent_container
80 *
81 * Deleting a watchpoint:
82 * * Remove itself from watch collisions this watchpoint participates in
83 * * Removes the watchpoint from PHPDBG_G(watchpoint_tree) and deactivates it (deactivation of a watchpoint = add PROT_WRITE to the pages the watched pointer resides on)
84 *
85 * A watched pointer is efree()'d:
86 * * Needs immediate action as we else may run into dereferencing a pointer into freed memory
87 * * Deletes the associated watchpoint, and for each watch element, if recursive, all its children elements
88 * * If the its watch elements are implicit, recursive roots or simple, they and all their children are dissociated from their watchpoints (i.e. removed from the watchpoint, if no other element is referencing it, it is deleted); adds these elements to PHPDBG_G(watch_recreation)
89 *
90 * Recreating watchpoints:
91 * * Upon each opcode, PHPDBG_G(watch_recreation) is checked and all its elements are searched for whether the watch is still reachable via the tree given by its implicits
92 * * In case they are not reachable, the watch is deleted (and thus all the related watch elements), else a new watchpoint is created for all the watch elements
93 * * The old and new values of the watches are compared and shown if changed
94 *
95 * Comparing watchpoints:
96 * * The old and new values of the watches are compared and shown if changed
97 * * If changed, it is checked whether the refcounted/indirect changed and watch collisions removed or created accordingly
98 * * If a zval/bucket watchpoint is recursive, watch elements are added or removed accordingly
99 * * If an array watchpoint is recursive, new array watchpoints are added if there are new ones in the array
100 * * If the watch (element with an id) is not reachable anymore due to changes in implicits, the watch is removed
101 */
102
103 #include "zend.h"
104 #include "phpdbg.h"
105 #include "phpdbg_btree.h"
106 #include "phpdbg_watch.h"
107 #include "phpdbg_utils.h"
108 #include "phpdbg_prompt.h"
109 #ifndef _WIN32
110 # include <unistd.h>
111 # include <sys/mman.h>
112 #endif
113
114 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
115
116 const phpdbg_command_t phpdbg_watch_commands[] = {
117 PHPDBG_COMMAND_D_EX(array, "create watchpoint on an array", 'a', watch_array, &phpdbg_prompt_commands[24], "s", 0),
118 PHPDBG_COMMAND_D_EX(delete, "delete watchpoint", 'd', watch_delete, &phpdbg_prompt_commands[24], "n", 0),
119 PHPDBG_COMMAND_D_EX(recursive, "create recursive watchpoints", 'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
120 PHPDBG_END_COMMAND
121 };
122
123 #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)
124
125 #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t)) /* we are not interested in gc and flags */
126 #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
127 #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
128
129 /* ### PRINTING POINTER DIFFERENCES ### */
phpdbg_check_watch_diff(phpdbg_watchtype type,void * oldPtr,void * newPtr)130 zend_bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) {
131 switch (type) {
132 case WATCH_ON_BUCKET:
133 if (memcmp(&((Bucket *) oldPtr)->h, &((Bucket *) newPtr)->h, sizeof(Bucket) - sizeof(zval) /* key/val comparison */) != 0) {
134 return 2;
135 }
136 case WATCH_ON_ZVAL:
137 return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
138 case WATCH_ON_HASHTABLE:
139 return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
140 case WATCH_ON_REFCOUNTED:
141 return memcmp(oldPtr, newPtr, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
142 case WATCH_ON_STR:
143 return memcmp(oldPtr, newPtr, *(size_t *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len)) != 0;
144 case WATCH_ON_HASHDATA:
145 ZEND_UNREACHABLE();
146 }
147 return 0;
148 }
149
phpdbg_print_watch_diff(phpdbg_watchtype type,zend_string * name,void * oldPtr,void * newPtr)150 void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr) {
151 int32_t elementDiff;
152
153 PHPDBG_G(watchpoint_hit) = 1;
154
155 phpdbg_notice("watchhit", "variable=\"%s\"", "Breaking on watchpoint %.*s", (int) ZSTR_LEN(name), ZSTR_VAL(name));
156 phpdbg_xml("<watchdata %r>");
157
158 switch (type) {
159 case WATCH_ON_BUCKET:
160 case WATCH_ON_ZVAL:
161 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
162 phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed");
163 } else if (Z_TYPE_P((zval *) oldPtr) == IS_INDIRECT) {
164 phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed (was indirect)");
165 } else {
166 phpdbg_out("Old value: ");
167 phpdbg_xml("<watchvalue %r type=\"old\">");
168 zend_print_flat_zval_r((zval *) oldPtr);
169 phpdbg_xml("</watchvalue>");
170 phpdbg_out("\n");
171 }
172
173 while (Z_TYPE_P((zval *) newPtr) == IS_INDIRECT) {
174 newPtr = Z_INDIRECT_P((zval *) newPtr);
175 }
176
177 phpdbg_out("New value%s: ", Z_ISREF_P((zval *) newPtr) ? " (reference)" : "");
178 phpdbg_xml("<watchvalue %r%s type=\"new\">", Z_ISREF_P((zval *) newPtr) ? " reference=\"reference\"" : "");
179 zend_print_flat_zval_r((zval *) newPtr);
180 phpdbg_xml("</watchvalue>");
181 phpdbg_out("\n");
182 break;
183
184 case WATCH_ON_HASHTABLE:
185 elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_PTR_HT(newPtr));
186 if (elementDiff > 0) {
187 phpdbg_writeln("watchsize", "removed=\"%d\"", "%d elements were removed from the array", (int) elementDiff);
188 } else if (elementDiff < 0) {
189 phpdbg_writeln("watchsize", "added=\"%d\"", "%d elements were added to the array", (int) -elementDiff);
190 }
191 break;
192
193 case WATCH_ON_REFCOUNTED:
194 phpdbg_writeln("watchrefcount", "type=\"old\" refcount=\"%d\"", "Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
195 phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", GC_REFCOUNT((zend_refcounted *) newPtr));
196 break;
197
198 case WATCH_ON_STR:
199 phpdbg_out("Old value: ");
200 phpdbg_xml("<watchvalue %r type=\"old\">");
201 zend_write((char *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) oldPtr);
202 phpdbg_xml("</watchvalue>");
203 phpdbg_out("\n");
204
205 phpdbg_out("New value: ");
206 phpdbg_xml("<watchvalue %r type=\"new\">");
207 zend_write((char *) newPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) newPtr);
208 phpdbg_xml("</watchvalue>");
209 phpdbg_out("\n");
210 break;
211
212 case WATCH_ON_HASHDATA:
213 ZEND_UNREACHABLE();
214 }
215
216 phpdbg_xml("</watchdata>");
217 }
218
219 /* ### LOW LEVEL WATCHPOINT HANDLING ### */
phpdbg_check_for_watchpoint(void * addr)220 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
221 phpdbg_watchpoint_t *watch;
222 phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong) phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
223
224 if (result == NULL) {
225 return NULL;
226 }
227
228 watch = result->ptr;
229
230 /* check if that addr is in a mprotect()'ed memory area */
231 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) {
232 /* failure */
233 return NULL;
234 }
235
236 return watch;
237 }
238
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access)239 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
240 /* pagesize is assumed to be in the range of 2^x */
241 mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
242 }
243
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch)244 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
245 phpdbg_change_watchpoint_access(watch, PROT_READ);
246 }
247
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch)248 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
249 phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
250 }
251
252 /* Note that consecutive pages need to be merged in order to avoid watchpoints spanning page boundaries to have part of their data in the one page, part in the other page */
253 #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr)254 int phpdbg_watchpoint_segfault_handler(void *addr) {
255 #else
256 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
257 #endif
258
259 void *page = phpdbg_get_page_boundary(
260 #ifdef _WIN32
261 addr
262 #else
263 info->si_addr
264 #endif
265 );
266
267 /* perhaps unnecessary, but check to be sure to not conflict with other segfault handlers */
268 if (phpdbg_check_for_watchpoint(page) == NULL) {
269 return FAILURE;
270 }
271
272 /* re-enable writing */
273 mprotect(page, phpdbg_pagesize, PROT_READ | PROT_WRITE);
274
275 zend_hash_index_add_empty_element(PHPDBG_G(watchlist_mem), (zend_ulong) page);
276
277 return SUCCESS;
278 }
279
280 /* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */
281 static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) {
282 phpdbg_btree_result *res;
283 ZEND_ASSERT((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL || res->ptr == watch);
284 phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
285 }
286
287 static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) {
288 phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
289 }
290
291 /* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */
292 void phpdbg_set_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
293 watch->addr.ptr = addr;
294 watch->size = size;
295 watch->ref = NULL;
296 watch->coll = NULL;
297 zend_hash_init(&watch->elements, 8, brml, NULL, 0);
298 }
299
300 void phpdbg_set_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
301 phpdbg_set_addr_watchpoint(zv, sizeof(zval) - sizeof(uint32_t), watch);
302 watch->type = WATCH_ON_ZVAL;
303 }
304
305 void phpdbg_set_bucket_watchpoint(Bucket *bucket, phpdbg_watchpoint_t *watch) {
306 phpdbg_set_addr_watchpoint(bucket, sizeof(Bucket), watch);
307 watch->type = WATCH_ON_BUCKET;
308 }
309
310 void phpdbg_set_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
311 phpdbg_set_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
312 watch->type = WATCH_ON_HASHTABLE;
313 }
314
315 void phpdbg_watch_backup_data(phpdbg_watchpoint_t *watch) {
316 switch (watch->type) {
317 case WATCH_ON_BUCKET:
318 case WATCH_ON_ZVAL:
319 case WATCH_ON_REFCOUNTED:
320 memcpy(&watch->backup, watch->addr.ptr, watch->size);
321 break;
322 case WATCH_ON_STR:
323 if (watch->backup.str) {
324 zend_string_release(watch->backup.str);
325 }
326 watch->backup.str = zend_string_init((char *) watch->addr.ptr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) watch->addr.ptr, 1);
327 GC_MAKE_PERSISTENT_LOCAL(watch->backup.str);
328 break;
329 case WATCH_ON_HASHTABLE:
330 memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
331 case WATCH_ON_HASHDATA:
332 break;
333 }
334 }
335
336 /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
337 /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
338 void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
339 phpdbg_watch_collision *coll;
340 if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
341 zend_hash_index_del(&coll->parents, (zend_ulong) watch);
342 if (zend_hash_num_elements(&coll->parents) == 0) {
343 phpdbg_deactivate_watchpoint(&coll->ref);
344 phpdbg_remove_watchpoint_btree(&coll->ref);
345
346 if (coll->ref.type == WATCH_ON_ZVAL) {
347 phpdbg_delete_watch_collision(&coll->ref);
348 } else if (coll->reference.addr.ptr) {
349 phpdbg_deactivate_watchpoint(&coll->reference);
350 phpdbg_remove_watchpoint_btree(&coll->reference);
351 phpdbg_delete_watch_collision(&coll->reference);
352 if (coll->reference.type == WATCH_ON_STR) {
353 zend_string_release(coll->reference.backup.str);
354 }
355 }
356
357 zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
358 zend_hash_destroy(&coll->parents);
359 efree(coll);
360 }
361 }
362 }
363
364 void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
365 phpdbg_watch_collision *coll;
366
367 ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
368 if (Z_REFCOUNTED_P(watch->addr.zv)) {
369 if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
370 return;
371 }
372
373 if (watch->ref != NULL) {
374 phpdbg_delete_watch_collision(watch);
375 }
376
377 watch->ref = Z_COUNTED_P(watch->addr.zv);
378
379 if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
380 coll = emalloc(sizeof(*coll));
381 coll->ref.type = WATCH_ON_REFCOUNTED;
382 phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
383 coll->ref.coll = coll;
384 phpdbg_store_watchpoint_btree(&coll->ref);
385 phpdbg_activate_watchpoint(&coll->ref);
386 phpdbg_watch_backup_data(&coll->ref);
387
388 if (Z_ISREF_P(watch->addr.zv)) {
389 phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
390 coll->reference.coll = coll;
391 phpdbg_update_watch_ref(&coll->reference);
392 phpdbg_store_watchpoint_btree(&coll->reference);
393 phpdbg_activate_watchpoint(&coll->reference);
394 phpdbg_watch_backup_data(&coll->reference);
395 } else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
396 coll->reference.type = WATCH_ON_STR;
397 phpdbg_set_addr_watchpoint(&Z_STRLEN_P(watch->addr.zv), XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len) + Z_STRLEN_P(watch->addr.zv) + 1, &coll->reference);
398 coll->reference.coll = coll;
399 phpdbg_store_watchpoint_btree(&coll->reference);
400 phpdbg_activate_watchpoint(&coll->reference);
401 coll->reference.backup.str = NULL;
402 phpdbg_watch_backup_data(&coll->reference);
403 } else {
404 coll->reference.addr.ptr = NULL;
405 }
406
407 zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
408 zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
409 }
410 zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
411 } else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
412 if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
413 return;
414 }
415
416 if (watch->ref != NULL) {
417 phpdbg_delete_watch_collision(watch);
418 }
419
420 watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
421
422 if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
423 coll = emalloc(sizeof(*coll));
424 phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
425 coll->ref.coll = coll;
426 phpdbg_update_watch_ref(&coll->ref);
427 phpdbg_store_watchpoint_btree(&coll->ref);
428 phpdbg_activate_watchpoint(&coll->ref);
429 phpdbg_watch_backup_data(&coll->ref);
430
431 zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
432 zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
433 }
434 zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
435 } else if (watch->ref) {
436 phpdbg_delete_watch_collision(watch);
437 watch->ref = NULL;
438 }
439 }
440
441 /* ### MANAGE WATCH ELEMENTS ### */
442 /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
443 void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
444 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
445 void phpdbg_free_watch_element(phpdbg_watch_element *element);
446 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
447 void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
448
449 phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
450 phpdbg_btree_result *res;
451 if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
452 phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
453 *mem = *watch;
454 watch = mem;
455 phpdbg_store_watchpoint_btree(watch);
456 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
457 phpdbg_update_watch_ref(watch);
458 }
459 phpdbg_activate_watchpoint(watch);
460 phpdbg_watch_backup_data(watch);
461 } else {
462 phpdbg_watch_element *old_element;
463 watch = res->ptr;
464 if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
465 phpdbg_free_watch_element(element);
466 return old_element;
467 }
468 }
469
470 element->watch = watch;
471 zend_hash_add_ptr(&watch->elements, element->str, element);
472
473 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
474 phpdbg_recurse_watch_element(element);
475 }
476
477 return element;
478 }
479
480 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
481 phpdbg_watchpoint_t watch;
482 phpdbg_set_bucket_watchpoint(bucket, &watch);
483 element = phpdbg_add_watch_element(&watch, element);
484 phpdbg_watch_parent_ht(element);
485 return element;
486 }
487
488 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
489 phpdbg_watchpoint_t watch;
490 HashTable *ht = HT_FROM_ZVP(zv);
491
492 if (!ht) {
493 return NULL;
494 }
495
496 element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
497 phpdbg_set_ht_watchpoint(ht, &watch);
498 return phpdbg_add_watch_element(&watch, element);
499 }
500
501 zend_bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
502 phpdbg_watch_element *next = element;
503 do {
504 element = next;
505 if (element->watch->addr.ptr == ptr) {
506 return 1;
507 }
508 next = element->parent;
509 } while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
510
511 return 0;
512 }
513
514 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
515 phpdbg_watch_element *child;
516 if (phpdbg_is_recursively_watched(zv, element)) {
517 return;
518 }
519
520 child = emalloc(sizeof(*child));
521 child->flags = PHPDBG_WATCH_RECURSIVE;
522 if (str) {
523 child->str = strpprintf(0, (element->flags & PHPDBG_WATCH_ARRAY) ? "%.*s[%s]" : "%.*s->%s", (int) ZSTR_LEN(element->str) - 2, ZSTR_VAL(element->str), phpdbg_get_property_key(ZSTR_VAL(str)));
524 } else {
525 child->str = strpprintf(0, (element->flags & PHPDBG_WATCH_ARRAY) ? "%.*s[" ZEND_LONG_FMT "]" : "%.*s->" ZEND_LONG_FMT, (int) ZSTR_LEN(element->str) - 2, ZSTR_VAL(element->str), idx);
526 }
527 if (!str) {
528 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
529 } else { str = zend_string_copy(str); }
530 child->name_in_parent = str;
531 child->parent = element;
532 child->child = NULL;
533 child->parent_container = HT_WATCH_HT(element->watch);
534 zend_hash_add_ptr(&element->child_container, child->str, child);
535 phpdbg_add_bucket_watch_element((Bucket *) zv, child);
536 }
537
538 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
539 phpdbg_watch_element *child;
540 zval *zv;
541
542 if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
543 zv = element->watch->addr.zv;
544 while (Z_TYPE_P(zv) == IS_INDIRECT) {
545 zv = Z_INDIRECT_P(zv);
546 }
547 ZVAL_DEREF(zv);
548
549 if (element->child) {
550 phpdbg_remove_watch_element_recursively(element->child);
551 }
552
553 if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
554 || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
555 if (element->child) {
556 phpdbg_free_watch_element(element->child);
557 element->child = NULL;
558 }
559 return;
560 }
561
562 if (element->child) {
563 child = element->child;
564 } else {
565 child = emalloc(sizeof(*child));
566 child->flags = PHPDBG_WATCH_RECURSIVE;
567 child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
568 child->name_in_parent = NULL;
569 child->parent = element;
570 child->child = NULL;
571 element->child = child;
572 }
573 zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
574 phpdbg_add_ht_watch_element(zv, child);
575 } else if (zend_hash_num_elements(&element->child_container) == 0) {
576 zend_string *str;
577 zend_long idx;
578
579 ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
580 ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
581 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
582 } ZEND_HASH_FOREACH_END();
583 }
584 }
585
586 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
587 if (element->watch->type == WATCH_ON_BUCKET) {
588 phpdbg_btree_result *res;
589 HashPosition pos;
590 phpdbg_watch_ht_info *hti;
591 ZEND_ASSERT(element->parent_container);
592 if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
593 hti = emalloc(sizeof(*hti));
594 hti->ht = element->parent_container;
595
596 zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
597 phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
598
599 phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
600 hti->hash_watch.type = WATCH_ON_HASHDATA;
601 phpdbg_store_watchpoint_btree(&hti->hash_watch);
602 phpdbg_activate_watchpoint(&hti->hash_watch);
603 } else {
604 hti = (phpdbg_watch_ht_info *) res->ptr;
605 }
606
607 zend_hash_internal_pointer_end_ex(hti->ht, &pos);
608 hti->last = hti->ht->arData + pos;
609 hti->last_str = hti->last->key;
610 hti->last_idx = hti->last->h;
611
612 zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
613 }
614 }
615
616 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
617 if (element->watch->type == WATCH_ON_BUCKET) {
618 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
619 ZEND_ASSERT(element->parent_container);
620 if (res) {
621 phpdbg_watch_ht_info *hti = res->ptr;
622
623 if (zend_hash_num_elements(&hti->watches) == 1) {
624 zend_hash_destroy(&hti->watches);
625 phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
626 phpdbg_deactivate_watchpoint(&hti->hash_watch);
627 phpdbg_remove_watchpoint_btree(&hti->hash_watch);
628 efree(hti);
629 } else {
630 zend_hash_del(&hti->watches, element->name_in_parent);
631 }
632 }
633 }
634 }
635
636 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
637 /* implicit watchpoints may change (especially because of separation); elements updated by remove & re-add etc.; thus we need to wait a little bit (until next opcode) and then compare whether the watchpoint still exists and if not, remove it */
638
639 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
640 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
641
642 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
643 /* store lowermost element */
644 phpdbg_watch_element *prev;
645
646 if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
647 phpdbg_watch_element *child = prev;
648 do {
649 if (child == element) {
650 return;
651 }
652 child = child->child;
653 } while (child);
654 }
655 zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
656
657 /* dissociate from watchpoint to avoid dangling memory watches */
658 phpdbg_dissociate_watch_element(element, prev);
659
660 if (!element->parent) {
661 /* HERE BE DRAGONS; i.e. we assume HashTable is directly allocated via emalloc() ... (which *should be* the case for every user-accessible array and symbol tables) */
662 zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
663 }
664 }
665
666 zend_bool phpdbg_try_readding_watch_element(zval *parent, phpdbg_watch_element *element) {
667 zval *zv;
668 HashTable *ht = HT_FROM_ZVP(parent);
669
670 if (!ht) {
671 return 0;
672 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
673 char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
674 char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
675 if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
676 phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
677 }
678
679 phpdbg_add_ht_watch_element(parent, element);
680 } else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
681 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
682 zval *next = zv;
683
684 while (Z_TYPE_P(next) == IS_INDIRECT) {
685 next = Z_INDIRECT_P(next);
686 }
687 if (Z_ISREF_P(next)) {
688 next = Z_REFVAL_P(next);
689 }
690
691 if (!phpdbg_try_readding_watch_element(next, element->child)) {
692 return 0;
693 }
694 } else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
695 phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
696 }
697
698 element->parent_container = ht;
699 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
700 phpdbg_watch_parent_ht(element);
701 } else {
702 return 0;
703 }
704
705 return 1;
706 }
707
708 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
709 phpdbg_watch_element *child = element;
710 while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
711 child = child->child;
712 }
713 PHPDBG_G(watchpoint_hit) = 1;
714 if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
715 phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "%.*s has been removed, removing watchpoint%s", (int) ZSTR_LEN(child->str), ZSTR_VAL(child->str), (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) ? " recursively" : "");
716 }
717 phpdbg_free_watch_element_tree(element);
718 }
719
720 void phpdbg_dequeue_elements_for_recreation() {
721 phpdbg_watch_element *element;
722
723 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
724 ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
725 if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
726 zval _zv, *zv = &_zv;
727 if (element->parent) {
728 ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
729 zv = element->parent->watch->addr.zv;
730 while (Z_TYPE_P(zv) == IS_INDIRECT) {
731 zv = Z_INDIRECT_P(zv);
732 }
733 ZVAL_DEREF(zv);
734 } else {
735 ZVAL_ARR(zv, element->parent_container);
736 }
737 if (!phpdbg_try_readding_watch_element(zv, element)) {
738 phpdbg_automatic_dequeue_free(element);
739 }
740 } else {
741 phpdbg_automatic_dequeue_free(element);
742 }
743 } ZEND_HASH_FOREACH_END();
744
745 zend_hash_clean(&PHPDBG_G(watch_recreation));
746 zend_hash_clean(&PHPDBG_G(watch_free));
747 }
748
749 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
750 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
751
752 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
753 zend_string_release(element->str);
754 if (element->name_in_parent) {
755 zend_string_release(element->name_in_parent);
756 }
757 efree(element);
758 }
759
760 /* note: does *not* free the passed element, only clean */
761 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
762 if (element->child) {
763 phpdbg_remove_watch_element_recursively(element->child);
764 phpdbg_free_watch_element(element->child);
765 element->child = NULL;
766 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
767 phpdbg_watch_element *child;
768 ZEND_HASH_FOREACH_PTR(&element->child_container, child) {
769 phpdbg_remove_watch_element_recursively(child);
770 phpdbg_free_watch_element(child);
771 } ZEND_HASH_FOREACH_END();
772 zend_hash_destroy(&element->child_container);
773 }
774
775 phpdbg_clean_watch_element(element);
776 }
777
778 /* remove single watch (i.e. manual unset) or implicit removed */
779 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
780 phpdbg_watch_element *parent = element->parent, *child = element->child;
781 while (parent) {
782 phpdbg_watch_element *cur = parent;
783 parent = parent->parent;
784 phpdbg_clean_watch_element(cur);
785 phpdbg_free_watch_element(cur);
786 }
787 while (child) {
788 phpdbg_watch_element *cur = child;
789 child = child->child;
790 if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
791 phpdbg_remove_watch_element_recursively(cur);
792 child = NULL;
793 } else {
794 phpdbg_clean_watch_element(cur);
795 }
796 phpdbg_free_watch_element(cur);
797 }
798 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
799 phpdbg_remove_watch_element_recursively(element);
800 } else {
801 phpdbg_clean_watch_element(element);
802 }
803 zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
804 phpdbg_free_watch_element(element);
805 }
806
807 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
808 memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
809 }
810
811 /* until argument to prevent double remove of children elements */
812 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
813 phpdbg_watch_element *child = element;
814 ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
815
816 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
817 phpdbg_backup_watch_element(element);
818 phpdbg_remove_watch_element_recursively(element);
819 return;
820 }
821
822 while (child->child != until) {
823 child = child->child;
824 if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
825 phpdbg_backup_watch_element(child);
826 phpdbg_remove_watch_element_recursively(child);
827 child->child = NULL;
828 break;
829 }
830 if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
831 phpdbg_backup_watch_element(child);
832 }
833 phpdbg_clean_watch_element(child);
834 }
835 /* element needs to be removed last! */
836 if (element->child == NULL) {
837 phpdbg_backup_watch_element(element);
838 }
839 phpdbg_clean_watch_element(element);
840 }
841
842 /* unlike phpdbg_remove_watch_element this *only* frees and does not clean up element + children! Only use after previous cleanup (e.g. phpdbg_dissociate_watch_element) */
843 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
844 phpdbg_watch_element *parent = element->parent, *child = element->child;
845 while (parent) {
846 phpdbg_watch_element *cur = parent;
847 parent = parent->parent;
848 phpdbg_clean_watch_element(cur);
849 phpdbg_free_watch_element(cur);
850 }
851 while (child) {
852 phpdbg_watch_element *cur = child;
853 child = child->child;
854 phpdbg_free_watch_element(cur);
855 }
856 phpdbg_free_watch_element(element);
857 }
858
859 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
860 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
861 phpdbg_watch_element *child = element->child;
862 while (child->flags & PHPDBG_WATCH_IMPLICIT) {
863 child = child->child;
864 }
865
866 ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
867 phpdbg_queue_element_for_recreation(element);
868 } else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
869 phpdbg_queue_element_for_recreation(element);
870 } else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
871 phpdbg_remove_watch_element_recursively(element);
872 if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
873 zend_hash_del(&element->parent->child_container, element->str);
874 } else {
875 element->parent->child = NULL;
876 }
877 phpdbg_free_watch_element(element);
878 }
879 }
880
881 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
882 phpdbg_watchpoint_t *parent;
883 phpdbg_watch_element *element;
884
885 ZEND_HASH_FOREACH_PTR(&watch->coll->parents, parent) {
886 if (parent->coll) {
887 phpdbg_update_watch_collision_elements(parent);
888 } else {
889 ZEND_HASH_FOREACH_PTR(&parent->elements, element) {
890 phpdbg_update_watch_element_watch(element);
891 } ZEND_HASH_FOREACH_END();
892 }
893 } ZEND_HASH_FOREACH_END();
894 }
895
896 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
897 phpdbg_watch_element *element;
898
899 phpdbg_deactivate_watchpoint(watch);
900 phpdbg_remove_watchpoint_btree(watch);
901 phpdbg_delete_watch_collision(watch);
902
903 if (watch->coll) {
904 phpdbg_update_watch_collision_elements(watch);
905 return;
906 }
907
908 watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
909 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
910 phpdbg_update_watch_element_watch(element);
911 } ZEND_HASH_FOREACH_END();
912 zend_hash_destroy(&watch->elements);
913
914 efree(watch);
915 }
916
917 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
918 HashTable *elements = &element->watch->elements;
919 phpdbg_unwatch_parent_ht(element);
920 zend_hash_del(elements, element->str);
921 if (zend_hash_num_elements(elements) == 0) {
922 phpdbg_remove_watchpoint(element->watch);
923 }
924 }
925
926 /* TODO: compile a name of all hit watchpoints (ids ??) */
927 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
928 phpdbg_watchpoint_t *parent;
929 phpdbg_watch_element *element;
930 zend_string *name = NULL;
931 if (watch->coll) {
932 ZEND_HASH_FOREACH_PTR(&watch->coll->parents, parent) {
933 if (name) {
934 zend_string_release(name);
935 }
936 name = phpdbg_watchpoint_change_collision_name(parent);
937 } ZEND_HASH_FOREACH_END();
938 return name;
939 }
940 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
941 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
942 if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
943 phpdbg_update_watch_element_watch(element->child);
944 }
945 continue;
946 }
947 name = element->str;
948 } ZEND_HASH_FOREACH_END();
949
950 return name ? zend_string_copy(name) : NULL;
951 }
952
953 /* ### WATCHING FOR CHANGES ### */
954 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
955 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
956 zend_string *name = NULL;
957 void *comparePtr;
958
959 if (watch->type == WATCH_ON_HASHTABLE) {
960 phpdbg_watch_element *element;
961 zend_string *str;
962 zend_long idx;
963 zval *zv;
964 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
965 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
966 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
967 phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
968
969 ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
970 if (!str) {
971 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
972 } else {
973 str = zend_string_copy(str);
974 }
975 if (hti && zend_hash_find(&hti->watches, str)) {
976 zend_string_release(str);
977 break;
978 }
979 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
980 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
981 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
982 }
983 } ZEND_HASH_FOREACH_END();
984 phpdbg_notice("watchadd", "element=\"%.*s\"", "Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
985 zend_string_release(str);
986 PHPDBG_G(watchpoint_hit) = 1;
987 } ZEND_HASH_FOREACH_END();
988
989 break;
990 }
991 } ZEND_HASH_FOREACH_END();
992 }
993 if (watch->type == WATCH_ON_HASHDATA) {
994 return;
995 }
996
997 switch (watch->type) {
998 case WATCH_ON_STR:
999 comparePtr = &ZSTR_LEN(watch->backup.str);
1000 break;
1001 case WATCH_ON_HASHTABLE:
1002 comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1003 break;
1004 default:
1005 comparePtr = &watch->backup;
1006 }
1007 if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1008 return;
1009 }
1010 if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1011 phpdbg_watch_backup_data(watch);
1012 return;
1013 }
1014 if (watch->type == WATCH_ON_BUCKET) {
1015 if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1016 phpdbg_watch_element *element = NULL;
1017 zval *new;
1018
1019 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1020 break;
1021 } ZEND_HASH_FOREACH_END();
1022
1023 ZEND_ASSERT(element); /* elements must be non-empty */
1024 new = zend_symtable_find(element->parent_container, element->name_in_parent);
1025
1026 if (!new) {
1027 /* dequeuing will take care of appropriate notification about removal */
1028 phpdbg_remove_watchpoint(watch);
1029 return;
1030 }
1031
1032 phpdbg_deactivate_watchpoint(watch);
1033 phpdbg_remove_watchpoint_btree(watch);
1034 watch->addr.zv = new;
1035 phpdbg_store_watchpoint_btree(watch);
1036 phpdbg_activate_watchpoint(watch);
1037
1038 if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1039 phpdbg_watch_backup_data(watch);
1040 return;
1041 }
1042 } else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1043 /* dequeuing will take care of appropriate notification about removal */
1044 phpdbg_remove_watchpoint(watch);
1045 return;
1046 }
1047 }
1048
1049 name = phpdbg_watchpoint_change_collision_name(watch);
1050
1051 if (name) {
1052 phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1053 zend_string_release(name);
1054 }
1055
1056 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1057 phpdbg_watch_element *element;
1058 phpdbg_update_watch_ref(watch);
1059 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1060 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1061 phpdbg_recurse_watch_element(element);
1062 }
1063 } ZEND_HASH_FOREACH_END();
1064 }
1065
1066 phpdbg_watch_backup_data(watch);
1067 }
1068
1069 void phpdbg_reenable_memory_watches(void) {
1070 zend_ulong page;
1071 phpdbg_btree_result *res;
1072 phpdbg_watchpoint_t *watch;
1073
1074 ZEND_HASH_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1075 /* Disable writing again if there are any watchers on that page */
1076 res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1077 if (res) {
1078 watch = res->ptr;
1079 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1080 mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1081 }
1082 }
1083 } ZEND_HASH_FOREACH_END();
1084 zend_hash_clean(PHPDBG_G(watchlist_mem));
1085 }
1086
1087 int phpdbg_print_changed_zvals(void) {
1088 int ret;
1089 zend_ulong page;
1090 phpdbg_watchpoint_t *watch;
1091 phpdbg_btree_result *res;
1092 HashTable *mem_list = NULL;
1093
1094 if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1095 return FAILURE;
1096 }
1097
1098 if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1099 /* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1100 mem_list = PHPDBG_G(watchlist_mem);
1101 PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1102
1103 ZEND_HASH_FOREACH_NUM_KEY(mem_list, page) {
1104 phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1105
1106 while ((res = phpdbg_btree_next(&pos))) {
1107 watch = res->ptr;
1108 phpdbg_check_watchpoint(watch);
1109 }
1110 if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1111 watch = res->ptr;
1112 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1113 phpdbg_check_watchpoint(watch);
1114 }
1115 }
1116 } ZEND_HASH_FOREACH_END();
1117 }
1118
1119 phpdbg_dequeue_elements_for_recreation();
1120
1121 phpdbg_reenable_memory_watches();
1122
1123 if (mem_list) {
1124 PHPDBG_G(watchlist_mem) = mem_list;
1125 phpdbg_reenable_memory_watches();
1126 }
1127
1128 ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1129 PHPDBG_G(watchpoint_hit) = 0;
1130
1131 return ret;
1132 }
1133
1134 void phpdbg_watch_efree(void *ptr) {
1135 phpdbg_btree_result *result;
1136
1137 /* only do expensive checks if there are any watches at all */
1138 if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1139 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1140 phpdbg_watchpoint_t *watch = result->ptr;
1141 if (watch->type != WATCH_ON_HASHDATA) {
1142 phpdbg_remove_watchpoint(watch);
1143 } else {
1144 /* remove all linked watchpoints, they will be dissociated from their elements */
1145 phpdbg_watch_element *element;
1146 phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1147
1148 ZEND_HASH_FOREACH_PTR(&hti->watches, element) {
1149 zend_ulong num = zend_hash_num_elements(&hti->watches);
1150 phpdbg_remove_watchpoint(element->watch);
1151 if (num == 1) { /* prevent access into freed memory */
1152 break;
1153 }
1154 } ZEND_HASH_FOREACH_END();
1155 }
1156 }
1157
1158 /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1159 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1160 phpdbg_watchpoint_t *watch = result->ptr;
1161 if (watch->type == WATCH_ON_HASHTABLE) {
1162 phpdbg_remove_watchpoint(watch);
1163 }
1164 }
1165
1166 zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1167 }
1168
1169 if (PHPDBG_G(original_free_function)) {
1170 PHPDBG_G(original_free_function)(ptr);
1171 }
1172 }
1173
1174 /* ### USER API ### */
1175 void phpdbg_list_watchpoints(void) {
1176 phpdbg_watch_element *element;
1177
1178 phpdbg_xml("<watchlist %r>");
1179
1180 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1181 phpdbg_writeln("watchvariable", "variable=\"%.*s\" on=\"%s\" type=\"%s\"", "%.*s (%s, %s)", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str), (element->flags & (PHPDBG_WATCH_ARRAY|PHPDBG_WATCH_OBJECT)) ? "array" : "variable", (element->flags & PHPDBG_WATCH_RECURSIVE) ? "recursive" : "simple");
1182 } ZEND_HASH_FOREACH_END();
1183
1184 phpdbg_xml("</watchlist>");
1185 }
1186
1187 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1188 element->flags = PHPDBG_WATCH_SIMPLE;
1189 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1190 return SUCCESS;
1191 }
1192
1193 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1194 phpdbg_watch_element *new;
1195 zend_string *str;
1196 zval *orig_zv = zv;
1197
1198 ZVAL_DEREF(zv);
1199 if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1200 return FAILURE;
1201 }
1202
1203 new = ecalloc(1, sizeof(phpdbg_watch_element));
1204
1205 str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1206 zend_string_release(element->str);
1207 element->str = str;
1208 element->flags = PHPDBG_WATCH_IMPLICIT;
1209 phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1210 element->child = new;
1211
1212 new->flags = PHPDBG_WATCH_SIMPLE;
1213 new->str = zend_string_copy(str);
1214 new->parent = element;
1215 phpdbg_add_ht_watch_element(zv, new);
1216 return SUCCESS;
1217 }
1218
1219 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1220 element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1221 element->child = NULL;
1222 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1223 return SUCCESS;
1224 }
1225
1226 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1227
1228 static int phpdbg_watchpoint_parse_wrapper(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, phpdbg_watch_parse_struct *info) {
1229 int ret;
1230 phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1231 element->str = zend_string_init(name, namelen, 0);
1232 element->name_in_parent = zend_string_init(key, keylen, 0);
1233 element->parent_container = parent;
1234 element->parent = PHPDBG_G(watch_tmp);
1235 element->child = NULL;
1236
1237 ret = info->callback(zv, element);
1238
1239 efree(name);
1240 efree(key);
1241
1242 if (ret != SUCCESS) {
1243 phpdbg_remove_watch_element(element);
1244 } else {
1245 if (PHPDBG_G(watch_tmp)) {
1246 PHPDBG_G(watch_tmp)->child = element;
1247 }
1248
1249 if (element->child) {
1250 element = element->child;
1251 }
1252
1253 /* work around missing API for extending an array with a new element, and getting its index */
1254 zend_hash_next_index_insert_ptr(&PHPDBG_G(watch_elements), element);
1255 element->id = PHPDBG_G(watch_elements).nNextFreeElement - 1;
1256
1257 phpdbg_notice("watchadd", "index=\"%d\" variable=\"%.*s\"", "Added%s watchpoint #%u for %.*s", (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) ? " recursive" : "", element->id, (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1258 }
1259
1260 PHPDBG_G(watch_tmp) = NULL;
1261
1262 return ret;
1263 }
1264
1265 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, phpdbg_watch_parse_struct *info, zend_bool silent) {
1266 return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1267 }
1268
1269 static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, phpdbg_watch_parse_struct *info) {
1270 phpdbg_watch_element *element;
1271
1272 /* do not install watch elements for references */
1273 if (PHPDBG_G(watch_tmp) && Z_ISREF_P(PHPDBG_G(watch_tmp)->watch->addr.zv) && Z_REFVAL_P(PHPDBG_G(watch_tmp)->watch->addr.zv) == zv) {
1274 efree(name);
1275 efree(key);
1276 return SUCCESS;
1277 }
1278
1279 element = ecalloc(1, sizeof(phpdbg_watch_element));
1280 element->flags = PHPDBG_WATCH_IMPLICIT;
1281 element->str = zend_string_copy(info->str);
1282 element->name_in_parent = zend_string_init(key, keylen, 0);
1283 element->parent_container = parent;
1284 element->parent = PHPDBG_G(watch_tmp);
1285 element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1286
1287 efree(name);
1288 efree(key);
1289
1290 if (PHPDBG_G(watch_tmp)) {
1291 PHPDBG_G(watch_tmp)->child = element;
1292 }
1293 PHPDBG_G(watch_tmp) = element;
1294
1295 return SUCCESS;
1296 }
1297
1298 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1299 zend_class_entry *scope = zend_get_executed_scope();
1300 phpdbg_watch_parse_struct info;
1301 int ret;
1302
1303 if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1304 zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1305 }
1306
1307 if (callback == phpdbg_create_array_watchpoint) {
1308 info.str = strpprintf(0, "%.*s[]", (int) len, input);
1309 } else {
1310 info.str = zend_string_init(input, len, 0);
1311 }
1312 info.callback = callback;
1313
1314 if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1315 zend_string_release(info.str);
1316 return SUCCESS;
1317 }
1318
1319 ret = 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, &info);
1320
1321 zend_string_release(info.str);
1322 return ret;
1323 }
1324
1325 PHPDBG_WATCH(delete) /* {{{ */
1326 {
1327 phpdbg_watch_element *element;
1328 switch (param->type) {
1329 case NUMERIC_PARAM:
1330 if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1331 phpdbg_remove_watch_element(element);
1332 phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %d", (int) param->num);
1333 } else {
1334 phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
1335 }
1336 break;
1337
1338 phpdbg_default_switch_case();
1339 }
1340
1341 return SUCCESS;
1342 } /* }}} */
1343
1344 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1345 if (phpdbg_rebuild_symtable() == FAILURE) {
1346 return FAILURE;
1347 }
1348
1349 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1350 }
1351
1352 PHPDBG_WATCH(recursive) /* {{{ */
1353 {
1354 if (phpdbg_rebuild_symtable() == FAILURE) {
1355 return SUCCESS;
1356 }
1357
1358 switch (param->type) {
1359 case STR_PARAM:
1360 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1361 break;
1362
1363 phpdbg_default_switch_case();
1364 }
1365
1366 return SUCCESS;
1367 } /* }}} */
1368
1369 PHPDBG_WATCH(array) /* {{{ */
1370 {
1371 if (phpdbg_rebuild_symtable() == FAILURE) {
1372 return SUCCESS;
1373 }
1374
1375 switch (param->type) {
1376 case STR_PARAM:
1377 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1378 break;
1379
1380 phpdbg_default_switch_case();
1381 }
1382
1383 return SUCCESS;
1384 } /* }}} */
1385
1386
1387 void phpdbg_setup_watchpoints(void) {
1388 #if defined(_SC_PAGE_SIZE)
1389 phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1390 #elif defined(_SC_PAGESIZE)
1391 phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1392 #elif defined(_SC_NUTC_OS_PAGESIZE)
1393 phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1394 #else
1395 phpdbg_pagesize = 4096; /* common pagesize */
1396 #endif
1397
1398 phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1399 phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1400 zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1401 zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1402 zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1403 zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1404
1405 /* put these on a separate page, to avoid conflicts with other memory */
1406 PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1407 zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1408 PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1409 zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1410
1411 PHPDBG_G(watch_tmp) = NULL;
1412 }
1413
1414 void phpdbg_destroy_watchpoints(void) {
1415 phpdbg_watch_element *element;
1416 phpdbg_btree_position pos;
1417 phpdbg_btree_result *res;
1418
1419 /* unconditionally free all remaining elements to avoid memory leaks */
1420 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1421 phpdbg_automatic_dequeue_free(element);
1422 } ZEND_HASH_FOREACH_END();
1423
1424 /* upon fatal errors etc. (i.e. CG(unclean_shutdown) == 1), some watchpoints may still be active. Ensure memory is not watched anymore for next run. Do not care about memory freeing here, shutdown is unclean and near anyway. */
1425 pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1426 while ((res = phpdbg_btree_next(&pos))) {
1427 phpdbg_deactivate_watchpoint(res->ptr);
1428 }
1429
1430 zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1431 zend_hash_destroy(&PHPDBG_G(watch_recreation));
1432 zend_hash_destroy(&PHPDBG_G(watch_free));
1433 zend_hash_destroy(&PHPDBG_G(watch_collisions));
1434 zend_hash_destroy(PHPDBG_G(watchlist_mem));
1435 free(PHPDBG_G(watchlist_mem));
1436 zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1437 free(PHPDBG_G(watchlist_mem_backup));
1438 }
1439
1440 void phpdbg_purge_watchpoint_tree(void) {
1441 phpdbg_btree_position pos;
1442 phpdbg_btree_result *res;
1443
1444 pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1445 while ((res = phpdbg_btree_next(&pos))) {
1446 phpdbg_deactivate_watchpoint(res->ptr);
1447 }
1448 }
1449