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 if (element != old_element) {
520 phpdbg_free_watch_element(element);
521 }
522 return old_element;
523 }
524 }
525
526 element->watch = watch;
527 zend_hash_add_ptr(&watch->elements, element->str, element);
528
529 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
530 phpdbg_recurse_watch_element(element);
531 }
532
533 return element;
534 }
535
536 phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
537 phpdbg_watchpoint_t watch;
538 phpdbg_set_bucket_watchpoint(bucket, &watch);
539 element = phpdbg_add_watch_element(&watch, element);
540 phpdbg_watch_parent_ht(element);
541 return element;
542 }
543
544 phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
545 phpdbg_watchpoint_t watch;
546 HashTable *ht = HT_FROM_ZVP(zv);
547
548 if (!ht) {
549 return NULL;
550 }
551
552 element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
553 phpdbg_set_ht_watchpoint(ht, &watch);
554 return phpdbg_add_watch_element(&watch, element);
555 }
556
557 bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
558 phpdbg_watch_element *next = element;
559 do {
560 element = next;
561 if (element->watch->addr.ptr == ptr) {
562 return 1;
563 }
564 next = element->parent;
565 } while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
566
567 return 0;
568 }
569
570 void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
571 phpdbg_watch_element *child;
572 if (phpdbg_is_recursively_watched(zv, element)) {
573 return;
574 }
575
576 child = emalloc(sizeof(*child));
577 child->flags = PHPDBG_WATCH_RECURSIVE;
578 if (str) {
579 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)));
580 } else {
581 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);
582 }
583 if (!str) {
584 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
585 } else { str = zend_string_copy(str); }
586 child->name_in_parent = str;
587 child->parent = element;
588 child->child = NULL;
589 child->parent_container = HT_WATCH_HT(element->watch);
590 zend_hash_add_ptr(&element->child_container, child->str, child);
591 phpdbg_add_bucket_watch_element((Bucket *) zv, child);
592 }
593
594 void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
595 phpdbg_watch_element *child;
596 zval *zv;
597
598 if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
599 zv = element->watch->addr.zv;
600 while (Z_TYPE_P(zv) == IS_INDIRECT) {
601 zv = Z_INDIRECT_P(zv);
602 }
603 ZVAL_DEREF(zv);
604
605 if (element->child) {
606 phpdbg_remove_watch_element_recursively(element->child);
607 }
608
609 if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
610 || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
611 if (element->child) {
612 phpdbg_free_watch_element(element->child);
613 element->child = NULL;
614 }
615 return;
616 }
617
618 if (element->child) {
619 child = element->child;
620 } else {
621 child = emalloc(sizeof(*child));
622 child->flags = PHPDBG_WATCH_RECURSIVE;
623 child->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
624 child->name_in_parent = NULL;
625 child->parent = element;
626 child->child = NULL;
627 element->child = child;
628 }
629 zend_hash_init(&child->child_container, 8, NULL, NULL, 0);
630 phpdbg_add_ht_watch_element(zv, child);
631 } else if (zend_hash_num_elements(&element->child_container) == 0) {
632 zend_string *str;
633 zend_long idx;
634
635 ZEND_ASSERT(element->watch->type == WATCH_ON_HASHTABLE);
636 ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(element->watch), idx, str, zv) {
637 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
638 } ZEND_HASH_FOREACH_END();
639 }
640 }
641
642 void phpdbg_watch_parent_ht(phpdbg_watch_element *element) {
643 if (element->watch->type == WATCH_ON_BUCKET) {
644 phpdbg_btree_result *res;
645 HashPosition pos;
646 phpdbg_watch_ht_info *hti;
647 ZEND_ASSERT(element->parent_container);
648 if (!(res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container))) {
649 hti = emalloc(sizeof(*hti));
650 hti->ht = element->parent_container;
651
652 zend_hash_init(&hti->watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
653 phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht, hti);
654
655 phpdbg_set_addr_watchpoint(HT_GET_DATA_ADDR(hti->ht), HT_HASH_SIZE(hti->ht->nTableMask), &hti->hash_watch);
656 hti->hash_watch.type = WATCH_ON_HASHDATA;
657 phpdbg_store_watchpoint_btree(&hti->hash_watch);
658 phpdbg_activate_watchpoint(&hti->hash_watch);
659 } else {
660 hti = (phpdbg_watch_ht_info *) res->ptr;
661 }
662
663 zend_hash_internal_pointer_end_ex(hti->ht, &pos);
664 hti->last = hti->ht->arData + pos;
665 hti->last_str = hti->last->key;
666 hti->last_idx = hti->last->h;
667
668 zend_hash_add_ptr(&hti->watches, element->name_in_parent, element);
669 }
670 }
671
672 void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
673 if (element->watch && element->watch->type == WATCH_ON_BUCKET) {
674 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container);
675 ZEND_ASSERT(element->parent_container);
676 if (res) {
677 phpdbg_watch_ht_info *hti = res->ptr;
678
679 if (zend_hash_num_elements(&hti->watches) == 1) {
680 zend_hash_destroy(&hti->watches);
681 phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) hti->ht);
682 phpdbg_remove_watchpoint_btree(&hti->hash_watch);
683 phpdbg_deactivate_watchpoint(&hti->hash_watch);
684 efree(hti);
685 } else {
686 zend_hash_del(&hti->watches, element->name_in_parent);
687 }
688 }
689 }
690 }
691
692 /* ### DE/QUEUE WATCH ELEMENTS ### to be used by watch element manager only */
693 /* 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 */
694
695 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until);
696 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element);
697
698 void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element) {
699 /* store lowermost element */
700 phpdbg_watch_element *prev;
701
702 if ((prev = zend_hash_find_ptr(&PHPDBG_G(watch_recreation), element->str))) {
703 phpdbg_watch_element *child = prev;
704 do {
705 if (child == element) {
706 return;
707 }
708 child = child->child;
709 } while (child);
710 }
711 zend_hash_update_ptr(&PHPDBG_G(watch_recreation), element->str, element);
712
713 /* dissociate from watchpoint to avoid dangling memory watches */
714 phpdbg_dissociate_watch_element(element, prev);
715
716 if (!element->parent) {
717 /* 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) */
718 zend_hash_index_add_empty_element(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container);
719 }
720 }
721
722 bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *element) {
723 zval *zv;
724 HashTable *ht = HT_FROM_ZVP(parent);
725
726 if (!ht) {
727 return 0;
728 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
729 char *htPtr = ((char *) ht) + HT_WATCH_OFFSET;
730 char *oldPtr = ((char *) &element->backup.ht) + HT_WATCH_OFFSET;
731 if (phpdbg_check_watch_diff(WATCH_ON_HASHTABLE, oldPtr, htPtr)) {
732 phpdbg_print_watch_diff(WATCH_ON_HASHTABLE, element->str, oldPtr, htPtr);
733 }
734
735 phpdbg_add_ht_watch_element(parent, element);
736 } else if ((zv = zend_symtable_find(ht, element->name_in_parent))) {
737 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
738 zval *next = zv;
739
740 while (Z_TYPE_P(next) == IS_INDIRECT) {
741 next = Z_INDIRECT_P(next);
742 }
743 if (Z_ISREF_P(next)) {
744 next = Z_REFVAL_P(next);
745 }
746
747 if (!phpdbg_try_re_adding_watch_element(next, element->child)) {
748 return 0;
749 }
750 } else if (phpdbg_check_watch_diff(WATCH_ON_ZVAL, &element->backup.zv, zv)) {
751 phpdbg_print_watch_diff(WATCH_ON_ZVAL, element->str, &element->backup.zv, zv);
752 }
753
754 element->parent_container = ht;
755 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
756 phpdbg_watch_parent_ht(element);
757 } else {
758 return 0;
759 }
760
761 return 1;
762 }
763
764 void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element) {
765 phpdbg_watch_element *child = element;
766 while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
767 child = child->child;
768 }
769 PHPDBG_G(watchpoint_hit) = 1;
770 if (zend_hash_index_del(&PHPDBG_G(watch_elements), child->id) == SUCCESS) {
771 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" : "");
772 }
773 phpdbg_free_watch_element_tree(element);
774 }
775
776 void phpdbg_dequeue_elements_for_recreation(void) {
777 phpdbg_watch_element *element;
778
779 ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
780 ZEND_ASSERT(element->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE));
781 if (element->parent || zend_hash_index_find(&PHPDBG_G(watch_free), (zend_ulong) element->parent_container)) {
782 zval _zv, *zv = &_zv;
783 if (element->parent) {
784 ZEND_ASSERT(element->parent->watch->type == WATCH_ON_ZVAL || element->parent->watch->type == WATCH_ON_BUCKET);
785 zv = element->parent->watch->addr.zv;
786 while (Z_TYPE_P(zv) == IS_INDIRECT) {
787 zv = Z_INDIRECT_P(zv);
788 }
789 ZVAL_DEREF(zv);
790 } else {
791 ZVAL_ARR(zv, element->parent_container);
792 }
793 if (!phpdbg_try_re_adding_watch_element(zv, element)) {
794 phpdbg_automatic_dequeue_free(element);
795 }
796 } else {
797 phpdbg_automatic_dequeue_free(element);
798 }
799 } ZEND_HASH_FOREACH_END();
800
801 zend_hash_clean(&PHPDBG_G(watch_recreation));
802 zend_hash_clean(&PHPDBG_G(watch_free));
803 }
804
805 /* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
806 void phpdbg_clean_watch_element(phpdbg_watch_element *element);
807
808 void phpdbg_free_watch_element(phpdbg_watch_element *element) {
809 zend_string_release(element->str);
810 if (element->name_in_parent) {
811 zend_string_release(element->name_in_parent);
812 }
813 efree(element);
814 }
815
816 /* note: does *not* free the passed element, only clean */
817 void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element) {
818 if (element->child) {
819 phpdbg_remove_watch_element_recursively(element->child);
820 phpdbg_free_watch_element(element->child);
821 element->child = NULL;
822 } else if (element->flags & (PHPDBG_WATCH_ARRAY | PHPDBG_WATCH_OBJECT)) {
823 phpdbg_watch_element *child;
824 ZEND_HASH_MAP_FOREACH_PTR(&element->child_container, child) {
825 phpdbg_remove_watch_element_recursively(child);
826 phpdbg_free_watch_element(child);
827 } ZEND_HASH_FOREACH_END();
828 zend_hash_destroy(&element->child_container);
829 }
830
831 phpdbg_clean_watch_element(element);
832 }
833
834 /* remove single watch (i.e. manual unset) or implicit removed */
835 void phpdbg_remove_watch_element(phpdbg_watch_element *element) {
836 phpdbg_watch_element *parent = element->parent, *child = element->child;
837 while (parent) {
838 phpdbg_watch_element *cur = parent;
839 parent = parent->parent;
840 phpdbg_clean_watch_element(cur);
841 phpdbg_free_watch_element(cur);
842 }
843 while (child) {
844 phpdbg_watch_element *cur = child;
845 child = child->child;
846 if (cur->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
847 phpdbg_remove_watch_element_recursively(cur);
848 child = NULL;
849 } else {
850 phpdbg_clean_watch_element(cur);
851 }
852 phpdbg_free_watch_element(cur);
853 }
854 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
855 phpdbg_remove_watch_element_recursively(element);
856 } else {
857 phpdbg_clean_watch_element(element);
858 }
859 zend_hash_index_del(&PHPDBG_G(watch_elements), element->id);
860 phpdbg_free_watch_element(element);
861 }
862
863 void phpdbg_backup_watch_element(phpdbg_watch_element *element) {
864 memcpy(&element->backup, &element->watch->backup, /* element->watch->size */ sizeof(element->backup));
865 }
866
867 /* until argument to prevent double remove of children elements */
868 void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until) {
869 phpdbg_watch_element *child = element;
870 ZEND_ASSERT((element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_RECURSIVE)) != PHPDBG_WATCH_RECURSIVE);
871
872 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
873 phpdbg_backup_watch_element(element);
874 phpdbg_remove_watch_element_recursively(element);
875 return;
876 }
877
878 while (child->child != until) {
879 child = child->child;
880 if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
881 phpdbg_backup_watch_element(child);
882 phpdbg_remove_watch_element_recursively(child);
883 child->child = NULL;
884 break;
885 }
886 if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
887 phpdbg_backup_watch_element(child);
888 }
889 phpdbg_clean_watch_element(child);
890 }
891 /* element needs to be removed last! */
892 if (element->child == NULL) {
893 phpdbg_backup_watch_element(element);
894 }
895 phpdbg_clean_watch_element(element);
896 }
897
898 /* 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) */
899 void phpdbg_free_watch_element_tree(phpdbg_watch_element *element) {
900 phpdbg_watch_element *parent = element->parent, *child = element->child;
901 while (parent) {
902 phpdbg_watch_element *cur = parent;
903 parent = parent->parent;
904 phpdbg_clean_watch_element(cur);
905 phpdbg_free_watch_element(cur);
906 }
907 while (child) {
908 phpdbg_watch_element *cur = child;
909 child = child->child;
910 phpdbg_free_watch_element(cur);
911 }
912 phpdbg_free_watch_element(element);
913 }
914
915 void phpdbg_update_watch_element_watch(phpdbg_watch_element *element) {
916 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
917 phpdbg_watch_element *child = element->child;
918 while (child->flags & PHPDBG_WATCH_IMPLICIT) {
919 child = child->child;
920 }
921
922 ZEND_ASSERT(element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET);
923 phpdbg_queue_element_for_recreation(element);
924 } else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
925 phpdbg_queue_element_for_recreation(element);
926 } else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
927 phpdbg_remove_watch_element_recursively(element);
928 if (element->parent->flags & (PHPDBG_WATCH_OBJECT | PHPDBG_WATCH_ARRAY)) {
929 zend_hash_del(&element->parent->child_container, element->str);
930 } else {
931 element->parent->child = NULL;
932 }
933 phpdbg_free_watch_element(element);
934 }
935 }
936
937 void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
938 phpdbg_watchpoint_t *parent;
939 phpdbg_watch_element *element;
940
941 ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
942 if (parent->coll) {
943 phpdbg_update_watch_collision_elements(parent);
944 } else {
945 ZEND_HASH_MAP_FOREACH_PTR(&parent->elements, element) {
946 phpdbg_update_watch_element_watch(element);
947 } ZEND_HASH_FOREACH_END();
948 }
949 } ZEND_HASH_FOREACH_END();
950 }
951
952 void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
953 phpdbg_watch_element *element;
954
955 phpdbg_remove_watchpoint_btree(watch);
956 phpdbg_deactivate_watchpoint(watch);
957 phpdbg_delete_watch_collision(watch);
958
959 if (watch->coll) {
960 phpdbg_update_watch_collision_elements(watch);
961 return;
962 }
963
964 watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
965 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
966 phpdbg_update_watch_element_watch(element);
967 } ZEND_HASH_FOREACH_END();
968 zend_hash_destroy(&watch->elements);
969
970 efree(watch);
971 }
972
973 void phpdbg_clean_watch_element(phpdbg_watch_element *element) {
974 phpdbg_unwatch_parent_ht(element);
975
976 if (element->watch) {
977 HashTable *elements = &element->watch->elements;
978 zend_hash_del(elements, element->str);
979 if (zend_hash_num_elements(elements) == 0) {
980 phpdbg_remove_watchpoint(element->watch);
981 }
982 }
983 }
984
985 /* TODO: compile a name of all hit watchpoints (ids ??) */
986 zend_string *phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch) {
987 phpdbg_watchpoint_t *parent;
988 phpdbg_watch_element *element;
989 zend_string *name = NULL;
990 if (watch->coll) {
991 ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
992 if (name) {
993 zend_string_release(name);
994 }
995 name = phpdbg_watchpoint_change_collision_name(parent);
996 } ZEND_HASH_FOREACH_END();
997 return name;
998 }
999 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1000 if (element->flags & PHPDBG_WATCH_IMPLICIT) {
1001 if ((watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) && Z_TYPE(watch->backup.zv) > IS_STRING) {
1002 phpdbg_update_watch_element_watch(element->child);
1003 }
1004 continue;
1005 }
1006 name = element->str;
1007 } ZEND_HASH_FOREACH_END();
1008
1009 return name ? zend_string_copy(name) : NULL;
1010 }
1011
1012 /* ### WATCHING FOR CHANGES ### */
1013 /* TODO: enforce order: first parents, then children, in order to avoid false positives */
1014 void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
1015 zend_string *name = NULL;
1016 void *comparePtr;
1017
1018 if (watch->type == WATCH_ON_HASHTABLE) {
1019 phpdbg_watch_element *element;
1020 zend_string *str;
1021 zend_long idx;
1022 zval *zv;
1023 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1024 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1025 phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch));
1026 phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
1027
1028 ZEND_HASH_REVERSE_FOREACH_KEY_VAL(HT_WATCH_HT(watch), idx, str, zv) {
1029 if (!str) {
1030 str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
1031 } else {
1032 str = zend_string_copy(str);
1033 }
1034 if (hti && zend_hash_find(&hti->watches, str)) {
1035 zend_string_release(str);
1036 break;
1037 }
1038 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1039 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1040 phpdbg_add_recursive_watch_from_ht(element, idx, str, zv);
1041 }
1042 } ZEND_HASH_FOREACH_END();
1043 phpdbg_notice("Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
1044 zend_string_release(str);
1045 PHPDBG_G(watchpoint_hit) = 1;
1046 } ZEND_HASH_FOREACH_END();
1047
1048 break;
1049 }
1050 } ZEND_HASH_FOREACH_END();
1051 }
1052 if (watch->type == WATCH_ON_HASHDATA) {
1053 return;
1054 }
1055
1056 switch (watch->type) {
1057 case WATCH_ON_STR:
1058 comparePtr = &ZSTR_LEN(watch->backup.str);
1059 break;
1060 case WATCH_ON_HASHTABLE:
1061 comparePtr = (char *) &watch->backup.ht + HT_WATCH_OFFSET;
1062 break;
1063 default:
1064 comparePtr = &watch->backup;
1065 }
1066 if (!phpdbg_check_watch_diff(watch->type, comparePtr, watch->addr.ptr)) {
1067 return;
1068 }
1069 if (watch->type == WATCH_ON_REFCOUNTED && !(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1070 phpdbg_watch_backup_data(watch);
1071 return;
1072 }
1073 if (watch->type == WATCH_ON_BUCKET) {
1074 if (watch->backup.bucket.key != watch->addr.bucket->key || (watch->backup.bucket.key != NULL && watch->backup.bucket.h != watch->addr.bucket->h)) {
1075 phpdbg_watch_element *element = NULL;
1076 zval *new;
1077
1078 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1079 break;
1080 } ZEND_HASH_FOREACH_END();
1081
1082 ZEND_ASSERT(element); /* elements must be non-empty */
1083 new = zend_symtable_find(element->parent_container, element->name_in_parent);
1084
1085 if (!new) {
1086 /* dequeuing will take care of appropriate notification about removal */
1087 phpdbg_remove_watchpoint(watch);
1088 return;
1089 }
1090
1091 phpdbg_remove_watchpoint_btree(watch);
1092 phpdbg_deactivate_watchpoint(watch);
1093 watch->addr.zv = new;
1094 phpdbg_store_watchpoint_btree(watch);
1095 phpdbg_activate_watchpoint(watch);
1096
1097 if (!phpdbg_check_watch_diff(WATCH_ON_ZVAL, &watch->backup.bucket.val, watch->addr.ptr)) {
1098 phpdbg_watch_backup_data(watch);
1099 return;
1100 }
1101 } else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1102 /* dequeuing will take care of appropriate notification about removal */
1103 phpdbg_remove_watchpoint(watch);
1104 return;
1105 }
1106 }
1107
1108 name = phpdbg_watchpoint_change_collision_name(watch);
1109
1110 if (name) {
1111 phpdbg_print_watch_diff(watch->type, name, comparePtr, watch->addr.ptr);
1112 zend_string_release(name);
1113 }
1114
1115 if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
1116 phpdbg_watch_element *element;
1117 phpdbg_update_watch_ref(watch);
1118 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1119 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1120 phpdbg_recurse_watch_element(element);
1121 }
1122 } ZEND_HASH_FOREACH_END();
1123 }
1124
1125 phpdbg_watch_backup_data(watch);
1126 }
1127
1128 void phpdbg_reenable_memory_watches(void) {
1129 zend_ulong page;
1130 phpdbg_btree_result *res;
1131 phpdbg_watchpoint_t *watch;
1132
1133 ZEND_HASH_MAP_FOREACH_NUM_KEY(PHPDBG_G(watchlist_mem), page) {
1134 /* Disable writing again if there are any watchers on that page */
1135 res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page + phpdbg_pagesize - 1);
1136 if (res) {
1137 watch = res->ptr;
1138 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1139 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1140 if (PHPDBG_G(watch_userfaultfd)) {
1141 struct uffdio_writeprotect protect = {
1142 .mode = UFFDIO_WRITEPROTECT_MODE_WP,
1143 .range = {
1144 .start = (__u64) page,
1145 .len = phpdbg_pagesize
1146 }
1147 };
1148 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_WRITEPROTECT, &protect);
1149 } else
1150 #endif
1151 {
1152 mprotect((void *) page, phpdbg_pagesize, PROT_READ);
1153 }
1154 }
1155 }
1156 } ZEND_HASH_FOREACH_END();
1157 zend_hash_clean(PHPDBG_G(watchlist_mem));
1158 }
1159
1160 int phpdbg_print_changed_zvals(void) {
1161 int ret;
1162 zend_ulong page;
1163 phpdbg_watchpoint_t *watch;
1164 phpdbg_btree_result *res;
1165 HashTable *mem_list = NULL;
1166
1167 if (zend_hash_num_elements(&PHPDBG_G(watch_elements)) == 0) {
1168 return FAILURE;
1169 }
1170
1171 if (zend_hash_num_elements(PHPDBG_G(watchlist_mem)) > 0) {
1172 /* we must not add elements to the hashtable while iterating over it (resize => read into freed memory) */
1173 mem_list = PHPDBG_G(watchlist_mem);
1174 PHPDBG_G(watchlist_mem) = PHPDBG_G(watchlist_mem_backup);
1175
1176 ZEND_HASH_MAP_FOREACH_NUM_KEY(mem_list, page) {
1177 phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), page, page + phpdbg_pagesize);
1178
1179 while ((res = phpdbg_btree_next(&pos))) {
1180 watch = res->ptr;
1181 phpdbg_check_watchpoint(watch);
1182 }
1183 if ((res = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), page - 1))) {
1184 watch = res->ptr;
1185 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1186 phpdbg_check_watchpoint(watch);
1187 }
1188 }
1189 } ZEND_HASH_FOREACH_END();
1190 }
1191
1192 phpdbg_dequeue_elements_for_recreation();
1193
1194 phpdbg_reenable_memory_watches();
1195
1196 if (mem_list) {
1197 PHPDBG_G(watchlist_mem) = mem_list;
1198 phpdbg_reenable_memory_watches();
1199 }
1200
1201 ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1202 PHPDBG_G(watchpoint_hit) = 0;
1203
1204 return ret;
1205 }
1206
1207 void phpdbg_watch_efree(void *ptr) {
1208 phpdbg_btree_result *result;
1209
1210 /* only do expensive checks if there are any watches at all */
1211 if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1212 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr))) {
1213 phpdbg_watchpoint_t *watch = result->ptr;
1214 if (watch->type != WATCH_ON_HASHDATA) {
1215 phpdbg_remove_watchpoint(watch);
1216 } else {
1217 /* remove all linked watchpoints, they will be dissociated from their elements */
1218 phpdbg_watch_element *element;
1219 phpdbg_watch_ht_info *hti = (phpdbg_watch_ht_info *) watch;
1220
1221 ZEND_HASH_MAP_FOREACH_PTR(&hti->watches, element) {
1222 zend_ulong num = zend_hash_num_elements(&hti->watches);
1223 phpdbg_remove_watchpoint(element->watch);
1224 if (num == 1) { /* prevent access into freed memory */
1225 break;
1226 }
1227 } ZEND_HASH_FOREACH_END();
1228 }
1229 }
1230
1231 /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1232 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), HT_WATCH_OFFSET + (zend_ulong) ptr))) {
1233 phpdbg_watchpoint_t *watch = result->ptr;
1234 if (watch->type == WATCH_ON_HASHTABLE) {
1235 phpdbg_remove_watchpoint(watch);
1236 }
1237 }
1238
1239 zend_hash_index_del(&PHPDBG_G(watch_free), (zend_ulong) ptr);
1240 }
1241
1242 if (PHPDBG_G(original_free_function)) {
1243 PHPDBG_G(original_free_function)(ptr);
1244 }
1245 }
1246
1247 /* ### USER API ### */
1248 void phpdbg_list_watchpoints(void) {
1249 phpdbg_watch_element *element;
1250
1251 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watch_elements), element) {
1252 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");
1253 } ZEND_HASH_FOREACH_END();
1254 }
1255
1256 static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1257 element->flags = PHPDBG_WATCH_SIMPLE;
1258 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1259 return SUCCESS;
1260 }
1261
1262 static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
1263 phpdbg_watch_element *new;
1264 zend_string *str;
1265 zval *orig_zv = zv;
1266
1267 ZVAL_DEREF(zv);
1268 if (Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT) {
1269 return FAILURE;
1270 }
1271
1272 new = ecalloc(1, sizeof(phpdbg_watch_element));
1273
1274 str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(element->str), ZSTR_VAL(element->str));
1275 zend_string_release(element->str);
1276 element->str = str;
1277 element->flags = PHPDBG_WATCH_IMPLICIT;
1278 phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element);
1279 element->child = new;
1280
1281 new->flags = PHPDBG_WATCH_SIMPLE;
1282 new->str = zend_string_copy(str);
1283 new->parent = element;
1284 phpdbg_add_ht_watch_element(zv, new);
1285 return SUCCESS;
1286 }
1287
1288 static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1289 element->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT;
1290 element->child = NULL;
1291 phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1292 return SUCCESS;
1293 }
1294
1295 typedef struct { int (*callback)(zval *zv, phpdbg_watch_element *); zend_string *str; } phpdbg_watch_parse_struct;
1296
1297 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) {
1298 int ret;
1299 phpdbg_watch_element *element = ecalloc(1, sizeof(phpdbg_watch_element));
1300 element->str = zend_string_init(name, namelen, 0);
1301 element->name_in_parent = zend_string_init(key, keylen, 0);
1302 element->parent_container = parent;
1303 element->parent = PHPDBG_G(watch_tmp);
1304 element->child = NULL;
1305
1306 ret = info->callback(zv, element);
1307
1308 efree(name);
1309 efree(key);
1310
1311 if (ret != SUCCESS) {
1312 phpdbg_remove_watch_element(element);
1313 } else {
1314 if (PHPDBG_G(watch_tmp)) {
1315 PHPDBG_G(watch_tmp)->child = element;
1316 }
1317
1318 if (element->child) {
1319 element = element->child;
1320 }
1321
1322 /* work around missing API for extending an array with a new element, and getting its index */
1323 zend_hash_next_index_insert_ptr(&PHPDBG_G(watch_elements), element);
1324 element->id = PHPDBG_G(watch_elements).nNextFreeElement - 1;
1325
1326 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));
1327 }
1328
1329 PHPDBG_G(watch_tmp) = NULL;
1330
1331 return ret;
1332 }
1333
1334 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, phpdbg_watch_parse_struct *info, bool silent) {
1335 return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, info);
1336 }
1337
1338 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) {
1339 phpdbg_watch_element *element;
1340
1341 /* do not install watch elements for references */
1342 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) {
1343 efree(name);
1344 efree(key);
1345 return SUCCESS;
1346 }
1347
1348 element = ecalloc(1, sizeof(phpdbg_watch_element));
1349 element->flags = PHPDBG_WATCH_IMPLICIT;
1350 element->str = zend_string_copy(info->str);
1351 element->name_in_parent = zend_string_init(key, keylen, 0);
1352 element->parent_container = parent;
1353 element->parent = PHPDBG_G(watch_tmp);
1354 element = phpdbg_add_bucket_watch_element((Bucket *) zv, element);
1355
1356 efree(name);
1357 efree(key);
1358
1359 if (PHPDBG_G(watch_tmp)) {
1360 PHPDBG_G(watch_tmp)->child = element;
1361 }
1362 PHPDBG_G(watch_tmp) = element;
1363
1364 return SUCCESS;
1365 }
1366
1367 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
1368 zend_class_entry *scope = zend_get_executed_scope();
1369 phpdbg_watch_parse_struct info;
1370 int ret;
1371
1372 if (scope && len >= 5 && !memcmp("$this", input, 5)) {
1373 zend_hash_add(EG(current_execute_data)->symbol_table, ZSTR_KNOWN(ZEND_STR_THIS), &EG(current_execute_data)->This);
1374 }
1375
1376 if (callback == phpdbg_create_array_watchpoint) {
1377 info.str = strpprintf(0, "%.*s[]", (int) len, input);
1378 } else {
1379 info.str = zend_string_init(input, len, 0);
1380 }
1381 info.callback = callback;
1382
1383 if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, &info, 1) != FAILURE) {
1384 zend_string_release(info.str);
1385 return SUCCESS;
1386 }
1387
1388 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);
1389
1390 zend_string_release(info.str);
1391 return ret;
1392 }
1393
1394 PHPDBG_WATCH(delete) /* {{{ */
1395 {
1396 phpdbg_watch_element *element;
1397 switch (param->type) {
1398 case NUMERIC_PARAM:
1399 if ((element = zend_hash_index_find_ptr(&PHPDBG_G(watch_elements), param->num))) {
1400 phpdbg_remove_watch_element(element);
1401 phpdbg_notice("Removed watchpoint %d", (int) param->num);
1402 } else {
1403 phpdbg_error("Nothing was deleted, no corresponding watchpoint found");
1404 }
1405 break;
1406
1407 phpdbg_default_switch_case();
1408 }
1409
1410 return SUCCESS;
1411 } /* }}} */
1412
1413 int phpdbg_create_var_watchpoint(char *input, size_t len) {
1414 if (phpdbg_rebuild_symtable() == FAILURE) {
1415 return FAILURE;
1416 }
1417
1418 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1419 }
1420
1421 PHPDBG_WATCH(recursive) /* {{{ */
1422 {
1423 if (phpdbg_rebuild_symtable() == FAILURE) {
1424 return SUCCESS;
1425 }
1426
1427 switch (param->type) {
1428 case STR_PARAM:
1429 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint);
1430 break;
1431
1432 phpdbg_default_switch_case();
1433 }
1434
1435 return SUCCESS;
1436 } /* }}} */
1437
1438 PHPDBG_WATCH(array) /* {{{ */
1439 {
1440 if (phpdbg_rebuild_symtable() == FAILURE) {
1441 return SUCCESS;
1442 }
1443
1444 switch (param->type) {
1445 case STR_PARAM:
1446 phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint);
1447 break;
1448
1449 phpdbg_default_switch_case();
1450 }
1451
1452 return SUCCESS;
1453 } /* }}} */
1454
1455
1456 void phpdbg_setup_watchpoints(void) {
1457 #if defined(_SC_PAGE_SIZE)
1458 phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
1459 #elif defined(_SC_PAGESIZE)
1460 phpdbg_pagesize = sysconf(_SC_PAGESIZE);
1461 #elif defined(_SC_NUTC_OS_PAGESIZE)
1462 phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
1463 #else
1464 phpdbg_pagesize = 4096; /* common pagesize */
1465 #endif
1466
1467 phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
1468 phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
1469 zend_hash_init(&PHPDBG_G(watch_elements), 8, NULL, NULL, 0);
1470 zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, NULL, 0);
1471 zend_hash_init(&PHPDBG_G(watch_recreation), 8, NULL, NULL, 0);
1472 zend_hash_init(&PHPDBG_G(watch_free), 8, NULL, NULL, 0);
1473
1474 /* put these on a separate page, to avoid conflicts with other memory */
1475 PHPDBG_G(watchlist_mem) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1476 PHPDBG_G(original_watchlist_mem) = PHPDBG_G(watchlist_mem);
1477 zend_hash_init(PHPDBG_G(watchlist_mem), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1478 PHPDBG_G(watchlist_mem_backup) = malloc(phpdbg_pagesize > sizeof(HashTable) ? phpdbg_pagesize : sizeof(HashTable));
1479 zend_hash_init(PHPDBG_G(watchlist_mem_backup), phpdbg_pagesize / (sizeof(Bucket) + sizeof(uint32_t)), NULL, NULL, 1);
1480
1481 PHPDBG_G(watch_tmp) = NULL;
1482 PHPDBG_G(watchpoint_hit) = false;
1483
1484 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1485 PHPDBG_G(watch_userfaultfd) = syscall(SYS_userfaultfd, O_CLOEXEC);
1486 if (PHPDBG_G(watch_userfaultfd) < 0) {
1487 PHPDBG_G(watch_userfaultfd) = 0;
1488 } else {
1489 struct uffdio_api userfaultfd_features = {0};
1490 userfaultfd_features.api = UFFD_API;
1491 userfaultfd_features.features = UFFD_FEATURE_PAGEFAULT_FLAG_WP;
1492 ioctl(PHPDBG_G(watch_userfaultfd), UFFDIO_API, &userfaultfd_features);
1493 if (userfaultfd_features.features & UFFD_FEATURE_PAGEFAULT_FLAG_WP) {
1494 pthread_create(&PHPDBG_G(watch_userfault_thread), NULL, phpdbg_watchpoint_userfaultfd_thread, ZEND_MODULE_GLOBALS_BULK(phpdbg));
1495 } else {
1496 PHPDBG_G(watch_userfaultfd) = 0;
1497 }
1498 }
1499 #endif
1500 }
1501
1502 void phpdbg_destroy_watchpoints(void) {
1503 phpdbg_watch_element *element;
1504
1505 /* unconditionally free all remaining elements to avoid memory leaks */
1506 ZEND_HASH_MAP_FOREACH_PTR(&PHPDBG_G(watch_recreation), element) {
1507 phpdbg_automatic_dequeue_free(element);
1508 } ZEND_HASH_FOREACH_END();
1509
1510 /* 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. */
1511 phpdbg_purge_watchpoint_tree();
1512
1513 #ifdef HAVE_USERFAULTFD_WRITEFAULT
1514 if (PHPDBG_G(watch_userfaultfd)) {
1515 pthread_cancel(PHPDBG_G(watch_userfault_thread));
1516 close(PHPDBG_G(watch_userfaultfd));
1517 }
1518 #endif
1519
1520 zend_hash_destroy(&PHPDBG_G(watch_elements)); PHPDBG_G(watch_elements).nNumOfElements = 0; /* phpdbg_watch_efree() is checking against this arrays size */
1521 zend_hash_destroy(&PHPDBG_G(watch_recreation));
1522 zend_hash_destroy(&PHPDBG_G(watch_free));
1523 zend_hash_destroy(&PHPDBG_G(watch_collisions));
1524 zend_hash_destroy(PHPDBG_G(original_watchlist_mem));
1525 free(PHPDBG_G(original_watchlist_mem));
1526 zend_hash_destroy(PHPDBG_G(watchlist_mem_backup));
1527 free(PHPDBG_G(watchlist_mem_backup));
1528 }
1529
1530 void phpdbg_purge_watchpoint_tree(void) {
1531 phpdbg_btree_position pos;
1532 phpdbg_btree_result *res;
1533
1534 pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), 0, -1);
1535 while ((res = phpdbg_btree_next(&pos))) {
1536 phpdbg_deactivate_watchpoint(res->ptr);
1537 }
1538 }
1539