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