This source file includes following definitions.
- phpdbg_check_for_watchpoint
- phpdbg_change_watchpoint_access
- phpdbg_activate_watchpoint
- phpdbg_deactivate_watchpoint
- phpdbg_store_watchpoint
- phpdbg_remove_watchpoint
- phpdbg_create_addr_watchpoint
- phpdbg_create_zval_watchpoint
- phpdbg_create_ht_watchpoint
- phpdbg_free_watch
- phpdbg_create_refcounted_watchpoint
- phpdbg_add_watch_collision
- phpdbg_remove_watch_collision
- phpdbg_create_reference_watch
- phpdbg_get_refcount_watch
- phpdbg_create_watchpoint
- phpdbg_create_simple_watchpoint
- phpdbg_create_array_watchpoint
- phpdbg_create_recursive_watchpoint
- phpdbg_create_recursive_ht_watch
- phpdbg_create_recursive_zval_watch
- phpdbg_delete_implicit_parents
- phpdbg_delete_watchpoint_recursive
- phpdbg_delete_ht_watchpoints_recursive
- phpdbg_delete_zval_watchpoints_recursive
- phpdbg_delete_watchpoints_recursive
- phpdbg_delete_watchpoint
- phpdbg_watchpoint_parse_wrapper
- phpdbg_watchpoint_parse_input
- phpdbg_watchpoint_parse_step
- phpdbg_watchpoint_parse_symtables
- PHPDBG_WATCH
- PHPDBG_WATCH
- PHPDBG_WATCH
- phpdbg_watch_HashTable_dtor
- phpdbg_create_var_watchpoint
- phpdbg_delete_var_watchpoint
- phpdbg_watchpoint_segfault_handler
- phpdbg_watchpoint_segfault_handler
- phpdbg_watchpoints_clean
- phpdbg_watch_dtor
- phpdbg_watch_mem_dtor
- phpdbg_watch_free_ptr_dtor
- phpdbg_setup_watchpoints
- phpdbg_print_changed_zval
- phpdbg_print_changed_zvals
- phpdbg_list_watchpoints
- phpdbg_watch_efree
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 #include "zend.h"
73 #include "phpdbg.h"
74 #include "phpdbg_btree.h"
75 #include "phpdbg_watch.h"
76 #include "phpdbg_utils.h"
77 #include "phpdbg_prompt.h"
78 #ifndef _WIN32
79 # include <unistd.h>
80 # include <sys/mman.h>
81 #endif
82
83 ZEND_EXTERN_MODULE_GLOBALS(phpdbg)
84
85 const phpdbg_command_t phpdbg_watch_commands[] = {
86 PHPDBG_COMMAND_D_EX(array, "create watchpoint on an array", 'a', watch_array, &phpdbg_prompt_commands[24], "s", 0),
87 PHPDBG_COMMAND_D_EX(delete, "delete watchpoint", 'd', watch_delete, &phpdbg_prompt_commands[24], "s", 0),
88 PHPDBG_COMMAND_D_EX(recursive, "create recursive watchpoints", 'r', watch_recursive, &phpdbg_prompt_commands[24], "s", 0),
89 PHPDBG_END_COMMAND
90 };
91
92
93 #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)
94
95 #define HT_WATCH_OFFSET (sizeof(zend_refcounted *) + sizeof(uint32_t))
96 #define HT_PTR_HT(ptr) ((HashTable *) (((char *) (ptr)) - HT_WATCH_OFFSET))
97 #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr)
98
99 typedef struct {
100 void *page;
101 size_t size;
102 char reenable_writing;
103
104 void *data;
105 } phpdbg_watch_memdump;
106
107 #define MEMDUMP_SIZE(size) (sizeof(phpdbg_watch_memdump) - sizeof(void *) + (size))
108
109
110 static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
111 phpdbg_watchpoint_t *watch;
112 phpdbg_btree_result *result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong)phpdbg_get_page_boundary(addr) + phpdbg_pagesize - 1);
113
114 if (result == NULL) {
115 return NULL;
116 }
117
118 watch = result->ptr;
119
120
121 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) {
122
123 return NULL;
124 }
125
126 return watch;
127 }
128
129 static void phpdbg_change_watchpoint_access(phpdbg_watchpoint_t *watch, int access) {
130
131 mprotect(phpdbg_get_page_boundary(watch->addr.ptr), phpdbg_get_total_page_size(watch->addr.ptr, watch->size), access);
132 }
133
134 static inline void phpdbg_activate_watchpoint(phpdbg_watchpoint_t *watch) {
135 phpdbg_change_watchpoint_access(watch, PROT_READ);
136 }
137
138 static inline void phpdbg_deactivate_watchpoint(phpdbg_watchpoint_t *watch) {
139 phpdbg_change_watchpoint_access(watch, PROT_READ | PROT_WRITE);
140 }
141
142 static inline void phpdbg_store_watchpoint(phpdbg_watchpoint_t *watch) {
143 phpdbg_btree_insert(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr, watch);
144 }
145
146 static inline void phpdbg_remove_watchpoint(phpdbg_watchpoint_t *watch) {
147 phpdbg_btree_delete(&PHPDBG_G(watchpoint_tree), (zend_ulong) watch->addr.ptr);
148 }
149
150 void phpdbg_create_addr_watchpoint(void *addr, size_t size, phpdbg_watchpoint_t *watch) {
151 watch->addr.ptr = addr;
152 watch->size = size;
153 }
154
155 void phpdbg_create_zval_watchpoint(zval *zv, phpdbg_watchpoint_t *watch) {
156 phpdbg_create_addr_watchpoint(zv, sizeof(zval), watch);
157 watch->type = WATCH_ON_ZVAL;
158 }
159
160 void phpdbg_create_ht_watchpoint(HashTable *ht, phpdbg_watchpoint_t *watch) {
161 phpdbg_create_addr_watchpoint(((char *) ht) + HT_WATCH_OFFSET, sizeof(HashTable) - HT_WATCH_OFFSET, watch);
162 watch->type = WATCH_ON_HASHTABLE;
163 watch->implicit_ht_count = 0;
164 }
165
166 static int phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t *watch);
167 static int phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t *watch);
168
169 void phpdbg_watch_HashTable_dtor(zval *ptr);
170
171 static void phpdbg_free_watch(phpdbg_watchpoint_t *watch) {
172 zend_string_release(watch->str);
173 zend_string_release(watch->name_in_parent);
174 }
175
176 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch);
177 static void phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t *watch);
178 static void phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t *watch);
179 static void phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t *watch);
180
181
182 static phpdbg_watchpoint_t *phpdbg_create_refcounted_watchpoint(phpdbg_watchpoint_t *parent, zend_refcounted *ref) {
183 phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
184 watch->flags = parent->flags;
185 watch->parent = parent;
186 watch->str = parent->str;
187 ++GC_REFCOUNT(parent->str);
188 phpdbg_create_addr_watchpoint(&GC_REFCOUNT(ref), sizeof(uint32_t), watch);
189 watch->type = WATCH_ON_REFCOUNTED;
190
191 return watch;
192 }
193
194
195 static void phpdbg_add_watch_collision(phpdbg_watchpoint_t *watch) {
196 phpdbg_watch_collision *cur;
197
198
199 ZEND_ASSERT(((watch->flags & PHPDBG_WATCH_RECURSIVE) == 0) ^ ((watch->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_SIMPLE)) == 0));
200
201 if ((cur = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) watch->addr.ref))) {
202 phpdbg_watchpoint_t *old;
203 int flags = 0;
204 if ((old = zend_hash_find_ptr(&cur->watches, watch->str)) || (old = zend_hash_find_ptr(&cur->implicit_watches, watch->str))) {
205 if (((old->flags ^ watch->flags) & (PHPDBG_WATCH_NORMAL|PHPDBG_WATCH_IMPLICIT)) == 0) {
206 return;
207 }
208
209 flags = old->flags;
210
211 if (flags & PHPDBG_WATCH_RECURSIVE) {
212 if (!(watch->flags & PHPDBG_WATCH_RECURSIVE) && !--cur->refs) {
213 phpdbg_delete_watchpoints_recursive(watch);
214 }
215 }
216 if (flags & PHPDBG_WATCH_NORMAL) {
217 zend_hash_del(&cur->watches, watch->str);
218 if (zend_hash_num_elements(&cur->watches) > 0) {
219 cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->watches, NULL));
220 } else {
221 cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->implicit_watches, NULL));
222 }
223 }
224 if (flags & PHPDBG_WATCH_IMPLICIT) {
225 zend_hash_del(&cur->implicit_watches, watch->str);
226 }
227
228 old->flags = watch->flags;
229 phpdbg_free_watch(watch);
230 efree(watch);
231 watch = old;
232 }
233 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
234 if (!(flags & PHPDBG_WATCH_RECURSIVE) && !cur->refs++) {
235 phpdbg_create_recursive_zval_watch(watch->parent);
236 }
237 }
238 } else {
239 phpdbg_watch_collision coll;
240 coll.refs = (watch->flags & PHPDBG_WATCH_RECURSIVE) != 0;
241 coll.watch = watch;
242 zend_hash_init(&coll.watches, 8, arghs, NULL, 0);
243 zend_hash_init(&coll.implicit_watches, 8, ..., NULL, 0);
244 cur = zend_hash_index_add_mem(&PHPDBG_G(watch_collisions), (zend_ulong) watch->addr.ref, &coll, sizeof(phpdbg_watch_collision));
245 phpdbg_store_watchpoint(cur->watch);
246 phpdbg_activate_watchpoint(cur->watch);
247 if (coll.refs) {
248 phpdbg_create_recursive_zval_watch(watch->parent);
249 }
250 }
251
252 if (watch->flags & PHPDBG_WATCH_NORMAL) {
253 cur->watch = watch;
254 zend_hash_add_ptr(&cur->watches, watch->str, watch->parent);
255 }
256 if (watch->flags & PHPDBG_WATCH_IMPLICIT) {
257 zend_hash_add_ptr(&cur->implicit_watches, watch->str, watch->parent);
258 }
259 }
260
261 static void phpdbg_remove_watch_collision(phpdbg_watchpoint_t *watch) {
262 phpdbg_watch_collision *cur;
263 if ((cur = zend_hash_index_find_ptr(&PHPDBG_G(watch_collisions), (zend_ulong) Z_COUNTED_P(watch->addr.zv)))) {
264 if (cur->refs && !--cur->refs) {
265 phpdbg_delete_watchpoints_recursive(watch);
266 }
267
268 zend_hash_del(&cur->watches, watch->str);
269 zend_hash_del(&cur->implicit_watches, watch->str);
270
271 if (zend_hash_num_elements(&cur->watches) > 0) {
272 cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->watches, NULL));
273 } else if (zend_hash_num_elements(&cur->implicit_watches) > 0) {
274 cur->watch = Z_PTR_P(zend_hash_get_current_data_ex(&cur->implicit_watches, NULL));
275 } else {
276 phpdbg_deactivate_watchpoint(cur->watch);
277 phpdbg_remove_watchpoint(cur->watch);
278
279 zend_hash_index_del(&PHPDBG_G(watch_collisions), (zend_ulong) Z_COUNTED_P(watch->addr.zv));
280 }
281 }
282 }
283
284 static phpdbg_watchpoint_t *phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch);
285
286 static phpdbg_watchpoint_t *phpdbg_create_reference_watch(phpdbg_watchpoint_t *watch) {
287 phpdbg_watchpoint_t *ref = emalloc(sizeof(phpdbg_watchpoint_t));
288 watch->reference = ref;
289 ref->flags = watch->flags;
290 ref->str = watch->str;
291 ++GC_REFCOUNT(ref->str);
292 ref->parent = watch;
293 ref->parent_container = NULL;
294 phpdbg_create_zval_watchpoint(Z_REFVAL_P(watch->addr.zv), ref);
295
296 phpdbg_create_watchpoint(ref);
297
298 return ref;
299 }
300
301 static phpdbg_watchpoint_t *phpdbg_get_refcount_watch(phpdbg_watchpoint_t *parent) {
302 zend_refcounted *ref;
303 phpdbg_btree_result *res;
304
305 if (parent->type == WATCH_ON_HASHTABLE) {
306 parent = parent->parent;
307 if (!parent) {
308 return NULL;
309 }
310 }
311
312 ZEND_ASSERT(parent->type == WATCH_ON_ZVAL);
313 ref = Z_COUNTED_P(parent->addr.zv);
314
315 res = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ref);
316 if (res) {
317 return res->ptr;
318 }
319 return NULL;
320 }
321
322 static phpdbg_watchpoint_t *phpdbg_create_watchpoint(phpdbg_watchpoint_t *watch) {
323 phpdbg_watchpoint_t *ret = watch;
324
325 if (watch->type == WATCH_ON_ZVAL) {
326 switch (Z_TYPE_P(watch->addr.zv)) {
327 case IS_NULL:
328 case IS_UNDEF:
329 case IS_TRUE:
330 case IS_FALSE:
331 memset(watch->addr.zv, 0, sizeof(zend_value));
332 }
333 }
334
335
336 if (!watch->parent || watch->parent->type != WATCH_ON_ZVAL || watch->type == WATCH_ON_HASHTABLE) {
337 phpdbg_watchpoint_t *old_watch = zend_hash_find_ptr(&PHPDBG_G(watchpoints), watch->str);
338
339 if (old_watch) {
340 #define return_and_free_watch(x) { \
341 phpdbg_watchpoint_t *ref = phpdbg_get_refcount_watch(old_watch); \
342 if (ref) { \
343 phpdbg_add_watch_collision(ref); \
344 } \
345 if (watch != old_watch) { \
346 phpdbg_free_watch(watch); \
347 efree(watch); \
348 } \
349 return (x); \
350 }
351 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
352 if (old_watch->flags & PHPDBG_WATCH_RECURSIVE) {
353 return_and_free_watch(NULL);
354 } else {
355 old_watch->flags &= ~(PHPDBG_WATCH_SIMPLE | PHPDBG_WATCH_IMPLICIT);
356 old_watch->flags |= PHPDBG_WATCH_RECURSIVE;
357 return_and_free_watch(old_watch);
358 }
359 } else {
360 if (!(old_watch->flags & PHPDBG_WATCH_RECURSIVE)) {
361 old_watch->flags |= watch->flags & (PHPDBG_WATCH_IMPLICIT | PHPDBG_WATCH_SIMPLE);
362 }
363 return_and_free_watch(NULL);
364 }
365 } else {
366 if (watch->parent && watch->parent->type == WATCH_ON_HASHTABLE) {
367 watch->parent->implicit_ht_count++;
368 }
369 zend_hash_add_ptr(&PHPDBG_G(watchpoints), watch->str, watch);
370 }
371 }
372
373 phpdbg_store_watchpoint(watch);
374
375 if (watch->parent && watch->parent->type == WATCH_ON_ZVAL && Z_REFCOUNTED_P(watch->parent->addr.zv)) {
376 phpdbg_add_watch_collision(phpdbg_create_refcounted_watchpoint(watch, Z_COUNTED_P(watch->parent->addr.zv)));
377 }
378
379 if (watch->type == WATCH_ON_ZVAL) {
380 if (watch->parent_container) {
381 HashTable *ht_watches;
382 phpdbg_btree_result *find;
383 if (!(find = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container))) {
384 phpdbg_watch_ht_info *hti = emalloc(sizeof(*hti));
385 hti->dtor = watch->parent_container->pDestructor;
386 ht_watches = &hti->watches;
387 zend_hash_init(ht_watches, 0, grrrrr, ZVAL_PTR_DTOR, 0);
388 phpdbg_btree_insert(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container, hti);
389 watch->parent_container->pDestructor = (dtor_func_t) phpdbg_watch_HashTable_dtor;
390 } else {
391 ht_watches = &((phpdbg_watch_ht_info *) find->ptr)->watches;
392 }
393 zend_hash_add_ptr(ht_watches, watch->name_in_parent, watch);
394 }
395
396 if (Z_ISREF_P(watch->addr.zv)) {
397 ret = phpdbg_create_reference_watch(watch);
398 }
399 }
400
401 phpdbg_activate_watchpoint(watch);
402
403 return ret;
404 }
405
406 static int phpdbg_create_simple_watchpoint(phpdbg_watchpoint_t *watch) {
407 watch->flags |= PHPDBG_WATCH_SIMPLE;
408
409 phpdbg_create_watchpoint(watch);
410
411 return SUCCESS;
412 }
413
414 static int phpdbg_create_array_watchpoint(phpdbg_watchpoint_t *zv_watch) {
415 zval *zv = zv_watch->addr.zv;
416 phpdbg_watchpoint_t *watch = emalloc(sizeof(phpdbg_watchpoint_t));
417 HashTable *ht = HT_FROM_ZVP(zv);
418
419 watch->parent = zv_watch;
420
421 if (!ht) {
422 return FAILURE;
423 }
424
425 phpdbg_create_ht_watchpoint(ht, watch);
426
427 if (phpdbg_create_watchpoint(watch) == NULL) {
428 return SUCCESS;
429 }
430
431 if (Z_TYPE_P(zv) == IS_ARRAY) {
432 watch->flags |= PHPDBG_WATCH_ARRAY;
433 } else {
434 watch->flags |= PHPDBG_WATCH_OBJECT;
435 }
436
437 phpdbg_add_watch_collision(phpdbg_create_refcounted_watchpoint(watch, Z_COUNTED_P(zv)));
438
439 return SUCCESS;
440 }
441
442 static int phpdbg_create_recursive_watchpoint(phpdbg_watchpoint_t *watch) {
443 if (watch->type != WATCH_ON_ZVAL) {
444 return FAILURE;
445 }
446
447 watch->flags |= PHPDBG_WATCH_RECURSIVE;
448 watch = phpdbg_create_watchpoint(watch);
449
450 return SUCCESS;
451 }
452
453 static int phpdbg_create_recursive_ht_watch(phpdbg_watchpoint_t *watch) {
454 zval *zv;
455 zend_string *key;
456 zend_long h;
457
458 ZEND_ASSERT(watch->type == WATCH_ON_HASHTABLE);
459
460 ZEND_HASH_FOREACH_KEY_VAL(HT_WATCH_HT(watch), h, key, zv) {
461 phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
462
463 new_watch->flags = PHPDBG_WATCH_RECURSIVE;
464 new_watch->parent = watch;
465 new_watch->parent_container = HT_WATCH_HT(watch);
466
467 if (key) {
468 new_watch->name_in_parent = key;
469 ++GC_REFCOUNT(key);
470 } else {
471 new_watch->name_in_parent = strpprintf(0, ZEND_LONG_FMT, h);
472 }
473
474 new_watch->str = strpprintf(0, "%.*s%s%s%s", (int) ZSTR_LEN(watch->str) - 2, ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", phpdbg_get_property_key(ZSTR_VAL(new_watch->name_in_parent)), (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
475
476 while (Z_TYPE_P(zv) == IS_INDIRECT) {
477 zv = Z_INDIRECT_P(zv);
478 }
479
480 phpdbg_create_zval_watchpoint(zv, new_watch);
481 new_watch = phpdbg_create_watchpoint(new_watch);
482 phpdbg_create_recursive_zval_watch(new_watch);
483 } ZEND_HASH_FOREACH_END();
484
485 return SUCCESS;
486 }
487
488 static int phpdbg_create_recursive_zval_watch(phpdbg_watchpoint_t *watch) {
489 HashTable *ht;
490 zval *zvp;
491
492 ZEND_ASSERT(watch->type == WATCH_ON_ZVAL);
493
494 zvp = watch->addr.zv;
495 ZVAL_DEREF(zvp);
496
497 if ((ht = HT_FROM_ZVP(zvp))) {
498 phpdbg_watchpoint_t *new_watch = emalloc(sizeof(phpdbg_watchpoint_t));
499
500 new_watch->flags = PHPDBG_WATCH_RECURSIVE;
501 new_watch->parent = watch;
502 new_watch->parent_container = watch->parent_container;
503 new_watch->name_in_parent = watch->name_in_parent;
504 ++GC_REFCOUNT(new_watch->name_in_parent);
505 new_watch->str = strpprintf(0, "%.*s[]", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
506
507 if (Z_TYPE_P(zvp) == IS_ARRAY) {
508 new_watch->flags |= PHPDBG_WATCH_ARRAY;
509 } else {
510 new_watch->flags |= PHPDBG_WATCH_OBJECT;
511 }
512
513 phpdbg_create_ht_watchpoint(ht, new_watch);
514
515 phpdbg_create_recursive_ht_watch(new_watch);
516
517 phpdbg_create_watchpoint(new_watch);
518 }
519
520 return SUCCESS;
521 }
522
523 static void phpdbg_delete_implicit_parents(phpdbg_watchpoint_t *watch) {
524 phpdbg_watchpoint_t *parent = watch->parent;
525 if (!parent) {
526 return;
527 }
528
529 ZEND_ASSERT(!(parent->flags & PHPDBG_WATCH_RECURSIVE));
530 ZEND_ASSERT(parent->flags & PHPDBG_WATCH_IMPLICIT);
531
532 if (parent->type == WATCH_ON_HASHTABLE && --parent->implicit_ht_count) {
533 return;
534 }
535
536 parent->flags &= ~PHPDBG_WATCH_IMPLICIT;
537 if (!(parent->flags & PHPDBG_WATCH_SIMPLE)) {
538 if (parent->type == WATCH_ON_ZVAL && Z_REFCOUNTED_P(watch->addr.zv)) {
539 phpdbg_remove_watch_collision(parent);
540 }
541 zend_hash_del(&PHPDBG_G(watchpoints), parent->str);
542 }
543 }
544
545
546 static int phpdbg_delete_watchpoint_recursive(phpdbg_watchpoint_t *watch, zend_bool user_request) {
547 if (watch->type == WATCH_ON_HASHTABLE) {
548 if (user_request) {
549 phpdbg_delete_ht_watchpoints_recursive(watch);
550 } else {
551 HashTable *ht;
552 phpdbg_btree_result *result;
553
554 ht = HT_FROM_ZVP(watch->addr.zv);
555
556 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) ht))) {
557 phpdbg_delete_watchpoint_recursive((phpdbg_watchpoint_t *) result->ptr, user_request);
558 }
559 }
560 } else if (watch->type == WATCH_ON_ZVAL) {
561 phpdbg_delete_zval_watchpoints_recursive(watch);
562 }
563
564 return zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
565 }
566
567 static void phpdbg_delete_ht_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
568 zend_string *str, *strkey;
569 zend_long numkey;
570 phpdbg_watchpoint_t *watchpoint;
571
572 ZEND_HASH_FOREACH_KEY(HT_WATCH_HT(watch), numkey, strkey) {
573 if (strkey) {
574 str = strpprintf(0, "%.*s%s%s%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", phpdbg_get_property_key(ZSTR_VAL(strkey)), (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
575 } else {
576 str = strpprintf(0, "%.*s%s" ZEND_LONG_FMT "%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_ARRAY) ? "[" : "->", numkey, (watch->flags & PHPDBG_WATCH_ARRAY) ? "]" : "");
577 }
578
579 if ((watchpoint = zend_hash_find_ptr(&PHPDBG_G(watchpoints), str))) {
580 phpdbg_delete_watchpoint_recursive(watchpoint, 1);
581 }
582
583 zend_string_release(str);
584 } ZEND_HASH_FOREACH_END();
585 }
586
587 static void phpdbg_delete_zval_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
588 if (Z_REFCOUNTED_P(watch->addr.zv)) {
589 phpdbg_remove_watch_collision(watch);
590 }
591 }
592
593
594 static void phpdbg_delete_watchpoints_recursive(phpdbg_watchpoint_t *watch) {
595 if (watch->type == WATCH_ON_ZVAL) {
596 phpdbg_delete_zval_watchpoints_recursive(watch);
597 } else if (watch->type == WATCH_ON_HASHTABLE) {
598 phpdbg_delete_ht_watchpoints_recursive(watch);
599 }
600 }
601
602 static int phpdbg_delete_watchpoint(phpdbg_watchpoint_t *tmp_watch) {
603 int ret;
604 phpdbg_watchpoint_t *watch;
605 phpdbg_btree_result *result;
606
607 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) tmp_watch->addr.ptr)) == NULL) {
608 return FAILURE;
609 }
610
611 watch = result->ptr;
612
613 if (!(watch->flags & PHPDBG_WATCH_NORMAL) || (watch->parent && (watch->parent->flags & PHPDBG_WATCH_RECURSIVE))) {
614 return FAILURE;
615 }
616
617 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
618 ret = phpdbg_delete_watchpoint_recursive(watch, 1);
619 } else {
620 ret = zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
621 }
622
623 phpdbg_free_watch(tmp_watch);
624 efree(tmp_watch);
625
626 return ret;
627 }
628
629 static int phpdbg_watchpoint_parse_wrapper(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, int (*callback)(phpdbg_watchpoint_t *)) {
630 int ret;
631 phpdbg_watchpoint_t *watch = ecalloc(1, sizeof(phpdbg_watchpoint_t));
632 watch->str = zend_string_init(name, namelen, 0);
633 watch->name_in_parent = zend_string_init(key, keylen, 0);
634 watch->parent_container = parent;
635 phpdbg_create_zval_watchpoint(zv, watch);
636
637 ret = callback(watch);
638
639 efree(name);
640 efree(key);
641
642 if (ret != SUCCESS) {
643 phpdbg_free_watch(watch);
644 efree(watch);
645 }
646
647 PHPDBG_G(watch_tmp) = NULL;
648
649 return ret;
650 }
651
652 PHPDBG_API int phpdbg_watchpoint_parse_input(char *input, size_t len, HashTable *parent, size_t i, int (*callback)(phpdbg_watchpoint_t *), zend_bool silent) {
653 return phpdbg_parse_variable_with_arg(input, len, parent, i, (phpdbg_parse_var_with_arg_func) phpdbg_watchpoint_parse_wrapper, NULL, 0, callback);
654 }
655
656 static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, size_t keylen, HashTable *parent, zval *zv, int (*callback)(phpdbg_watchpoint_t *)) {
657 phpdbg_watchpoint_t *watch;
658
659 if ((watch = zend_hash_str_find_ptr(&PHPDBG_G(watchpoints), name, namelen))) {
660 watch->flags |= PHPDBG_WATCH_IMPLICIT;
661 PHPDBG_G(watch_tmp) = watch;
662 return SUCCESS;
663 }
664
665 watch = ecalloc(1, sizeof(phpdbg_watchpoint_t));
666 watch->flags = PHPDBG_WATCH_IMPLICIT;
667 watch->str = zend_string_init(name, namelen, 0);
668 watch->name_in_parent = zend_string_init(key, keylen, 0);
669 watch->parent_container = parent;
670 watch->parent = PHPDBG_G(watch_tmp);
671 phpdbg_create_zval_watchpoint(zv, watch);
672
673 phpdbg_create_watchpoint(watch);
674
675 efree(name);
676 efree(key);
677
678 PHPDBG_G(watch_tmp) = watch;
679 return SUCCESS;
680 }
681
682 static int phpdbg_watchpoint_parse_symtables(char *input, size_t len, int (*callback)(phpdbg_watchpoint_t *)) {
683 if (EG(scope) && len >= 5 && !memcmp("$this", input, 5)) {
684 zend_hash_str_add(EG(current_execute_data)->symbol_table, ZEND_STRL("this"), &EG(current_execute_data)->This);
685 }
686
687 if (phpdbg_is_auto_global(input, len) && phpdbg_watchpoint_parse_input(input, len, &EG(symbol_table), 0, callback, 1) != FAILURE) {
688 return SUCCESS;
689 }
690
691 return 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, callback);
692 }
693
694 PHPDBG_WATCH(delete)
695 {
696 switch (param->type) {
697 case STR_PARAM:
698 if (phpdbg_delete_var_watchpoint(param->str, param->len) == FAILURE) {
699 phpdbg_error("watchdelete", "type=\"nowatch\"", "Nothing was deleted, no corresponding watchpoint found");
700 } else {
701 phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Removed watchpoint %.*s", (int) param->len, param->str);
702 }
703 break;
704
705 phpdbg_default_switch_case();
706 }
707
708 return SUCCESS;
709 }
710
711 PHPDBG_WATCH(recursive)
712 {
713 if (phpdbg_rebuild_symtable() == FAILURE) {
714 return SUCCESS;
715 }
716
717 switch (param->type) {
718 case STR_PARAM:
719 if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_recursive_watchpoint) != FAILURE) {
720 phpdbg_notice("watchrecursive", "variable=\"%.*s\"", "Set recursive watchpoint on %.*s", (int)param->len, param->str);
721 }
722 break;
723
724 phpdbg_default_switch_case();
725 }
726
727 return SUCCESS;
728 }
729
730 PHPDBG_WATCH(array)
731 {
732 if (phpdbg_rebuild_symtable() == FAILURE) {
733 return SUCCESS;
734 }
735
736 switch (param->type) {
737 case STR_PARAM:
738 if (phpdbg_watchpoint_parse_symtables(param->str, param->len, phpdbg_create_array_watchpoint) != FAILURE) {
739 phpdbg_notice("watcharray", "variable=\"%.*s\"", "Set array watchpoint on %.*s", (int)param->len, param->str);
740 }
741 break;
742
743 phpdbg_default_switch_case();
744 }
745
746 return SUCCESS;
747 }
748
749 void phpdbg_watch_HashTable_dtor(zval *zv) {
750 phpdbg_btree_result *result;
751 zval *orig_zv = zv;
752
753 while (Z_TYPE_P(zv) == IS_INDIRECT) {
754 zv = Z_INDIRECT_P(zv);
755 }
756
757 if ((result = phpdbg_btree_find(&PHPDBG_G(watchpoint_tree), (zend_ulong) zv))) {
758 phpdbg_watchpoint_t *watch = result->ptr;
759
760 if (watch->flags & PHPDBG_WATCH_NORMAL) {
761 PHPDBG_G(watchpoint_hit) = 1;
762
763 phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "%.*s was removed, removing watchpoint%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_RECURSIVE) ? " recursively" : "");
764 }
765
766 if ((result = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container))) {
767 phpdbg_watch_ht_info *hti = result->ptr;
768 hti->dtor(orig_zv);
769 zend_hash_del(&hti->watches, watch->name_in_parent);
770 if (zend_hash_num_elements(&hti->watches) == 0) {
771 watch->parent_container->pDestructor = hti->dtor;
772 zend_hash_destroy(&hti->watches);
773 phpdbg_btree_delete(&PHPDBG_G(watch_HashTables), (zend_ulong) watch->parent_container);
774 efree(hti);
775 }
776 } else {
777 zval_ptr_dtor_wrapper(orig_zv);
778 }
779
780 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
781 phpdbg_delete_watchpoint_recursive(watch, 0);
782 } else {
783 zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
784 }
785 }
786 }
787
788 int phpdbg_create_var_watchpoint(char *input, size_t len) {
789 if (phpdbg_rebuild_symtable() == FAILURE) {
790 return FAILURE;
791 }
792
793 return phpdbg_watchpoint_parse_symtables(input, len, phpdbg_create_simple_watchpoint);
794 }
795
796 int phpdbg_delete_var_watchpoint(char *input, size_t len) {
797 if (phpdbg_rebuild_symtable() == FAILURE) {
798 return FAILURE;
799 }
800
801 return phpdbg_watchpoint_parse_input(input, len, EG(current_execute_data)->symbol_table, 0, phpdbg_delete_watchpoint, 0);
802 }
803
804 #ifdef _WIN32
805 int phpdbg_watchpoint_segfault_handler(void *addr) {
806 #else
807 int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
808 #endif
809 void *page;
810 phpdbg_watch_memdump *dump;
811 phpdbg_watchpoint_t *watch;
812 size_t size;
813
814 watch = phpdbg_check_for_watchpoint(
815 #ifdef _WIN32
816 addr
817 #else
818 info->si_addr
819 #endif
820 );
821
822 if (watch == NULL) {
823 return FAILURE;
824 }
825
826 page = phpdbg_get_page_boundary(watch->addr.ptr);
827 size = phpdbg_get_total_page_size(watch->addr.ptr, watch->size);
828
829
830 mprotect(page, size, PROT_READ | PROT_WRITE);
831
832 dump = malloc(MEMDUMP_SIZE(size));
833 dump->page = page;
834 dump->size = size;
835 dump->reenable_writing = 0;
836
837 memcpy(&dump->data, page, size);
838
839 zend_llist_add_element(&PHPDBG_G(watchlist_mem), &dump);
840
841 return SUCCESS;
842 }
843
844 void phpdbg_watchpoints_clean(void) {
845 zend_hash_clean(&PHPDBG_G(watchpoints));
846 }
847
848
849 static void phpdbg_watch_dtor(zval *pDest) {
850 phpdbg_watchpoint_t *watch = (phpdbg_watchpoint_t *) Z_PTR_P(pDest);
851
852 if (watch->flags & PHPDBG_WATCH_IMPLICIT) {
853 watch->flags = PHPDBG_WATCH_SIMPLE;
854
855 if (watch->type == WATCH_ON_ZVAL) {
856 phpdbg_delete_zval_watchpoints_recursive(watch);
857 } else if (watch->type == WATCH_ON_HASHTABLE) {
858 phpdbg_watchpoint_t *watchpoint;
859
860 watch->implicit_ht_count++;
861
862 ZEND_HASH_FOREACH_PTR(&((phpdbg_watch_ht_info *) phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) HT_WATCH_HT(watch))->ptr)->watches, watchpoint) {
863 phpdbg_delete_watchpoint_recursive(watchpoint, 1);
864 } ZEND_HASH_FOREACH_END();
865 }
866 }
867
868 phpdbg_delete_implicit_parents(watch);
869
870 phpdbg_deactivate_watchpoint(watch);
871 phpdbg_remove_watchpoint(watch);
872
873 phpdbg_free_watch(watch);
874 efree(watch);
875 }
876
877 static void phpdbg_watch_mem_dtor(void *llist_data) {
878 phpdbg_watch_memdump *dump = *(phpdbg_watch_memdump **) llist_data;
879
880
881 if (dump->reenable_writing) {
882 mprotect(dump->page, dump->size, PROT_READ);
883 }
884
885 free(dump);
886 }
887
888 static void phpdbg_watch_free_ptr_dtor(zval *ptr) {
889 efree(Z_PTR_P(ptr));
890 }
891
892 void phpdbg_setup_watchpoints(void) {
893 #if _SC_PAGE_SIZE
894 phpdbg_pagesize = sysconf(_SC_PAGE_SIZE);
895 #elif _SC_PAGESIZE
896 phpdbg_pagesize = sysconf(_SC_PAGESIZE);
897 #elif _SC_NUTC_OS_PAGESIZE
898 phpdbg_pagesize = sysconf(_SC_NUTC_OS_PAGESIZE);
899 #else
900 phpdbg_pagesize = 4096;
901 #endif
902
903 zend_llist_init(&PHPDBG_G(watchlist_mem), sizeof(void *), phpdbg_watch_mem_dtor, 1);
904 phpdbg_btree_init(&PHPDBG_G(watchpoint_tree), sizeof(void *) * 8);
905 phpdbg_btree_init(&PHPDBG_G(watch_HashTables), sizeof(void *) * 8);
906 zend_hash_init(&PHPDBG_G(watchpoints), 8, NULL, phpdbg_watch_dtor, 0);
907 zend_hash_init(&PHPDBG_G(watch_collisions), 8, NULL, phpdbg_watch_free_ptr_dtor, 0);
908 }
909
910 static void phpdbg_print_changed_zval(phpdbg_watch_memdump *dump) {
911
912 phpdbg_btree_position pos = phpdbg_btree_find_between(&PHPDBG_G(watchpoint_tree), (zend_ulong) dump->page, (zend_ulong) dump->page + dump->size);
913 phpdbg_btree_result *result;
914 int elementDiff;
915 void *curTest;
916
917 dump->reenable_writing = 0;
918
919 while ((result = phpdbg_btree_next(&pos))) {
920 phpdbg_watchpoint_t *watch = result->ptr;
921 void *oldPtr = (char *) &dump->data + ((size_t) watch->addr.ptr - (size_t) dump->page);
922 char reenable = 1;
923 int removed = 0;
924
925 if ((size_t) watch->addr.ptr < (size_t) dump->page || (size_t) watch->addr.ptr + watch->size > (size_t) dump->page + dump->size) {
926 continue;
927 }
928
929
930 if ((watch->type == WATCH_ON_HASHTABLE || watch->type == WATCH_ON_ZVAL) && watch->parent_container) {
931 if ((curTest = zend_symtable_find(watch->parent_container, watch->name_in_parent))) {
932 while (Z_TYPE_P((zval *) curTest) == IS_INDIRECT) {
933 curTest = Z_INDIRECT_P((zval *) curTest);
934 }
935
936 if (watch->type == WATCH_ON_HASHTABLE) {
937 switch (Z_TYPE_P((zval *) curTest)) {
938 case IS_ARRAY:
939 curTest = (void *) Z_ARRVAL_P((zval *) curTest);
940 break;
941 case IS_OBJECT:
942 curTest = (void *) Z_OBJPROP_P((zval *) curTest);
943 break;
944 }
945 }
946
947 if (curTest != watch->addr.ptr) {
948 phpdbg_deactivate_watchpoint(watch);
949 phpdbg_remove_watchpoint(watch);
950 watch->addr.ptr = curTest;
951 phpdbg_store_watchpoint(watch);
952 phpdbg_activate_watchpoint(watch);
953
954 reenable = 0;
955 }
956 } else {
957 removed = 1;
958 }
959 }
960
961
962 {
963 zend_bool do_break = 0;
964
965 switch (watch->type) {
966 case WATCH_ON_ZVAL:
967 do_break = memcmp(oldPtr, watch->addr.zv, sizeof(zend_value) + sizeof(uint32_t) ) != 0;
968 if (!do_break) {
969 goto end;
970 }
971 break;
972 case WATCH_ON_HASHTABLE:
973 do_break = zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_WATCH_HT(watch));
974 if (!do_break) {
975 goto end;
976 }
977 break;
978 case WATCH_ON_REFCOUNTED:
979 do_break = memcmp(oldPtr, watch->addr.ref, sizeof(uint32_t) ) != 0;
980 if (!do_break) {
981 goto end;
982 }
983 if (!(PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
984 do_break = 0;
985 }
986 break;
987 }
988
989 if (!(watch->flags & PHPDBG_WATCH_NORMAL)) {
990 do_break = 0;
991 }
992
993 if (do_break) {
994 PHPDBG_G(watchpoint_hit) = 1;
995
996 phpdbg_notice("watchhit", "variable=\"%s\"", "Breaking on watchpoint %.*s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
997 phpdbg_xml("<watchdata %r>");
998 }
999
1000 switch (watch->type) {
1001 case WATCH_ON_ZVAL: {
1002 zend_bool show_value = memcmp(oldPtr, watch->addr.zv, sizeof(zend_value) + sizeof(uint32_t) ) != 0;
1003
1004 if ((watch->flags & PHPDBG_WATCH_NORMAL) && (removed || show_value)) {
1005
1006 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1007 phpdbg_writeln("watchvalue", "type=\"old\" inaccessible=\"inaccessible\"", "Old value inaccessible or destroyed");
1008 } else {
1009 phpdbg_out("Old value: ");
1010 phpdbg_xml("<watchvalue %r type=\"old\">");
1011 zend_print_flat_zval_r((zval *) oldPtr);
1012 phpdbg_xml("</watchvalue>");
1013 phpdbg_out("\n");
1014 }
1015 }
1016
1017
1018 if (removed) {
1019 if (watch->flags & PHPDBG_WATCH_NORMAL) {
1020 phpdbg_notice("watchdelete", "variable=\"%.*s\"", "Watchpoint %.*s was unset, removing watchpoint", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str));
1021 }
1022 zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
1023
1024 reenable = 0;
1025
1026 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1027 phpdbg_remove_watch_collision(watch);
1028 }
1029 break;
1030 }
1031
1032 if ((watch->flags & PHPDBG_WATCH_NORMAL) && show_value) {
1033 phpdbg_out("New value%s: ", Z_ISREF_P(watch->addr.zv) ? " (reference)" : "");
1034 phpdbg_xml("<watchvalue %r%s type=\"new\">", Z_ISREF_P(watch->addr.zv) ? " reference=\"reference\"" : "");
1035 zend_print_flat_zval_r(watch->addr.zv);
1036 phpdbg_xml("</watchvalue>");
1037 phpdbg_out("\n");
1038 }
1039
1040
1041 if (Z_PTR_P(watch->addr.zv) != Z_PTR_P((zval *) oldPtr) || Z_TYPE_P(watch->addr.zv) != Z_TYPE_P((zval *) oldPtr)) {
1042 if (Z_REFCOUNTED_P((zval *) oldPtr)) {
1043 zval *new_zv = watch->addr.zv;
1044 watch->addr.ptr = oldPtr;
1045 phpdbg_remove_watch_collision(watch);
1046 watch->addr.zv = new_zv;
1047 }
1048 if (Z_REFCOUNTED_P(watch->addr.zv)) {
1049 if ((watch->flags & PHPDBG_WATCH_NORMAL) && (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1050 phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", Z_REFCOUNT_P(watch->addr.zv));
1051 }
1052 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
1053 phpdbg_create_recursive_watchpoint(watch);
1054 } else if (Z_ISREF_P(watch->addr.zv)) {
1055 phpdbg_create_reference_watch(watch);
1056 }
1057 }
1058 }
1059
1060 break;
1061 }
1062 case WATCH_ON_HASHTABLE:
1063
1064 elementDiff = zend_hash_num_elements(HT_PTR_HT(oldPtr)) - zend_hash_num_elements(HT_WATCH_HT(watch));
1065 if ((watch->flags & PHPDBG_WATCH_NORMAL) && elementDiff) {
1066 if (elementDiff > 0) {
1067 phpdbg_writeln("watchsize", "removed=\"%d\"", "%d elements were removed from the array", elementDiff);
1068 } else {
1069 phpdbg_writeln("watchsize", "added=\"%d\"", "%d elements were added to the array", -elementDiff);
1070 }
1071 }
1072
1073 if (watch->flags & PHPDBG_WATCH_RECURSIVE) {
1074 phpdbg_create_recursive_ht_watch(watch);
1075 }
1076 if ((watch->flags & PHPDBG_WATCH_NORMAL) && HT_WATCH_HT(watch)->nInternalPointer != HT_PTR_HT(oldPtr)->nInternalPointer) {
1077 phpdbg_writeln("watcharrayptr", "", "Internal pointer of array was changed");
1078 }
1079 break;
1080 case WATCH_ON_REFCOUNTED: {
1081 if ((watch->flags & PHPDBG_WATCH_NORMAL) && (PHPDBG_G(flags) & PHPDBG_SHOW_REFCOUNTS)) {
1082 phpdbg_writeln("watchrefcount", "type=\"old\" refcount=\"%d\"", "Old refcount: %d", GC_REFCOUNT((zend_refcounted *) oldPtr));
1083 phpdbg_writeln("watchrefcount", "type=\"new\" refcount=\"%d\"", "New refcount: %d", GC_REFCOUNT(watch->addr.ref));
1084 }
1085 break;
1086 }
1087 }
1088
1089 if (do_break) {
1090 phpdbg_xml("</watchdata>");
1091 }
1092 } end:
1093
1094 dump->reenable_writing = dump->reenable_writing | reenable;
1095 }
1096 }
1097
1098 int phpdbg_print_changed_zvals(void) {
1099 zend_llist_position pos;
1100 phpdbg_watch_memdump **dump;
1101 int ret;
1102
1103 if (zend_llist_count(&PHPDBG_G(watchlist_mem)) == 0) {
1104 return FAILURE;
1105 }
1106
1107 dump = (phpdbg_watch_memdump **) zend_llist_get_last_ex(&PHPDBG_G(watchlist_mem), &pos);
1108
1109 do {
1110 phpdbg_print_changed_zval(*dump);
1111 } while ((dump = (phpdbg_watch_memdump **) zend_llist_get_prev_ex(&PHPDBG_G(watchlist_mem), &pos)));
1112
1113 zend_llist_clean(&PHPDBG_G(watchlist_mem));
1114
1115 ret = PHPDBG_G(watchpoint_hit) ? SUCCESS : FAILURE;
1116 PHPDBG_G(watchpoint_hit) = 0;
1117
1118 return ret;
1119 }
1120
1121 void phpdbg_list_watchpoints(void) {
1122 phpdbg_watchpoint_t *watch;
1123
1124 phpdbg_xml("<watchlist %r>");
1125
1126 ZEND_HASH_FOREACH_PTR(&PHPDBG_G(watchpoints), watch) {
1127 if (watch->flags & PHPDBG_WATCH_NORMAL) {
1128 phpdbg_writeln("watchvariable", "variable=\"%.*s\" on=\"%s\" type=\"%s\"", "%.*s (%s, %s)", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), watch->type == WATCH_ON_HASHTABLE ? "array" : watch->type == WATCH_ON_REFCOUNTED ? "refcount" : "variable", watch->flags == PHPDBG_WATCH_RECURSIVE ? "recursive" : "simple");
1129 }
1130 } ZEND_HASH_FOREACH_END();
1131
1132 phpdbg_xml("</watchlist>");
1133 }
1134
1135 void phpdbg_watch_efree(void *ptr) {
1136 phpdbg_btree_result *result;
1137
1138 result = phpdbg_btree_find_closest(&PHPDBG_G(watchpoint_tree), (zend_ulong) ptr);
1139
1140 if (result) {
1141 phpdbg_watchpoint_t *watch = result->ptr;
1142
1143 if ((size_t) watch->addr.ptr + watch->size > (size_t) ptr) {
1144 if (watch->type == WATCH_ON_REFCOUNTED) {
1145
1146 phpdbg_deactivate_watchpoint(watch);
1147 phpdbg_remove_watchpoint(watch);
1148 } else {
1149 if (watch->type == WATCH_ON_ZVAL) {
1150 phpdbg_remove_watch_collision(watch);
1151 }
1152 if (watch->type == WATCH_ON_HASHTABLE && (watch->flags & PHPDBG_WATCH_SIMPLE)) {
1153
1154 phpdbg_notice("watchdelete", "variable=\"%.*s\" recursive=\"%s\"", "Array %.*s was removed, removing watchpoint%s", (int) ZSTR_LEN(watch->str), ZSTR_VAL(watch->str), (watch->flags & PHPDBG_WATCH_RECURSIVE) ? " recursively" : "");
1155 }
1156 if (watch->type == WATCH_ON_HASHTABLE || watch->parent == NULL || watch->parent->type != WATCH_ON_ZVAL) {
1157 zend_hash_del(&PHPDBG_G(watchpoints), watch->str);
1158 }
1159 }
1160 }
1161 }
1162
1163 if (PHPDBG_G(original_free_function)) {
1164 PHPDBG_G(original_free_function)(ptr);
1165 }
1166 }