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 #ifdef HAVE_USERFAULTFD_WRITEFAULT
115 # include <pthread.h>
116 # include <linux/userfaultfd.h>
117 # include <sys/ioctl.h>
118 # include <sys/syscall.h>
119 #endif
120
121 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
122
123 const phpdbg_command_t phpdbg_watch_commands[] = {
124 PHPDBG_COMMAND_D_EX(array, "create watchpoint on an array", 'a', watch_array, &phpdbg_prompt_commands[24], "s", 0),
125 PHPDBG_COMMAND_D_EX(delete, "delete watchpoint", 'd', watch_delete, &phpdbg_prompt_commands[24], "n", 0),
126 PHPDBG_COMMAND_D_EX(recursive, "create recursive watchpoints", 'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
127 PHPDBG_END_COMMAND
128 };
129
130 #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)
131
132 #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t)) /* we are not interested in gc and flags */
133 #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
134 #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
135
136 /* ### PRINTING POINTER DIFFERENCES ### */
phpdbg_check_watch_diff(phpdbg_watchtype type,void * oldPtr,void * newPtr)137 bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) {
138 switch (type) {
139 case WATCH_ON_BUCKET:
140 if (memcmp(&((Bucket *) oldPtr)->h, &((Bucket *) newPtr)->h, sizeof(Bucket) - sizeof(zval) /* key/val comparison */) != 0) {
141 return 2;
142 }
143 /* TODO: Is this intentional? */
144 ZEND_FALLTHROUGH;
145 case WATCH_ON_ZVAL:
146 return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
147 case WATCH_ON_HASHTABLE:
148 return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
149 case WATCH_ON_REFCOUNTED:
150 return memcmp(oldPtr, newPtr, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
151 case WATCH_ON_STR:
152 return memcmp(oldPtr, newPtr, *(size_t *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len)) != 0;
153 case WATCH_ON_HASHDATA:
154 ZEND_UNREACHABLE();
155 }
156 return 0;
157 }
158
phpdbg_print_watch_diff(phpdbg_watchtype type,zend_string * name,void * oldPtr,void * newPtr)159 void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr) {
160 int32_t elementDiff;
161
162 PHPDBG_G(watchpoint_hit) = 1;
163
164 phpdbg_notice("Breaking on watchpoint %.*s", (int) ZSTR_LEN(name), ZSTR_VAL(name));
165
166 switch (type) {
167 case WATCH_ON_BUCKET:
168 case WATCH_ON_ZVAL:
169 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
170 phpdbg_writeln("Old value inaccessible or destroyed");
171 } else if (Z_TYPE_P((zval *) oldPtr) == IS_INDIRECT) {
172 phpdbg_writeln("Old value inaccessible or destroyed (was indirect)");
173 } else {
174 phpdbg_out("Old value: ");
175 zend_print_flat_zval_r((zval *) oldPtr);
176 phpdbg_out("\n");
177 }
178
179 while (Z_TYPE_P((zval *) newPtr) == IS_INDIRECT) {
180 newPtr = Z_INDIRECT_P((zval *) newPtr);
181 }
182
183 phpdbg_out("New value%s: ", Z_ISREF_P((zval *) newPtr) ? " (reference)" : "");
184 zend_print_flat_zval_r((zval *) newPtr);
185 phpdbg_out("\n");
186 break;
187
188 case WATCH_ON_HASHTABLE:
189 elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_PTR_HT(newPtr));
190 if (elementDiff > 0) {
191 phpdbg_writeln("%d elements were removed from the array", (int) elementDiff);
192 } else if (elementDiff < 0) {
193 phpdbg_writeln("%d elements were added to the array", (int) -elementDiff);
194 }
195 break;
196
197 case WATCH_ON_REFCOUNTED:
198 phpdbg_writeln("Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
199 phpdbg_writeln("New refcount: %d", GC_REFCOUNT((zend_refcounted *) newPtr));
200 break;
201
202 case WATCH_ON_STR:
203 phpdbg_out("Old value: ");
204 zend_write((char *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) oldPtr);
205 phpdbg_out("\n");
206
207 phpdbg_out("New value: ");
208 zend_write((char *) newPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) newPtr);
209 phpdbg_out("\n");
210 break;
211
212 case WATCH_ON_HASHDATA:
213 ZEND_UNREACHABLE();
214 }
215 }
216
217 /* ### LOW LEVEL WATCHPOINT HANDLING ### */
phpdbg_check_for_watchpoint(phpdbg_btree * tree,void * addr)218 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(phpdbg_btree *tree, void *addr) {
219 phpdbg_watchpoint_t *watch;
220 phpdbg_btree_result *result = phpdbg_btree_find_closest(tree, (zend_ulong) phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
221
222 if (result == NULL) {
223 return NULL;
224 }
225
226 watch = result->ptr;
227
228 /* check if that addr is in a mprotect()'ed memory area */
229 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) {
230 /* failure */
231 return NULL;
232 }
233
234 return watch;
235 }
236
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access)237 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
238 void *page_addr = phpdbg_get_page_boundary(watch->addr.ptr);
239 size_t size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);
240 #ifdef HAVE_USERFAULTFD_WRITEFAULT
241 if (PHPDBG_G(watch_userfaultfd)) {
242 struct uffdio_range range = {
243 .start = (__u64)(uintptr_t) page_addr,
244 .len = size
245 };
246 if (access == PROT_READ) {
247 struct uffdio_register reg = {
248 .mode = UFFDIO_REGISTER_MODE_WP,
249 .range = range
250 };
251 struct uffdio_writeprotect protect = {
252 .mode = UFFDIO_WRITEPROTECT_MODE_WP,
253 .range = range
254 };
255 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_REGISTER, ®);
256 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_WRITEPROTECT, &protect);
257 } else {
258 struct uffdio_register reg = {
259 .mode = UFFDIO_REGISTER_MODE_WP,
260 .range = range
261 };
262 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_UNREGISTER, ®);
263 }
264 } else
265 #endif
266 /* pagesize is assumed to be in the range of 2^x */
267 {
268 mprotect(page_addr, size, access);
269 }
270 }
271
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch)272 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
273 phpdbg_change_watchpoint_access(watch, PROT_READ);
274 }
275
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch)276 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
277 phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
278 }
279
280 /* 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 */
281 #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr)282 int phpdbg_watchpoint_segfault_handler(void *addr) {
283 #else
284 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
285 #endif
286
287 void *page = phpdbg_get_page_boundary(
288 #ifdef _WIN32
289 addr
290 #else
291 info->si_addr
292 #endif
293 );
294
295 /* perhaps unnecessary, but check to be sure to not conflict with other segfault handlers */
296 if (phpdbg_check_for_watchpoint(&PHPDBG_G(watchpoint_tree), page) == NULL) {
297 return FAILURE;
298 }
299
300 /* re-enable writing */
301 mprotect(page, phpdbg_pagesize, PROT_READ | PROT_WRITE);
302
303 zend_hash_index_add_empty_element(PHPDBG_G(watchlist_mem), (zend_ulong) page);
304
305 return SUCCESS;
306 }
307
308 #ifdef HAVE_USERFAULTFD_WRITEFAULT
309 # if defined(__GNUC__) && !defined(__clang__)
310 __attribute__((no_sanitize_address))
311 # endif
312 void *phpdbg_watchpoint_userfaultfd_thread(void *phpdbg_globals) {
313 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
314 zend_phpdbg_globals *globals = (zend_phpdbg_globals *) phpdbg_globals;
315
316 struct uffd_msg fault_msg = {0};
317 while (read(globals->watch_userfaultfd, &fault_msg, sizeof(fault_msg)) == sizeof(fault_msg)) {
318 void *page = phpdbg_get_page_boundary((char *)(uintptr_t) fault_msg.arg.pagefault.address);
319 zend_hash_index_add_empty_element(globals->watchlist_mem, (zend_ulong) page);
320 struct uffdio_writeprotect unprotect = {
321 .mode = 0,
322 .range = {
323 .start = (__u64)(uintptr_t) page,
324 .len = phpdbg_pagesize
325 }
326 };
327 ioctl(globals->watch_userfaultfd, UFFDIO_WRITEPROTECT, &unprotect);
328 }
329
330 return NULL;
331 }
332 #endif
333
334 /* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */
335 static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) {
336 phpdbg_btree_result *res;
337 ZEND_ASSERT((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL || res->ptr == watch);
338 phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
339 }
340
341 static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) {
342 phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
343 }
344
345 /* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */
346 void phpdbg_set_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
347 watch->addr.ptr = addr;
348 watch->size = size;
349 watch->ref = NULL;
350 watch->coll = NULL;
351 zend_hash_init(&watch->elements, 8, brml, NULL, 0);
352 }
353
354 void phpdbg_set_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
355 phpdbg_set_addr_watchpoint(zv, sizeof(zval) - sizeof(uint32_t), watch);
356 watch->type = WATCH_ON_ZVAL;
357 }
358
359 void phpdbg_set_bucket_watchpoint(Bucket *bucket, phpdbg_watchpoint_t *watch) {
360 phpdbg_set_addr_watchpoint(bucket, sizeof(Bucket), watch);
361 watch->type = WATCH_ON_BUCKET;
362 }
363
364 void phpdbg_set_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
365 phpdbg_set_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
366 watch->type = WATCH_ON_HASHTABLE;
367 }
368
369 void phpdbg_watch_backup_data(phpdbg_watchpoint_t *watch) {
370 switch (watch->type) {
371 case WATCH_ON_BUCKET:
372 case WATCH_ON_ZVAL:
373 case WATCH_ON_REFCOUNTED:
374 memcpy(&watch->backup, watch->addr.ptr, watch->size);
375 break;
376 case WATCH_ON_STR:
377 if (watch->backup.str) {
378 zend_string_release(watch->backup.str);
379 }
380 watch->backup.str = zend_string_init((char *) watch->addr.ptr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) watch->addr.ptr, 1);
381 GC_MAKE_PERSISTENT_LOCAL(watch->backup.str);
382 break;
383 case WATCH_ON_HASHTABLE:
384 memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
385 case WATCH_ON_HASHDATA:
386 break;
387 }
388 }
389
390 /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
391 /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
392 void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
393 phpdbg_watch_collision *coll;
394 if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
395 zend_hash_index_del(&coll->parents, (zend_ulong) watch);
396 if (zend_hash_num_elements(&coll->parents) == 0) {
397 phpdbg_remove_watchpoint_btree(&coll->ref);
398 phpdbg_deactivate_watchpoint(&coll->ref);
399
400 if (coll->ref.type == WATCH_ON_ZVAL) {
401 phpdbg_delete_watch_collision(&coll->ref);
402 } else if (coll->reference.addr.ptr) {
403 phpdbg_remove_watchpoint_btree(&coll->reference);
404 phpdbg_deactivate_watchpoint(&coll->reference);
405 phpdbg_delete_watch_collision(&coll->reference);
406 if (coll->reference.type == WATCH_ON_STR) {
407 zend_string_release(coll->reference.backup.str);
408 }
409 }
410
411 zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
412 zend_hash_destroy(&coll->parents);
413 efree(coll);
414 }
415 }
416 }
417
418 void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
419 phpdbg_watch_collision *coll;
420
421 ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
422 if (Z_REFCOUNTED_P(watch->addr.zv)) {
423 if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
424 return;
425 }
426
427 if (watch->ref != NULL) {
428 phpdbg_delete_watch_collision(watch);
429 }
430
431 watch->ref = Z_COUNTED_P(watch->addr.zv);
432
433 if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
434 coll = emalloc(sizeof(*coll));
435 coll->ref.type = WATCH_ON_REFCOUNTED;
436 phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
437 coll->ref.coll = coll;
438 phpdbg_store_watchpoint_btree(&coll->ref);
439 phpdbg_activate_watchpoint(&coll->ref);
440 phpdbg_watch_backup_data(&coll->ref);
441
442 if (Z_ISREF_P(watch->addr.zv)) {
443 phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
444 coll->reference.coll = coll;
445 phpdbg_update_watch_ref(&coll->reference);
446 phpdbg_store_watchpoint_btree(&coll->reference);
447 phpdbg_activate_watchpoint(&coll->reference);
448 phpdbg_watch_backup_data(&coll->reference);
449 } else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
450 coll->reference.type = WATCH_ON_STR;
451 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);
452 coll->reference.coll = coll;
453 phpdbg_store_watchpoint_btree(&coll->reference);
454 phpdbg_activate_watchpoint(&coll->reference);
455 coll->reference.backup.str = NULL;
456 phpdbg_watch_backup_data(&coll->reference);
457 } else {
458 coll->reference.addr.ptr = NULL;
459 }
460
461 zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
462 zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
463 }
464 zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
465 } else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
466 if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
467 return;
468 }
469
470 if (watch->ref != NULL) {
471 phpdbg_delete_watch_collision(watch);
472 }
473
474 watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
475
476 if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
477 coll = emalloc(sizeof(*coll));
478 phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
479 coll->ref.coll = coll;
480 phpdbg_update_watch_ref(&coll->ref);
481 phpdbg_store_watchpoint_btree(&coll->ref);
482 phpdbg_activate_watchpoint(&coll->ref);
483 phpdbg_watch_backup_data(&coll->ref);
484
485 zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
486 zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
487 }
488 zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
489 } else if (watch->ref) {
490 phpdbg_delete_watch_collision(watch);
491 watch->ref = NULL;
492 }
493 }
494
495 /* ### MANAGE WATCH ELEMENTS ### */
496 /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
497 void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
498 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
499 void phpdbg_free_watch_element(phpdbg_watch_element *element);
500 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
501 void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
502
503 phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
504 phpdbg_btree_result *res;
505 if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
506 phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
507 *mem = *watch;
508 watch = mem;
509 phpdbg_store_watchpoint_btree(watch);
510 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
511 phpdbg_update_watch_ref(watch);
512 }
513 phpdbg_activate_watchpoint(watch);
514 phpdbg_watch_backup_data(watch);
515 } else {
516 phpdbg_watch_element *old_element;
517 watch = res->ptr;
518 if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
519 phpdbg_free_watch_element(element);
520 return old_element;
521 }
522 }
523
524 element->watch = watch;
525 zend_hash_add_ptr(&watch->elements, element->str, element);
526
527 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
528 phpdbg_recurse_watch_element(element);
529 }
530
531 return element;
532 }
533
534 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
535 phpdbg_watchpoint_t watch;
536 phpdbg_set_bucket_watchpoint(bucket, &watch);
537 element = phpdbg_add_watch_element(&watch, element);
538 phpdbg_watch_parent_ht(element);
539 return element;
540 }
541
542 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
543 phpdbg_watchpoint_t watch;
544 HashTable *ht = HT_FROM_ZVP(zv);
545
546 if (!ht) {
547 return NULL;
548 }
549
550 element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
551 phpdbg_set_ht_watchpoint(ht, &watch);
552 return phpdbg_add_watch_element(&watch, element);
553 }
554
555 bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
556 phpdbg_watch_element *next = element;
557 do {
558 element = next;
559 if (element->watch->addr.ptr == ptr) {
560 return 1;
561 }
562 next = element->parent;
563 } while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
564
565 return 0;
566 }
567
568 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
569 phpdbg_watch_element *child;
570 if (phpdbg_is_recursively_watched(zv, element)) {
571 return;
572 }
573
574 child = emalloc(sizeof(*child));
575 child->flags = PHPDBG_WATCH_RECURSIVE;
576 if (str) {
577 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)));
578 } else {
579 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);
580 }
581 if (!str) {
582 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
583 } else { str = zend_string_copy(str); }
584 child->name_in_parent = str;
585 child->parent = element;
586 child->child = NULL;
587 child->parent_container = HT_WATCH_HT(element->watch);
588 zend_hash_add_ptr(&element->child_container, child->str, child);
589 phpdbg_add_bucket_watch_element((Bucket *) zv, child);
590 }
591
592 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
593 phpdbg_watch_element *child;
594 zval *zv;
595
596 if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
597 zv = element->watch->addr.zv;
598 while (Z_TYPE_P(zv) == IS_INDIRECT) {
599 zv = Z_INDIRECT_P(zv);
600 }
601 ZVAL_DEREF(zv);
602
603 if (element->child) {
604 phpdbg_remove_watch_element_recursively(element->child);
605 }
606
607 if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
608 || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
609 if (element->child) {
610 phpdbg_free_watch_element(element->child);
611 element->child = NULL;
612 }
613 return;
614 }
615
616 if (element->child) {
617 child = element->child;
618 } else {
619 child = emalloc(sizeof(*child));
620 child->flags = PHPDBG_WATCH_RECURSIVE;
621 child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
622 child->name_in_parent = NULL;
623 child->parent = element;
624 child->child = NULL;
625 element->child = child;
626 }
627 zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
628 phpdbg_add_ht_watch_element(zv, child);
629 } else if (zend_hash_num_elements(&element->child_container) == 0) {
630 zend_string *str;
631 zend_long idx;
632
633 ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
634 ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
635 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
636 } ZEND_HASH_FOREACH_END();
637 }
638 }
639
640 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
641 if (element->watch->type == WATCH_ON_BUCKET) {
642 phpdbg_btree_result *res;
643 HashPosition pos;
644 phpdbg_watch_ht_info *hti;
645 ZEND_ASSERT(element->parent_container);
646 if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
647 hti = emalloc(sizeof(*hti));
648 hti->ht = element->parent_container;
649
650 zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
651 phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
652
653 phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
654 hti->hash_watch.type = WATCH_ON_HASHDATA;
655 phpdbg_store_watchpoint_btree(&hti->hash_watch);
656 phpdbg_activate_watchpoint(&hti->hash_watch);
657 } else {
658 hti = (phpdbg_watch_ht_info *) res->ptr;
659 }
660
661 zend_hash_internal_pointer_end_ex(hti->ht, &pos);
662 hti->last = hti->ht->arData + pos;
663 hti->last_str = hti->last->key;
664 hti->last_idx = hti->last->h;
665
666 zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
667 }
668 }
669
670 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
671 if (element->watch && element->watch->type == WATCH_ON_BUCKET) {
672 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
673 ZEND_ASSERT(element->parent_container);
674 if (res) {
675 phpdbg_watch_ht_info *hti = res->ptr;
676
677 if (zend_hash_num_elements(&hti->watches) == 1) {
678 zend_hash_destroy(&hti->watches);
679 phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
680 phpdbg_remove_watchpoint_btree(&hti->hash_watch);
681 phpdbg_deactivate_watchpoint(&hti->hash_watch);
682 efree(hti);
683 } else {
684 zend_hash_del(&hti->watches, element->name_in_parent);
685 }
686 }
687 }
688 }
689
690 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
691 /* 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 */
692
693 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
694 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
695
696 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
697 /* store lowermost element */
698 phpdbg_watch_element *prev;
699
700 if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
701 phpdbg_watch_element *child = prev;
702 do {
703 if (child == element) {
704 return;
705 }
706 child = child->child;
707 } while (child);
708 }
709 zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
710
711 /* dissociate from watchpoint to avoid dangling memory watches */
712 phpdbg_dissociate_watch_element(element, prev);
713
714 if (!element->parent) {
715 /* 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) */
716 zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
717 }
718 }
719
720 bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *element) {
721 zval *zv;
722 HashTable *ht = HT_FROM_ZVP(parent);
723
724 if (!ht) {
725 return 0;
726 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
727 char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
728 char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
729 if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
730 phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
731 }
732
733 phpdbg_add_ht_watch_element(parent, element);
734 } else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
735 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
736 zval *next = zv;
737
738 while (Z_TYPE_P(next) == IS_INDIRECT) {
739 next = Z_INDIRECT_P(next);
740 }
741 if (Z_ISREF_P(next)) {
742 next = Z_REFVAL_P(next);
743 }
744
745 if (!phpdbg_try_re_adding_watch_element(next, element->child)) {
746 return 0;
747 }
748 } else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
749 phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
750 }
751
752 element->parent_container = ht;
753 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
754 phpdbg_watch_parent_ht(element);
755 } else {
756 return 0;
757 }
758
759 return 1;
760 }
761
762 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
763 phpdbg_watch_element *child = element;
764 while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
765 child = child->child;
766 }
767 PHPDBG_G(watchpoint_hit) = 1;
768 if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
769 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" : "");
770 }
771 phpdbg_free_watch_element_tree(element);
772 }
773
774 void phpdbg_dequeue_elements_for_recreation(void) {
775 phpdbg_watch_element *element;
776
777 ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
778 ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
779 if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
780 zval _zv, *zv = &_zv;
781 if (element->parent) {
782 ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
783 zv = element->parent->watch->addr.zv;
784 while (Z_TYPE_P(zv) == IS_INDIRECT) {
785 zv = Z_INDIRECT_P(zv);
786 }
787 ZVAL_DEREF(zv);
788 } else {
789 ZVAL_ARR(zv, element->parent_container);
790 }
791 if (!phpdbg_try_re_adding_watch_element(zv, element)) {
792 phpdbg_automatic_dequeue_free(element);
793 }
794 } else {
795 phpdbg_automatic_dequeue_free(element);
796 }
797 } ZEND_HASH_FOREACH_END();
798
799 zend_hash_clean(&PHPDBG_G(watch_recreation));
800 zend_hash_clean(&PHPDBG_G(watch_free));
801 }
802
803 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
804 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
805
806 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
807 zend_string_release(element->str);
808 if (element->name_in_parent) {
809 zend_string_release(element->name_in_parent);
810 }
811 efree(element);
812 }
813
814 /* note: does *not* free the passed element, only clean */
815 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
816 if (element->child) {
817 phpdbg_remove_watch_element_recursively(element->child);
818 phpdbg_free_watch_element(element->child);
819 element->child = NULL;
820 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
821 phpdbg_watch_element *child;
822 ZEND_HASH_MAP_FOREACH_PTR(&element->child_container, child) {
823 phpdbg_remove_watch_element_recursively(child);
824 phpdbg_free_watch_element(child);
825 } ZEND_HASH_FOREACH_END();
826 zend_hash_destroy(&element->child_container);
827 }
828
829 phpdbg_clean_watch_element(element);
830 }
831
832 /* remove single watch (i.e. manual unset) or implicit removed */
833 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
834 phpdbg_watch_element *parent = element->parent, *child = element->child;
835 while (parent) {
836 phpdbg_watch_element *cur = parent;
837 parent = parent->parent;
838 phpdbg_clean_watch_element(cur);
839 phpdbg_free_watch_element(cur);
840 }
841 while (child) {
842 phpdbg_watch_element *cur = child;
843 child = child->child;
844 if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
845 phpdbg_remove_watch_element_recursively(cur);
846 child = NULL;
847 } else {
848 phpdbg_clean_watch_element(cur);
849 }
850 phpdbg_free_watch_element(cur);
851 }
852 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
853 phpdbg_remove_watch_element_recursively(element);
854 } else {
855 phpdbg_clean_watch_element(element);
856 }
857 zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
858 phpdbg_free_watch_element(element);
859 }
860
861 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
862 memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
863 }
864
865 /* until argument to prevent double remove of children elements */
866 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
867 phpdbg_watch_element *child = element;
868 ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
869
870 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
871 phpdbg_backup_watch_element(element);
872 phpdbg_remove_watch_element_recursively(element);
873 return;
874 }
875
876 while (child->child != until) {
877 child = child->child;
878 if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
879 phpdbg_backup_watch_element(child);
880 phpdbg_remove_watch_element_recursively(child);
881 child->child = NULL;
882 break;
883 }
884 if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
885 phpdbg_backup_watch_element(child);
886 }
887 phpdbg_clean_watch_element(child);
888 }
889 /* element needs to be removed last! */
890 if (element->child == NULL) {
891 phpdbg_backup_watch_element(element);
892 }
893 phpdbg_clean_watch_element(element);
894 }
895
896 /* 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) */
897 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
898 phpdbg_watch_element *parent = element->parent, *child = element->child;
899 while (parent) {
900 phpdbg_watch_element *cur = parent;
901 parent = parent->parent;
902 phpdbg_clean_watch_element(cur);
903 phpdbg_free_watch_element(cur);
904 }
905 while (child) {
906 phpdbg_watch_element *cur = child;
907 child = child->child;
908 phpdbg_free_watch_element(cur);
909 }
910 phpdbg_free_watch_element(element);
911 }
912
913 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
914 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
915 phpdbg_watch_element *child = element->child;
916 while (child->flags & PHPDBG_WATCH_IMPLICIT) {
917 child = child->child;
918 }
919
920 ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
921 phpdbg_queue_element_for_recreation(element);
922 } else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
923 phpdbg_queue_element_for_recreation(element);
924 } else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
925 phpdbg_remove_watch_element_recursively(element);
926 if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
927 zend_hash_del(&element->parent->child_container, element->str);
928 } else {
929 element->parent->child = NULL;
930 }
931 phpdbg_free_watch_element(element);
932 }
933 }
934
935 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
936 phpdbg_watchpoint_t *parent;
937 phpdbg_watch_element *element;
938
939 ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
940 if (parent->coll) {
941 phpdbg_update_watch_collision_elements(parent);
942 } else {
943 ZEND_HASH_MAP_FOREACH_PTR(&parent->elements, element) {
944 phpdbg_update_watch_element_watch(element);
945 } ZEND_HASH_FOREACH_END();
946 }
947 } ZEND_HASH_FOREACH_END();
948 }
949
950 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
951 phpdbg_watch_element *element;
952
953 phpdbg_remove_watchpoint_btree(watch);
954 phpdbg_deactivate_watchpoint(watch);
955 phpdbg_delete_watch_collision(watch);
956
957 if (watch->coll) {
958 phpdbg_update_watch_collision_elements(watch);
959 return;
960 }
961
962 watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
963 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
964 phpdbg_update_watch_element_watch(element);
965 } ZEND_HASH_FOREACH_END();
966 zend_hash_destroy(&watch->elements);
967
968 efree(watch);
969 }
970
971 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
972 phpdbg_unwatch_parent_ht(element);
973
974 if (element->watch) {
975 HashTable *elements = &element->watch->elements;
976 zend_hash_del(elements, element->str);
977 if (zend_hash_num_elements(elements) == 0) {
978 phpdbg_remove_watchpoint(element->watch);
979 }
980 }
981 }
982
983 /* TODO: compile a name of all hit watchpoints (ids ??) */
984 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
985 phpdbg_watchpoint_t *parent;
986 phpdbg_watch_element *element;
987 zend_string *name = NULL;
988 if (watch->coll) {
989 ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
990 if (name) {
991 zend_string_release(name);
992 }
993 name = phpdbg_watchpoint_change_collision_name(parent);
994 } ZEND_HASH_FOREACH_END();
995 return name;
996 }
997 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
998 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
999 if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
1000 phpdbg_update_watch_element_watch(element->child);
1001 }
1002 continue;
1003 }
1004 name = element->str;
1005 } ZEND_HASH_FOREACH_END();
1006
1007 return name ? zend_string_copy(name) : NULL;
1008 }
1009
1010 /* ### WATCHING FOR CHANGES ### */
1011 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
1012 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
1013 zend_string *name = NULL;
1014 void *comparePtr;
1015
1016 if (watch->type == WATCH_ON_HASHTABLE) {
1017 phpdbg_watch_element *element;
1018 zend_string *str;
1019 zend_long idx;
1020 zval *zv;
1021 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1022 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1023 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
1024 phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
1025
1026 ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
1027 if (!str) {
1028 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
1029 } else {
1030 str = zend_string_copy(str);
1031 }
1032 if (hti && zend_hash_find(&hti->watches, str)) {
1033 zend_string_release(str);
1034 break;
1035 }
1036 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1037 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1038 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
1039 }
1040 } ZEND_HASH_FOREACH_END();
1041 phpdbg_notice("Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
1042 zend_string_release(str);
1043 PHPDBG_G(watchpoint_hit) = 1;
1044 } ZEND_HASH_FOREACH_END();
1045
1046 break;
1047 }
1048 } ZEND_HASH_FOREACH_END();
1049 }
1050 if (watch->type == WATCH_ON_HASHDATA) {
1051 return;
1052 }
1053
1054 switch (watch->type) {
1055 case WATCH_ON_STR:
1056 comparePtr = &ZSTR_LEN(watch->backup.str);
1057 break;
1058 case WATCH_ON_HASHTABLE:
1059 comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1060 break;
1061 default:
1062 comparePtr = &watch->backup;
1063 }
1064 if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1065 return;
1066 }
1067 if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1068 phpdbg_watch_backup_data(watch);
1069 return;
1070 }
1071 if (watch->type == WATCH_ON_BUCKET) {
1072 if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1073 phpdbg_watch_element *element = NULL;
1074 zval *new;
1075
1076 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1077 break;
1078 } ZEND_HASH_FOREACH_END();
1079
1080 ZEND_ASSERT(element); /* elements must be non-empty */
1081 new = zend_symtable_find(element->parent_container, element->name_in_parent);
1082
1083 if (!new) {
1084 /* dequeuing will take care of appropriate notification about removal */
1085 phpdbg_remove_watchpoint(watch);
1086 return;
1087 }
1088
1089 phpdbg_remove_watchpoint_btree(watch);
1090 phpdbg_deactivate_watchpoint(watch);
1091 watch->addr.zv = new;
1092 phpdbg_store_watchpoint_btree(watch);
1093 phpdbg_activate_watchpoint(watch);
1094
1095 if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1096 phpdbg_watch_backup_data(watch);
1097 return;
1098 }
1099 } else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1100 /* dequeuing will take care of appropriate notification about removal */
1101 phpdbg_remove_watchpoint(watch);
1102 return;
1103 }
1104 }
1105
1106 name = phpdbg_watchpoint_change_collision_name(watch);
1107
1108 if (name) {
1109 phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1110 zend_string_release(name);
1111 }
1112
1113 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1114 phpdbg_watch_element *element;
1115 phpdbg_update_watch_ref(watch);
1116 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1117 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1118 phpdbg_recurse_watch_element(element);
1119 }
1120 } ZEND_HASH_FOREACH_END();
1121 }
1122
1123 phpdbg_watch_backup_data(watch);
1124 }
1125
1126 void phpdbg_reenable_memory_watches(void) {
1127 zend_ulong page;
1128 phpdbg_btree_result *res;
1129 phpdbg_watchpoint_t *watch;
1130
1131 ZEND_HASH_MAP_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1132 /* Disable writing again if there are any watchers on that page */
1133 res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1134 if (res) {
1135 watch = res->ptr;
1136 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1137 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1138 if (PHPDBG_G(watch_userfaultfd)) {
1139 struct uffdio_writeprotect protect = {
1140 .mode = UFFDIO_WRITEPROTECT_MODE_WP,
1141 .range = {
1142 .start = (__u64) page,
1143 .len = phpdbg_pagesize
1144 }
1145 };
1146 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_WRITEPROTECT, &protect);
1147 } else
1148 #endif
1149 {
1150 mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1151 }
1152 }
1153 }
1154 } ZEND_HASH_FOREACH_END();
1155 zend_hash_clean(PHPDBG_G(watchlist_mem));
1156 }
1157
1158 int phpdbg_print_changed_zvals(void) {
1159 int ret;
1160 zend_ulong page;
1161 phpdbg_watchpoint_t *watch;
1162 phpdbg_btree_result *res;
1163 HashTable *mem_list = NULL;
1164
1165 if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1166 return FAILURE;
1167 }
1168
1169 if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1170 /* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1171 mem_list = PHPDBG_G(watchlist_mem);
1172 PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1173
1174 ZEND_HASH_MAP_FOREACH_NUM_KEY(mem_list, page) {
1175 phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1176
1177 while ((res = phpdbg_btree_next(&pos))) {
1178 watch = res->ptr;
1179 phpdbg_check_watchpoint(watch);
1180 }
1181 if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1182 watch = res->ptr;
1183 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1184 phpdbg_check_watchpoint(watch);
1185 }
1186 }
1187 } ZEND_HASH_FOREACH_END();
1188 }
1189
1190 phpdbg_dequeue_elements_for_recreation();
1191
1192 phpdbg_reenable_memory_watches();
1193
1194 if (mem_list) {
1195 PHPDBG_G(watchlist_mem) = mem_list;
1196 phpdbg_reenable_memory_watches();
1197 }
1198
1199 ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1200 PHPDBG_G(watchpoint_hit) = 0;
1201
1202 return ret;
1203 }
1204
1205 void phpdbg_watch_efree(void *ptr) {
1206 phpdbg_btree_result *result;
1207
1208 /* only do expensive checks if there are any watches at all */
1209 if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1210 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1211 phpdbg_watchpoint_t *watch = result->ptr;
1212 if (watch->type != WATCH_ON_HASHDATA) {
1213 phpdbg_remove_watchpoint(watch);
1214 } else {
1215 /* remove all linked watchpoints, they will be dissociated from their elements */
1216 phpdbg_watch_element *element;
1217 phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1218
1219 ZEND_HASH_MAP_FOREACH_PTR(&hti->watches, element) {
1220 zend_ulong num = zend_hash_num_elements(&hti->watches);
1221 phpdbg_remove_watchpoint(element->watch);
1222 if (num == 1) { /* prevent access into freed memory */
1223 break;
1224 }
1225 } ZEND_HASH_FOREACH_END();
1226 }
1227 }
1228
1229 /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1230 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1231 phpdbg_watchpoint_t *watch = result->ptr;
1232 if (watch->type == WATCH_ON_HASHTABLE) {
1233 phpdbg_remove_watchpoint(watch);
1234 }
1235 }
1236
1237 zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1238 }
1239
1240 if (PHPDBG_G(original_free_function)) {
1241 PHPDBG_G(original_free_function)(ptr);
1242 }
1243 }
1244
1245 /* ### USER API ### */
1246 void phpdbg_list_watchpoints(void) {
1247 phpdbg_watch_element *element;
1248
1249 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1250 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");
1251 } ZEND_HASH_FOREACH_END();
1252 }
1253
1254 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1255 element->flags = PHPDBG_WATCH_SIMPLE;
1256 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1257 return SUCCESS;
1258 }
1259
1260 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1261 phpdbg_watch_element *new;
1262 zend_string *str;
1263 zval *orig_zv = zv;
1264
1265 ZVAL_DEREF(zv);
1266 if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1267 return FAILURE;
1268 }
1269
1270 new = ecalloc(1, sizeof(phpdbg_watch_element));
1271
1272 str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1273 zend_string_release(element->str);
1274 element->str = str;
1275 element->flags = PHPDBG_WATCH_IMPLICIT;
1276 phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1277 element->child = new;
1278
1279 new->flags = PHPDBG_WATCH_SIMPLE;
1280 new->str = zend_string_copy(str);
1281 new->parent = element;
1282 phpdbg_add_ht_watch_element(zv, new);
1283 return SUCCESS;
1284 }
1285
1286 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1287 element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1288 element->child = NULL;
1289 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1290 return SUCCESS;
1291 }
1292
1293 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1294
1295 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) {
1296 int ret;
1297 phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1298 element->str = zend_string_init(name, namelen, 0);
1299 element->name_in_parent = zend_string_init(key, keylen, 0);
1300 element->parent_container = parent;
1301 element->parent = PHPDBG_G(watch_tmp);
1302 element->child = NULL;
1303
1304 ret = info->callback(zv, element);
1305
1306 efree(name);
1307 efree(key);
1308
1309 if (ret != SUCCESS) {
1310 phpdbg_remove_watch_element(element);
1311 } else {
1312 if (PHPDBG_G(watch_tmp)) {
1313 PHPDBG_G(watch_tmp)->child = element;
1314 }
1315
1316 if (element->child) {
1317 element = element->child;
1318 }
1319
1320 /* work around missing API for extending an array with a new element, and getting its index */
1321 zend_hash_next_index_insert_ptr(&PHPDBG_G(watch_elements), element);
1322 element->id = PHPDBG_G(watch_elements).nNextFreeElement - 1;
1323
1324 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));
1325 }
1326
1327 PHPDBG_G(watch_tmp) = NULL;
1328
1329 return ret;
1330 }
1331
1332 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, phpdbg_watch_parse_struct *info, bool silent) {
1333 return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1334 }
1335
1336 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) {
1337 phpdbg_watch_element *element;
1338
1339 /* do not install watch elements for references */
1340 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) {
1341 efree(name);
1342 efree(key);
1343 return SUCCESS;
1344 }
1345
1346 element = ecalloc(1, sizeof(phpdbg_watch_element));
1347 element->flags = PHPDBG_WATCH_IMPLICIT;
1348 element->str = zend_string_copy(info->str);
1349 element->name_in_parent = zend_string_init(key, keylen, 0);
1350 element->parent_container = parent;
1351 element->parent = PHPDBG_G(watch_tmp);
1352 element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1353
1354 efree(name);
1355 efree(key);
1356
1357 if (PHPDBG_G(watch_tmp)) {
1358 PHPDBG_G(watch_tmp)->child = element;
1359 }
1360 PHPDBG_G(watch_tmp) = element;
1361
1362 return SUCCESS;
1363 }
1364
1365 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1366 zend_class_entry *scope = zend_get_executed_scope();
1367 phpdbg_watch_parse_struct info;
1368 int ret;
1369
1370 if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1371 zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
1372 }
1373
1374 if (callback == phpdbg_create_array_watchpoint) {
1375 info.str = strpprintf(0, "%.*s[]", (int) len, input);
1376 } else {
1377 info.str = zend_string_init(input, len, 0);
1378 }
1379 info.callback = callback;
1380
1381 if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1382 zend_string_release(info.str);
1383 return SUCCESS;
1384 }
1385
1386 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);
1387
1388 zend_string_release(info.str);
1389 return ret;
1390 }
1391
1392 PHPDBG_WATCH(delete) /* {{{ */
1393 {
1394 phpdbg_watch_element *element;
1395 switch (param->type) {
1396 case NUMERIC_PARAM:
1397 if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1398 phpdbg_remove_watch_element(element);
1399 phpdbg_notice("Removed watchpoint %d", (int) param->num);
1400 } else {
1401 phpdbg_error("Nothing was deleted, no corresponding watchpoint found");
1402 }
1403 break;
1404
1405 phpdbg_default_switch_case();
1406 }
1407
1408 return SUCCESS;
1409 } /* }}} */
1410
1411 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1412 if (phpdbg_rebuild_symtable() == FAILURE) {
1413 return FAILURE;
1414 }
1415
1416 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1417 }
1418
1419 PHPDBG_WATCH(recursive) /* {{{ */
1420 {
1421 if (phpdbg_rebuild_symtable() == FAILURE) {
1422 return SUCCESS;
1423 }
1424
1425 switch (param->type) {
1426 case STR_PARAM:
1427 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1428 break;
1429
1430 phpdbg_default_switch_case();
1431 }
1432
1433 return SUCCESS;
1434 } /* }}} */
1435
1436 PHPDBG_WATCH(array) /* {{{ */
1437 {
1438 if (phpdbg_rebuild_symtable() == FAILURE) {
1439 return SUCCESS;
1440 }
1441
1442 switch (param->type) {
1443 case STR_PARAM:
1444 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1445 break;
1446
1447 phpdbg_default_switch_case();
1448 }
1449
1450 return SUCCESS;
1451 } /* }}} */
1452
1453
1454 void phpdbg_setup_watchpoints(void) {
1455 #if defined(_SC_PAGE_SIZE)
1456 phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1457 #elif defined(_SC_PAGESIZE)
1458 phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1459 #elif defined(_SC_NUTC_OS_PAGESIZE)
1460 phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1461 #else
1462 phpdbg_pagesize = 4096; /* common pagesize */
1463 #endif
1464
1465 phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1466 phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1467 zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1468 zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1469 zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1470 zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1471
1472 /* put these on a separate page, to avoid conflicts with other memory */
1473 PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1474 zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1475 PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1476 zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1477
1478 PHPDBG_G(watch_tmp) = NULL;
1479
1480 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1481 PHPDBG_G(watch_userfaultfd) = syscall(SYS_userfaultfd, O_CLOEXEC);
1482 if (PHPDBG_G(watch_userfaultfd) < 0) {
1483 PHPDBG_G(watch_userfaultfd) = 0;
1484 } else {
1485 struct uffdio_api userfaultfd_features = {0};
1486 userfaultfd_features.api = UFFD_API;
1487 userfaultfd_features.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
1488 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_API, &userfaultfd_features);
1489 if (userfaultfd_features.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP) {
1490 pthread_create(&PHPDBG_G(watch_userfault_thread), NULL, phpdbg_watchpoint_userfaultfd_thread, ZEND_MODULE_GLOBALS_BULK(phpdbg));
1491 } else {
1492 PHPDBG_G(watch_userfaultfd) = 0;
1493 }
1494 }
1495 #endif
1496 }
1497
1498 void phpdbg_destroy_watchpoints(void) {
1499 phpdbg_watch_element *element;
1500
1501 /* unconditionally free all remaining elements to avoid memory leaks */
1502 ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1503 phpdbg_automatic_dequeue_free(element);
1504 } ZEND_HASH_FOREACH_END();
1505
1506 /* 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. */
1507 phpdbg_purge_watchpoint_tree();
1508
1509 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1510 if (PHPDBG_G(watch_userfaultfd)) {
1511 pthread_cancel(PHPDBG_G(watch_userfault_thread));
1512 close(PHPDBG_G(watch_userfaultfd));
1513 }
1514 #endif
1515
1516 zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1517 zend_hash_destroy(&PHPDBG_G(watch_recreation));
1518 zend_hash_destroy(&PHPDBG_G(watch_free));
1519 zend_hash_destroy(&PHPDBG_G(watch_collisions));
1520 zend_hash_destroy(PHPDBG_G(watchlist_mem));
1521 free(PHPDBG_G(watchlist_mem));
1522 zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1523 free(PHPDBG_G(watchlist_mem_backup));
1524 }
1525
1526 void phpdbg_purge_watchpoint_tree(void) {
1527 phpdbg_btree_position pos;
1528 phpdbg_btree_result *res;
1529
1530 pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1531 while ((res = phpdbg_btree_next(&pos))) {
1532 phpdbg_deactivate_watchpoint(res->ptr);
1533 }
1534 }
1535