xref: /PHP-7.4/sapi/phpdbg/phpdbg_watch.c (revision af4a9bf1)
185b97c0fSBob Weinand /*
285b97c0fSBob Weinand    +----------------------------------------------------------------------+
3a4384bd3SLior Kaplan    | PHP Version 7                                                        |
485b97c0fSBob Weinand    +----------------------------------------------------------------------+
50cf7de1cSZeev Suraski    | Copyright (c) The PHP Group                                          |
685b97c0fSBob Weinand    +----------------------------------------------------------------------+
785b97c0fSBob Weinand    | This source file is subject to version 3.01 of the PHP license,	  |
885b97c0fSBob Weinand    | that is bundled with this package in the file LICENSE, and is        |
985b97c0fSBob Weinand    | available through the world-wide-web at the following url:           |
1085b97c0fSBob Weinand    | http://www.php.net/license/3_01.txt                                  |
1185b97c0fSBob Weinand    | If you did not receive a copy of the PHP license and are unable to   |
1285b97c0fSBob Weinand    | obtain it through the world-wide-web, please send a note to          |
1385b97c0fSBob Weinand    | license@php.net so we can mail you a copy immediately.               |
1485b97c0fSBob Weinand    +----------------------------------------------------------------------+
1585b97c0fSBob Weinand    | Authors: Felipe Pena <felipe@php.net>                                |
1685b97c0fSBob Weinand    | Authors: Joe Watkins <joe.watkins@live.co.uk>                        |
1785b97c0fSBob Weinand    | Authors: Bob Weinand <bwoebi@php.net>                                |
1885b97c0fSBob Weinand    +----------------------------------------------------------------------+
1985b97c0fSBob Weinand */
2085b97c0fSBob Weinand 
21740c86bcSBob Weinand /* Some information for the reader...
22740c86bcSBob Weinand  *
23b0037688SBob Weinand  * The main structure managing the direct observations is the watchpoint (phpdbg_watchpoint_t). There are several types of watchpoints currently:
24b0037688SBob Weinand  * 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)
25b0037688SBob Weinand  * WATCH_ON_ZVAL: a watchpoint on a bare zval (&zend_reference.val, zval.value.indirect)
26b0037688SBob Weinand  * WATCH_ON_STR: a watchpoint on a zend_string (put on &ZSTR_LEN() in order to not watch refcount/hash)
27b0037688SBob Weinand  * WATCH_ON_HASHTABLE: a watchpoint on a HashTable (currently only used to observe size changes, put after flags in order to not watch refcount)
28b0037688SBob Weinand  * WATCH_ON_REFCOUNTED: a watchpoint on a zend_refcounted, observes the refcount and serves as reference pointer in the custom efree handler
29b0037688SBob Weinand  * WATCH_ON_HASHDATA: special watchpoint to watch for HT_GET_DATA_ADDR(ht) being efree()'d to be able to properly relocate Bucket watches
30b0037688SBob Weinand  *
31b0037688SBob Weinand  * Watch elements are either simple, recursive or implicit (PHPDBG_WATCH_* flags)
322d48d734SGabriel Caruso  * Simple means that a particular watchpoint was explicitly defined
33b0037688SBob Weinand  * Recursive watch elements are created recursively (recursive root flag is to distinguish the root element easily from its children recursive elements)
3484b195d9SGabriel Caruso  * Implicit  watch elements are implicitly created on all ancestors of simple or recursive watch elements
35b0037688SBob Weinand  * Recursive and (simple or implicit) watch elements are mutually exclusive
36b0037688SBob Weinand  * Array/Object to distinguish watch elements on arrays
37b0037688SBob Weinand  *
38b0037688SBob Weinand  * 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
39b0037688SBob Weinand  * Each watch has its independent structure of watch elements, watchpoints are responsible for managing collisions and preventing pointers being watched multiple times
40740c86bcSBob Weinand  *
41740c86bcSBob Weinand  * PHPDBG_G(watchpoint_tree) contains all watchpoints identified by the watch target address
42b0037688SBob Weinand  * PHPDBG_G(watch_HashTables) contains the addresses of parent_containers of watch elements
43b0037688SBob Weinand  * PHPDBG_G(watch_elements) contains all directly defined watch elements (i.e. those which have an individual id)
44b0037688SBob Weinand  * 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)
45b0037688SBob Weinand  * PHPDBG_G(watch_free) is a set of pointers to watch for being freed (like HashTables referenced by phpdbg_watch_element.parent_container)
46b0037688SBob Weinand  * PHPDBG_G(watch_recreation) is the list of watch elements whose watchpoint has been removed (via efree() for example) and needs to be recreated
47b0037688SBob Weinand  * PHPDBG_G(watchlist_mem) is the list of unprotected memory pages; used to watch which pages need their PROT_WRITE attribute removed after checking
48b0037688SBob Weinand  *
49b0037688SBob Weinand  * Watching on addresses:
50b0037688SBob Weinand  * * Address and size are transformed into memory page aligned address and size
51b0037688SBob Weinand  * * mprotect() enables or disables them (depending on flags) - Windows has a transparent compatibility layer in phpdbg_win.c
52b0037688SBob Weinand  * * segfault handler stores the address of the page and marks it again as writable
53b0037688SBob Weinand  * * later watchpoints pointing inside these pages are compared against their current value and eventually reactivated (or deleted)
54740c86bcSBob Weinand  *
55b0037688SBob Weinand  * Creating a watch:
56b0037688SBob Weinand  * * Implicit watch elements for each element in the hierarchy (starting from base, which typically is current symbol table) except the last one
57b0037688SBob Weinand  * * Create a watch element with either simple flag or recursive [+ root] flags
58b0037688SBob Weinand  * * If the element has recursive flag, create elements recursively for every referenced HashTable and zval
59740c86bcSBob Weinand  *
60b0037688SBob Weinand  * Creating a watch element:
61b0037688SBob Weinand  * * 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
62b0037688SBob Weinand  * * 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]
63b0037688SBob Weinand  *
64b0037688SBob Weinand  * Creation of watchpoints:
65b0037688SBob Weinand  * * Watchpoints create a watch collision for each refcounted or indirect on the zval (if type is WATCH_ON_BUCKET or WATCH_ON_ZVAL)
66b0037688SBob Weinand  * * Backs the current value of the watched pointer up
67b0037688SBob Weinand  * * 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)
68740c86bcSBob Weinand  *
69740c86bcSBob Weinand  * Watch collisions:
70b0037688SBob Weinand  * * Manages a watchpoint on the refcount (WATCH_ON_REFCOUNTED) or indirect zval (WATCH_ON_ZVAL)
71b0037688SBob Weinand  * * 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)
72b0037688SBob Weinand  * * Contains a list of parents, i.e. which watchpoints reference it (via watch->ref)
73b0037688SBob Weinand  * * If no watchpoint is referencing it anymore, the watch collision and its associated watchpoints (phpdbg_watch_collision.ref/reference) are removed
74740c86bcSBob Weinand  *
75b0037688SBob Weinand  * Deleting a watch:
76b0037688SBob Weinand  * * Watches are stored by an id in PHPDBG_G(watch_elements); the associated watch element is then deleted
77b0037688SBob Weinand  * * Deletes all parent and children implicit watch elements
78b0037688SBob Weinand  *
79b0037688SBob Weinand  * Deleting a watch element:
80b0037688SBob Weinand  * * Removes itself from the parent list of the associated watchpoints; if that parent list is empty, also delete the watchpoint
81b0037688SBob Weinand  * * Removes itself from the related phpdbg_watch_ht_info if it has a parent_container
82740c86bcSBob Weinand  *
83b0037688SBob Weinand  * Deleting a watchpoint:
84b0037688SBob Weinand  * * Remove itself from watch collisions this watchpoint participates in
85b0037688SBob Weinand  * * 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)
86b0037688SBob Weinand  *
87b0037688SBob Weinand  * A watched pointer is efree()'d:
88b0037688SBob Weinand  * * Needs immediate action as we else may run into dereferencing a pointer into freed memory
89b0037688SBob Weinand  * * Deletes the associated watchpoint, and for each watch element, if recursive, all its children elements
90b0037688SBob Weinand  * * 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)
91740c86bcSBob Weinand  *
92b0037688SBob Weinand  * Recreating watchpoints:
93b0037688SBob Weinand  * * 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
94b0037688SBob Weinand  * * 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
95b0037688SBob Weinand  * * The old and new values of the watches are compared and shown if changed
96b0037688SBob Weinand  *
97b0037688SBob Weinand  * Comparing watchpoints:
98b0037688SBob Weinand  * * The old and new values of the watches are compared and shown if changed
99b0037688SBob Weinand  * * If changed, it is checked whether the refcounted/indirect changed and watch collisions removed or created accordingly
100b0037688SBob Weinand  * * If a zval/bucket watchpoint is recursive, watch elements are added or removed accordingly
101b0037688SBob Weinand  * * If an array watchpoint is recursive, new array watchpoints are added if there are new ones in the array
102b0037688SBob Weinand  * * If the watch (element with an id) is not reachable anymore due to changes in implicits, the watch is removed
103740c86bcSBob Weinand  */
104740c86bcSBob Weinand 
1052e5d7819SBob Weinand #include "zend.h"
1062e5d7819SBob Weinand #include "phpdbg.h"
1079ce9f48fSBob Weinand #include "phpdbg_btree.h"
1082e5d7819SBob Weinand #include "phpdbg_watch.h"
1095e351695SBob Weinand #include "phpdbg_utils.h"
110278adf99SBob Weinand #include "phpdbg_prompt.h"
1113fcdd6abSBob Weinand #ifndef _WIN32
1123fcdd6abSBob Weinand # include <unistd.h>
1133fcdd6abSBob Weinand # include <sys/mman.h>
1143fcdd6abSBob Weinand #endif
1152e5d7819SBob Weinand 
116c4b18887SJames Titcumb ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
1172e5d7819SBob Weinand 
1182bcac53bSBob Weinand const phpdbg_command_t phpdbg_watch_commands[] = {
119278adf99SBob Weinand 	PHPDBG_COMMAND_D_EX(array,      "create watchpoint on an array", 'a', watch_array,     &phpdbg_prompt_commands[24], "s", 0),
120b0037688SBob Weinand 	PHPDBG_COMMAND_D_EX(delete,     "delete watchpoint",             'd', watch_delete,    &phpdbg_prompt_commands[24], "n", 0),
121278adf99SBob Weinand 	PHPDBG_COMMAND_D_EX(recursive,  "create recursive watchpoints",  'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
1222bcac53bSBob Weinand 	PHPDBG_END_COMMAND
1232bcac53bSBob Weinand };
1242bcac53bSBob Weinand 
1252bcac53bSBob Weinand #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)
1262e5d7819SBob Weinand 
127740c86bcSBob Weinand #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t)) /* we are not interested in gc and flags */
128740c86bcSBob Weinand #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
129740c86bcSBob Weinand #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
130740c86bcSBob Weinand 
131b0037688SBob Weinand /* ### PRINTING POINTER DIFFERENCES ### */
phpdbg_check_watch_diff(phpdbg_watchtype type,void * oldPtr,void * newPtr)132b0037688SBob Weinand zend_bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) {
133b0037688SBob Weinand 	switch (type) {
134b0037688SBob Weinand 		case WATCH_ON_BUCKET:
135b0037688SBob Weinand 			if (memcmp(&((Bucket *) oldPtr)->h, &((Bucket *) newPtr)->h, sizeof(Bucket) - sizeof(zval) /* key/val comparison */) != 0) {
136b0037688SBob Weinand 				return 2;
137b0037688SBob Weinand 			}
138b0037688SBob Weinand 		case WATCH_ON_ZVAL:
139b0037688SBob Weinand 			return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0;
140b0037688SBob Weinand 		case WATCH_ON_HASHTABLE:
141b0037688SBob Weinand 			return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr));
142b0037688SBob Weinand 		case WATCH_ON_REFCOUNTED:
143b0037688SBob Weinand 			return memcmp(oldPtr, newPtr, sizeof(uint32_t) /* no zend_refcounted metadata info */) != 0;
144b0037688SBob Weinand 		case WATCH_ON_STR:
145b0037688SBob Weinand 			return memcmp(oldPtr, newPtr, *(size_t *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len)) != 0;
146b0037688SBob Weinand 		case WATCH_ON_HASHDATA:
147b0037688SBob Weinand 			ZEND_ASSERT(0);
148b0037688SBob Weinand 	}
149b0037688SBob Weinand 	return 0;
150b0037688SBob Weinand }
151b0037688SBob Weinand 
phpdbg_print_watch_diff(phpdbg_watchtype type,zend_string * name,void * oldPtr,void * newPtr)152b0037688SBob Weinand void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *oldPtr, void *newPtr) {
153b0037688SBob Weinand 	int32_t elementDiff;
154b0037688SBob Weinand 
155b0037688SBob Weinand 	PHPDBG_G(watchpoint_hit) = 1;
156b0037688SBob Weinand 
157b0037688SBob Weinand 	phpdbg_notice("watchhit", "variable=\"%s\"", "Breaking on watchpoint %.*s", (int) ZSTR_LEN(name), ZSTR_VAL(name));
158b0037688SBob Weinand 	phpdbg_xml("<watchdata %r>");
159b0037688SBob Weinand 
160b0037688SBob Weinand 	switch (type) {
161b0037688SBob Weinand 		case WATCH_ON_BUCKET:
162b0037688SBob Weinand 		case WATCH_ON_ZVAL:
163b0037688SBob Weinand 			if (Z_REFCOUNTED_P((zval *) oldPtr)) {
164b0037688SBob Weinand 				phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed");
165b0037688SBob Weinand 			} else if (Z_TYPE_P((zval *) oldPtr) == IS_INDIRECT) {
166b0037688SBob Weinand 				phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed (was indirect)");
167b0037688SBob Weinand 			} else {
168b0037688SBob Weinand 				phpdbg_out("Old value: ");
169b0037688SBob Weinand 				phpdbg_xml("<watchvalue %r type=\"old\">");
170b0037688SBob Weinand 				zend_print_flat_zval_r((zval *) oldPtr);
171b0037688SBob Weinand 				phpdbg_xml("</watchvalue>");
172b0037688SBob Weinand 				phpdbg_out("\n");
173b0037688SBob Weinand 			}
174b0037688SBob Weinand 
175b0037688SBob Weinand 			while (Z_TYPE_P((zval *) newPtr) == IS_INDIRECT) {
176b0037688SBob Weinand 				newPtr = Z_INDIRECT_P((zval *) newPtr);
177b0037688SBob Weinand 			}
178b0037688SBob Weinand 
179b0037688SBob Weinand 			phpdbg_out("New value%s: ", Z_ISREF_P((zval *) newPtr) ? " (reference)" : "");
180b0037688SBob Weinand 			phpdbg_xml("<watchvalue %r%s type=\"new\">", Z_ISREF_P((zval *) newPtr) ? " reference=\"reference\"" : "");
181b0037688SBob Weinand 			zend_print_flat_zval_r((zval *) newPtr);
182b0037688SBob Weinand 			phpdbg_xml("</watchvalue>");
183b0037688SBob Weinand 			phpdbg_out("\n");
184b0037688SBob Weinand 			break;
185b0037688SBob Weinand 
186b0037688SBob Weinand 		case WATCH_ON_HASHTABLE:
187b0037688SBob Weinand 			elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_PTR_HT(newPtr));
188b0037688SBob Weinand 			if (elementDiff > 0) {
189b0037688SBob Weinand 				phpdbg_writeln("watchsize", "removed=\"%d\"", "%d elements were removed from the array", (int) elementDiff);
190b0037688SBob Weinand 			} else if (elementDiff < 0) {
191b0037688SBob Weinand 				phpdbg_writeln("watchsize", "added=\"%d\"", "%d elements were added to the array", (int) -elementDiff);
192b0037688SBob Weinand 			}
193b0037688SBob Weinand 			break;
194b0037688SBob Weinand 
195b0037688SBob Weinand 		case WATCH_ON_REFCOUNTED:
196b0037688SBob Weinand 			phpdbg_writeln("watchrefcount", "type=\"old\" refcount=\"%d\"", "Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
197b0037688SBob Weinand 			phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", GC_REFCOUNT((zend_refcounted *) newPtr));
198b0037688SBob Weinand 			break;
199b0037688SBob Weinand 
200b0037688SBob Weinand 		case WATCH_ON_STR:
201b0037688SBob Weinand 			phpdbg_out("Old value: ");
202b0037688SBob Weinand 			phpdbg_xml("<watchvalue %r type=\"old\">");
203b0037688SBob Weinand 			zend_write((char *) oldPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) oldPtr);
204b0037688SBob Weinand 			phpdbg_xml("</watchvalue>");
205b0037688SBob Weinand 			phpdbg_out("\n");
206b0037688SBob Weinand 
207b0037688SBob Weinand 			phpdbg_out("New value: ");
208b0037688SBob Weinand 			phpdbg_xml("<watchvalue %r type=\"new\">");
209b0037688SBob Weinand 			zend_write((char *) newPtr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) newPtr);
210b0037688SBob Weinand 			phpdbg_xml("</watchvalue>");
211b0037688SBob Weinand 			phpdbg_out("\n");
212b0037688SBob Weinand 			break;
21385b97c0fSBob Weinand 
214b0037688SBob Weinand 		case WATCH_ON_HASHDATA:
215b0037688SBob Weinand 			ZEND_ASSERT(0);
216b0037688SBob Weinand 	}
21785b97c0fSBob Weinand 
218b0037688SBob Weinand 	phpdbg_xml("</watchdata>");
219b0037688SBob Weinand }
22085b97c0fSBob Weinand 
221b0037688SBob Weinand /* ### LOW LEVEL WATCHPOINT HANDLING ### */
phpdbg_check_for_watchpoint(void * addr)222bdeb220fSAnatol Belski static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
2233f70e9f8SBob Weinand 	phpdbg_watchpoint_t *watch;
224b0037688SBob Weinand 	phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong) phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
2253f70e9f8SBob Weinand 
2263f70e9f8SBob Weinand 	if (result == NULL) {
2273f70e9f8SBob Weinand 		return NULL;
2283f70e9f8SBob Weinand 	}
2293f70e9f8SBob Weinand 
2303f70e9f8SBob Weinand 	watch = result->ptr;
231f312f6baSBob Weinand 
232f312f6baSBob Weinand 	/* check if that addr is in a mprotect()'ed memory area */
2332bcac53bSBob Weinand 	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) {
23485b97c0fSBob Weinand 		/* failure */
23585b97c0fSBob Weinand 		return NULL;
23685b97c0fSBob Weinand 	}
23785b97c0fSBob Weinand 
23885b97c0fSBob Weinand 	return watch;
2392e5d7819SBob Weinand }
2402e5d7819SBob Weinand 
phpdbg_change_watchpoint_access(phpdbg_watchpoint_t * watch,int access)241bdeb220fSAnatol Belski static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
2420a8c20e7SBob Weinand 	/* pagesize is assumed to be in the range of 2^x */
2432bcac53bSBob Weinand 	mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
2440a8c20e7SBob Weinand }
2450a8c20e7SBob Weinand 
phpdbg_activate_watchpoint(phpdbg_watchpoint_t * watch)246bdeb220fSAnatol Belski static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
247bdeb220fSAnatol Belski 	phpdbg_change_watchpoint_access(watch, PROT_READ);
2480a8c20e7SBob Weinand }
2490a8c20e7SBob Weinand 
phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t * watch)250bdeb220fSAnatol Belski static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
251bdeb220fSAnatol Belski 	phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
2520a8c20e7SBob Weinand }
2530a8c20e7SBob Weinand 
254b0037688SBob Weinand /* 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 */
255b0037688SBob Weinand #ifdef _WIN32
phpdbg_watchpoint_segfault_handler(void * addr)256b0037688SBob Weinand int phpdbg_watchpoint_segfault_handler(void *addr) {
257b0037688SBob Weinand #else
258b0037688SBob Weinand int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
259b0037688SBob Weinand #endif
260b0037688SBob Weinand 
261b0037688SBob Weinand 	void *page = phpdbg_get_page_boundary(
262b0037688SBob Weinand #ifdef _WIN32
263b0037688SBob Weinand 		addr
264b0037688SBob Weinand #else
265b0037688SBob Weinand 		info->si_addr
266b0037688SBob Weinand #endif
267b0037688SBob Weinand 	);
268b0037688SBob Weinand 
269b0037688SBob Weinand 	/* perhaps unnecessary, but check to be sure to not conflict with other segfault handlers */
270b0037688SBob Weinand 	if (phpdbg_check_for_watchpoint(page) == NULL) {
271b0037688SBob Weinand 		return FAILURE;
272b0037688SBob Weinand 	}
273b0037688SBob Weinand 
274b0037688SBob Weinand 	/* re-enable writing */
275b0037688SBob Weinand 	mprotect(page, phpdbg_pagesize, PROT_READ | PROT_WRITE);
276b0037688SBob Weinand 
277b0037688SBob Weinand 	zend_hash_index_add_empty_element(PHPDBG_G(watchlist_mem), (zend_ulong) page);
278b0037688SBob Weinand 
279b0037688SBob Weinand 	return SUCCESS;
280b0037688SBob Weinand }
281b0037688SBob Weinand 
282b0037688SBob Weinand /* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */
283b0037688SBob Weinand static inline void phpdbg_store_watchpoint_btree(phpdbg_watchpoint_t *watch) {
284b0037688SBob Weinand 	phpdbg_btree_result *res;
285b0037688SBob Weinand 	ZEND_ASSERT((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL || res->ptr == watch);
2862bcac53bSBob Weinand 	phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
287008842c7SBob Weinand }
288008842c7SBob Weinand 
289b0037688SBob Weinand static inline void phpdbg_remove_watchpoint_btree(phpdbg_watchpoint_t *watch) {
2902bcac53bSBob Weinand 	phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
291008842c7SBob Weinand }
292008842c7SBob Weinand 
293b0037688SBob Weinand /* ### SET WATCHPOINT ADDR ### To be used only by watch element and collision managers ### */
294b0037688SBob Weinand void phpdbg_set_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
2950a8c20e7SBob Weinand 	watch->addr.ptr = addr;
2960a8c20e7SBob Weinand 	watch->size = size;
297b0037688SBob Weinand 	watch->ref = NULL;
298b0037688SBob Weinand 	watch->coll = NULL;
299b0037688SBob Weinand 	zend_hash_init(&watch->elements, 8, brml, NULL, 0);
3000a8c20e7SBob Weinand }
3010a8c20e7SBob Weinand 
302b0037688SBob Weinand void phpdbg_set_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
303b0037688SBob Weinand 	phpdbg_set_addr_watchpoint(zv, sizeof(zval) - sizeof(uint32_t), watch);
3040a8c20e7SBob Weinand 	watch->type = WATCH_ON_ZVAL;
3050a8c20e7SBob Weinand }
3060a8c20e7SBob Weinand 
307b0037688SBob Weinand void phpdbg_set_bucket_watchpoint(Bucket *bucket, phpdbg_watchpoint_t *watch) {
308b0037688SBob Weinand 	phpdbg_set_addr_watchpoint(bucket, sizeof(Bucket), watch);
309b0037688SBob Weinand 	watch->type = WATCH_ON_BUCKET;
31078e274afSBob Weinand }
31178e274afSBob Weinand 
312b0037688SBob Weinand void phpdbg_set_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
313b0037688SBob Weinand 	phpdbg_set_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
314b0037688SBob Weinand 	watch->type = WATCH_ON_HASHTABLE;
315b0037688SBob Weinand }
3162bcac53bSBob Weinand 
317b0037688SBob Weinand void phpdbg_watch_backup_data(phpdbg_watchpoint_t *watch) {
318b0037688SBob Weinand 	switch (watch->type) {
319b0037688SBob Weinand 		case WATCH_ON_BUCKET:
320b0037688SBob Weinand 		case WATCH_ON_ZVAL:
321b0037688SBob Weinand 		case WATCH_ON_REFCOUNTED:
322b0037688SBob Weinand 			memcpy(&watch->backup, watch->addr.ptr, watch->size);
323b0037688SBob Weinand 			break;
324b0037688SBob Weinand 		case WATCH_ON_STR:
325b0037688SBob Weinand 			if (watch->backup.str) {
326b0037688SBob Weinand 				zend_string_release(watch->backup.str);
327b0037688SBob Weinand 			}
328b0037688SBob Weinand 			watch->backup.str = zend_string_init((char *) watch->addr.ptr + XtOffsetOf(zend_string, val) - XtOffsetOf(zend_string, len), *(size_t *) watch->addr.ptr, 1);
329b59c9fb4SDmitry Stogov 			GC_MAKE_PERSISTENT_LOCAL(watch->backup.str);
330b0037688SBob Weinand 			break;
331b0037688SBob Weinand 		case WATCH_ON_HASHTABLE:
332b0037688SBob Weinand 			memcpy((char *) &watch->backup + HT_WATCH_OFFSET, watch->addr.ptr, watch->size);
333b0037688SBob Weinand 		case WATCH_ON_HASHDATA:
334b0037688SBob Weinand 			break;
335b0037688SBob Weinand 	}
336740c86bcSBob Weinand }
337740c86bcSBob Weinand 
338b0037688SBob Weinand /* ### MANAGE WATCH COLLISIONS ### To be used only by watch element manager and memory differ ### */
339b0037688SBob Weinand /* watch collisions are responsible for having only one watcher on a given refcounted/refval and having a mapping back to the parent zvals */
340b0037688SBob Weinand void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
341b0037688SBob Weinand 	phpdbg_watch_collision *coll;
342b0037688SBob Weinand 	if ((coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
343b0037688SBob Weinand 		zend_hash_index_del(&coll->parents, (zend_ulong) watch);
344b0037688SBob Weinand 		if (zend_hash_num_elements(&coll->parents) == 0) {
345b0037688SBob Weinand 			phpdbg_deactivate_watchpoint(&coll->ref);
346b0037688SBob Weinand 			phpdbg_remove_watchpoint_btree(&coll->ref);
347b0037688SBob Weinand 
348b0037688SBob Weinand 			if (coll->ref.type == WATCH_ON_ZVAL) {
349b0037688SBob Weinand 				phpdbg_delete_watch_collision(&coll->ref);
350b0037688SBob Weinand 			} else if (coll->reference.addr.ptr) {
351b0037688SBob Weinand 				phpdbg_deactivate_watchpoint(&coll->reference);
352b0037688SBob Weinand 				phpdbg_remove_watchpoint_btree(&coll->reference);
353b0037688SBob Weinand 				phpdbg_delete_watch_collision(&coll->reference);
354b0037688SBob Weinand 				if (coll->reference.type == WATCH_ON_STR) {
355b0037688SBob Weinand 					zend_string_release(coll->reference.backup.str);
356b0037688SBob Weinand 				}
357b0037688SBob Weinand 			}
3582bcac53bSBob Weinand 
359b0037688SBob Weinand 			zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref);
360b0037688SBob Weinand 			zend_hash_destroy(&coll->parents);
361b0037688SBob Weinand 			efree(coll);
362b0037688SBob Weinand 		}
363b0037688SBob Weinand 	}
3642bcac53bSBob Weinand }
3652bcac53bSBob Weinand 
366b0037688SBob Weinand void phpdbg_update_watch_ref(phpdbg_watchpoint_t *watch) {
367b0037688SBob Weinand 	phpdbg_watch_collision *coll;
368740c86bcSBob Weinand 
369b0037688SBob Weinand 	ZEND_ASSERT(watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET);
370b0037688SBob Weinand 	if (Z_REFCOUNTED_P(watch->addr.zv)) {
371b0037688SBob Weinand 		if (Z_COUNTED_P(watch->addr.zv) == watch->ref) {
372b0037688SBob Weinand 			return;
373b0037688SBob Weinand 		}
374740c86bcSBob Weinand 
375b0037688SBob Weinand 		if (watch->ref != NULL) {
376b0037688SBob Weinand 			phpdbg_delete_watch_collision(watch);
377b0037688SBob Weinand 		}
378740c86bcSBob Weinand 
379b0037688SBob Weinand 		watch->ref = Z_COUNTED_P(watch->addr.zv);
380b0037688SBob Weinand 
381b0037688SBob Weinand 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
382b0037688SBob Weinand 			coll = emalloc(sizeof(*coll));
383b0037688SBob Weinand 			coll->ref.type = WATCH_ON_REFCOUNTED;
384b0037688SBob Weinand 			phpdbg_set_addr_watchpoint(Z_COUNTED_P(watch->addr.zv), sizeof(uint32_t), &coll->ref);
385b0037688SBob Weinand 			coll->ref.coll = coll;
386b0037688SBob Weinand 			phpdbg_store_watchpoint_btree(&coll->ref);
387b0037688SBob Weinand 			phpdbg_activate_watchpoint(&coll->ref);
388b0037688SBob Weinand 			phpdbg_watch_backup_data(&coll->ref);
389b0037688SBob Weinand 
390b0037688SBob Weinand 			if (Z_ISREF_P(watch->addr.zv)) {
391b0037688SBob Weinand 				phpdbg_set_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), &coll->reference);
392b0037688SBob Weinand 				coll->reference.coll = coll;
393b0037688SBob Weinand 				phpdbg_update_watch_ref(&coll->reference);
394b0037688SBob Weinand 				phpdbg_store_watchpoint_btree(&coll->reference);
395b0037688SBob Weinand 				phpdbg_activate_watchpoint(&coll->reference);
396b0037688SBob Weinand 				phpdbg_watch_backup_data(&coll->reference);
397b0037688SBob Weinand 			} else if (Z_TYPE_P(watch->addr.zv) == IS_STRING) {
398b0037688SBob Weinand 				coll->reference.type = WATCH_ON_STR;
399b0037688SBob Weinand 				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);
400b0037688SBob Weinand 				coll->reference.coll = coll;
401b0037688SBob Weinand 				phpdbg_store_watchpoint_btree(&coll->reference);
402b0037688SBob Weinand 				phpdbg_activate_watchpoint(&coll->reference);
403b0037688SBob Weinand 				coll->reference.backup.str = NULL;
404b0037688SBob Weinand 				phpdbg_watch_backup_data(&coll->reference);
405b0037688SBob Weinand 			} else {
406b0037688SBob Weinand 				coll->reference.addr.ptr = NULL;
407740c86bcSBob Weinand 			}
408af0d6256SBob Weinand 
409b0037688SBob Weinand 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
410b0037688SBob Weinand 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
411740c86bcSBob Weinand 		}
412b0037688SBob Weinand 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
413b0037688SBob Weinand 	} else if (Z_TYPE_P(watch->addr.zv) == IS_INDIRECT) {
414b0037688SBob Weinand 		if ((zend_refcounted *) Z_INDIRECT_P(watch->addr.zv) == watch->ref) {
415b0037688SBob Weinand 			return;
4162bcac53bSBob Weinand 		}
417b0037688SBob Weinand 
418b0037688SBob Weinand 		if (watch->ref != NULL) {
419b0037688SBob Weinand 			phpdbg_delete_watch_collision(watch);
420740c86bcSBob Weinand 		}
4212bcac53bSBob Weinand 
422b0037688SBob Weinand 		watch->ref = (zend_refcounted *) Z_INDIRECT_P(watch->addr.zv);
423b0037688SBob Weinand 
424b0037688SBob Weinand 		if (!(coll = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref))) {
425b0037688SBob Weinand 			coll = emalloc(sizeof(*coll));
426b0037688SBob Weinand 			phpdbg_set_zval_watchpoint(Z_INDIRECT_P(watch->addr.zv), &coll->ref);
427b0037688SBob Weinand 			coll->ref.coll = coll;
428b0037688SBob Weinand 			phpdbg_update_watch_ref(&coll->ref);
429b0037688SBob Weinand 			phpdbg_store_watchpoint_btree(&coll->ref);
430b0037688SBob Weinand 			phpdbg_activate_watchpoint(&coll->ref);
431b0037688SBob Weinand 			phpdbg_watch_backup_data(&coll->ref);
43264002648SGabriel Caruso 
433b0037688SBob Weinand 			zend_hash_init(&coll->parents, 8, shitty stupid parameter, NULL, 0);
434b0037688SBob Weinand 			zend_hash_index_add_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->ref, coll);
435b0037688SBob Weinand 		}
436b0037688SBob Weinand 		zend_hash_index_add_ptr(&coll->parents, (zend_long) watch, watch);
437b0037688SBob Weinand 	} else if (watch->ref) {
438b0037688SBob Weinand 		phpdbg_delete_watch_collision(watch);
439b0037688SBob Weinand 		watch->ref = NULL;
440740c86bcSBob Weinand 	}
4412bcac53bSBob Weinand }
4422bcac53bSBob Weinand 
443b0037688SBob Weinand /* ### MANAGE WATCH ELEMENTS ### */
444b0037688SBob Weinand /* watchpoints must be unique per element. Only one watchpoint may point to one element. But many elements may point to one watchpoint. */
445b0037688SBob Weinand void phpdbg_recurse_watch_element(phpdbg_watch_element *element);
446b0037688SBob Weinand void phpdbg_remove_watch_element_recursively(phpdbg_watch_element *element);
447b0037688SBob Weinand void phpdbg_free_watch_element(phpdbg_watch_element *element);
448b0037688SBob Weinand void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch);
449b0037688SBob Weinand void phpdbg_watch_parent_ht(phpdbg_watch_element *element);
4502bcac53bSBob Weinand 
451b0037688SBob Weinand phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdbg_watch_element *element) {
452b0037688SBob Weinand 	phpdbg_btree_result *res;
453b0037688SBob Weinand 	if ((res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr)) == NULL) {
454b0037688SBob Weinand 		phpdbg_watchpoint_t *mem = emalloc(sizeof(*mem));
455b0037688SBob Weinand 		*mem = *watch;
456b0037688SBob Weinand 		watch = mem;
457b0037688SBob Weinand 		phpdbg_store_watchpoint_btree(watch);
458b0037688SBob Weinand 		if (watch->type == WATCH_ON_ZVAL || watch->type == WATCH_ON_BUCKET) {
459b0037688SBob Weinand 			phpdbg_update_watch_ref(watch);
460b0037688SBob Weinand 		}
461b0037688SBob Weinand 		phpdbg_activate_watchpoint(watch);
462b0037688SBob Weinand 		phpdbg_watch_backup_data(watch);
463b0037688SBob Weinand 	} else {
464b0037688SBob Weinand 		phpdbg_watch_element *old_element;
465b0037688SBob Weinand 		watch = res->ptr;
466b0037688SBob Weinand 		if ((old_element = zend_hash_find_ptr(&watch->elements, element->str))) {
467b0037688SBob Weinand 			phpdbg_free_watch_element(element);
468b0037688SBob Weinand 			return old_element;
469b0037688SBob Weinand 		}
470b0037688SBob Weinand 	}
4712bcac53bSBob Weinand 
472b0037688SBob Weinand 	element->watch = watch;
473b0037688SBob Weinand 	zend_hash_add_ptr(&watch->elements, element->str, element);
4742bcac53bSBob Weinand 
475b0037688SBob Weinand 	if (element->flags & PHPDBG_WATCH_RECURSIVE) {
476b0037688SBob Weinand 		phpdbg_recurse_watch_element(element);
4772bcac53bSBob Weinand 	}
478b0037688SBob Weinand 
479b0037688SBob Weinand 	return element;
4802bcac53bSBob Weinand }
4817b021921SBob Weinand 
482b0037688SBob Weinand phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element) {
483b0037688SBob Weinand 	phpdbg_watchpoint_t watch;
484b0037688SBob Weinand 	phpdbg_set_bucket_watchpoint(bucket, &watch);
485b0037688SBob Weinand 	element = phpdbg_add_watch_element(&watch, element);
486b0037688SBob Weinand 	phpdbg_watch_parent_ht(element);
487b0037688SBob Weinand 	return element;
488b0037688SBob Weinand }
489740c86bcSBob Weinand 
490b0037688SBob Weinand phpdbg_watch_element *phpdbg_add_ht_watch_element(zval *zv, phpdbg_watch_element *element) {
491b0037688SBob Weinand 	phpdbg_watchpoint_t watch;
492b0037688SBob Weinand 	HashTable *ht = HT_FROM_ZVP(zv);
49397887e37SBob Weinand 
494b0037688SBob Weinand 	if (!ht) {
495b0037688SBob Weinand 		return NULL;
496b0037688SBob Weinand 	}
497740c86bcSBob Weinand 
498b0037688SBob Weinand 	element->flags |= Z_TYPE_P(zv) == IS_ARRAY ? PHPDBG_WATCH_ARRAY : PHPDBG_WATCH_OBJECT;
499b0037688SBob Weinand 	phpdbg_set_ht_watchpoint(ht, &watch);
500b0037688SBob Weinand 	return phpdbg_add_watch_element(&watch, element);
50197887e37SBob Weinand }
50297887e37SBob Weinand 
503b0037688SBob Weinand zend_bool phpdbg_is_recursively_watched(void *ptr, phpdbg_watch_element *element) {
504b0037688SBob Weinand 	phpdbg_watch_element *next = element;
505b0037688SBob Weinand 	do {
506b0037688SBob Weinand 		element = next;
507b0037688SBob Weinand 		if (element->watch->addr.ptr == ptr) {
508b0037688SBob Weinand 			return 1;
509740c86bcSBob Weinand 		}
510b0037688SBob Weinand 		next = element->parent;
511b0037688SBob Weinand 	} while (!(element->flags & PHPDBG_WATCH_RECURSIVE_ROOT));
512740c86bcSBob Weinand 
513b0037688SBob Weinand 	return 0;
514b0037688SBob Weinand }
515b0037688SBob Weinand 
516b0037688SBob Weinand void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long idx, zend_string *str, zval *zv) {
517b0037688SBob Weinand 	phpdbg_watch_element *child;
518b0037688SBob Weinand 	if (phpdbg_is_recursively_watched(zv, element)) {
519b0037688SBob Weinand 		return;
520b0037688SBob Weinand 	}
521740c86bcSBob Weinand 
522b0037688SBob Weinand 	child = emalloc(sizeof(*child));
523b0037688SBob Weinand 	child->flags = PHPDBG_WATCH_RECURSIVE;
524b0037688SBob Weinand 	if (str) {
525b0037688SBob Weinand 		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)));
526b0037688SBob Weinand 	} else {
527b0037688SBob Weinand 		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);
528740c86bcSBob Weinand 	}
529b0037688SBob Weinand 	if (!str) {
530b0037688SBob Weinand 		str = zend_long_to_str(idx); // TODO: hack, use proper int handling for name in parent
531b0037688SBob Weinand 	} else { str = zend_string_copy(str); }
532b0037688SBob Weinand 	child->name_in_parent = str;
533b0037688SBob Weinand 	child->parent = element;
534b0037688SBob Weinand 	child->child = NULL;
535b0037688SBob Weinand 	child->parent_container = HT_WATCH_HT(element->watch);
536b0037688SBob Weinand 	zend_hash_add_ptr(&element->child_container, child->str, child);
537b0037688SBob Weinand 	phpdbg_add_bucket_watch_element((Bucket *) zv, child);
538740c86bcSBob Weinand }
539740c86bcSBob Weinand 
540b0037688SBob Weinand void phpdbg_recurse_watch_element(phpdbg_watch_element *element) {
541b0037688SBob Weinand 	phpdbg_watch_element *child;
542b0037688SBob Weinand 	zval *zv;
543740c86bcSBob Weinand 
544b0037688SBob Weinand 	if (element->watch->type == WATCH_ON_ZVAL || element->watch->type == WATCH_ON_BUCKET) {
545b0037688SBob Weinand 		zv = element->watch->addr.zv;
546b0037688SBob Weinand 		while (Z_TYPE_P(zv) == IS_INDIRECT) {
547b0037688SBob Weinand 			zv = Z_INDIRECT_P(zv);
548c17afbc4SBob Weinand 		}
549b0037688SBob Weinand 		ZVAL_DEREF(zv);
550c17afbc4SBob Weinand 
551b0037688SBob Weinand 		if (element->child) {
552b0037688SBob Weinand 			phpdbg_remove_watch_element_recursively(element->child);
553b0037688SBob Weinand 		}
554b0037688SBob Weinand 
555b0037688SBob Weinand 		if ((Z_TYPE_P(zv) != IS_ARRAY && Z_TYPE_P(zv) != IS_OBJECT)
556b0037688SBob Weinand 		    || phpdbg_is_recursively_watched(HT_WATCH_OFFSET + (char *) HT_FROM_ZVP(zv), element)) {
557b0037688SBob Weinand 			if (element->child) {
558b0037688SBob Weinand 				phpdbg_free_watch_element(element->child);