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 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 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 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 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" : "");
717 zend_hash_index_del(&PHPDBG_G(watch_elements), child->id);
718 phpdbg_free_watch_element_tree(element);
719 }
720
721 void phpdbg_dequeue_elements_for_recreation() {
722 phpdbg_watch_element *element;
723
724 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
725 ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
726 if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
727 zval _zv, *zv = &_zv;
728 if (element->parent) {
729 ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
730 zv = element->parent->watch->addr.zv;
731 while (Z_TYPE_P(zv) == IS_INDIRECT) {
732 zv = Z_INDIRECT_P(zv);
733 }
734 ZVAL_DEREF(zv);
735 } else {
736 ZVAL_ARR(zv, element->parent_container);
737 }
738 if (!phpdbg_try_readding_watch_element(zv, element)) {
739 phpdbg_automatic_dequeue_free(element);
740 }
741 } else {
742 phpdbg_automatic_dequeue_free(element);
743 }
744 } ZEND_HASH_FOREACH_END();
745
746 zend_hash_clean(&PHPDBG_G(watch_recreation));
747 zend_hash_clean(&PHPDBG_G(watch_free));
748 }
749
750 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
751 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
752
753 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
754 zend_string_release(element->str);
755 if (element->name_in_parent) {
756 zend_string_release(element->name_in_parent);
757 }
758 efree(element);
759 }
760
761 /* note: does *not* free the passed element, only clean */
762 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
763 if (element->child) {
764 phpdbg_remove_watch_element_recursively(element->child);
765 phpdbg_free_watch_element(element->child);
766 element->child = NULL;
767 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
768 phpdbg_watch_element *child;
769 ZEND_HASH_FOREACH_PTR(&element->child_container, child) {
770 phpdbg_remove_watch_element_recursively(child);
771 phpdbg_free_watch_element(child);
772 } ZEND_HASH_FOREACH_END();
773 zend_hash_destroy(&element->child_container);
774 }
775
776 phpdbg_clean_watch_element(element);
777 }
778
779 /* remove single watch (i.e. manual unset) or implicit removed */
780 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
781 phpdbg_watch_element *parent = element->parent, *child = element->child;
782 while (parent) {
783 phpdbg_watch_element *cur = parent;
784 parent = parent->parent;
785 phpdbg_clean_watch_element(cur);
786 phpdbg_free_watch_element(cur);
787 }
788 while (child) {
789 phpdbg_watch_element *cur = child;
790 child = child->child;
791 if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
792 phpdbg_remove_watch_element_recursively(cur);
793 child = NULL;
794 } else {
795 phpdbg_clean_watch_element(cur);
796 }
797 phpdbg_free_watch_element(cur);
798 }
799 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
800 phpdbg_remove_watch_element_recursively(element);
801 } else {
802 phpdbg_clean_watch_element(element);
803 }
804 zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
805 phpdbg_free_watch_element(element);
806 }
807
808 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
809 memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
810 }
811
812 /* until argument to prevent double remove of children elements */
813 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
814 phpdbg_watch_element *child = element;
815 ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
816
817 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
818 phpdbg_backup_watch_element(element);
819 phpdbg_remove_watch_element_recursively(element);
820 return;
821 }
822
823 while (child->child != until) {
824 child = child->child;
825 if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
826 phpdbg_backup_watch_element(child);
827 phpdbg_remove_watch_element_recursively(child);
828 child->child = NULL;
829 break;
830 }
831 if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
832 phpdbg_backup_watch_element(child);
833 }
834 phpdbg_clean_watch_element(child);
835 }
836 /* element needs to be removed last! */
837 if (element->child == NULL) {
838 phpdbg_backup_watch_element(element);
839 }
840 phpdbg_clean_watch_element(element);
841 }
842
843 /* 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) */
844 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
845 phpdbg_watch_element *parent = element->parent, *child = element->child;
846 while (parent) {
847 phpdbg_watch_element *cur = parent;
848 parent = parent->parent;
849 phpdbg_clean_watch_element(cur);
850 phpdbg_free_watch_element(cur);
851 }
852 while (child) {
853 phpdbg_watch_element *cur = child;
854 child = child->child;
855 phpdbg_free_watch_element(cur);
856 }
857 phpdbg_free_watch_element(element);
858 }
859
860 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
861 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
862 phpdbg_watch_element *child = element->child;
863 while (child->flags & PHPDBG_WATCH_IMPLICIT) {
864 child = child->child;
865 }
866
867 ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
868 phpdbg_queue_element_for_recreation(element);
869 } else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
870 phpdbg_queue_element_for_recreation(element);
871 } else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
872 phpdbg_remove_watch_element_recursively(element);
873 if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
874 zend_hash_del(&element->parent->child_container, element->str);
875 } else {
876 element->parent->child = NULL;
877 }
878 phpdbg_free_watch_element(element);
879 }
880 }
881
882 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
883 phpdbg_watchpoint_t *parent;
884 phpdbg_watch_element *element;
885
886 ZEND_HASH_FOREACH_PTR(&watch->coll->parents, parent) {
887 if (parent->coll) {
888 phpdbg_update_watch_collision_elements(parent);
889 } else {
890 ZEND_HASH_FOREACH_PTR(&parent->elements, element) {
891 phpdbg_update_watch_element_watch(element);
892 } ZEND_HASH_FOREACH_END();
893 }
894 } ZEND_HASH_FOREACH_END();
895 }
896
897 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
898 phpdbg_watch_element *element;
899
900 phpdbg_deactivate_watchpoint(watch);
901 phpdbg_remove_watchpoint_btree(watch);
902 phpdbg_delete_watch_collision(watch);
903
904 if (watch->coll) {
905 phpdbg_update_watch_collision_elements(watch);
906 return;
907 }
908
909 watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
910 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
911 phpdbg_update_watch_element_watch(element);
912 } ZEND_HASH_FOREACH_END();
913 zend_hash_destroy(&watch->elements);
914
915 efree(watch);
916 }
917
918 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
919 HashTable *elements = &element->watch->elements;
920 phpdbg_unwatch_parent_ht(element);
921 zend_hash_del(elements, element->str);
922 if (zend_hash_num_elements(elements) == 0) {
923 phpdbg_remove_watchpoint(element->watch);
924 }
925 }
926
927 /* TODO: compile a name of all hit watchpoints (ids ??) */
928 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
929 phpdbg_watchpoint_t *parent;
930 phpdbg_watch_element *element;
931 zend_string *name = NULL;
932 if (watch->coll) {
933 ZEND_HASH_FOREACH_PTR(&watch->coll->parents, parent) {
934 if (name) {
935 zend_string_release(name);
936 }
937 name = phpdbg_watchpoint_change_collision_name(parent);
938 } ZEND_HASH_FOREACH_END();
939 return name;
940 }
941 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
942 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
943 if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
944 phpdbg_update_watch_element_watch(element->child);
945 }
946 continue;
947 }
948 name = element->str;
949 } ZEND_HASH_FOREACH_END();
950
951 return name ? zend_string_copy(name) : NULL;
952 }
953
954 /* ### WATCHING FOR CHANGES ### */
955 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
956 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
957 zend_string *name = NULL;
958 void *comparePtr;
959
960 if (watch->type == WATCH_ON_HASHTABLE) {
961 phpdbg_watch_element *element;
962 zend_string *str;
963 zend_long idx;
964 zval *zv;
965 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
966 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
967 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
968 phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
969
970 ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
971 if (!str) {
972 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
973 } else {
974 str = zend_string_copy(str);
975 }
976 if (hti && zend_hash_find(&hti->watches, str)) {
977 zend_string_release(str);
978 break;
979 }
980 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
981 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
982 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
983 }
984 } ZEND_HASH_FOREACH_END();
985 phpdbg_notice("watchadd", "element=\"%.*s\"", "Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
986 zend_string_release(str);
987 PHPDBG_G(watchpoint_hit) = 1;
988 } ZEND_HASH_FOREACH_END();
989
990 break;
991 }
992 } ZEND_HASH_FOREACH_END();
993 }
994 if (watch->type == WATCH_ON_HASHDATA) {
995 return;
996 }
997
998 switch (watch->type) {
999 case WATCH_ON_STR:
1000 comparePtr = &ZSTR_LEN(watch->backup.str);
1001 break;
1002 case WATCH_ON_HASHTABLE:
1003 comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1004 break;
1005 default:
1006 comparePtr = &watch->backup;
1007 }
1008 if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1009 return;
1010 }
1011 if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1012 phpdbg_watch_backup_data(watch);
1013 return;
1014 }
1015 if (watch->type == WATCH_ON_BUCKET) {
1016 if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1017 phpdbg_watch_element *element;
1018 zval *new;
1019
1020 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1021 break;
1022 } ZEND_HASH_FOREACH_END();
1023
1024 new = zend_symtable_find(element->parent_container, element->name_in_parent);
1025
1026 if (!new) {
1027 /* dequeuing will take care of appropriate notification about removal */
1028 phpdbg_remove_watchpoint(watch);
1029 return;
1030 }
1031
1032 phpdbg_deactivate_watchpoint(watch);
1033 phpdbg_remove_watchpoint_btree(watch);
1034 watch->addr.zv = new;
1035 phpdbg_store_watchpoint_btree(watch);
1036 phpdbg_activate_watchpoint(watch);
1037
1038 if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1039 phpdbg_watch_backup_data(watch);
1040 return;
1041 }
1042 } else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1043 /* dequeuing will take care of appropriate notification about removal */
1044 phpdbg_remove_watchpoint(watch);
1045 return;
1046 }
1047 }
1048
1049 name = phpdbg_watchpoint_change_collision_name(watch);
1050
1051 if (name) {
1052 phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1053 zend_string_release(name);
1054 }
1055
1056 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1057 phpdbg_watch_element *element;
1058 phpdbg_update_watch_ref(watch);
1059 ZEND_HASH_FOREACH_PTR(&watch->elements, element) {
1060 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1061 phpdbg_recurse_watch_element(element);
1062 }
1063 } ZEND_HASH_FOREACH_END();
1064 }
1065
1066 phpdbg_watch_backup_data(watch);
1067 }
1068
1069 void phpdbg_reenable_memory_watches(void) {
1070 zend_ulong page;
1071 phpdbg_btree_result *res;
1072 phpdbg_watchpoint_t *watch;
1073
1074 ZEND_HASH_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1075 /* Disble writing again if there are any watchers on that page */
1076 res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1077 if (res) {
1078 watch = res->ptr;
1079 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1080 mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1081 }
1082 }
1083 } ZEND_HASH_FOREACH_END();
1084 zend_hash_clean(PHPDBG_G(watchlist_mem));
1085 }
1086
1087 int phpdbg_print_changed_zvals(void) {
1088 int ret;
1089 zend_ulong page;
1090 phpdbg_watchpoint_t *watch;
1091 phpdbg_btree_result *res;
1092 HashTable *mem_list = NULL;
1093
1094 if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1095 return FAILURE;
1096 }
1097
1098 if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1099 /* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1100 mem_list = PHPDBG_G(watchlist_mem);
1101 PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1102
1103 ZEND_HASH_FOREACH_NUM_KEY(mem_list, page) {
1104 phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1105
1106 while ((res = phpdbg_btree_next(&pos))) {
1107 watch = res->ptr;
1108 phpdbg_check_watchpoint(watch);
1109 }
1110 if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1111 watch = res->ptr;
1112 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1113 phpdbg_check_watchpoint(watch);
1114 }
1115 }
1116 } ZEND_HASH_FOREACH_END();
1117 }
1118
1119 phpdbg_dequeue_elements_for_recreation();
1120
1121 phpdbg_reenable_memory_watches();
1122
1123 if (mem_list) {
1124 PHPDBG_G(watchlist_mem) = mem_list;
1125 phpdbg_reenable_memory_watches();
1126 }
1127
1128 ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1129 PHPDBG_G(watchpoint_hit) = 0;
1130
1131 return ret;
1132 }
1133
1134 void phpdbg_watch_efree(void *ptr) {
1135 phpdbg_btree_result *result;
1136
1137 /* only do expensive checks if there are any watches at all */
1138 if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1139 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1140 phpdbg_watchpoint_t *watch = result->ptr;
1141 if (watch->type != WATCH_ON_HASHDATA) {
1142 phpdbg_remove_watchpoint(watch);
1143 } else {
1144 /* remove all linked watchpoints, they will be dissociated from their elements */
1145 phpdbg_watch_element *element;
1146 phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1147
1148 ZEND_HASH_FOREACH_PTR(&hti->watches, element) {
1149 zend_ulong num = zend_hash_num_elements(&hti->watches);
1150 phpdbg_remove_watchpoint(element->watch);
1151 if (num == 1) { /* prevent access into freed memory */
1152 break;
1153 }
1154 } ZEND_HASH_FOREACH_END();
1155 }
1156 }
1157
1158 /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1159 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1160 phpdbg_watchpoint_t *watch = result->ptr;
1161 if (watch->type == WATCH_ON_HASHTABLE) {
1162 phpdbg_remove_watchpoint(watch);
1163 }
1164 }
1165
1166 zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1167 }
1168
1169 if (PHPDBG_G(original_free_function)) {
1170 PHPDBG_G(original_free_function)(ptr);
1171 }
1172 }
1173
1174 /* ### USER API ### */
1175 void phpdbg_list_watchpoints(void) {
1176 phpdbg_watch_element *element;
1177
1178 phpdbg_xml("<watchlist %r>");
1179
1180 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1181 phpdbg_writeln("watchvariable", "variable=\"%.*s\" on=\"%s\" type=\"%s\"", "%.*s (%s, %s)", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str), (element->flags & (PHPDBG_WATCH_ARRAY|PHPDBG_WATCH_OBJECT)) ? "array" : "variable", (element->flags & PHPDBG_WATCH_RECURSIVE) ? "recursive" : "simple");
1182 } ZEND_HASH_FOREACH_END();
1183
1184 phpdbg_xml("</watchlist>");
1185 }
1186
1187 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1188 element->flags = PHPDBG_WATCH_SIMPLE;
1189 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1190 return SUCCESS;
1191 }
1192
1193 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1194 phpdbg_watch_element *new;
1195 zend_string *str;
1196 zval *orig_zv = zv;
1197
1198 ZVAL_DEREF(zv);
1199 if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1200 return FAILURE;
1201 }
1202
1203 new = ecalloc(1, sizeof(phpdbg_watch_element));
1204
1205 str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1206 zend_string_release(element->str);
1207 element->str = str;
1208 element->flags = PHPDBG_WATCH_IMPLICIT;
1209 phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1210 element->child = new;
1211
1212 new->flags = PHPDBG_WATCH_SIMPLE;
1213 new->str = zend_string_copy(str);
1214 new->parent = element;
1215 phpdbg_add_ht_watch_element(zv, new);
1216 return SUCCESS;
1217 }
1218
1219 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1220 element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1221 element->child = NULL;
1222 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1223 return SUCCESS;
1224 }
1225
1226 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1227
1228 static int phpdbg_watchpoint_parse_wrapper(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, phpdbg_watch_parse_struct *info) {
1229 int ret;
1230 phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1231 element->str = zend_string_init(name, namelen, 0);
1232 element->name_in_parent = zend_string_init(key, keylen, 0);
1233 element->parent_container = parent;
1234 element->parent = PHPDBG_G(watch_tmp);
1235 element->child = NULL;
1236
1237 ret = info->callback(zv, element);
1238
1239 efree(name);
1240 efree(key);
1241
1242 if (ret != SUCCESS) {
1243 phpdbg_remove_watch_element(element);
1244 } else {
1245 if (PHPDBG_G(watch_tmp)) {
1246 PHPDBG_G(watch_tmp)->child = element;
1247 }
1248
1249 if (element->child) {
1250 element = element->child;
1251 }
1252 element->id = PHPDBG_G(watch_elements).nNextFreeElement;
1253 zend_hash_index_add_ptr(&PHPDBG_G(watch_elements), element->id, element);
1254
1255 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));
1256 }
1257
1258 PHPDBG_G(watch_tmp) = NULL;
1259
1260 return ret;
1261 }
1262
1263 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) {
1264 return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1265 }
1266
1267 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) {
1268 phpdbg_watch_element *element;
1269
1270 /* do not install watch elements for references */
1271 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) {
1272 efree(name);
1273 efree(key);
1274 return SUCCESS;
1275 }
1276
1277 element = ecalloc(1, sizeof(phpdbg_watch_element));
1278 element->flags = PHPDBG_WATCH_IMPLICIT;
1279 element->str = zend_string_copy(info->str);
1280 element->name_in_parent = zend_string_init(key, keylen, 0);
1281 element->parent_container = parent;
1282 element->parent = PHPDBG_G(watch_tmp);
1283 element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1284
1285 efree(name);
1286 efree(key);
1287
1288 if (PHPDBG_G(watch_tmp)) {
1289 PHPDBG_G(watch_tmp)->child = element;
1290 }
1291 PHPDBG_G(watch_tmp) = element;
1292
1293 return SUCCESS;
1294 }
1295
1296 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1297 zend_class_entry *scope = zend_get_executed_scope();
1298 phpdbg_watch_parse_struct info;
1299 int ret;
1300
1301 if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1302 zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1303 }
1304
1305 if (callback == phpdbg_create_array_watchpoint) {
1306 info.str = strpprintf(0, "%.*s[]", (int) len, input);
1307 } else {
1308 info.str = zend_string_init(input, len, 0);
1309 }
1310 info.callback = callback;
1311
1312 if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1313 zend_string_release(info.str);
1314 return SUCCESS;
1315 }
1316
1317 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);
1318
1319 zend_string_release(info.str);
1320 return ret;
1321 }
1322
1323 PHPDBG_WATCH(delete) /* {{{ */
1324 {
1325 phpdbg_watch_element *element;
1326 switch (param->type) {
1327 case NUMERIC_PARAM:
1328 if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1329 phpdbg_remove_watch_element(element);
1330 phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %d", (int) param->num);
1331 } else {
1332 phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
1333 }
1334 break;
1335
1336 phpdbg_default_switch_case();
1337 }
1338
1339 return SUCCESS;
1340 } /* }}} */
1341
1342 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1343 if (phpdbg_rebuild_symtable() == FAILURE) {
1344 return FAILURE;
1345 }
1346
1347 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1348 }
1349
1350 PHPDBG_WATCH(recursive) /* {{{ */
1351 {
1352 if (phpdbg_rebuild_symtable() == FAILURE) {
1353 return SUCCESS;
1354 }
1355
1356 switch (param->type) {
1357 case STR_PARAM:
1358 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1359 break;
1360
1361 phpdbg_default_switch_case();
1362 }
1363
1364 return SUCCESS;
1365 } /* }}} */
1366
1367 PHPDBG_WATCH(array) /* {{{ */
1368 {
1369 if (phpdbg_rebuild_symtable() == FAILURE) {
1370 return SUCCESS;
1371 }
1372
1373 switch (param->type) {
1374 case STR_PARAM:
1375 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1376 break;
1377
1378 phpdbg_default_switch_case();
1379 }
1380
1381 return SUCCESS;
1382 } /* }}} */
1383
1384
1385 void phpdbg_setup_watchpoints(void) {
1386 #if _SC_PAGE_SIZE
1387 phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1388 #elif _SC_PAGESIZE
1389 phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1390 #elif _SC_NUTC_OS_PAGESIZE
1391 phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1392 #else
1393 phpdbg_pagesize = 4096; /* common pagesize */
1394 #endif
1395
1396 phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1397 phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1398 zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1399 zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1400 zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1401 zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1402
1403 /* put these on a separate page, to avoid conflicts with other memory */
1404 PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1405 zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1406 PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1407 zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1408 }
1409
1410 void phpdbg_destroy_watchpoints(void) {
1411 phpdbg_watch_element *element;
1412 phpdbg_btree_position pos;
1413 phpdbg_btree_result *res;
1414
1415 /* unconditionally free all remaining elements to avoid memory leaks */
1416 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1417 phpdbg_automatic_dequeue_free(element);
1418 } ZEND_HASH_FOREACH_END();
1419
1420 /* 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. */
1421 pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1422 while ((res = phpdbg_btree_next(&pos))) {
1423 phpdbg_deactivate_watchpoint(res->ptr);
1424 }
1425
1426 zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1427 zend_hash_destroy(&PHPDBG_G(watch_recreation));
1428 zend_hash_destroy(&PHPDBG_G(watch_free));
1429 zend_hash_destroy(&PHPDBG_G(watch_collisions));
1430 zend_hash_destroy(PHPDBG_G(watchlist_mem));
1431 free(PHPDBG_G(watchlist_mem));
1432 zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1433 free(PHPDBG_G(watchlist_mem_backup));
1434 }
1435