cpu_patches: Patch just-in-time using signal handlers. (#852)

* cpu_patches: Patch just-in-time using illegal instruction handler.

* core: Add common signal dispatch system and use for on-demand TCB patches.

* page_manager: Re-add userfaultfd implementation.
This commit is contained in:
squidbus 2024-09-15 13:48:34 -07:00 committed by GitHub
parent b09b28c7f3
commit 75a4df53a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 404 additions and 143 deletions

View file

@ -2,21 +2,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include <boost/icl/interval_set.hpp>
#include "common/alignment.h"
#include "common/arch.h"
#include "common/assert.h"
#include "common/error.h"
#include "core/signals.h"
#include "video_core/page_manager.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#ifndef _WIN64
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#ifdef ENABLE_USERFAULTFD
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <sys/ioctl.h>
#endif
#else
#include <windows.h>
@ -27,45 +27,7 @@ namespace VideoCore {
constexpr size_t PAGESIZE = 4_KB;
constexpr size_t PAGEBITS = 12;
#ifdef _WIN64
struct PageManager::Impl {
Impl(Vulkan::Rasterizer* rasterizer_) {
rasterizer = rasterizer_;
veh_handle = AddVectoredExceptionHandler(0, GuestFaultSignalHandler);
ASSERT_MSG(veh_handle, "Failed to register an exception handler");
}
void OnMap(VAddr address, size_t size) {}
void OnUnmap(VAddr address, size_t size) {}
void Protect(VAddr address, size_t size, bool allow_write) {
DWORD prot = allow_write ? PAGE_READWRITE : PAGE_READONLY;
DWORD old_prot{};
BOOL result = VirtualProtect(std::bit_cast<LPVOID>(address), size, prot, &old_prot);
ASSERT_MSG(result != 0, "Region protection failed");
}
static LONG WINAPI GuestFaultSignalHandler(EXCEPTION_POINTERS* pExp) noexcept {
const u32 ec = pExp->ExceptionRecord->ExceptionCode;
if (ec == EXCEPTION_ACCESS_VIOLATION) {
const auto info = pExp->ExceptionRecord->ExceptionInformation;
if (info[0] == 1) { // Write violation
const VAddr addr_aligned = Common::AlignDown(info[1], PAGESIZE);
rasterizer->InvalidateMemory(addr_aligned, PAGESIZE);
return EXCEPTION_CONTINUE_EXECUTION;
} /* else {
UNREACHABLE();
}*/
}
return EXCEPTION_CONTINUE_SEARCH; // pass further
}
inline static Vulkan::Rasterizer* rasterizer;
void* veh_handle{};
};
#elif ENABLE_USERFAULTFD
#if ENABLE_USERFAULTFD
struct PageManager::Impl {
Impl(Vulkan::Rasterizer* rasterizer_) : rasterizer{rasterizer_} {
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
@ -160,72 +122,48 @@ struct PageManager::Impl {
int uffd;
};
#else
#if defined(__APPLE__)
#if defined(ARCH_X86_64)
#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__err & 0x2)
#elif defined(ARCH_ARM64)
#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext->__es.__esr & 0x40)
#endif
#else
#if defined(ARCH_X86_64)
#define IS_WRITE_ERROR(ctx) ((ctx)->uc_mcontext.gregs[REG_ERR] & 0x2)
#endif
#endif
#ifndef IS_WRITE_ERROR
#error "Missing IS_WRITE_ERROR() implementation for target OS and CPU architecture.
#endif
struct PageManager::Impl {
Impl(Vulkan::Rasterizer* rasterizer_) {
rasterizer = rasterizer_;
#ifdef __APPLE__
// Read-only memory write results in SIGBUS on Apple.
static constexpr int SignalType = SIGBUS;
#else
static constexpr int SignalType = SIGSEGV;
#endif
sigset_t signal_mask;
sigemptyset(&signal_mask);
sigaddset(&signal_mask, SignalType);
using HandlerType = decltype(sigaction::sa_sigaction);
struct sigaction guest_access_fault {};
guest_access_fault.sa_flags = SA_SIGINFO | SA_ONSTACK;
guest_access_fault.sa_sigaction = &GuestFaultSignalHandler;
guest_access_fault.sa_mask = signal_mask;
sigaction(SignalType, &guest_access_fault, nullptr);
// Should be called first.
constexpr auto priority = std::numeric_limits<u32>::min();
Core::Signals::Instance()->RegisterAccessViolationHandler(GuestFaultSignalHandler,
priority);
}
void OnMap(VAddr address, size_t size) {}
void OnMap(VAddr address, size_t size) {
owned_ranges += boost::icl::interval<VAddr>::right_open(address, address + size);
}
void OnUnmap(VAddr address, size_t size) {}
void OnUnmap(VAddr address, size_t size) {
owned_ranges -= boost::icl::interval<VAddr>::right_open(address, address + size);
}
void Protect(VAddr address, size_t size, bool allow_write) {
#ifdef _WIN32
DWORD prot = allow_write ? PAGE_READWRITE : PAGE_READONLY;
DWORD old_prot{};
BOOL result = VirtualProtect(std::bit_cast<LPVOID>(address), size, prot, &old_prot);
ASSERT_MSG(result != 0, "Region protection failed");
#else
mprotect(reinterpret_cast<void*>(address), size,
PROT_READ | (allow_write ? PROT_WRITE : 0));
#endif
}
static void GuestFaultSignalHandler(int sig, siginfo_t* info, void* raw_context) {
ucontext_t* ctx = reinterpret_cast<ucontext_t*>(raw_context);
const VAddr address = reinterpret_cast<VAddr>(info->si_addr);
if (IS_WRITE_ERROR(ctx)) {
const VAddr addr_aligned = Common::AlignDown(address, PAGESIZE);
static bool GuestFaultSignalHandler(void* code_address, void* fault_address, bool is_write) {
const auto addr = reinterpret_cast<VAddr>(fault_address);
if (is_write && owned_ranges.find(addr) != owned_ranges.end()) {
const VAddr addr_aligned = Common::AlignDown(addr, PAGESIZE);
rasterizer->InvalidateMemory(addr_aligned, PAGESIZE);
} else {
// Read not supported!
UNREACHABLE();
return true;
}
return false;
}
inline static Vulkan::Rasterizer* rasterizer;
inline static boost::icl::interval_set<VAddr> owned_ranges;
};
#endif