111
111
# include <sys/mman.h>
112
112
#endif
113
113
114
+ #ifdef HAVE_USERFAULTFD_WRITEFAULT
115
+ # include <pthread.h>
116
+ # include <linux/userfaultfd.h>
117
+ # include <sys/ioctl.h>
118
+ # include <sys/syscall.h>
119
+ #endif
120
+
114
121
ZEND_EXTERN_MODULE_GLOBALS (phpdbg )
115
122
116
123
const phpdbg_command_t phpdbg_watch_commands [] = {
@@ -208,9 +215,9 @@ void phpdbg_print_watch_diff(phpdbg_watchtype type, zend_string *name, void *old
208
215
}
209
216
210
217
/* ### LOW LEVEL WATCHPOINT HANDLING ### */
211
- static phpdbg_watchpoint_t * phpdbg_check_for_watchpoint (void * addr ) {
218
+ static phpdbg_watchpoint_t * phpdbg_check_for_watchpoint (phpdbg_btree * tree , void * addr ) {
212
219
phpdbg_watchpoint_t * watch ;
213
- phpdbg_btree_result * result = phpdbg_btree_find_closest (& PHPDBG_G ( watchpoint_tree ) , (zend_ulong ) phpdbg_get_page_boundary (addr ) + phpdbg_pagesize - 1 );
220
+ phpdbg_btree_result * result = phpdbg_btree_find_closest (tree , (zend_ulong ) phpdbg_get_page_boundary (addr ) + phpdbg_pagesize - 1 );
214
221
215
222
if (result == NULL ) {
216
223
return NULL ;
@@ -228,8 +235,38 @@ static phpdbg_watchpoint_t *phpdbg_check_for_watchpoint(void *addr) {
228
235
}
229
236
230
237
static void phpdbg_change_watchpoint_access (phpdbg_watchpoint_t * watch , int access ) {
238
+ void * page_addr = phpdbg_get_page_boundary (watch -> addr .ptr );
239
+ size_t size = phpdbg_get_total_page_size (watch -> addr .ptr , watch -> size );
240
+ #ifdef HAVE_USERFAULTFD_WRITEFAULT
241
+ if (PHPDBG_G (watch_userfaultfd )) {
242
+ struct uffdio_range range = {
243
+ .start = (__u64 ) page_addr ,
244
+ .len = size
245
+ };
246
+ if (access == PROT_READ ) {
247
+ struct uffdio_register reg = {
248
+ .mode = UFFDIO_REGISTER_MODE_WP ,
249
+ .range = range
250
+ };
251
+ struct uffdio_writeprotect protect = {
252
+ .mode = UFFDIO_WRITEPROTECT_MODE_WP ,
253
+ .range = range
254
+ };
255
+ ioctl (PHPDBG_G (watch_userfaultfd ), UFFDIO_REGISTER , & reg );
256
+ ioctl (PHPDBG_G (watch_userfaultfd ), UFFDIO_WRITEPROTECT , & protect );
257
+ } else {
258
+ struct uffdio_register reg = {
259
+ .mode = UFFDIO_REGISTER_MODE_WP ,
260
+ .range = range
261
+ };
262
+ ioctl (PHPDBG_G (watch_userfaultfd ), UFFDIO_UNREGISTER , & reg );
263
+ }
264
+ } else
265
+ #endif
231
266
/* pagesize is assumed to be in the range of 2^x */
232
- mprotect (phpdbg_get_page_boundary (watch -> addr .ptr ), phpdbg_get_total_page_size (watch -> addr .ptr , watch -> size ), access );
267
+ {
268
+ mprotect (page_addr , size , access );
269
+ }
233
270
}
234
271
235
272
static inline void phpdbg_activate_watchpoint (phpdbg_watchpoint_t * watch ) {
@@ -256,7 +293,7 @@ int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
256
293
);
257
294
258
295
/* perhaps unnecessary, but check to be sure to not conflict with other segfault handlers */
259
- if (phpdbg_check_for_watchpoint (page ) == NULL ) {
296
+ if (phpdbg_check_for_watchpoint (& PHPDBG_G ( watchpoint_tree ), page ) == NULL ) {
260
297
return FAILURE ;
261
298
}
262
299
@@ -268,6 +305,29 @@ int phpdbg_watchpoint_segfault_handler(siginfo_t *info, void *context) {
268
305
return SUCCESS ;
269
306
}
270
307
308
+ #ifdef HAVE_USERFAULTFD_WRITEFAULT
309
+ void * phpdbg_watchpoint_userfaultfd_thread (void * phpdbg_globals ) {
310
+ pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS , NULL );
311
+ zend_phpdbg_globals * globals = (zend_phpdbg_globals * ) phpdbg_globals ;
312
+
313
+ struct uffd_msg fault_msg = {0 };
314
+ while (read (globals -> watch_userfaultfd , & fault_msg , sizeof (fault_msg )) == sizeof (fault_msg )) {
315
+ void * page = phpdbg_get_page_boundary ((char * ) fault_msg .arg .pagefault .address );
316
+ zend_hash_index_add_empty_element (globals -> watchlist_mem , (zend_ulong ) page );
317
+ struct uffdio_writeprotect unprotect = {
318
+ .mode = 0 ,
319
+ .range = {
320
+ .start = (__u64 ) page ,
321
+ .len = phpdbg_pagesize
322
+ }
323
+ };
324
+ ioctl (globals -> watch_userfaultfd , UFFDIO_WRITEPROTECT , & unprotect );
325
+ }
326
+
327
+ return NULL ;
328
+ }
329
+ #endif
330
+
271
331
/* ### REGISTER WATCHPOINT ### To be used only by watch element and collision managers ### */
272
332
static inline void phpdbg_store_watchpoint_btree (phpdbg_watchpoint_t * watch ) {
273
333
phpdbg_btree_result * res ;
@@ -331,14 +391,14 @@ void phpdbg_delete_watch_collision(phpdbg_watchpoint_t *watch) {
331
391
if ((coll = zend_hash_index_find_ptr (& PHPDBG_G (watch_collisions ), (zend_ulong ) watch -> ref ))) {
332
392
zend_hash_index_del (& coll -> parents , (zend_ulong ) watch );
333
393
if (zend_hash_num_elements (& coll -> parents ) == 0 ) {
334
- phpdbg_deactivate_watchpoint (& coll -> ref );
335
394
phpdbg_remove_watchpoint_btree (& coll -> ref );
395
+ phpdbg_deactivate_watchpoint (& coll -> ref );
336
396
337
397
if (coll -> ref .type == WATCH_ON_ZVAL ) {
338
398
phpdbg_delete_watch_collision (& coll -> ref );
339
399
} else if (coll -> reference .addr .ptr ) {
340
- phpdbg_deactivate_watchpoint (& coll -> reference );
341
400
phpdbg_remove_watchpoint_btree (& coll -> reference );
401
+ phpdbg_deactivate_watchpoint (& coll -> reference );
342
402
phpdbg_delete_watch_collision (& coll -> reference );
343
403
if (coll -> reference .type == WATCH_ON_STR ) {
344
404
zend_string_release (coll -> reference .backup .str );
@@ -614,8 +674,8 @@ void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) {
614
674
if (zend_hash_num_elements (& hti -> watches ) == 1 ) {
615
675
zend_hash_destroy (& hti -> watches );
616
676
phpdbg_btree_delete (& PHPDBG_G (watch_HashTables ), (zend_ulong ) hti -> ht );
617
- phpdbg_deactivate_watchpoint (& hti -> hash_watch );
618
677
phpdbg_remove_watchpoint_btree (& hti -> hash_watch );
678
+ phpdbg_deactivate_watchpoint (& hti -> hash_watch );
619
679
efree (hti );
620
680
} else {
621
681
zend_hash_del (& hti -> watches , element -> name_in_parent );
@@ -887,8 +947,8 @@ void phpdbg_update_watch_collision_elements(phpdbg_watchpoint_t *watch) {
887
947
void phpdbg_remove_watchpoint (phpdbg_watchpoint_t * watch ) {
888
948
phpdbg_watch_element * element ;
889
949
890
- phpdbg_deactivate_watchpoint (watch );
891
950
phpdbg_remove_watchpoint_btree (watch );
951
+ phpdbg_deactivate_watchpoint (watch );
892
952
phpdbg_delete_watch_collision (watch );
893
953
894
954
if (watch -> coll ) {
@@ -1020,8 +1080,8 @@ void phpdbg_check_watchpoint(phpdbg_watchpoint_t *watch) {
1020
1080
return ;
1021
1081
}
1022
1082
1023
- phpdbg_deactivate_watchpoint (watch );
1024
1083
phpdbg_remove_watchpoint_btree (watch );
1084
+ phpdbg_deactivate_watchpoint (watch );
1025
1085
watch -> addr .zv = new ;
1026
1086
phpdbg_store_watchpoint_btree (watch );
1027
1087
phpdbg_activate_watchpoint (watch );
@@ -1068,7 +1128,21 @@ void phpdbg_reenable_memory_watches(void) {
1068
1128
if (res ) {
1069
1129
watch = res -> ptr ;
1070
1130
if ((char * ) page < (char * ) watch -> addr .ptr + watch -> size ) {
1071
- mprotect ((void * ) page , phpdbg_pagesize , PROT_READ );
1131
+ #ifdef HAVE_USERFAULTFD_WRITEFAULT
1132
+ if (PHPDBG_G (watch_userfaultfd )) {
1133
+ struct uffdio_writeprotect protect = {
1134
+ .mode = UFFDIO_WRITEPROTECT_MODE_WP ,
1135
+ .range = {
1136
+ .start = (__u64 ) page ,
1137
+ .len = phpdbg_pagesize
1138
+ }
1139
+ };
1140
+ ioctl (PHPDBG_G (watch_userfaultfd ), UFFDIO_WRITEPROTECT , & protect );
1141
+ } else
1142
+ #endif
1143
+ {
1144
+ mprotect ((void * ) page , phpdbg_pagesize , PROT_READ );
1145
+ }
1072
1146
}
1073
1147
}
1074
1148
} ZEND_HASH_FOREACH_END ();
@@ -1396,23 +1470,42 @@ void phpdbg_setup_watchpoints(void) {
1396
1470
zend_hash_init (PHPDBG_G (watchlist_mem_backup ), phpdbg_pagesize / (sizeof (Bucket ) + sizeof (uint32_t )), NULL , NULL , 1 );
1397
1471
1398
1472
PHPDBG_G (watch_tmp ) = NULL ;
1473
+
1474
+ #ifdef HAVE_USERFAULTFD_WRITEFAULT
1475
+ PHPDBG_G (watch_userfaultfd ) = syscall (SYS_userfaultfd , O_CLOEXEC );
1476
+ if (PHPDBG_G (watch_userfaultfd ) < 0 ) {
1477
+ PHPDBG_G (watch_userfaultfd ) = 0 ;
1478
+ } else {
1479
+ struct uffdio_api userfaultfd_features = {0 };
1480
+ userfaultfd_features .api = UFFD_API ;
1481
+ userfaultfd_features .features = UFFD_FEATURE_PAGEFAULT_FLAG_WP ;
1482
+ ioctl (PHPDBG_G (watch_userfaultfd ), UFFDIO_API , & userfaultfd_features );
1483
+ if (userfaultfd_features .features & UFFD_FEATURE_PAGEFAULT_FLAG_WP ) {
1484
+ pthread_create (& PHPDBG_G (watch_userfault_thread ), NULL , phpdbg_watchpoint_userfaultfd_thread , ZEND_MODULE_GLOBALS_BULK (phpdbg ));
1485
+ } else {
1486
+ PHPDBG_G (watch_userfaultfd ) = 0 ;
1487
+ }
1488
+ }
1489
+ #endif
1399
1490
}
1400
1491
1401
1492
void phpdbg_destroy_watchpoints (void ) {
1402
1493
phpdbg_watch_element * element ;
1403
- phpdbg_btree_position pos ;
1404
- phpdbg_btree_result * res ;
1405
1494
1406
1495
/* unconditionally free all remaining elements to avoid memory leaks */
1407
1496
ZEND_HASH_FOREACH_PTR (& PHPDBG_G (watch_recreation ), element ) {
1408
1497
phpdbg_automatic_dequeue_free (element );
1409
1498
} ZEND_HASH_FOREACH_END ();
1410
1499
1411
1500
/* 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. */
1412
- pos = phpdbg_btree_find_between (& PHPDBG_G (watchpoint_tree ), 0 , -1 );
1413
- while ((res = phpdbg_btree_next (& pos ))) {
1414
- phpdbg_deactivate_watchpoint (res -> ptr );
1501
+ phpdbg_purge_watchpoint_tree ();
1502
+
1503
+ #ifdef HAVE_USERFAULTFD_WRITEFAULT
1504
+ if (PHPDBG_G (watch_userfaultfd )) {
1505
+ pthread_cancel (PHPDBG_G (watch_userfault_thread ));
1506
+ close (PHPDBG_G (watch_userfaultfd ));
1415
1507
}
1508
+ #endif
1416
1509
1417
1510
zend_hash_destroy (& PHPDBG_G (watch_elements )); PHPDBG_G (watch_elements ).nNumOfElements = 0 ; /* phpdbg_watch_efree() is checking against this arrays size */
1418
1511
zend_hash_destroy (& PHPDBG_G (watch_recreation ));
0 commit comments