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