php-internal-docs 8.4.8
Unofficial docs for php/php-src
Loading...
Searching...
No Matches
phpdbg_watch.c
Go to the documentation of this file.
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
123
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),
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 ### */
138bool 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) /* key/val comparison */) != 0) {
142 return 2;
143 }
144 /* TODO: Is this intentional? */
146 case WATCH_ON_ZVAL:
147 return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
149 return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
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;
156 }
157 return 0;
158}
159
160void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr) {
161 int32_t elementDiff;
162
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
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
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
215 }
216}
217
218/* ### LOW LEVEL WATCHPOINT HANDLING ### */
219static 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
238static 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, &reg);
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, &reg);
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
273static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
274 phpdbg_change_watchpoint_access(watch, PROT_READ);
275}
276
277static 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
284#else
285int 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
305
306 return SUCCESS;
307}
308
309#ifdef HAVE_USERFAULTFD_WRITEFAULT
310# if defined(__GNUC__) && !defined(__clang__)
311__attribute__((no_sanitize_address))
312# endif
313void *phpdbg_watchpoint_userfaultfd_thread(void *phpdbg_globals) {
314 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
315 zend_phpdbg_globals *globals = (zend_phpdbg_globals *) phpdbg_globals;
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 ### */
336static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) {
337#if ZEND_DEBUG
339 ZEND_ASSERT(res == NULL || res->ptr == watch);
340#endif
342}
343
344static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) {
346}
347
348/* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */
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
358 phpdbg_set_addr_watchpoint(zv, sizeof(zval) - sizeof(uint32_t), watch);
359 watch->type = WATCH_ON_ZVAL;
360}
361
363 phpdbg_set_addr_watchpoint(bucket, sizeof(Bucket), watch);
364 watch->type = WATCH_ON_BUCKET;
365}
366
371
373 switch (watch->type) {
374 case WATCH_ON_BUCKET:
375 case WATCH_ON_ZVAL:
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);
385 break;
387 memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
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 */
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) {
405 } else if (coll->reference.addr.ptr) {
406 phpdbg_remove_watchpoint_btree(&coll->reference);
407 phpdbg_deactivate_watchpoint(&coll->reference);
409 if (coll->reference.type == WATCH_ON_STR) {
410 zend_string_release(coll->reference.backup.str);
411 }
412 }
413
416 efree(coll);
417 }
418 }
419}
420
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) {
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));
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);
444
445 if (Z_ISREF_P(watch->addr.zv)) {
447 coll->reference.coll = coll;
449 phpdbg_store_watchpoint_btree(&coll->reference);
450 phpdbg_activate_watchpoint(&coll->reference);
452 } else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
455 coll->reference.coll = coll;
456 phpdbg_store_watchpoint_btree(&coll->reference);
457 phpdbg_activate_watchpoint(&coll->reference);
458 coll->reference.backup.str = NULL;
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) {
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));
482 coll->ref.coll = coll;
484 phpdbg_store_watchpoint_btree(&coll->ref);
485 phpdbg_activate_watchpoint(&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) {
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. */
505
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) {
515 }
516 phpdbg_activate_watchpoint(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) {
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) {
534 }
535
536 return element;
537}
538
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
550
551 if (!ht) {
552 return NULL;
553 }
554
557 return phpdbg_add_watch_element(&watch, element);
558}
559
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
575 if (phpdbg_is_recursively_watched(zv, element)) {
576 return;
577 }
578
579 child = emalloc(sizeof(*child));
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);
595}
596
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) {
610 }
611
612 if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
614 if (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));
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);
634 } else if (zend_hash_num_elements(&element->child_container) == 0) {
635 zend_string *str;
636 zend_long idx;
637
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);
642 }
643}
644
646 if (element->watch->type == WATCH_ON_BUCKET) {
651 hti = emalloc(sizeof(*hti));
652 hti->ht = element->parent_container;
653
656
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
670 if (element->watch && element->watch->type == WATCH_ON_BUCKET) {
673 if (res) {
674 phpdbg_watch_ht_info *hti = res->ptr;
675
676 if (zend_hash_num_elements(&hti->watches) == 1) {
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
694
696 /* store lowermost element */
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 */
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) */
716 }
717}
718
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) {
739 }
740 if (Z_ISREF_P(next)) {
742 }
743
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;
753 phpdbg_watch_parent_ht(element);
754 } else {
755 return 0;
756 }
757
758 return 1;
759}
760
762 phpdbg_watch_element *child = element;
763 while (child->child && !(child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
764 child = child->child;
765 }
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 }
771}
772
774 phpdbg_watch_element *element;
775
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)) {
792 }
793 } else {
795 }
797
800}
801
802/* ### WATCH ELEMENT DELETION ### only use phpdbg_remove_watch_element from the exterior */
804
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 */
830
831/* remove single watch (i.e. manual unset) or implicit removed */
833 phpdbg_watch_element *parent = element->parent, *child = element->child;
834 while (parent) {
835 phpdbg_watch_element *cur = parent;
836 parent = parent->parent;
839 }
840 while (child) {
841 phpdbg_watch_element *cur = child;
842 child = child->child;
845 child = NULL;
846 } else {
848 }
850 }
851 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
853 } else {
855 }
858}
859
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 */
866 phpdbg_watch_element *child = element;
868
869 if (element->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
872 return;
873 }
874
875 while (child->child != until) {
876 child = child->child;
877 if (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT) {
880 child->child = NULL;
881 break;
882 }
883 if (child->child == NULL || (child->flags & PHPDBG_WATCH_RECURSIVE_ROOT)) {
885 }
887 }
888 /* element needs to be removed last! */
889 if (element->child == NULL) {
891 }
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) */
897 phpdbg_watch_element *parent = element->parent, *child = element->child;
898 while (parent) {
899 phpdbg_watch_element *cur = parent;
900 parent = parent->parent;
903 }
904 while (child) {
905 phpdbg_watch_element *cur = child;
906 child = child->child;
908 }
910}
911
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);
921 } else if (element->flags & (PHPDBG_WATCH_RECURSIVE_ROOT | PHPDBG_WATCH_SIMPLE)) {
923 } else if (element->flags & PHPDBG_WATCH_RECURSIVE) {
926 zend_hash_del(&element->parent->child_container, element->str);
927 } else {
928 element->parent->child = NULL;
929 }
931 }
932}
933
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) {
941 } else {
942 ZEND_HASH_MAP_FOREACH_PTR(&parent->elements, element) {
945 }
947}
948
950 phpdbg_watch_element *element;
951
952 phpdbg_remove_watchpoint_btree(watch);
953 phpdbg_deactivate_watchpoint(watch);
955
956 if (watch->coll) {
958 return;
959 }
960
961 watch->elements.nNumOfElements++; /* dirty hack to avoid double free */
962 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
966
967 efree(watch);
968}
969
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) {
978 }
979 }
980}
981
982/* TODO: compile a name of all hit watchpoints (ids ??) */
984 phpdbg_watchpoint_t *parent;
985 phpdbg_watch_element *element;
987 if (watch->coll) {
988 ZEND_HASH_MAP_FOREACH_PTR(&watch->coll->parents, parent) {
989 if (name) {
990 zend_string_release(name);
991 }
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) {
1000 }
1001 continue;
1002 }
1003 name = element->str;
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 */
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) {
1023 phpdbg_watch_ht_info *hti = res ? res->ptr : NULL;
1024
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 }
1040 phpdbg_notice("Element %.*s has been added to watchpoint", (int) ZSTR_LEN(str), ZSTR_VAL(str));
1041 zend_string_release(str);
1044
1045 break;
1046 }
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 }
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;
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 */
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
1096 return;
1097 }
1098 } else if (Z_TYPE_P(watch->addr.zv) == IS_UNDEF) {
1099 /* dequeuing will take care of appropriate notification about removal */
1101 return;
1102 }
1103 }
1104
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;
1115 ZEND_HASH_MAP_FOREACH_PTR(&watch->elements, element) {
1116 if (element->flags & PHPDBG_WATCH_RECURSIVE) {
1118 }
1120 }
1121
1123}
1124
1126 zend_ulong page;
1128 phpdbg_watchpoint_t *watch;
1129
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 }
1155}
1156
1158 int ret;
1159 zend_ulong page;
1160 phpdbg_watchpoint_t *watch;
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);
1172
1173 ZEND_HASH_MAP_FOREACH_NUM_KEY(mem_list, page) {
1175
1176 while ((res = phpdbg_btree_next(&pos))) {
1177 watch = res->ptr;
1179 }
1181 watch = res->ptr;
1182 if ((char *) page < (char *) watch->addr.ptr + watch->size) {
1184 }
1185 }
1187 }
1188
1190
1192
1193 if (mem_list) {
1194 PHPDBG_G(watchlist_mem) = mem_list;
1196 }
1197
1200
1201 return ret;
1202}
1203
1206
1207 /* only do expensive checks if there are any watches at all */
1208 if (zend_hash_num_elements(&PHPDBG_G(watch_elements))) {
1210 phpdbg_watchpoint_t *watch = result->ptr;
1211 if (watch->type != WATCH_ON_HASHDATA) {
1213 } else {
1214 /* remove all linked watchpoints, they will be dissociated from their elements */
1215 phpdbg_watch_element *element;
1217
1218 ZEND_HASH_MAP_FOREACH_PTR(&hti->watches, element) {
1219 zend_ulong num = zend_hash_num_elements(&hti->watches);
1221 if (num == 1) { /* prevent access into freed memory */
1222 break;
1223 }
1225 }
1226 }
1227
1228 /* special case watchpoints as they aren't on ptr but on ptr + HT_WATCH_OFFSET */
1230 phpdbg_watchpoint_t *watch = result->ptr;
1231 if (watch->type == WATCH_ON_HASHTABLE) {
1233 }
1234 }
1235
1237 }
1238
1241 }
1242}
1243
1244/* ### USER API ### */
1246 phpdbg_watch_element *element;
1247
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");
1251}
1252
1253static int phpdbg_create_simple_watchpoint(zval *zv, phpdbg_watch_element *element) {
1254 element->flags = PHPDBG_WATCH_SIMPLE;
1256 return SUCCESS;
1257}
1258
1259static int phpdbg_create_array_watchpoint(zval *zv, phpdbg_watch_element *element) {
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
1279 new->str = zend_string_copy(str);
1280 new->parent = element;
1282 return SUCCESS;
1283}
1284
1285static int phpdbg_create_recursive_watchpoint(zval *zv, phpdbg_watch_element *element) {
1287 element->child = NULL;
1289 return SUCCESS;
1290}
1291
1293
1294static 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) {
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
1327
1328 return ret;
1329}
1330
1331PHPDBG_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
1335static 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 */
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
1364static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(zval *, phpdbg_watch_element *)) {
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
1391PHPDBG_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))) {
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
1405 }
1406
1407 return SUCCESS;
1408} /* }}} */
1409
1410int phpdbg_create_var_watchpoint(char *input, size_t len) {
1412 return FAILURE;
1413 }
1414
1415 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
1416}
1417
1418PHPDBG_WATCH(recursive) /* {{{ */
1419{
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
1430 }
1431
1432 return SUCCESS;
1433} /* }}} */
1434
1435PHPDBG_WATCH(array) /* {{{ */
1436{
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
1447 }
1448
1449 return SUCCESS;
1450} /* }}} */
1451
1452
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);
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));
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
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
1507 phpdbg_watch_element *element;
1508
1509 /* unconditionally free all remaining elements to avoid memory leaks */
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. */
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 */
1532}
1533
1537
1539 while ((res = phpdbg_btree_next(&pos))) {
1540 phpdbg_deactivate_watchpoint(res->ptr);
1541 }
1542}
size_t len
Definition apprentice.c:174
zval callback
Definition assert.c:25
prev(array|object &$array)
Definition test.php:8
#define _WIN32
Definition config-win.h:20
zend_ffi_type * type
Definition ffi.c:3812
zval * zv
Definition ffi.c:3975
new_type size
Definition ffi.c:4365
zend_string * res
Definition ffi.c:4692
void * ptr
Definition ffi.c:3814
memcpy(ptr1, ptr2, size)
zval * val
Definition ffi.c:4262
HashTable * ht
Definition ffi.c:4838
#define __attribute__(a)
Definition file.h:131
#define O_CLOEXEC
Definition file.h:144
#define NULL
Definition gdcache.h:45
#define SUCCESS
Definition hash_sha3.c:261
#define next(ls)
Definition minilua.c:2661
unsigned const char * pos
Definition php_ffi.h:52
unsigned char key[REFLECTION_KEY_LEN]
HashTable * watchlist_mem_backup
Definition phpdbg.h:258
HashTable watch_collisions
Definition phpdbg.h:253
HashTable watch_free
Definition phpdbg.h:255
phpdbg_watch_element * watch_tmp
Definition phpdbg.h:261
HashTable * watchlist_mem
Definition phpdbg.h:256
#define PHPDBG_SHOW_REFCOUNTS
Definition phpdbg.h:163
HashTable watch_recreation
Definition phpdbg.h:254
phpdbg_btree watchpoint_tree
Definition phpdbg.h:250
#define PHPDBG_G(v)
Definition phpdbg.h:102
#define PHPDBG_API
Definition phpdbg.h:27
HashTable watch_elements
Definition phpdbg.h:252
phpdbg_btree watch_HashTables
Definition phpdbg.h:251
HashTable * original_watchlist_mem
Definition phpdbg.h:257
bool watchpoint_hit
Definition phpdbg.h:259
void(* original_free_function)(void *ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
Definition phpdbg.h:260
phpdbg_btree_result * phpdbg_btree_next(phpdbg_btree_position *pos)
void phpdbg_btree_init(phpdbg_btree *tree, zend_ulong depth)
phpdbg_btree_result * phpdbg_btree_find(phpdbg_btree *tree, zend_ulong idx)
int phpdbg_btree_delete(phpdbg_btree *tree, zend_ulong idx)
phpdbg_btree_result * phpdbg_btree_find_closest(phpdbg_btree *tree, zend_ulong idx)
phpdbg_btree_position phpdbg_btree_find_between(phpdbg_btree *tree, zend_ulong lower_idx, zend_ulong higher_idx)
#define phpdbg_btree_insert(tree, idx, ptr)
@ STR_PARAM
Definition phpdbg_cmd.h:38
@ NUMERIC_PARAM
Definition phpdbg_cmd.h:39
#define PHPDBG_END_COMMAND
Definition phpdbg_cmd.h:171
#define phpdbg_default_switch_case()
Definition phpdbg_cmd.h:176
struct _phpdbg_command_t phpdbg_command_t
Definition phpdbg_cmd.h:88
#define PHPDBG_COMMAND_D_EX(name, tip, alias, handler, children, args, flags)
Definition phpdbg_cmd.h:161
#define phpdbg_error(strfmt,...)
Definition phpdbg_out.h:43
#define phpdbg_out(fmt,...)
Definition phpdbg_out.h:49
#define phpdbg_notice(strfmt,...)
Definition phpdbg_out.h:44
#define phpdbg_writeln(strfmt,...)
Definition phpdbg_out.h:45
const phpdbg_command_t phpdbg_prompt_commands[]
int phpdbg_is_auto_global(char *name, int len)
PHPDBG_API int phpdbg_parse_variable_with_arg(char *input, size_t len, HashTable *parent, size_t i, phpdbg_parse_var_with_arg_func callback, phpdbg_parse_var_with_arg_func step_cb, bool silent, void *arg)
char * phpdbg_get_property_key(char *key)
int phpdbg_rebuild_symtable(void)
int(* phpdbg_parse_var_with_arg_func)(char *name, size_t len, char *keyname, size_t keylen, HashTable *parent, zval *zv, void *arg)
PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, phpdbg_watch_parse_struct *info, bool silent)
void phpdbg_dequeue_elements_for_recreation(void)
zend_string * phpdbg_watchpoint_change_collision_name(phpdbg_watchpoint_t *watch)
bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *element)
void phpdbg_watch_backup_data(phpdbg_watchpoint_t *watch)
void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch)
int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context)
void phpdbg_remove_watch_element(phpdbg_watch_element *element)
phpdbg_watch_element * phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element)
void phpdbg_purge_watchpoint_tree(void)
void phpdbg_free_watch_element_tree(phpdbg_watch_element *element)
int phpdbg_print_changed_zvals(void)
int phpdbg_create_var_watchpoint(char *input, size_t len)
void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch)
void phpdbg_list_watchpoints(void)
void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr)
void phpdbg_set_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch)
const phpdbg_command_t phpdbg_watch_commands[]
void phpdbg_destroy_watchpoints(void)
void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch)
void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch)
void phpdbg_recurse_watch_element(phpdbg_watch_element *element)
void phpdbg_watch_efree(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
void phpdbg_dissociate_watch_element(phpdbg_watch_element *element, phpdbg_watch_element *until)
void phpdbg_clean_watch_element(phpdbg_watch_element *element)
void phpdbg_queue_element_for_recreation(phpdbg_watch_element *element)
void phpdbg_backup_watch_element(phpdbg_watch_element *element)
#define HT_WATCH_HT(watch)
void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element)
bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr)
#define HT_PTR_HT(ptr)
void phpdbg_watch_parent_ht(phpdbg_watch_element *element)
phpdbg_watch_element * phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element)
void phpdbg_set_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch)
void phpdbg_free_watch_element(phpdbg_watch_element *element)
void phpdbg_reenable_memory_watches(void)
void phpdbg_set_bucket_watchpoint(Bucket *bucket, phpdbg_watchpoint_t *watch)
void phpdbg_setup_watchpoints(void)
void phpdbg_update_watch_element_watch(phpdbg_watch_element *element)
bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element)
phpdbg_watch_element * phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element)
#define HT_WATCH_OFFSET
#define HT_FROM_ZVP(zvp)
void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch)
void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element)
void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv)
void phpdbg_automatic_dequeue_free(phpdbg_watch_element *element)
void phpdbg_set_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch)
struct _phpdbg_watchpoint_t phpdbg_watchpoint_t
#define PHPDBG_WATCH_RECURSIVE_ROOT
#define PHPDBG_WATCH_SIMPLE
phpdbg_watchtype
@ WATCH_ON_BUCKET
@ WATCH_ON_REFCOUNTED
@ WATCH_ON_HASHDATA
@ WATCH_ON_ZVAL
@ WATCH_ON_HASHTABLE
@ WATCH_ON_STR
#define PHPDBG_WATCH_IMPLICIT
#define PHPDBG_WATCH_RECURSIVE
#define PHPDBG_WATCH(name)
struct _phpdbg_watch_collision phpdbg_watch_collision
#define PHPDBG_WATCH_OBJECT
#define PHPDBG_WATCH_ARRAY
struct _phpdbg_watch_element phpdbg_watch_element
int mprotect(void *addr, size_t size, int protection)
Definition phpdbg_win.c:22
#define PROT_READ
Definition phpdbg_win.h:26
#define PROT_WRITE
Definition phpdbg_win.h:27
ptrdiff_t namelen
Definition session.c:1097
#define strpprintf
Definition spprintf.h:30
zend_ulong h
Definition zend_types.h:382
zend_string * key
Definition zend_types.h:383
zval val
Definition zend_types.h:381
phpdbg_watchpoint_t ref
phpdbg_watchpoint_t reference
HashTable * parent_container
union _phpdbg_watch_element::@143130252033274031345066232267337246132126076271 backup
phpdbg_watchpoint_t * watch
zend_string * name_in_parent
struct _phpdbg_watch_element * parent
struct _phpdbg_watch_element * child
zend_refcounted * ref
zend_string * str
phpdbg_watchtype type
phpdbg_watch_collision * coll
union _phpdbg_watchpoint_t::@311026210203112077031145043110251145306324303024 addr
union _phpdbg_watchpoint_t::@353001364072153044355140237352142031313101134215 backup
uint32_t nTableMask
Definition zend_types.h:400
uint32_t nNumOfElements
Definition zend_types.h:407
Definition dce.c:49
phpdbg_watchpoint_t hash_watch
int(* callback)(zval *zv, phpdbg_watch_element *)
#define close(a)
ZEND_API void zend_print_flat_zval_r(zval *expr)
Definition zend.c:534
ZEND_API zend_write_func_t zend_write
Definition zend.c:85
#define ZEND_MODULE_GLOBALS_BULK(module_name)
Definition zend_API.h:275
#define ZEND_EXTERN_MODULE_GLOBALS(module_name)
Definition zend_API.h:270
#define ecalloc(nmemb, size)
Definition zend_alloc.h:158
#define efree(ptr)
Definition zend_alloc.h:155
#define emalloc(size)
Definition zend_alloc.h:151
struct _zval_struct zval
ZEND_API zend_class_entry * zend_get_executed_scope(void)
#define EG(v)
ZEND_API void ZEND_FASTCALL zend_hash_destroy(HashTable *ht)
Definition zend_hash.c:1727
ZEND_API zval *ZEND_FASTCALL zend_hash_index_add_empty_element(HashTable *ht, zend_ulong h)
Definition zend_hash.c:1059
ZEND_API void ZEND_FASTCALL zend_hash_clean(HashTable *ht)
Definition zend_hash.c:1869
ZEND_API zend_result ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h)
Definition zend_hash.c:1692
ZEND_API zend_result ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key)
Definition zend_hash.c:1534
ZEND_API zval *ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key)
Definition zend_hash.c:2668
ZEND_API zval *ZEND_FASTCALL zend_hash_add(HashTable *ht, zend_string *key, zval *pData)
Definition zend_hash.c:992
ZEND_API zval *ZEND_FASTCALL zend_hash_index_find(const HashTable *ht, zend_ulong h)
Definition zend_hash.c:2701
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent)
Definition zend_hash.h:108
#define ZEND_HASH_MAP_FOREACH_PTR(ht, _ptr)
Definition zend_hash.h:1326
#define ZEND_HASH_REVERSE_FOREACH_KEY_VAL(ht, _h, _key, _val)
Definition zend_hash.h:1187
#define ZEND_HASH_FOREACH_PTR(ht, _ptr)
Definition zend_hash.h:1118
#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val)
Definition zend_hash.h:1181
#define ZEND_HASH_MAP_FOREACH_NUM_KEY(ht, _h)
Definition zend_hash.h:1338
#define ZEND_HASH_FOREACH_END()
Definition zend_hash.h:1086
int32_t zend_long
Definition zend_long.h:42
uint32_t zend_ulong
Definition zend_long.h:43
#define ZEND_LONG_FMT
Definition zend_long.h:87
struct _zend_string zend_string
ZEND_API zend_string *ZEND_FASTCALL zend_long_to_str(zend_long num)
#define ZEND_FALLTHROUGH
#define ZEND_FILE_LINE_DC
#define ZEND_FILE_LINE_ORIG_RELAY_CC
#define XtOffsetOf(s_type, field)
#define ZEND_ASSERT(c)
#define ZEND_UNREACHABLE()
#define ZEND_FILE_LINE_RELAY_CC
#define ZEND_FILE_LINE_ORIG_DC
struct _zend_class_entry zend_class_entry
#define ZSTR_VAL(zstr)
Definition zend_string.h:68
#define ZSTR_KNOWN(idx)
#define ZSTR_LEN(zstr)
Definition zend_string.h:69
#define Z_TYPE_P(zval_p)
Definition zend_types.h:660
#define Z_ISREF_P(zval_p)
Definition zend_types.h:954
#define Z_REFVAL_P(zval_p)
#define IS_UNDEF
Definition zend_types.h:600
#define HT_HASH_SIZE(nTableMask)
Definition zend_types.h:468
#define ZVAL_DEREF(z)
#define IS_STRING
Definition zend_types.h:606
#define Z_REFCOUNTED_P(zval_p)
Definition zend_types.h:921
struct _zend_array HashTable
Definition zend_types.h:386
union _zend_value zend_value
#define IS_ARRAY
Definition zend_types.h:607
#define Z_COUNTED_P(zval_p)
Definition zend_types.h:699
#define GC_MAKE_PERSISTENT_LOCAL(p)
#define Z_STRLEN_P(zval_p)
Definition zend_types.h:978
@ FAILURE
Definition zend_types.h:61
#define IS_OBJECT
Definition zend_types.h:608
#define ZVAL_ARR(z, a)
#define HT_GET_DATA_ADDR(ht)
Definition zend_types.h:545
struct _zend_refcounted zend_refcounted
Definition zend_types.h:95
struct _Bucket Bucket
#define Z_INDIRECT_P(zval_p)
#define GC_REFCOUNT(p)
Definition zend_types.h:707
#define Z_TYPE(zval)
Definition zend_types.h:659
#define IS_INDIRECT
Definition zend_types.h:623
#define ZVAL_PTR_DTOR
zend_string * name
bool result
zval * ret
new_op_array scope