mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 00:22:57 -05:00
Register copied the protection it had recorded for pages it already watched over the protection the kernel had just reported. A guest that reserves address space, which registers it as PROT_NONE, then maps direct memory over it read-write and registers it again kept the stale PROT_NONE. Collect only write-protects pages with PROT_WRITE, so writes to that memory were reported once and never again. With PPSA12544 on the macOS port, the title menu text blinked and glyphs went missing. Register now keeps a recorded protection only where its own write protection explains what the kernel reports, and takes the kernel's protection otherwise. A protected page that was mapped again also loses its protected mark, so erase no longer restores the old protection over the new mapping. The kernel is read under the lock, so a concurrent Collect cannot protect a page between the read and the merge. Test write_watch_mapped: a reservation, a mapping over it and a second registration keep reporting writes, and a protected page reserved again stays PROT_NONE. AI-assisted: yes
740 lines
29 KiB
C++
740 lines
29 KiB
C++
#include "prx/libc/include/GuestWriteWatch.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <mutex>
|
|
#include <shared_mutex>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#if defined(__linux__)
|
|
#include <cerrno>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/userfaultfd.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/syscall.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if defined(__APPLE__)
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <csignal>
|
|
#include <cstring>
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_vm.h>
|
|
#include <sched.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#if (defined(__linux__) && defined(PAGEMAP_SCAN) && defined(UFFD_FEATURE_WP_ASYNC)) || defined(__APPLE__)
|
|
#define APS5_GUEST_WRITE_WATCH 1
|
|
#endif
|
|
|
|
namespace GuestWriteWatch {
|
|
namespace {
|
|
|
|
#if defined(__linux__) && defined(PAGEMAP_SCAN) && defined(UFFD_FEATURE_WP_ASYNC)
|
|
constexpr std::uintptr_t PageBytes = 4096;
|
|
|
|
class Watch {
|
|
public:
|
|
static Watch& Get() {
|
|
static Watch watch;
|
|
return watch;
|
|
}
|
|
|
|
bool Available() const {
|
|
return _pagemap >= 0;
|
|
}
|
|
|
|
void Register(std::uintptr_t begin, std::uintptr_t end) {
|
|
if (!Available() || end <= begin) return;
|
|
std::unique_lock lock(_lock);
|
|
remove(_ranges, begin, end);
|
|
remove(_fresh, begin, end);
|
|
uffdio_register registration{};
|
|
registration.range.start = begin;
|
|
registration.range.len = end - begin;
|
|
registration.mode = UFFDIO_REGISTER_MODE_WP;
|
|
if (ioctl(_uffd, UFFDIO_REGISTER, ®istration) != 0) {
|
|
static bool reported = false;
|
|
if (!reported) {
|
|
reported = true;
|
|
std::fprintf(stderr, "[memory] write watch: cannot register 0x%llx+0x%llx (%s); the range stays unwatched\n", static_cast<unsigned long long>(begin), static_cast<unsigned long long>(end - begin), std::strerror(errno));
|
|
}
|
|
return;
|
|
}
|
|
insert(_ranges, begin, end);
|
|
insert(_fresh, begin, end);
|
|
}
|
|
|
|
bool Unregister(std::uintptr_t begin, std::uintptr_t end) {
|
|
if (!Available() || end <= begin) return false;
|
|
std::unique_lock lock(_lock);
|
|
const bool watched = remove(_ranges, begin, end);
|
|
remove(_fresh, begin, end);
|
|
return watched;
|
|
}
|
|
|
|
bool Covers(std::uintptr_t begin, std::uintptr_t end) {
|
|
if (!Available()) return false;
|
|
std::shared_lock lock(_lock);
|
|
return covers(begin, end);
|
|
}
|
|
|
|
bool Collect(std::uintptr_t begin, std::uintptr_t end, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context) {
|
|
if (!Available()) return false;
|
|
begin &= ~(PageBytes - 1);
|
|
end = (end + PageBytes - 1) & ~(PageBytes - 1);
|
|
if (end <= begin) return true;
|
|
if (!takeFresh(begin, end, true, written, context)) return false;
|
|
return scanWritten(begin, end, written, context);
|
|
}
|
|
|
|
bool CollectFresh(std::uintptr_t begin, std::uintptr_t end, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context) {
|
|
if (!Available()) return false;
|
|
begin &= ~(PageBytes - 1);
|
|
end = (end + PageBytes - 1) & ~(PageBytes - 1);
|
|
if (end <= begin) return true;
|
|
{
|
|
std::shared_lock lock(_lock);
|
|
auto it = _fresh.upper_bound(begin);
|
|
if (it != _fresh.begin()) --it;
|
|
while (it != _fresh.end() && it->first < end && it->second <= begin) ++it;
|
|
if (it == _fresh.end() || it->first >= end) return true;
|
|
}
|
|
return takeFresh(begin, end, false, written, context);
|
|
}
|
|
|
|
bool CollectArmed(std::uintptr_t begin, std::uintptr_t end, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context) {
|
|
if (!Available()) return false;
|
|
begin &= ~(PageBytes - 1);
|
|
end = (end + PageBytes - 1) & ~(PageBytes - 1);
|
|
if (end <= begin) return true;
|
|
std::vector<std::pair<std::uintptr_t, std::uintptr_t>> armed;
|
|
{
|
|
std::shared_lock lock(_lock);
|
|
if (!covers(begin, end)) return false;
|
|
auto cursor = begin;
|
|
auto it = _fresh.upper_bound(begin);
|
|
if (it != _fresh.begin()) --it;
|
|
for (; it != _fresh.end() && it->first < end; ++it) {
|
|
if (it->second <= cursor) continue;
|
|
if (it->first > cursor) armed.emplace_back(cursor, it->first);
|
|
cursor = std::max(cursor, it->second);
|
|
}
|
|
if (cursor < end) armed.emplace_back(cursor, end);
|
|
}
|
|
for (const auto& [from, to] : armed) {
|
|
if (!scanWritten(from, to, written, context)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
bool takeFresh(std::uintptr_t begin, std::uintptr_t end, bool whole, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context) {
|
|
std::vector<std::pair<std::uintptr_t, std::uintptr_t>> fresh;
|
|
{
|
|
std::unique_lock lock(_lock);
|
|
if (whole && !covers(begin, end)) return false;
|
|
auto it = _fresh.upper_bound(begin);
|
|
if (it != _fresh.begin()) --it;
|
|
for (; it != _fresh.end() && it->first < end; ++it) {
|
|
const auto from = std::max(it->first, begin);
|
|
const auto to = std::min(it->second, end);
|
|
if (from < to) fresh.emplace_back(from, to);
|
|
}
|
|
for (const auto& [from, to] : fresh) remove(_fresh, from, to);
|
|
}
|
|
for (const auto& [from, to] : fresh) {
|
|
written(context, from, to);
|
|
if (protect(from, to)) continue;
|
|
std::unique_lock lock(_lock);
|
|
for (const auto& [left, right] : fresh) {
|
|
if (!covers(left, right)) continue;
|
|
remove(_fresh, left, right);
|
|
insert(_fresh, left, right);
|
|
}
|
|
if (whole) written(context, begin, end);
|
|
else for (const auto& [left, right] : fresh) written(context, left, right);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool scanWritten(std::uintptr_t begin, std::uintptr_t end, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context) const {
|
|
std::array<page_region, 256> regions;
|
|
auto cursor = begin;
|
|
while (cursor < end) {
|
|
pm_scan_arg scan{};
|
|
scan.size = sizeof(scan);
|
|
scan.flags = PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC;
|
|
scan.start = cursor;
|
|
scan.end = end;
|
|
scan.vec = reinterpret_cast<std::uintptr_t>(regions.data());
|
|
scan.vec_len = regions.size();
|
|
scan.category_mask = PAGE_IS_WRITTEN;
|
|
scan.return_mask = PAGE_IS_WRITTEN;
|
|
const auto count = ioctl(_pagemap, PAGEMAP_SCAN, &scan);
|
|
if (count < 0) {
|
|
written(context, cursor, end);
|
|
return false;
|
|
}
|
|
for (long i = 0; i < count; ++i) written(context, regions[i].start, regions[i].end);
|
|
if (scan.walk_end <= cursor || scan.walk_end >= end) break;
|
|
cursor = scan.walk_end;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
Watch() {
|
|
if (std::getenv("APS5_NO_WRITE_WATCH") == nullptr) open();
|
|
}
|
|
|
|
void open() {
|
|
_uffd = static_cast<int>(syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK));
|
|
if (_uffd < 0 && errno == EPERM) _uffd = static_cast<int>(syscall(SYS_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY));
|
|
if (_uffd < 0) return unavailable("userfaultfd");
|
|
uffdio_api api{};
|
|
api.api = UFFD_API;
|
|
api.features = UFFD_FEATURE_WP_ASYNC | UFFD_FEATURE_WP_UNPOPULATED;
|
|
if (ioctl(_uffd, UFFDIO_API, &api) != 0 || (api.features & UFFD_FEATURE_WP_ASYNC) == 0 || (api.features & UFFD_FEATURE_WP_UNPOPULATED) == 0) return unavailable("asynchronous userfaultfd write protection");
|
|
const int pagemap = ::open("/proc/self/pagemap", O_RDONLY | O_CLOEXEC);
|
|
if (pagemap < 0) return unavailable("/proc/self/pagemap");
|
|
if (!probe(pagemap)) {
|
|
close(pagemap);
|
|
return unavailable("PAGEMAP_SCAN");
|
|
}
|
|
_pagemap = pagemap;
|
|
}
|
|
|
|
void unavailable(const char* what) {
|
|
const int error = errno;
|
|
if (_uffd >= 0) close(_uffd);
|
|
_uffd = -1;
|
|
std::fprintf(stderr, "[memory] write watch unavailable: %s failed (%s); guest memory is compared instead\n", what, std::strerror(error));
|
|
}
|
|
|
|
bool protect(std::uintptr_t begin, std::uintptr_t end) const {
|
|
uffdio_writeprotect protection{};
|
|
protection.range.start = begin;
|
|
protection.range.len = end - begin;
|
|
protection.mode = UFFDIO_WRITEPROTECT_MODE_WP;
|
|
return ioctl(_uffd, UFFDIO_WRITEPROTECT, &protection) == 0;
|
|
}
|
|
|
|
bool probe(int pagemap) {
|
|
constexpr std::uintptr_t probeBytes = 4 * PageBytes;
|
|
void* pages = mmap(nullptr, probeBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (pages == MAP_FAILED) return false;
|
|
const auto address = reinterpret_cast<std::uintptr_t>(pages);
|
|
auto* bytes = static_cast<volatile char*>(pages);
|
|
bytes[0] = 1;
|
|
uffdio_register registration{};
|
|
registration.range.start = address;
|
|
registration.range.len = probeBytes;
|
|
registration.mode = UFFDIO_REGISTER_MODE_WP;
|
|
bool working = ioctl(_uffd, UFFDIO_REGISTER, ®istration) == 0 && protect(address, address + probeBytes);
|
|
std::array<page_region, 4> regions{};
|
|
const auto scan = [&]() -> long {
|
|
pm_scan_arg arguments{};
|
|
arguments.size = sizeof(arguments);
|
|
arguments.flags = PM_SCAN_WP_MATCHING | PM_SCAN_CHECK_WPASYNC;
|
|
arguments.start = address;
|
|
arguments.end = address + probeBytes;
|
|
arguments.vec = reinterpret_cast<std::uintptr_t>(regions.data());
|
|
arguments.vec_len = regions.size();
|
|
arguments.category_mask = PAGE_IS_WRITTEN;
|
|
arguments.return_mask = PAGE_IS_WRITTEN;
|
|
const auto count = ioctl(pagemap, PAGEMAP_SCAN, &arguments);
|
|
if (count < 0) return -1;
|
|
long pagesWritten = 0;
|
|
for (long i = 0; i < count; ++i) pagesWritten += static_cast<long>((regions[i].end - regions[i].start) / PageBytes);
|
|
return pagesWritten;
|
|
};
|
|
working = working && scan() == 0;
|
|
if (working) {
|
|
bytes[0] = 2;
|
|
bytes[2 * PageBytes] = 1;
|
|
working = scan() == 2 && scan() == 0;
|
|
}
|
|
munmap(pages, probeBytes);
|
|
return working;
|
|
}
|
|
|
|
bool covers(std::uintptr_t begin, std::uintptr_t end) const {
|
|
auto next = _ranges.upper_bound(begin);
|
|
if (next == _ranges.begin()) return false;
|
|
return std::prev(next)->second >= end;
|
|
}
|
|
|
|
static void insert(std::map<std::uintptr_t, std::uintptr_t>& ranges, std::uintptr_t begin, std::uintptr_t end) {
|
|
auto next = ranges.upper_bound(begin);
|
|
if (next != ranges.begin() && std::prev(next)->second == begin) {
|
|
begin = std::prev(next)->first;
|
|
ranges.erase(std::prev(next));
|
|
}
|
|
if (next != ranges.end() && next->first == end) {
|
|
end = next->second;
|
|
ranges.erase(next);
|
|
}
|
|
ranges.emplace(begin, end);
|
|
}
|
|
|
|
static bool remove(std::map<std::uintptr_t, std::uintptr_t>& ranges, std::uintptr_t begin, std::uintptr_t end) {
|
|
bool removed = false;
|
|
auto it = ranges.upper_bound(begin);
|
|
if (it != ranges.begin()) --it;
|
|
while (it != ranges.end() && it->first < end) {
|
|
const auto rangeBegin = it->first;
|
|
const auto rangeEnd = it->second;
|
|
if (rangeEnd <= begin) {
|
|
++it;
|
|
continue;
|
|
}
|
|
removed = true;
|
|
it = ranges.erase(it);
|
|
if (rangeBegin < begin) ranges.emplace(rangeBegin, begin);
|
|
if (rangeEnd > end) ranges.emplace(end, rangeEnd);
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
int _uffd = -1;
|
|
int _pagemap = -1;
|
|
std::shared_mutex _lock;
|
|
std::map<std::uintptr_t, std::uintptr_t> _ranges;
|
|
std::map<std::uintptr_t, std::uintptr_t> _fresh;
|
|
};
|
|
|
|
const bool g_opened = (Watch::Get(), true);
|
|
#elif defined(__APPLE__)
|
|
class Watch {
|
|
public:
|
|
static Watch& Get() {
|
|
static Watch watch;
|
|
return watch;
|
|
}
|
|
|
|
bool Available() const {
|
|
return _available;
|
|
}
|
|
|
|
void Register(std::uintptr_t begin, std::uintptr_t end) {
|
|
begin = pageStart(begin);
|
|
end = pageEnd(end);
|
|
if (!_available || end <= begin) return;
|
|
std::vector<std::uint8_t> pages((end - begin) / _page, Written);
|
|
std::vector<bool> queried(pages.size(), false);
|
|
const Lock lock(_lock);
|
|
for (auto cursor = begin; cursor < end;) {
|
|
mach_vm_address_t address = cursor;
|
|
mach_vm_size_t size = 0;
|
|
vm_region_basic_info_data_64_t info{};
|
|
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
|
|
mach_port_t object = MACH_PORT_NULL;
|
|
if (mach_vm_region(mach_task_self(), &address, &size, VM_REGION_BASIC_INFO_64, reinterpret_cast<vm_region_info_t>(&info), &count, &object) != KERN_SUCCESS || address >= end) break;
|
|
const auto first = std::max<std::uintptr_t>(cursor, address);
|
|
const auto last = std::min<std::uintptr_t>(end, address + size);
|
|
const std::uint8_t protection = ((info.protection & VM_PROT_READ) ? PROT_READ : 0) | ((info.protection & VM_PROT_WRITE) ? PROT_WRITE : 0) | ((info.protection & VM_PROT_EXECUTE) ? PROT_EXEC : 0);
|
|
for (auto page = first; page < last; page += _page) {
|
|
pages[(page - begin) / _page] = Written | protection;
|
|
queried[(page - begin) / _page] = true;
|
|
}
|
|
cursor = last;
|
|
}
|
|
forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) {
|
|
auto& entry = pages[(page - begin) / _page];
|
|
const std::uint8_t recorded = state & ProtectionMask;
|
|
const bool armed = (state & Protected) != 0 && (entry & ProtectionMask) == (recorded & ~PROT_WRITE);
|
|
if (armed || !queried[(page - begin) / _page]) entry = static_cast<std::uint8_t>(Written | recorded);
|
|
else state = static_cast<std::uint8_t>(state & ~Protected);
|
|
});
|
|
erase(begin, end);
|
|
_ranges.emplace(begin, Range{end, std::move(pages)});
|
|
}
|
|
|
|
bool Unregister(std::uintptr_t begin, std::uintptr_t end) {
|
|
begin = pageStart(begin);
|
|
end = pageEnd(end);
|
|
if (!_available || end <= begin) return false;
|
|
const Lock lock(_lock);
|
|
return erase(begin, end);
|
|
}
|
|
|
|
bool Covers(std::uintptr_t begin, std::uintptr_t end) {
|
|
if (!_available) return false;
|
|
const Lock lock(_lock);
|
|
return covers(begin, end);
|
|
}
|
|
|
|
bool Collect(std::uintptr_t begin, std::uintptr_t end, void (*written)(void*, std::uintptr_t, std::uintptr_t), void* context) {
|
|
if (!_available) return false;
|
|
begin = pageStart(begin);
|
|
end = pageEnd(end);
|
|
if (end <= begin) return true;
|
|
std::vector<std::pair<std::uintptr_t, std::uintptr_t>> runs;
|
|
{
|
|
const Lock lock(_lock);
|
|
if (!covers(begin, end)) return false;
|
|
forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) {
|
|
if ((state & Written) == 0) return;
|
|
if (!runs.empty() && runs.back().second == page) runs.back().second = page + _page;
|
|
else runs.emplace_back(page, page + _page);
|
|
for (const auto& [first, last] : _hostWrites)
|
|
if (page >= first && page < last) return;
|
|
state &= static_cast<std::uint8_t>(~Written);
|
|
if ((state & PROT_WRITE) != 0) state |= Protected;
|
|
});
|
|
for (const auto& [first, last] : runs) protect(first, last);
|
|
}
|
|
for (const auto& [first, last] : runs) written(context, first, last);
|
|
return true;
|
|
}
|
|
|
|
void ProtectionChanged(std::uintptr_t begin, std::uintptr_t end, int protection) {
|
|
begin = pageStart(begin);
|
|
end = pageEnd(end);
|
|
if (!_available || end <= begin) return;
|
|
const Lock lock(_lock);
|
|
forEachPage(begin, end, [&](std::uintptr_t, std::uint8_t& state) {
|
|
state = static_cast<std::uint8_t>(Written | (protection & ProtectionMask));
|
|
});
|
|
}
|
|
|
|
void BeginHostWrite(std::uintptr_t begin, std::uintptr_t end) {
|
|
begin = pageStart(begin);
|
|
end = pageEnd(end);
|
|
if (!_available || end <= begin) return;
|
|
const Lock lock(_lock);
|
|
_hostWrites.emplace_back(begin, end);
|
|
forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) {
|
|
if ((state & Protected) != 0) ::mprotect(reinterpret_cast<void*>(page), _page, state & ProtectionMask);
|
|
state = static_cast<std::uint8_t>((state & ProtectionMask) | Written);
|
|
});
|
|
}
|
|
|
|
void EndHostWrite(std::uintptr_t begin, std::uintptr_t end) {
|
|
begin = pageStart(begin);
|
|
end = pageEnd(end);
|
|
if (!_available || end <= begin) return;
|
|
const Lock lock(_lock);
|
|
const auto found = std::find(_hostWrites.begin(), _hostWrites.end(), std::pair{begin, end});
|
|
if (found != _hostWrites.end()) _hostWrites.erase(found);
|
|
}
|
|
|
|
int Protection(std::uintptr_t address, std::uintptr_t limit, std::uintptr_t* runEnd) {
|
|
const auto page = pageStart(address);
|
|
const Lock lock(_lock);
|
|
auto it = _ranges.upper_bound(page);
|
|
if (_available && it != _ranges.begin() && page < std::prev(it)->second.end) {
|
|
--it;
|
|
const auto& pages = it->second.pages;
|
|
const std::uint8_t protection = pages[(page - it->first) / _page] & ProtectionMask;
|
|
auto end = page + _page;
|
|
while (end < it->second.end && end < limit && (pages[(end - it->first) / _page] & ProtectionMask) == protection) end += _page;
|
|
*runEnd = std::min(end, limit);
|
|
return protection;
|
|
}
|
|
*runEnd = it != _ranges.end() ? std::min(it->first, limit) : limit;
|
|
return -1;
|
|
}
|
|
|
|
private:
|
|
static constexpr std::uint8_t ProtectionMask = PROT_READ | PROT_WRITE | PROT_EXEC;
|
|
static constexpr std::uint8_t Written = 0x10;
|
|
static constexpr std::uint8_t Protected = 0x20;
|
|
|
|
struct Range {
|
|
std::uintptr_t end;
|
|
std::vector<std::uint8_t> pages;
|
|
};
|
|
|
|
struct Turns {
|
|
std::atomic<std::uint64_t> next{0};
|
|
std::atomic<std::uint64_t> serving{0};
|
|
};
|
|
|
|
struct Lock {
|
|
explicit Lock(Turns& turns) : _turns(turns), _turn(turns.next.fetch_add(1, std::memory_order_relaxed)) {
|
|
while (_turns.serving.load(std::memory_order_acquire) != _turn) ::sched_yield();
|
|
}
|
|
~Lock() {
|
|
_turns.serving.store(_turn + 1, std::memory_order_release);
|
|
}
|
|
Turns& _turns;
|
|
std::uint64_t _turn;
|
|
};
|
|
|
|
Watch() {
|
|
if (std::getenv("APS5_NO_WRITE_WATCH") != nullptr) return;
|
|
struct sigaction action {};
|
|
action.sa_sigaction = &Watch::fault;
|
|
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
sigemptyset(&action.sa_mask);
|
|
if (sigaction(SIGBUS, &action, &_previousBus) != 0 || sigaction(SIGSEGV, &action, &_previousSegv) != 0) {
|
|
std::fprintf(stderr, "[memory] write watch: cannot install the fault handler (%s); guest buffers are fully re-read every use\n", std::strerror(errno));
|
|
return;
|
|
}
|
|
_available = true;
|
|
}
|
|
|
|
static void fault(int signal, siginfo_t* info, void* context) {
|
|
auto& watch = Get();
|
|
if (watch.record(reinterpret_cast<std::uintptr_t>(info->si_addr))) return;
|
|
const auto& previous = signal == SIGBUS ? watch._previousBus : watch._previousSegv;
|
|
if ((previous.sa_flags & SA_SIGINFO) != 0 && previous.sa_sigaction != nullptr) {
|
|
previous.sa_sigaction(signal, info, context);
|
|
return;
|
|
}
|
|
if (previous.sa_handler != SIG_DFL && previous.sa_handler != SIG_IGN && previous.sa_handler != nullptr) {
|
|
previous.sa_handler(signal);
|
|
return;
|
|
}
|
|
::signal(signal, SIG_DFL);
|
|
}
|
|
|
|
bool record(std::uintptr_t address) {
|
|
const auto page = pageStart(address);
|
|
const Lock lock(_lock);
|
|
auto it = _ranges.upper_bound(page);
|
|
if (it == _ranges.begin()) return false;
|
|
--it;
|
|
if (page >= it->second.end) return false;
|
|
auto& state = it->second.pages[(page - it->first) / _page];
|
|
if ((state & Protected) != 0) {
|
|
state = static_cast<std::uint8_t>((state & ProtectionMask) | Written);
|
|
::mprotect(reinterpret_cast<void*>(page), _page, state & ProtectionMask);
|
|
return true;
|
|
}
|
|
return (state & PROT_WRITE) != 0;
|
|
}
|
|
|
|
std::uintptr_t pageStart(std::uintptr_t address) const {
|
|
return address & ~(_page - 1);
|
|
}
|
|
|
|
std::uintptr_t pageEnd(std::uintptr_t address) const {
|
|
return (address + _page - 1) & ~(_page - 1);
|
|
}
|
|
|
|
template <typename TFunction>
|
|
void forEachPage(std::uintptr_t begin, std::uintptr_t end, TFunction function) {
|
|
auto it = _ranges.upper_bound(begin);
|
|
if (it != _ranges.begin()) --it;
|
|
for (; it != _ranges.end() && it->first < end; ++it) {
|
|
const auto first = std::max(begin, it->first);
|
|
const auto last = std::min(end, it->second.end);
|
|
for (auto page = first; page < last; page += _page) function(page, it->second.pages[(page - it->first) / _page]);
|
|
}
|
|
}
|
|
|
|
void protect(std::uintptr_t begin, std::uintptr_t end) {
|
|
std::uintptr_t runStart = 0;
|
|
std::uintptr_t previous = begin;
|
|
int runProtection = -1;
|
|
const auto flush = [&](std::uintptr_t runEnd) {
|
|
if (runProtection >= 0) ::mprotect(reinterpret_cast<void*>(runStart), runEnd - runStart, runProtection);
|
|
runProtection = -1;
|
|
};
|
|
forEachPage(begin, end, [&](std::uintptr_t page, std::uint8_t& state) {
|
|
const int wanted = (state & Protected) != 0 ? (state & ProtectionMask & ~PROT_WRITE) : -1;
|
|
if (wanted != runProtection || page != previous) {
|
|
flush(previous);
|
|
runStart = page;
|
|
runProtection = wanted;
|
|
}
|
|
previous = page + _page;
|
|
});
|
|
flush(previous);
|
|
}
|
|
|
|
bool covers(std::uintptr_t begin, std::uintptr_t end) const {
|
|
auto it = _ranges.upper_bound(begin);
|
|
if (it == _ranges.begin()) return false;
|
|
--it;
|
|
auto cursor = begin;
|
|
for (; it != _ranges.end() && it->first <= cursor; ++it) {
|
|
if (it->second.end > cursor) cursor = it->second.end;
|
|
if (cursor >= end) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool erase(std::uintptr_t begin, std::uintptr_t end) {
|
|
bool removed = false;
|
|
auto it = _ranges.upper_bound(begin);
|
|
if (it != _ranges.begin()) --it;
|
|
while (it != _ranges.end() && it->first < end) {
|
|
const auto rangeBegin = it->first;
|
|
const auto rangeEnd = it->second.end;
|
|
if (rangeEnd <= begin) {
|
|
++it;
|
|
continue;
|
|
}
|
|
removed = true;
|
|
auto pages = std::move(it->second.pages);
|
|
it = _ranges.erase(it);
|
|
const auto cutBegin = std::max(begin, rangeBegin);
|
|
const auto cutEnd = std::min(end, rangeEnd);
|
|
for (auto page = cutBegin; page < cutEnd; page += _page) {
|
|
const auto state = pages[(page - rangeBegin) / _page];
|
|
if ((state & Protected) != 0) ::mprotect(reinterpret_cast<void*>(page), _page, state & ProtectionMask);
|
|
}
|
|
if (rangeBegin < cutBegin) _ranges.emplace(rangeBegin, Range{cutBegin, std::vector<std::uint8_t>(pages.begin(), pages.begin() + (cutBegin - rangeBegin) / _page)});
|
|
if (cutEnd < rangeEnd) it = _ranges.emplace(cutEnd, Range{rangeEnd, std::vector<std::uint8_t>(pages.begin() + (cutEnd - rangeBegin) / _page, pages.end())}).first;
|
|
}
|
|
return removed;
|
|
}
|
|
|
|
const std::uintptr_t _page = static_cast<std::uintptr_t>(::getpagesize());
|
|
bool _available = false;
|
|
Turns _lock;
|
|
std::map<std::uintptr_t, Range> _ranges;
|
|
std::vector<std::pair<std::uintptr_t, std::uintptr_t>> _hostWrites;
|
|
struct sigaction _previousBus {};
|
|
struct sigaction _previousSegv {};
|
|
};
|
|
|
|
const bool g_opened = (Watch::Get(), true);
|
|
#endif
|
|
|
|
}
|
|
|
|
bool GuestWriteWatchAvailable_nid_postfix() {
|
|
#ifdef APS5_GUEST_WRITE_WATCH
|
|
return Watch::Get().Available();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void GuestWriteWatchRegister_nid_postfix(const void* pointer, std::size_t bytes) {
|
|
#ifdef APS5_GUEST_WRITE_WATCH
|
|
const auto begin = reinterpret_cast<std::uintptr_t>(pointer);
|
|
Watch::Get().Register(begin, begin + bytes);
|
|
#else
|
|
static_cast<void>(pointer);
|
|
static_cast<void>(bytes);
|
|
#endif
|
|
}
|
|
|
|
bool GuestWriteWatchUnregister_nid_postfix(const void* pointer, std::size_t bytes) {
|
|
#ifdef APS5_GUEST_WRITE_WATCH
|
|
const auto begin = reinterpret_cast<std::uintptr_t>(pointer);
|
|
return Watch::Get().Unregister(begin, begin + bytes);
|
|
#else
|
|
static_cast<void>(pointer);
|
|
static_cast<void>(bytes);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool GuestWriteWatchCovers_nid_postfix(std::uintptr_t address, std::size_t bytes) {
|
|
#ifdef APS5_GUEST_WRITE_WATCH
|
|
return bytes != 0 && address + bytes > address && Watch::Get().Covers(address, address + bytes);
|
|
#else
|
|
static_cast<void>(address);
|
|
static_cast<void>(bytes);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool GuestWriteWatchCollect_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context) {
|
|
#ifdef APS5_GUEST_WRITE_WATCH
|
|
if (bytes == 0 || address + bytes < address) return false;
|
|
return Watch::Get().Collect(address, address + bytes, written, context);
|
|
#else
|
|
static_cast<void>(address);
|
|
static_cast<void>(bytes);
|
|
static_cast<void>(written);
|
|
static_cast<void>(context);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void GuestWriteWatchBeginHostWrite_nid_postfix(const void* pointer, std::size_t bytes) {
|
|
#if defined(__APPLE__)
|
|
const auto begin = reinterpret_cast<std::uintptr_t>(pointer);
|
|
Watch::Get().BeginHostWrite(begin, begin + bytes);
|
|
#else
|
|
static_cast<void>(pointer);
|
|
static_cast<void>(bytes);
|
|
#endif
|
|
}
|
|
|
|
void GuestWriteWatchEndHostWrite_nid_postfix(const void* pointer, std::size_t bytes) {
|
|
#if defined(__APPLE__)
|
|
const auto begin = reinterpret_cast<std::uintptr_t>(pointer);
|
|
Watch::Get().EndHostWrite(begin, begin + bytes);
|
|
#else
|
|
static_cast<void>(pointer);
|
|
static_cast<void>(bytes);
|
|
#endif
|
|
}
|
|
|
|
void GuestWriteWatchProtectionChanged_nid_postfix(const void* pointer, std::size_t bytes, int protection) {
|
|
#if defined(__APPLE__)
|
|
const auto begin = reinterpret_cast<std::uintptr_t>(pointer);
|
|
Watch::Get().ProtectionChanged(begin, begin + bytes, protection);
|
|
#else
|
|
static_cast<void>(pointer);
|
|
static_cast<void>(bytes);
|
|
static_cast<void>(protection);
|
|
#endif
|
|
}
|
|
|
|
int GuestWriteWatchProtection_nid_postfix(std::uintptr_t address, std::uintptr_t limit, std::uintptr_t* runEnd) {
|
|
#if defined(__APPLE__)
|
|
return Watch::Get().Protection(address, limit, runEnd);
|
|
#else
|
|
static_cast<void>(address);
|
|
*runEnd = limit;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
bool GuestWriteWatchCollectFresh_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context) {
|
|
#if defined(__linux__) && defined(PAGEMAP_SCAN) && defined(UFFD_FEATURE_WP_ASYNC)
|
|
if (bytes == 0 || address + bytes < address) return false;
|
|
return Watch::Get().CollectFresh(address, address + bytes, written, context);
|
|
#elif defined(__APPLE__)
|
|
static_cast<void>(written);
|
|
static_cast<void>(context);
|
|
return bytes != 0 && address + bytes >= address;
|
|
#else
|
|
static_cast<void>(address);
|
|
static_cast<void>(bytes);
|
|
static_cast<void>(written);
|
|
static_cast<void>(context);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool GuestWriteWatchCollectArmed_nid_postfix(std::uintptr_t address, std::size_t bytes, void (*written)(void* context, std::uintptr_t begin, std::uintptr_t end), void* context) {
|
|
#if defined(__linux__) && defined(PAGEMAP_SCAN) && defined(UFFD_FEATURE_WP_ASYNC)
|
|
if (bytes == 0 || address + bytes < address) return false;
|
|
return Watch::Get().CollectArmed(address, address + bytes, written, context);
|
|
#elif defined(__APPLE__)
|
|
if (bytes == 0 || address + bytes < address) return false;
|
|
return Watch::Get().Collect(address, address + bytes, written, context);
|
|
#else
|
|
static_cast<void>(address);
|
|
static_cast<void>(bytes);
|
|
static_cast<void>(written);
|
|
static_cast<void>(context);
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
}
|