macOS has no counterpart to userfaultfd write-protect or
MEM_WRITE_WATCH, so GuestWriteWatch had no backend there and the driver
re-read every guest buffer on each use.
The macOS backend write-protects watched pages. The first write to a
page faults, the SIGBUS/SIGSEGV handler records the page and restores
its protection, and Collect reports the recorded pages and protects
them again. A page is protected before its record is cleared, under the
lock the handler takes, so a write that lands in between is recorded.
The lock hands out turns in order: an unfair one starved writers under
Rosetta.
The protection is real, which adds three entry points:
- GuestWriteWatchBeginHostWrite/EndHostWrite lift it around a system
call that writes guest memory, which would otherwise fail with
EFAULT. GuestArena::HostWrite calls them, as it calls the Windows
equivalent.
- GuestWriteWatchProtectionChanged tells the watch the protection given
to watched pages by mprotect or a new mapping.
- GuestWriteWatchProtection reports that protection, since a watched
page is read-only between a collection and the next write.
Register keeps the recorded protection of a page it already watches:
read from the kernel it would be the watch's own read-only protection,
and the page would never be watched again.
The tests build without the prx libraries and run natively and through
Rosetta.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>