1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2018 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 explicitely 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 implicitely 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 break;
330 case WATCH_ON_HASHTABLE:
331 memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
332 case WATCH_ON_HASHDATA:
333 break;
334 }
335 }
336
337 /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
338 /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
339 void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
340 phpdbg_watch_collision *coll;
341 if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
342 zend_hash_index_del(&coll->parents, (zend_ulong) watch);
343 if (zend_hash_num_elements(&coll->parents) == 0) {
344 phpdbg_deactivate_watchpoint(&coll->ref);
345 phpdbg_remove_watchpoint_btree(&coll->ref);
346
347 if (coll->ref.type == WATCH_ON_ZVAL) {
348 phpdbg_delete_watch_collision(&coll->ref);
349 } else if (coll->reference.addr.ptr) {
350 phpdbg_deactivate_watchpoint(&coll->reference);
351 phpdbg_remove_watchpoint_btree(&coll->reference);
352 phpdbg_delete_watch_collision(&coll->reference);
353 if (coll->reference.type == WATCH_ON_STR) {
354 zend_string_release(coll->reference.backup.str);
355 }
356 }
357
358 zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
359 zend_hash_destroy(&coll->parents);
360 efree(coll);
361 }
362 }
363 }
364
365 void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
366 phpdbg_watch_collision *coll;
367
368 ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
369 if (Z_REFCOUNTED_P(watch->addr.zv)) {
370 if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
371 return;
372 }
373
374 if (watch->ref != NULL) {
375 phpdbg_delete_watch_collision(watch);
376 }
377
378 watch->ref = Z_COUNTED_P(watch->addr.zv);
379
380 if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
381 coll = emalloc(sizeof(*coll));
382 coll->ref.type = WATCH_ON_REFCOUNTED;
383 phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
384 coll->ref.coll = coll;
385 phpdbg_store_watchpoint_btree(&coll->ref);
386 phpdbg_activate_watchpoint(&coll->ref);
387 phpdbg_watch_backup_data(&coll->ref);
388
389 if (Z_ISREF_P(watch->addr.zv)) {
390 phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
391 coll->reference.coll = coll;
392 phpdbg_update_watch_ref(&coll->reference);
393 phpdbg_store_watchpoint_btree(&coll->reference);
394 phpdbg_activate_watchpoint(&coll->reference);
395 phpdbg_watch_backup_data(&coll->reference);
396 } else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
397 coll->reference.type = WATCH_ON_STR;
398 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);
399 coll->reference.coll = coll;
400 phpdbg_store_watchpoint_btree(&coll->reference);
401 phpdbg_activate_watchpoint(&coll->reference);
402 coll->reference.backup.str = NULL;
403 phpdbg_watch_backup_data(&coll->reference);
404 } else {
405 coll->reference.addr.ptr = NULL;
406 }
407
408 zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
409 zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
410 }
411 zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
412 } else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
413 if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
414 return;
415 }
416
417 if (watch->ref != NULL) {
418 phpdbg_delete_watch_collision(watch);
419 }
420
421 watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
422
423 if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
424 coll = emalloc(sizeof(*coll));
425 phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
426 coll->ref.coll = coll;
427 phpdbg_update_watch_ref(&coll->ref);
428 phpdbg_store_watchpoint_btree(&coll->ref);
429 phpdbg_activate_watchpoint(&coll->ref);
430 phpdbg_watch_backup_data(&coll->ref);
431
432 zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
433 zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
434 }
435 zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
436 } else if (watch->ref) {
437 phpdbg_delete_watch_collision(watch);
438 watch->ref = NULL;
439 }
440 }
441
442 /* ### MANAGE WATCH ELEMENTS ### */
443 /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
444 void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
445 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
446 void phpdbg_free_watch_element(phpdbg_watch_element *element);
447 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
448 void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
449
450 phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
451 phpdbg_btree_result *res;
452 if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
453 phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
454 *mem = *watch;
455 watch = mem;
456 phpdbg_store_watchpoint_btree(watch);
457 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
458 phpdbg_update_watch_ref(watch);
459 }
460 phpdbg_activate_watchpoint(watch);
461 phpdbg_watch_backup_data(watch);
462 } else {
463 phpdbg_watch_element *old_element;
464 watch = res->ptr;
465 if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
466 phpdbg_free_watch_element(element);
467 return old_element;
468 }
469 }
470
471 element->watch = watch;
472 zend_hash_add_ptr(&watch->elements, element->str, element);
473
474 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
475 phpdbg_recurse_watch_element(element);
476 }
477
478 return element;
479 }
480
481 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
482 phpdbg_watchpoint_t watch;
483 phpdbg_set_bucket_watchpoint(bucket, &watch);
484 element = phpdbg_add_watch_element(&watch, element);
485 phpdbg_watch_parent_ht(element);
486 return element;
487 }
488
489 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
490 phpdbg_watchpoint_t watch;
491 HashTable *ht = HT_FROM_ZVP(zv);
492
493 if (!ht) {
494 return NULL;
495 }
496
497 element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
498 phpdbg_set_ht_watchpoint(ht, &watch);
499 return phpdbg_add_watch_element(&watch, element);
500 }
501
502 zend_bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
503 phpdbg_watch_element *next = element;
504 do {
505 element = next;
506 if (element->watch->addr.ptr == ptr) {
507 return 1;
508 }
509 next = element->parent;
510 } while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
511
512 return 0;
513 }
514
515 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
516 phpdbg_watch_element *child;
517 if (phpdbg_is_recursively_watched(zv, element)) {
518 return;
519 }
520
521 child = emalloc(sizeof(*child));
522 child->flags = PHPDBG_WATCH_RECURSIVE;
523 if (str) {
524 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)));
525 } else {
526 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);
527 }
528 if (!str) {
529 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
530 } else { str = zend_string_copy(str); }
531 child->name_in_parent = str;
532 child->parent = element;
533 child->child = NULL;
534 child->parent_container = HT_WATCH_HT(element->watch);
535 zend_hash_add_ptr(&element->child_container, child->str, child);
536 phpdbg_add_bucket_watch_element((Bucket *) zv, child);
537 }
538
539 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
540 phpdbg_watch_element *child;
541 zval *zv;
542
543 if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
544 zv = element->watch->addr.zv;
545 while (Z_TYPE_P(zv) == IS_INDIRECT) {
546 zv = Z_INDIRECT_P(zv);
547 }
548 ZVAL_DEREF(zv);
549
550 if (element->child) {
551 phpdbg_remove_watch_element_recursively(element->child);
552 }
553
554 if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
555 || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
556 if (element->child) {
557 phpdbg_free_watch_element(element->child);
558 element->child = NULL;
559 }
560 return;
561 }
562
563 if (element->child) {
564 child = element->child;
565 } else {
566 child = emalloc(sizeof(*child));
567 child->flags = PHPDBG_WATCH_RECURSIVE;
568 child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
569 child->name_in_parent = NULL;
570 child->parent = element;
571 child->child = NULL;
572 element->child = child;
573 }
574 zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
575 phpdbg_add_ht_watch_element(zv, child);
576 } else if (zend_hash_num_elements(&element->child_container) == 0) {
577 zend_string *str;
578 zend_long idx;
579
580 ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
581 ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
582 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
583 } ZEND_HASH_FOREACH_END();
584 }
585 }
586
587 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
588 if (element->watch->type == WATCH_ON_BUCKET) {
589 phpdbg_btree_result *res;
590 HashPosition pos;
591 phpdbg_watch_ht_info *hti;
592 ZEND_ASSERT(element->parent_container);
593 if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
594 hti = emalloc(sizeof(*hti));
595 hti->ht = element->parent_container;
596
597 zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
598 phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
599
600 phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
601 hti->hash_watch.type = WATCH_ON_HASHDATA;
602 phpdbg_store_watchpoint_btree(&hti->hash_watch);
603 phpdbg_activate_watchpoint(&hti->hash_watch);
604 } else {
605 hti = (phpdbg_watch_ht_info *) res->ptr;
606 }
607
608 zend_hash_internal_pointer_end_ex(hti->ht, &pos);
609 hti->last = hti->ht->arData + pos;
610 hti->last_str = hti->last->key;
611 hti->last_idx = hti->last->h;
612
613 zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
614 }
615 }
616
617 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
618 if (element->watch->type == WATCH_ON_BUCKET) {
619 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
620 ZEND_ASSERT(element->parent_container);
621 if (res) {
622 phpdbg_watch_ht_info *hti = res->ptr;
623
624 if (zend_hash_num_elements(&hti->watches) == 1) {
625 zend_hash_destroy(&hti->watches);
626 phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
627 phpdbg_deactivate_watchpoint(&hti->hash_watch);
628 phpdbg_remove_watchpoint_btree(&hti->hash_watch);
629 efree(hti);
630 } else {
631 zend_hash_del(&hti->watches, element->name_in_parent);
632 }
633 }
634 }
635 }
636
637 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
638 /* 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 */
639
640 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
641 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
642
643 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
644 /* store lowermost element */
645 phpdbg_watch_element *prev;
646
647 if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
648 phpdbg_watch_element *child = prev;
649 do {
650 if (child == element) {
651 return;
652 }
653 child = child->child;
654 } while (child);
655 }
656 zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
657
658 /* dissociate from watchpoint to avoid dangling memory watches */
659 phpdbg_dissociate_watch_element(element, prev);
660
661 if (!element->parent) {
662 /* 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) */
663 zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
664 }
665 }
666
667 zend_bool phpdbg_try_readding_watch_element(zval *parent, phpdbg_watch_element *element) {
668 zval *zv;
669 HashTable *ht = HT_FROM_ZVP(parent);
670
671 if (!ht) {
672 return 0;
673 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
674 char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
675 char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
676 if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
677 phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
678 }
679
680 phpdbg_add_ht_watch_element(parent, element);
681 } else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
682 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
683 zval *next = zv;
684
685 while (Z_TYPE_P(next) == IS_INDIRECT) {
686 next = Z_INDIRECT_P(next);
687 }
688 if (Z_ISREF_P(next)) {
689 next = Z_REFVAL_P(next);
690 }
691
692 if (!phpdbg_try_readding_watch_element(next, element->child)) {
693 return 0;
694 }
695 } else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
696 phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
697 }
698
699 element->parent_container = ht;
700 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
701 phpdbg_watch_parent_ht(element);
702 } else {
703 return 0;
704 }
705
706 return 1;
707 }
708
709 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
710 phpdbg_watch_element *child = element;
711 while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
712 child = child->child;
713 }
714 PHPDBG_G(watchpoint_hit) = 1;
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 zend_hash_index_del(&PHPDBG_G(watch_elements), child->id);
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;
1017 zval *new;
1018
1019 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1020 break;
1021 } ZEND_HASH_FOREACH_END();
1022
1023 new = zend_symtable_find(element->parent_container, element->name_in_parent);
1024
1025 if (!new) {
1026 /* dequeuing will take care of appropriate notification about removal */
1027 phpdbg_remove_watchpoint(watch);
1028 return;
1029 }
1030
1031 phpdbg_deactivate_watchpoint(watch);
1032 phpdbg_remove_watchpoint_btree(watch);
1033 watch->addr.zv = new;
1034 phpdbg_store_watchpoint_btree(watch);
1035 phpdbg_activate_watchpoint(watch);
1036
1037 if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1038 phpdbg_watch_backup_data(watch);
1039 return;
1040 }
1041 } else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1042 /* dequeuing will take care of appropriate notification about removal */
1043 phpdbg_remove_watchpoint(watch);
1044 return;
1045 }
1046 }
1047
1048 name = phpdbg_watchpoint_change_collision_name(watch);
1049
1050 if (name) {
1051 phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1052 zend_string_release(name);
1053 }
1054
1055 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1056 phpdbg_watch_element *element;
1057 phpdbg_update_watch_ref(watch);
1058 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1059 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1060 phpdbg_recurse_watch_element(element);
1061 }
1062 } ZEND_HASH_FOREACH_END();
1063 }
1064
1065 phpdbg_watch_backup_data(watch);
1066 }
1067
1068 void phpdbg_reenable_memory_watches(void) {
1069 zend_ulong page;
1070 phpdbg_btree_result *res;
1071 phpdbg_watchpoint_t *watch;
1072
1073 ZEND_HASH_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1074 /* Disble writing again if there are any watchers on that page */
1075 res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1076 if (res) {
1077 watch = res->ptr;
1078 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1079 mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1080 }
1081 }
1082 } ZEND_HASH_FOREACH_END();
1083 zend_hash_clean(PHPDBG_G(watchlist_mem));
1084 }
1085
1086 int phpdbg_print_changed_zvals(void) {
1087 int ret;
1088 zend_ulong page;
1089 phpdbg_watchpoint_t *watch;
1090 phpdbg_btree_result *res;
1091 HashTable *mem_list = NULL;
1092
1093 if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1094 return FAILURE;
1095 }
1096
1097 if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1098 /* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1099 mem_list = PHPDBG_G(watchlist_mem);
1100 PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1101
1102 ZEND_HASH_FOREACH_NUM_KEY(mem_list, page) {
1103 phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1104
1105 while ((res = phpdbg_btree_next(&pos))) {
1106 watch = res->ptr;
1107 phpdbg_check_watchpoint(watch);
1108 }
1109 if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1110 watch = res->ptr;
1111 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1112 phpdbg_check_watchpoint(watch);
1113 }
1114 }
1115 } ZEND_HASH_FOREACH_END();
1116 }
1117
1118 phpdbg_dequeue_elements_for_recreation();
1119
1120 phpdbg_reenable_memory_watches();
1121
1122 if (mem_list) {
1123 PHPDBG_G(watchlist_mem) = mem_list;
1124 phpdbg_reenable_memory_watches();
1125 }
1126
1127 ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1128 PHPDBG_G(watchpoint_hit) = 0;
1129
1130 return ret;
1131 }
1132
1133 void phpdbg_watch_efree(void *ptr) {
1134 phpdbg_btree_result *result;
1135
1136 /* only do expensive checks if there are any watches at all */
1137 if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1138 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1139 phpdbg_watchpoint_t *watch = result->ptr;
1140 if (watch->type != WATCH_ON_HASHDATA) {
1141 phpdbg_remove_watchpoint(watch);
1142 } else {
1143 /* remove all linked watchpoints, they will be dissociated from their elements */
1144 phpdbg_watch_element *element;
1145 phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1146
1147 ZEND_HASH_FOREACH_PTR(&hti->watches, element) {
1148 zend_ulong num = zend_hash_num_elements(&hti->watches);
1149 phpdbg_remove_watchpoint(element->watch);
1150 if (num == 1) { /* prevent access into freed memory */
1151 break;
1152 }
1153 } ZEND_HASH_FOREACH_END();
1154 }
1155 }
1156
1157 /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1158 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1159 phpdbg_watchpoint_t *watch = result->ptr;
1160 if (watch->type == WATCH_ON_HASHTABLE) {
1161 phpdbg_remove_watchpoint(watch);
1162 }
1163 }
1164
1165 zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1166 }
1167
1168 if (PHPDBG_G(original_free_function)) {
1169 PHPDBG_G(original_free_function)(ptr);
1170 }
1171 }
1172
1173 /* ### USER API ### */
1174 void phpdbg_list_watchpoints(void) {
1175 phpdbg_watch_element *element;
1176
1177 phpdbg_xml("<watchlist %r>");
1178
1179 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1180 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");
1181 } ZEND_HASH_FOREACH_END();
1182
1183 phpdbg_xml("</watchlist>");
1184 }
1185
1186 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1187 element->flags = PHPDBG_WATCH_SIMPLE;
1188 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1189 return SUCCESS;
1190 }
1191
1192 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1193 phpdbg_watch_element *new;
1194 zend_string *str;
1195 zval *orig_zv = zv;
1196
1197 ZVAL_DEREF(zv);
1198 if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1199 return FAILURE;
1200 }
1201
1202 new = ecalloc(1, sizeof(phpdbg_watch_element));
1203
1204 str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1205 zend_string_release(element->str);
1206 element->str = str;
1207 element->flags = PHPDBG_WATCH_IMPLICIT;
1208 phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1209 element->child = new;
1210
1211 new->flags = PHPDBG_WATCH_SIMPLE;
1212 new->str = zend_string_copy(str);
1213 new->parent = element;
1214 phpdbg_add_ht_watch_element(zv, new);
1215 return SUCCESS;
1216 }
1217
1218 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1219 element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1220 element->child = NULL;
1221 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1222 return SUCCESS;
1223 }
1224
1225 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1226
1227 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) {
1228 int ret;
1229 phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1230 element->str = zend_string_init(name, namelen, 0);
1231 element->name_in_parent = zend_string_init(key, keylen, 0);
1232 element->parent_container = parent;
1233 element->parent = PHPDBG_G(watch_tmp);
1234 element->child = NULL;
1235
1236 ret = info->callback(zv, element);
1237
1238 efree(name);
1239 efree(key);
1240
1241 if (ret != SUCCESS) {
1242 phpdbg_remove_watch_element(element);
1243 } else {
1244 if (PHPDBG_G(watch_tmp)) {
1245 PHPDBG_G(watch_tmp)->child = element;
1246 }
1247
1248 if (element->child) {
1249 element = element->child;
1250 }
1251 element->id = PHPDBG_G(watch_elements).nNextFreeElement;
1252 zend_hash_index_add_ptr(&PHPDBG_G(watch_elements), element->id, element);
1253
1254 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));
1255 }
1256
1257 PHPDBG_G(watch_tmp) = NULL;
1258
1259 return ret;
1260 }
1261
1262 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) {
1263 return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1264 }
1265
1266 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) {
1267 phpdbg_watch_element *element;
1268
1269 /* do not install watch elements for references */
1270 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) {
1271 efree(name);
1272 efree(key);
1273 return SUCCESS;
1274 }
1275
1276 element = ecalloc(1, sizeof(phpdbg_watch_element));
1277 element->flags = PHPDBG_WATCH_IMPLICIT;
1278 element->str = zend_string_copy(info->str);
1279 element->name_in_parent = zend_string_init(key, keylen, 0);
1280 element->parent_container = parent;
1281 element->parent = PHPDBG_G(watch_tmp);
1282 element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1283
1284 efree(name);
1285 efree(key);
1286
1287 if (PHPDBG_G(watch_tmp)) {
1288 PHPDBG_G(watch_tmp)->child = element;
1289 }
1290 PHPDBG_G(watch_tmp) = element;
1291
1292 return SUCCESS;
1293 }
1294
1295 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1296 zend_class_entry *scope = zend_get_executed_scope();
1297 phpdbg_watch_parse_struct info;
1298 int ret;
1299
1300 if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1301 zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1302 }
1303
1304 if (callback == phpdbg_create_array_watchpoint) {
1305 info.str = strpprintf(0, "%.*s[]", (int) len, input);
1306 } else {
1307 info.str = zend_string_init(input, len, 0);
1308 }
1309 info.callback = callback;
1310
1311 if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1312 zend_string_release(info.str);
1313 return SUCCESS;
1314 }
1315
1316 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);
1317
1318 zend_string_release(info.str);
1319 return ret;
1320 }
1321
1322 PHPDBG_WATCH(delete) /* {{{ */
1323 {
1324 phpdbg_watch_element *element;
1325 switch (param->type) {
1326 case NUMERIC_PARAM:
1327 if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1328 phpdbg_remove_watch_element(element);
1329 phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %d", (int) param->num);
1330 } else {
1331 phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
1332 }
1333 break;
1334
1335 phpdbg_default_switch_case();
1336 }
1337
1338 return SUCCESS;
1339 } /* }}} */
1340
1341 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1342 if (phpdbg_rebuild_symtable() == FAILURE) {
1343 return FAILURE;
1344 }
1345
1346 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1347 }
1348
1349 PHPDBG_WATCH(recursive) /* {{{ */
1350 {
1351 if (phpdbg_rebuild_symtable() == FAILURE) {
1352 return SUCCESS;
1353 }
1354
1355 switch (param->type) {
1356 case STR_PARAM:
1357 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1358 break;
1359
1360 phpdbg_default_switch_case();
1361 }
1362
1363 return SUCCESS;
1364 } /* }}} */
1365
1366 PHPDBG_WATCH(array) /* {{{ */
1367 {
1368 if (phpdbg_rebuild_symtable() == FAILURE) {
1369 return SUCCESS;
1370 }
1371
1372 switch (param->type) {
1373 case STR_PARAM:
1374 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1375 break;
1376
1377 phpdbg_default_switch_case();
1378 }
1379
1380 return SUCCESS;
1381 } /* }}} */
1382
1383
1384 void phpdbg_setup_watchpoints(void) {
1385 #if _SC_PAGE_SIZE
1386 phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1387 #elif _SC_PAGESIZE
1388 phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1389 #elif _SC_NUTC_OS_PAGESIZE
1390 phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1391 #else
1392 phpdbg_pagesize = 4096; /* common pagesize */
1393 #endif
1394
1395 phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1396 phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1397 zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1398 zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1399 zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1400 zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1401
1402 /* put these on a separate page, to avoid conflicts with other memory */
1403 PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1404 zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1405 PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1406 zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1407 }
1408
1409 void phpdbg_destroy_watchpoints(void) {
1410 phpdbg_watch_element *element;
1411 phpdbg_btree_position pos;
1412 phpdbg_btree_result *res;
1413
1414 /* unconditionally free all remaining elements to avoid memory leaks */
1415 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1416 phpdbg_automatic_dequeue_free(element);
1417 } ZEND_HASH_FOREACH_END();
1418
1419 /* 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. */
1420 pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1421 while ((res = phpdbg_btree_next(&pos))) {
1422 phpdbg_deactivate_watchpoint(res->ptr);
1423 }
1424
1425 zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1426 zend_hash_destroy(&PHPDBG_G(watch_recreation));
1427 zend_hash_destroy(&PHPDBG_G(watch_free));
1428 zend_hash_destroy(&PHPDBG_G(watch_collisions));
1429 zend_hash_destroy(PHPDBG_G(watchlist_mem));
1430 free(PHPDBG_G(watchlist_mem));
1431 zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1432 free(PHPDBG_G(watchlist_mem_backup));
1433 }
1434