mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 23:55:40 +00:00
Common: Add memory arena and page fault handler classes
This commit is contained in:
parent
11faf6ccfb
commit
ceb67b5018
|
@ -54,6 +54,10 @@ add_library(common
|
||||||
minizip_helpers.h
|
minizip_helpers.h
|
||||||
null_audio_stream.cpp
|
null_audio_stream.cpp
|
||||||
null_audio_stream.h
|
null_audio_stream.h
|
||||||
|
memory_arena.cpp
|
||||||
|
memory_arena.h
|
||||||
|
page_fault_handler.cpp
|
||||||
|
page_fault_handler.h
|
||||||
rectangle.h
|
rectangle.h
|
||||||
progress_callback.cpp
|
progress_callback.cpp
|
||||||
progress_callback.h
|
progress_callback.h
|
||||||
|
@ -183,3 +187,8 @@ if(APPLE AND NOT BUILD_LIBRETRO_CORE)
|
||||||
gl/context_agl.h
|
gl/context_agl.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
# We need -lrt for shm_unlink
|
||||||
|
target_link_libraries(common PRIVATE rt)
|
||||||
|
endif()
|
||||||
|
|
|
@ -90,6 +90,8 @@
|
||||||
<ClInclude Include="md5_digest.h" />
|
<ClInclude Include="md5_digest.h" />
|
||||||
<ClInclude Include="null_audio_stream.h" />
|
<ClInclude Include="null_audio_stream.h" />
|
||||||
<ClInclude Include="progress_callback.h" />
|
<ClInclude Include="progress_callback.h" />
|
||||||
|
<ClInclude Include="memory_arena.h" />
|
||||||
|
<ClInclude Include="page_fault_handler.h" />
|
||||||
<ClInclude Include="rectangle.h" />
|
<ClInclude Include="rectangle.h" />
|
||||||
<ClInclude Include="cd_subchannel_replacement.h" />
|
<ClInclude Include="cd_subchannel_replacement.h" />
|
||||||
<ClInclude Include="scope_guard.h" />
|
<ClInclude Include="scope_guard.h" />
|
||||||
|
@ -154,6 +156,8 @@
|
||||||
<ClCompile Include="null_audio_stream.cpp" />
|
<ClCompile Include="null_audio_stream.cpp" />
|
||||||
<ClCompile Include="progress_callback.cpp" />
|
<ClCompile Include="progress_callback.cpp" />
|
||||||
<ClCompile Include="shiftjis.cpp" />
|
<ClCompile Include="shiftjis.cpp" />
|
||||||
|
<ClCompile Include="memory_arena.cpp" />
|
||||||
|
<ClCompile Include="page_fault_handler.cpp" />
|
||||||
<ClCompile Include="state_wrapper.cpp" />
|
<ClCompile Include="state_wrapper.cpp" />
|
||||||
<ClCompile Include="cd_xa.cpp" />
|
<ClCompile Include="cd_xa.cpp" />
|
||||||
<ClCompile Include="string.cpp" />
|
<ClCompile Include="string.cpp" />
|
||||||
|
@ -743,4 +747,4 @@
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
<Import Project="$(VCTargetsPath)\BuildCustomizations\masm.targets" />
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -102,6 +102,8 @@
|
||||||
<ClInclude Include="win32_progress_callback.h" />
|
<ClInclude Include="win32_progress_callback.h" />
|
||||||
<ClInclude Include="make_array.h" />
|
<ClInclude Include="make_array.h" />
|
||||||
<ClInclude Include="shiftjis.h" />
|
<ClInclude Include="shiftjis.h" />
|
||||||
|
<ClInclude Include="memory_arena.h" />
|
||||||
|
<ClInclude Include="page_fault_handler.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="jit_code_buffer.cpp" />
|
<ClCompile Include="jit_code_buffer.cpp" />
|
||||||
|
@ -196,6 +198,8 @@
|
||||||
<ClCompile Include="minizip_helpers.cpp" />
|
<ClCompile Include="minizip_helpers.cpp" />
|
||||||
<ClCompile Include="win32_progress_callback.cpp" />
|
<ClCompile Include="win32_progress_callback.cpp" />
|
||||||
<ClCompile Include="shiftjis.cpp" />
|
<ClCompile Include="shiftjis.cpp" />
|
||||||
|
<ClCompile Include="memory_arena.cpp" />
|
||||||
|
<ClCompile Include="page_fault_handler.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Natvis Include="bitfield.natvis" />
|
<Natvis Include="bitfield.natvis" />
|
||||||
|
@ -211,4 +215,4 @@
|
||||||
<UniqueIdentifier>{642ff5eb-af39-4aab-a42f-6eb8188a11d7}</UniqueIdentifier>
|
<UniqueIdentifier>{642ff5eb-af39-4aab-a42f-6eb8188a11d7}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
294
src/common/memory_arena.cpp
Normal file
294
src/common/memory_arena.cpp
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
#include "memory_arena.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
Log_SetChannel(Common::MemoryArena);
|
||||||
|
|
||||||
|
#if defined(WIN32)
|
||||||
|
#include "common/windows_headers.h"
|
||||||
|
#elif defined(ANDROID)
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <linux/ashmem.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
#include <cerrno>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
// Borrowed from Dolphin
|
||||||
|
#ifdef ANDROID
|
||||||
|
#define ASHMEM_DEVICE "/dev/ashmem"
|
||||||
|
|
||||||
|
static int AshmemCreateFileMapping(const char* name, size_t size)
|
||||||
|
{
|
||||||
|
// ASharedMemory path - works on API >= 26 and falls through on API < 26:
|
||||||
|
|
||||||
|
// We can't call ASharedMemory_create the normal way without increasing the
|
||||||
|
// minimum version requirement to API 26, so we use dlopen/dlsym instead
|
||||||
|
static void* libandroid = dlopen("libandroid.so", RTLD_LAZY | RTLD_LOCAL);
|
||||||
|
static auto shared_memory_create =
|
||||||
|
reinterpret_cast<int (*)(const char*, size_t)>(dlsym(libandroid, "ASharedMemory_create"));
|
||||||
|
if (shared_memory_create)
|
||||||
|
return shared_memory_create(name, size);
|
||||||
|
|
||||||
|
// /dev/ashmem path - works on API < 29:
|
||||||
|
|
||||||
|
int fd, ret;
|
||||||
|
fd = open(ASHMEM_DEVICE, O_RDWR);
|
||||||
|
if (fd < 0)
|
||||||
|
return fd;
|
||||||
|
|
||||||
|
// We don't really care if we can't set the name, it is optional
|
||||||
|
ioctl(fd, ASHMEM_SET_NAME, name);
|
||||||
|
|
||||||
|
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
Log_ErrorPrintf("Ashmem returned error: 0x%08x", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
MemoryArena::MemoryArena() = default;
|
||||||
|
|
||||||
|
MemoryArena::~MemoryArena()
|
||||||
|
{
|
||||||
|
#if defined(WIN32)
|
||||||
|
if (m_file_handle)
|
||||||
|
CloseHandle(m_file_handle);
|
||||||
|
#elif defined(__linux__)
|
||||||
|
if (m_shmem_fd > 0)
|
||||||
|
close(m_shmem_fd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void* MemoryArena::FindBaseAddressForMapping(size_t size)
|
||||||
|
{
|
||||||
|
void* base_address;
|
||||||
|
#if defined(WIN32)
|
||||||
|
base_address = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
if (base_address)
|
||||||
|
VirtualFree(base_address, 0, MEM_RELEASE);
|
||||||
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
base_address = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||||
|
if (base_address)
|
||||||
|
munmap(base_address, size);
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
base_address = mmap(nullptr, size, PROT_NONE, MAP_ANON | MAP_SHARED, -1, 0);
|
||||||
|
if (base_address)
|
||||||
|
munmap(base_address, size);
|
||||||
|
#else
|
||||||
|
base_address = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!base_address)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to get base address for memory mapping of size %zu", size);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryArena::Create(size_t size, bool writable, bool executable)
|
||||||
|
{
|
||||||
|
#if defined(WIN32)
|
||||||
|
const std::string file_mapping_name = StringUtil::StdStringFromFormat("duckstation_%u", GetCurrentProcessId());
|
||||||
|
|
||||||
|
const DWORD protect = (writable ? (executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE) : PAGE_READONLY);
|
||||||
|
m_file_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, protect, Truncate32(size >> 32), Truncate32(size),
|
||||||
|
file_mapping_name.c_str());
|
||||||
|
if (!m_file_handle)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("CreateFileMapping failed: %u", GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#elif defined(ANDROID)
|
||||||
|
const std::string file_mapping_name =
|
||||||
|
StringUtil::StdStringFromFormat("duckstation_%u", static_cast<unsigned>(getpid()));
|
||||||
|
m_shmem_fd = AshmemCreateFileMapping(file_mapping_name.c_str(), size);
|
||||||
|
if (m_shmem_fd < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("AshmemCreateFileMapping failed: %d %d", m_shmem_fd, errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
const std::string file_mapping_name =
|
||||||
|
StringUtil::StdStringFromFormat("duckstation_%u", static_cast<unsigned>(getpid()));
|
||||||
|
m_shmem_fd = shm_open(file_mapping_name.c_str(), O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600);
|
||||||
|
if (m_shmem_fd < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("shm_open failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're not going to be opening this mapping in other processes, so remove the file
|
||||||
|
shm_unlink(file_mapping_name.c_str());
|
||||||
|
|
||||||
|
// ensure it's the correct size
|
||||||
|
if (ftruncate64(m_shmem_fd, static_cast<off64_t>(size)) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("ftruncate64(%zu) failed: %d", size, errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
const std::string file_mapping_name =
|
||||||
|
StringUtil::StdStringFromFormat("duckstation_%u", static_cast<unsigned>(getpid()));
|
||||||
|
m_shmem_fd = shm_open(file_mapping_name.c_str(), O_CREAT | O_EXCL | (writable ? O_RDWR : O_RDONLY), 0600);
|
||||||
|
if (m_shmem_fd < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("shm_open failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're not going to be opening this mapping in other processes, so remove the file
|
||||||
|
shm_unlink(file_mapping_name.c_str());
|
||||||
|
|
||||||
|
// ensure it's the correct size
|
||||||
|
if (ftruncate(m_shmem_fd, static_cast<off_t>(size)) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("ftruncate(%zu) failed: %d", size, errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<MemoryArena::View> MemoryArena::CreateView(size_t offset, size_t size, bool writable, bool executable,
|
||||||
|
void* fixed_address)
|
||||||
|
{
|
||||||
|
void* base_pointer = CreateViewPtr(offset, size, writable, executable, fixed_address);
|
||||||
|
if (!base_pointer)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return View(this, base_pointer, offset, size, writable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* MemoryArena::CreateViewPtr(size_t offset, size_t size, bool writable, bool executable,
|
||||||
|
void* fixed_address /*= nullptr*/)
|
||||||
|
{
|
||||||
|
void* base_pointer;
|
||||||
|
#if defined(WIN32)
|
||||||
|
const DWORD desired_access = FILE_MAP_READ | (writable ? FILE_MAP_WRITE : 0) | (executable ? FILE_MAP_EXECUTE : 0);
|
||||||
|
base_pointer =
|
||||||
|
MapViewOfFileEx(m_file_handle, desired_access, Truncate32(offset >> 32), Truncate32(offset), size, fixed_address);
|
||||||
|
if (!base_pointer)
|
||||||
|
return nullptr;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
const int flags = (fixed_address != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED;
|
||||||
|
const int prot = PROT_READ | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0);
|
||||||
|
base_pointer = mmap64(fixed_address, size, prot, flags, m_shmem_fd, static_cast<off64_t>(offset));
|
||||||
|
if (base_pointer == reinterpret_cast<void*>(-1))
|
||||||
|
return nullptr;
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
const int flags = (fixed_address != nullptr) ? (MAP_SHARED | MAP_FIXED) : MAP_SHARED;
|
||||||
|
const int prot = PROT_READ | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0);
|
||||||
|
base_pointer = mmap(fixed_address, size, prot, flags, m_shmem_fd, static_cast<off_t>(offset));
|
||||||
|
if (base_pointer == reinterpret_cast<void*>(-1))
|
||||||
|
return nullptr;
|
||||||
|
#else
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_num_views.fetch_add(1);
|
||||||
|
return base_pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryArena::FlushViewPtr(void* address, size_t size)
|
||||||
|
{
|
||||||
|
#if defined(WIN32)
|
||||||
|
return FlushViewOfFile(address, size);
|
||||||
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
return (msync(address, size, 0) >= 0);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryArena::ReleaseViewPtr(void* address, size_t size)
|
||||||
|
{
|
||||||
|
bool result;
|
||||||
|
#if defined(WIN32)
|
||||||
|
result = static_cast<bool>(UnmapViewOfFile(address));
|
||||||
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
result = (munmap(address, size) >= 0);
|
||||||
|
#else
|
||||||
|
result = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to unmap previously-created view at %p", address);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t prev_count = m_num_views.fetch_sub(1);
|
||||||
|
Assert(prev_count > 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryArena::SetPageProtection(void* address, size_t length, bool readable, bool writable, bool executable)
|
||||||
|
{
|
||||||
|
#if defined(WIN32)
|
||||||
|
static constexpr DWORD protection_table[2][2][2] = {
|
||||||
|
{{PAGE_NOACCESS, PAGE_EXECUTE}, {PAGE_WRITECOPY, PAGE_EXECUTE_WRITECOPY}},
|
||||||
|
{{PAGE_READONLY, PAGE_EXECUTE_READ}, {PAGE_READWRITE, PAGE_EXECUTE_READWRITE}}};
|
||||||
|
|
||||||
|
DWORD old_protect;
|
||||||
|
return static_cast<bool>(
|
||||||
|
VirtualProtect(address, length, protection_table[readable][writable][executable], &old_protect));
|
||||||
|
#elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__)
|
||||||
|
const int prot = (readable ? PROT_READ : 0) | (writable ? PROT_WRITE : 0) | (executable ? PROT_EXEC : 0);
|
||||||
|
return (mprotect(address, length, prot) >= 0);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryArena::View::View(MemoryArena* parent, void* base_pointer, size_t arena_offset, size_t mapping_size,
|
||||||
|
bool writable)
|
||||||
|
: m_parent(parent), m_base_pointer(base_pointer), m_arena_offset(arena_offset), m_mapping_size(mapping_size),
|
||||||
|
m_writable(writable)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryArena::View::View(View&& view)
|
||||||
|
: m_parent(view.m_parent), m_base_pointer(view.m_base_pointer), m_arena_offset(view.m_arena_offset),
|
||||||
|
m_mapping_size(view.m_mapping_size)
|
||||||
|
{
|
||||||
|
view.m_parent = nullptr;
|
||||||
|
view.m_base_pointer = nullptr;
|
||||||
|
view.m_arena_offset = 0;
|
||||||
|
view.m_mapping_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryArena::View::~View()
|
||||||
|
{
|
||||||
|
if (m_parent)
|
||||||
|
{
|
||||||
|
if (m_writable && !m_parent->FlushViewPtr(m_base_pointer, m_mapping_size))
|
||||||
|
Panic("Failed to flush previously-created view");
|
||||||
|
if (!m_parent->ReleaseViewPtr(m_base_pointer, m_mapping_size))
|
||||||
|
Panic("Failed to unmap previously-created view");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Common
|
58
src/common/memory_arena.h
Normal file
58
src/common/memory_arena.h
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma once
|
||||||
|
#include "types.h"
|
||||||
|
#include <atomic>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
class MemoryArena
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class View
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
View(MemoryArena* parent, void* base_pointer, size_t arena_offset, size_t mapping_size, bool writable);
|
||||||
|
View(View&& view);
|
||||||
|
~View();
|
||||||
|
|
||||||
|
void* GetBasePointer() const { return m_base_pointer; }
|
||||||
|
size_t GetArenaOffset() const { return m_arena_offset; }
|
||||||
|
size_t GetMappingSize() const { return m_mapping_size; }
|
||||||
|
bool IsWritable() const { return m_writable; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
MemoryArena* m_parent;
|
||||||
|
void* m_base_pointer;
|
||||||
|
size_t m_arena_offset;
|
||||||
|
size_t m_mapping_size;
|
||||||
|
bool m_writable;
|
||||||
|
};
|
||||||
|
|
||||||
|
MemoryArena();
|
||||||
|
~MemoryArena();
|
||||||
|
|
||||||
|
static void* FindBaseAddressForMapping(size_t size);
|
||||||
|
|
||||||
|
bool Create(size_t size, bool writable, bool executable);
|
||||||
|
|
||||||
|
std::optional<View> CreateView(size_t offset, size_t size, bool writable, bool executable,
|
||||||
|
void* fixed_address = nullptr);
|
||||||
|
|
||||||
|
void* CreateViewPtr(size_t offset, size_t size, bool writable, bool executable, void* fixed_address = nullptr);
|
||||||
|
bool FlushViewPtr(void* address, size_t size);
|
||||||
|
bool ReleaseViewPtr(void* address, size_t size);
|
||||||
|
|
||||||
|
static bool SetPageProtection(void* address, size_t length, bool readable, bool writable, bool executable);
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(WIN32)
|
||||||
|
void* m_file_handle = nullptr;
|
||||||
|
#elif defined(__linux__) || defined(ANDROID) || defined(__APPLE__)
|
||||||
|
int m_shmem_fd = -1;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::atomic_size_t m_num_views{0};
|
||||||
|
size_t m_size = 0;
|
||||||
|
bool m_writable = false;
|
||||||
|
bool m_executable = false;
|
||||||
|
};
|
||||||
|
} // namespace Common
|
275
src/common/page_fault_handler.cpp
Normal file
275
src/common/page_fault_handler.cpp
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
#include "page_fault_handler.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <mutex>
|
||||||
|
Log_SetChannel(Common::PageFaultHandler);
|
||||||
|
|
||||||
|
#if defined(WIN32)
|
||||||
|
#include "common/windows_headers.h"
|
||||||
|
#elif defined(__linux__) || defined(__ANDROID__)
|
||||||
|
#include <signal.h>
|
||||||
|
#include <ucontext.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define USE_SIGSEGV 1
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include <signal.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#define USE_SIGSEGV 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Common::PageFaultHandler {
|
||||||
|
|
||||||
|
struct RegisteredHandler
|
||||||
|
{
|
||||||
|
void* owner;
|
||||||
|
Callback callback;
|
||||||
|
};
|
||||||
|
static std::vector<RegisteredHandler> m_handlers;
|
||||||
|
static std::mutex m_handler_lock;
|
||||||
|
static thread_local bool s_in_handler;
|
||||||
|
|
||||||
|
#ifdef __aarch64__
|
||||||
|
static bool IsStoreInstruction(const void* ptr)
|
||||||
|
{
|
||||||
|
u32 bits;
|
||||||
|
std::memcpy(&bits, ptr, sizeof(bits));
|
||||||
|
|
||||||
|
// Based on vixl's disassembler Instruction::IsStore().
|
||||||
|
// if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed)
|
||||||
|
if ((bits & 0x0a000000) != 0x08000000)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed)
|
||||||
|
if ((bits & 0x3a000000) == 0x28000000)
|
||||||
|
{
|
||||||
|
// return Mask(LoadStorePairLBit) == 0
|
||||||
|
return (bits & (1 << 22)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (bits & 0xC4C00000)
|
||||||
|
{
|
||||||
|
case 0x00000000: // STRB_w
|
||||||
|
case 0x40000000: // STRH_w
|
||||||
|
case 0x80000000: // STR_w
|
||||||
|
case 0xC0000000: // STR_x
|
||||||
|
case 0x04000000: // STR_b
|
||||||
|
case 0x44000000: // STR_h
|
||||||
|
case 0x84000000: // STR_s
|
||||||
|
case 0xC4000000: // STR_d
|
||||||
|
case 0x04800000: // STR_q
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(WIN32)
|
||||||
|
static PVOID s_veh_handle;
|
||||||
|
|
||||||
|
static LONG ExceptionHandler(PEXCEPTION_POINTERS exi)
|
||||||
|
{
|
||||||
|
if (exi->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || s_in_handler)
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
|
||||||
|
s_in_handler = true;
|
||||||
|
|
||||||
|
#if defined(_M_AMD64)
|
||||||
|
void* const exception_pc = reinterpret_cast<void*>(exi->ContextRecord->Rip);
|
||||||
|
#elif defined(_M_ARM64)
|
||||||
|
void* const exception_pc = reinterpret_cast<void*>(exi->ContextRecord->Pc);
|
||||||
|
#else
|
||||||
|
void* const exception_pc = nullptr;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void* const exception_address = reinterpret_cast<void*>(exi->ExceptionRecord->ExceptionInformation[1]);
|
||||||
|
bool const is_write = exi->ExceptionRecord->ExceptionInformation[0] == 1;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> guard(m_handler_lock);
|
||||||
|
for (const RegisteredHandler& rh : m_handlers)
|
||||||
|
{
|
||||||
|
if (rh.callback(exception_pc, exception_address, is_write) == HandlerResult::ContinueExecution)
|
||||||
|
{
|
||||||
|
s_in_handler = false;
|
||||||
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s_in_handler = false;
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(USE_SIGSEGV)
|
||||||
|
|
||||||
|
static struct sigaction s_old_sigsegv_action;
|
||||||
|
#ifdef __APPLE__
|
||||||
|
static struct sigaction s_old_sigbus_action;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void SIGSEGVHandler(int sig, siginfo_t* info, void* ctx)
|
||||||
|
{
|
||||||
|
if ((info->si_code != SEGV_MAPERR && info->si_code != SEGV_ACCERR) || s_in_handler)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#ifndef __APPLE__
|
||||||
|
void* const exception_address = reinterpret_cast<void*>(info->si_addr);
|
||||||
|
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
|
||||||
|
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_ERR] & 2) != 0;
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.pc);
|
||||||
|
const bool is_write = IsStoreInstruction(exception_pc);
|
||||||
|
#else
|
||||||
|
void* const exception_pc = nullptr;
|
||||||
|
const bool is_write = false;
|
||||||
|
#endif
|
||||||
|
#else // __APPLE__
|
||||||
|
#if defined(__x86_64__)
|
||||||
|
void* const exception_address =
|
||||||
|
reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__faultvaddr);
|
||||||
|
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
|
||||||
|
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__err & 2) != 0;
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
|
||||||
|
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__pc);
|
||||||
|
const bool is_write = IsStoreInstruction(exception_pc);
|
||||||
|
#else
|
||||||
|
void* const exception_address = reinterpret_cast<void*>(info->si_addr);
|
||||||
|
void* const exception_pc = nullptr;
|
||||||
|
const bool is_write = false;
|
||||||
|
#endif
|
||||||
|
#endif // __APPLE__
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> guard(m_handler_lock);
|
||||||
|
for (const RegisteredHandler& rh : m_handlers)
|
||||||
|
{
|
||||||
|
if (rh.callback(exception_pc, exception_address, is_write) == HandlerResult::ContinueExecution)
|
||||||
|
{
|
||||||
|
s_in_handler = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// call old signal handler
|
||||||
|
#ifndef __APPLE__
|
||||||
|
const struct sigaction& sa = s_old_sigsegv_action;
|
||||||
|
#else
|
||||||
|
const struct sigaction& sa = (sig == SIGBUS) ? s_old_sigbus_action : s_old_sigsegv_action;
|
||||||
|
#endif
|
||||||
|
if (sa.sa_flags & SA_SIGINFO)
|
||||||
|
sa.sa_sigaction(sig, info, ctx);
|
||||||
|
else if (sa.sa_handler == SIG_DFL)
|
||||||
|
signal(sig, SIG_DFL);
|
||||||
|
else if (sa.sa_handler == SIG_IGN)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
sa.sa_handler(sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool InstallHandler(void* owner, Callback callback)
|
||||||
|
{
|
||||||
|
bool was_empty;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_handler_lock);
|
||||||
|
if (std::find_if(m_handlers.begin(), m_handlers.end(),
|
||||||
|
[owner](const RegisteredHandler& rh) { return rh.owner == owner; }) != m_handlers.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
was_empty = m_handlers.empty();
|
||||||
|
m_handlers.push_back(RegisteredHandler{owner, std::move(callback)});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_empty)
|
||||||
|
{
|
||||||
|
#if defined(WIN32)
|
||||||
|
s_veh_handle = AddVectoredExceptionHandler(1, ExceptionHandler);
|
||||||
|
if (!s_veh_handle)
|
||||||
|
{
|
||||||
|
Log_ErrorPrint("Failed to add vectored exception handler");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#elif defined(USE_SIGSEGV)
|
||||||
|
#if 0
|
||||||
|
// Alternative stack - we'll need this is we ever use the host stack for branches.
|
||||||
|
stack_t signal_stack = {};
|
||||||
|
signal_stack.ss_sp = malloc(SIGSTKSZ);
|
||||||
|
signal_stack.ss_size = SIGSTKSZ;
|
||||||
|
if (sigaltstack(&signal_stack, nullptr))
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("signaltstack() failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct sigaction sa = {};
|
||||||
|
sa.sa_sigaction = SIGSEGVHandler;
|
||||||
|
sa.sa_flags = SA_SIGINFO;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("sigaction(SIGSEGV) failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("sigaction(SIGBUS) failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RemoveHandler(void* owner)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_handler_lock);
|
||||||
|
auto it = std::find_if(m_handlers.begin(), m_handlers.end(),
|
||||||
|
[owner](const RegisteredHandler& rh) { return rh.owner == owner; });
|
||||||
|
if (it == m_handlers.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_handlers.erase(it);
|
||||||
|
|
||||||
|
if (m_handlers.empty())
|
||||||
|
{
|
||||||
|
#if defined(WIN32)
|
||||||
|
RemoveVectoredExceptionHandler(s_veh_handle);
|
||||||
|
s_veh_handle = nullptr;
|
||||||
|
#else
|
||||||
|
// restore old signal handler
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if (sigaction(SIGBUS, &s_old_sigbus_action, nullptr) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("sigaction(SIGBUS) failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
s_old_sigbus_action = {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (sigaction(SIGSEGV, &s_old_sigsegv_action, nullptr) < 0)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("sigaction(SIGSEGV) failed: %d", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_old_sigsegv_action = {};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common::PageFaultHandler
|
18
src/common/page_fault_handler.h
Normal file
18
src/common/page_fault_handler.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
#include "types.h"
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Common::PageFaultHandler {
|
||||||
|
enum class HandlerResult
|
||||||
|
{
|
||||||
|
ContinueExecution,
|
||||||
|
ExecuteNextHandler,
|
||||||
|
};
|
||||||
|
|
||||||
|
using Callback = std::function<HandlerResult(void* exception_pc, void* fault_address, bool is_write)>;
|
||||||
|
using Handle = void*;
|
||||||
|
|
||||||
|
bool InstallHandler(void* owner, Callback callback);
|
||||||
|
bool RemoveHandler(void* owner);
|
||||||
|
|
||||||
|
} // namespace Common::PageFaultHandler
|
Loading…
Reference in a new issue