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