mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
Linux: port the emulator to a working state on Linux (#117)
Linux: port the emulator to a working state
This commit is contained in:
@@ -25,6 +25,14 @@
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
#include <execinfo.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -83,6 +91,9 @@ static uint32_t MachQueryPageProt(uint64_t vaddr) {
|
||||
}
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
// Zero is the unknown protection sentinel.
|
||||
constexpr uint32_t UNKNOWN_PROTECTION = 0;
|
||||
#endif
|
||||
|
||||
thread_local bool g_in_fault_resolution = false;
|
||||
@@ -101,6 +112,10 @@ thread_local bool g_in_fault_resolution = false;
|
||||
std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
|
||||
address, address >= image_base ? address - image_base : 0);
|
||||
}
|
||||
#elif !defined(__APPLE__)
|
||||
void* frames[16] {};
|
||||
const int frame_count = ::backtrace(frames, static_cast<int>(std::size(frames)));
|
||||
::backtrace_symbols_fd(frames, frame_count, STDERR_FILENO);
|
||||
#endif
|
||||
std::fflush(stderr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
@@ -125,11 +140,134 @@ uint32_t CurrentThread() noexcept {
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__APPLE__)
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
#elif defined(__linux__)
|
||||
static thread_local const uint32_t tid = [] {
|
||||
const auto raw = static_cast<uint32_t>(::syscall(SYS_gettid));
|
||||
if (raw == 0) {
|
||||
FailFast("gettid returned the reserved zero owner token");
|
||||
}
|
||||
return raw;
|
||||
}();
|
||||
return tid;
|
||||
#else
|
||||
FailFast();
|
||||
FailFast("page tracking thread identity is unsupported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
int ToHostProtection(uint32_t protection) {
|
||||
switch (protection) {
|
||||
case NO_ACCESS_PROTECTION: return PROT_NONE;
|
||||
case READ_ONLY_PROTECTION: return PROT_READ;
|
||||
case READ_WRITE_PROTECTION: return PROT_READ | PROT_WRITE;
|
||||
default: Fatal("unmappable protection 0x%08" PRIx32, protection);
|
||||
}
|
||||
}
|
||||
|
||||
// Async-signal-safe lookup in the address-ordered /proc/self/maps.
|
||||
uint32_t QueryHostProtection(uint64_t vaddr) noexcept {
|
||||
int fd = ::open("/proc/self/maps", O_RDONLY | O_CLOEXEC); // NOLINT
|
||||
if (fd < 0) {
|
||||
return UNKNOWN_PROTECTION;
|
||||
}
|
||||
|
||||
enum class Field { Start, End, Perms, Rest };
|
||||
|
||||
uint32_t result = UNKNOWN_PROTECTION;
|
||||
auto field = Field::Start;
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
char perms[4] = {};
|
||||
uint32_t perms_len = 0;
|
||||
bool line_valid = true;
|
||||
|
||||
char buffer[8192];
|
||||
|
||||
for (bool done = false; !done;) {
|
||||
const auto got = ::read(fd, buffer, sizeof(buffer));
|
||||
if (got < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (got == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (ssize_t i = 0; i < got && !done; i++) {
|
||||
const char c = buffer[i];
|
||||
|
||||
if (c == '\n') {
|
||||
field = Field::Start;
|
||||
start = 0;
|
||||
end = 0;
|
||||
perms_len = 0;
|
||||
line_valid = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!line_valid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (field) {
|
||||
case Field::Start:
|
||||
case Field::End: {
|
||||
uint64_t digit = 0;
|
||||
if (c >= '0' && c <= '9') {
|
||||
digit = static_cast<uint64_t>(c - '0');
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
digit = static_cast<uint64_t>(c - 'a') + 10;
|
||||
} else if (c == '-' && field == Field::Start) {
|
||||
field = Field::End;
|
||||
break;
|
||||
} else if (c == ' ' && field == Field::End) {
|
||||
field = Field::Perms;
|
||||
perms_len = 0;
|
||||
break;
|
||||
} else {
|
||||
line_valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
auto& value = (field == Field::Start ? start : end);
|
||||
value = (value << 4u) | digit;
|
||||
break;
|
||||
}
|
||||
|
||||
case Field::Perms: {
|
||||
if (c != ' ') {
|
||||
if (perms_len < sizeof(perms)) {
|
||||
perms[perms_len] = c;
|
||||
}
|
||||
perms_len++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (vaddr < start) {
|
||||
done = true;
|
||||
} else if (vaddr < end && perms_len >= 2) {
|
||||
result = perms[1] == 'w' ? READ_WRITE_PROTECTION
|
||||
: perms[0] == 'r' ? READ_ONLY_PROTECTION
|
||||
: NO_ACCESS_PROTECTION;
|
||||
done = true;
|
||||
} else {
|
||||
field = Field::Rest;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Field::Rest: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::close(fd);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
class SpinGuard final {
|
||||
public:
|
||||
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
|
||||
@@ -171,6 +309,10 @@ struct PageManager::Impl {
|
||||
uint32_t access_watchers = 0;
|
||||
uint32_t original_protection = 0;
|
||||
uint32_t backing_writer = 0;
|
||||
#if defined(__linux__)
|
||||
// Shadow the protection applied through Protect().
|
||||
uint32_t current_protection = UNKNOWN_PROTECTION;
|
||||
#endif
|
||||
bool resolving = false;
|
||||
bool resolving_read_write = false;
|
||||
bool late_read_pending = false;
|
||||
@@ -199,7 +341,10 @@ struct PageManager::Impl {
|
||||
static_cast<uint32_t>(getpagesize()));
|
||||
}
|
||||
#else
|
||||
Fatal("page-fault invalidation is not implemented on this platform");
|
||||
const auto host_page_size = ::sysconf(_SC_PAGESIZE);
|
||||
if (host_page_size < 0 || static_cast<uint64_t>(host_page_size) != PAGE_SIZE) {
|
||||
Fatal("unsupported host page size %ld", static_cast<long>(host_page_size));
|
||||
}
|
||||
#endif
|
||||
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
|
||||
for (uint64_t i = 0; i < REGION_COUNT; i++) {
|
||||
@@ -267,7 +412,7 @@ struct PageManager::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t QueryProtection(uint64_t vaddr) {
|
||||
static uint32_t QueryProtection([[maybe_unused]] PageState& page, uint64_t vaddr) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
|
||||
@@ -287,12 +432,19 @@ struct PageManager::Impl {
|
||||
}
|
||||
return protection;
|
||||
#else
|
||||
(void)vaddr;
|
||||
Fatal("page query is unsupported on this platform");
|
||||
const auto host_protection = QueryHostProtection(vaddr);
|
||||
if (host_protection != READ_WRITE_PROTECTION) {
|
||||
Fatal("basic path requires a read/write mapping at 0x%016" PRIx64
|
||||
" (protection=0x%08" PRIx32 ")",
|
||||
vaddr, host_protection);
|
||||
}
|
||||
page.current_protection = host_protection;
|
||||
return host_protection;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool AllowsAccess(uint64_t vaddr, PageFaultAccess access) noexcept {
|
||||
static bool AllowsAccess([[maybe_unused]] const PageState& page, uint64_t vaddr,
|
||||
PageFaultAccess access) noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
|
||||
@@ -315,13 +467,24 @@ struct PageManager::Impl {
|
||||
default: return false;
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
return false;
|
||||
const auto permitted = [](uint32_t protection, PageFaultAccess wanted) {
|
||||
switch (wanted) {
|
||||
case PageFaultAccess::Read:
|
||||
return protection == READ_ONLY_PROTECTION || protection == READ_WRITE_PROTECTION;
|
||||
case PageFaultAccess::Write: return protection == READ_WRITE_PROTECTION;
|
||||
default: return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (!permitted(page.current_protection, access)) {
|
||||
return false;
|
||||
}
|
||||
return permitted(QueryHostProtection(vaddr), access);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Protect(uint64_t vaddr, uint32_t protection, uint32_t expected_old,
|
||||
bool fault_path) noexcept {
|
||||
static void Protect([[maybe_unused]] PageState& page, uint64_t vaddr, uint32_t protection,
|
||||
uint32_t expected_old, bool fault_path) noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
DWORD old_protection = 0;
|
||||
if (VirtualProtect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
|
||||
@@ -347,10 +510,24 @@ struct PageManager::Impl {
|
||||
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32, vaddr, protection);
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
(void)protection;
|
||||
(void)fault_path;
|
||||
FailFast("page protection is unsupported on this platform");
|
||||
if (page.current_protection != UNKNOWN_PROTECTION &&
|
||||
page.current_protection != expected_old) {
|
||||
if (fault_path) {
|
||||
FailFast("mprotect fault transition did not match expected protection");
|
||||
}
|
||||
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
|
||||
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
|
||||
vaddr, page.current_protection, expected_old, protection);
|
||||
}
|
||||
if (::mprotect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
|
||||
ToHostProtection(protection)) != 0) {
|
||||
if (fault_path) {
|
||||
FailFast("mprotect failed on the fault path");
|
||||
}
|
||||
Fatal("mprotect failed at 0x%016" PRIx64 ", new=0x%08" PRIx32 " (%s)", vaddr,
|
||||
protection, std::strerror(errno));
|
||||
}
|
||||
page.current_protection = protection;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -480,13 +657,13 @@ void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
}
|
||||
const bool first_watcher = page.write_watchers == 0 && page.access_watchers == 0;
|
||||
if (first_watcher) {
|
||||
page.original_protection = Impl::QueryProtection(page_vaddr);
|
||||
page.original_protection = Impl::QueryProtection(page, page_vaddr);
|
||||
}
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
watchers++;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
Impl::Protect(page, page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
switch (new_protection) {
|
||||
case NO_ACCESS_PROTECTION:
|
||||
@@ -507,7 +684,7 @@ void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
watchers--;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (page.backing_writer == 0 && new_protection != old_protection) {
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
Impl::Protect(page, page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
if (page.backing_writer == 0) {
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
@@ -540,6 +717,12 @@ void PageManager::OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access) {
|
||||
page.mappings++;
|
||||
page.gpu_read_mappings += gpu_read ? 1u : 0u;
|
||||
page.gpu_write_mappings += gpu_write ? 1u : 0u;
|
||||
#if defined(__linux__)
|
||||
// New guest mappings start read/write.
|
||||
if (page.current_protection == UNKNOWN_PROTECTION) {
|
||||
page.current_protection = READ_WRITE_PROTECTION;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,7 +844,7 @@ void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
const auto old_protection = NO_ACCESS_PROTECTION;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(address, new_protection, old_protection, false);
|
||||
Impl::Protect(page, address, new_protection, old_protection, false);
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
@@ -686,12 +869,12 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
|
||||
while (true) {
|
||||
SpinGuard lock(page.lock);
|
||||
if (access == PageFaultAccess::Read && page.late_read_pending &&
|
||||
Impl::AllowsAccess(fault_vaddr, access)) {
|
||||
Impl::AllowsAccess(page, fault_vaddr, access)) {
|
||||
page.late_read_pending = false;
|
||||
return true;
|
||||
}
|
||||
if (access == PageFaultAccess::Write && page.late_write_pending &&
|
||||
Impl::AllowsAccess(fault_vaddr, access)) {
|
||||
Impl::AllowsAccess(page, fault_vaddr, access)) {
|
||||
page.late_write_pending = false;
|
||||
return true;
|
||||
}
|
||||
@@ -713,7 +896,7 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
|
||||
}
|
||||
bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending
|
||||
: page.late_write_pending);
|
||||
const bool allowed = Impl::AllowsAccess(fault_vaddr, access);
|
||||
const bool allowed = Impl::AllowsAccess(page, fault_vaddr, access);
|
||||
pending = false;
|
||||
if (waited && !allowed) {
|
||||
FailFast("page remained inaccessible after waiting for its resolver");
|
||||
@@ -762,12 +945,12 @@ bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noex
|
||||
page.write_watchers = 0;
|
||||
}
|
||||
const auto restored_protection = Impl::WatcherProtection(page);
|
||||
Impl::Protect(PageStart(fault_vaddr), restored_protection, old_protection, true);
|
||||
Impl::Protect(page, PageStart(fault_vaddr), restored_protection, old_protection, true);
|
||||
if (page.write_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, restored_protection);
|
||||
} else if (!Impl::AllowsAccess(fault_vaddr, access)) {
|
||||
} else if (!Impl::AllowsAccess(page, fault_vaddr, access)) {
|
||||
FailFast("fault completion left the page inaccessible");
|
||||
}
|
||||
page.resolving = false;
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#undef max
|
||||
#elif defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
#elif defined(__linux__)
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
@@ -54,6 +57,9 @@ private:
|
||||
#elif defined(__APPLE__)
|
||||
// mach thread port is a nonzero per-thread id (0 is the "no owner" sentinel).
|
||||
return static_cast<uint32_t>(pthread_mach_thread_np(pthread_self()));
|
||||
#elif defined(__linux__)
|
||||
static thread_local const uint32_t tid = static_cast<uint32_t>(::syscall(SYS_gettid));
|
||||
return tid;
|
||||
#else
|
||||
EXIT("region tracking thread identity is unsupported on this platform\n");
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user