Linux: port the emulator to a working state on Linux (#117)

Linux: port the emulator to a working state
This commit is contained in:
Stefanos Costa
2026-07-29 00:25:58 +02:00
committed by GitHub
parent 3dc793d859
commit 2b9cba457e
30 changed files with 2320 additions and 381 deletions
-82
View File
@@ -1,82 +0,0 @@
name: Build KytyPS5 (Linux)
on:
workflow_dispatch:
push:
branches: [ main, master ]
pull_request:
jobs:
build:
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends --yes \
clang \
glslang-tools \
libgl1-mesa-dev \
libx11-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxi-dev \
libxrandr-dev \
libxss-dev \
lld \
ninja-build
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
ld.lld --version
glslangValidator --version
echo "Qt6_DIR=$Qt6_DIR"
- name: Configure
shell: bash
run: |
cmake -S src -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
- name: Build
shell: bash
run: |
cmake --build _Build/linux --target launcher --parallel
- name: Install
shell: bash
run: |
cmake --install _Build/linux --prefix _Build/linux/install
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-Linux
path: _Build/linux/install/**
if-no-files-found: error
+137 -3
View File
@@ -198,10 +198,137 @@ jobs:
path: _Build/macos/install/**
if-no-files-found: error
linux:
name: Build KytyPS5 (Linux)
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install build dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends --yes \
clang \
glslang-tools \
libasound2-dev \
libdbus-1-dev \
libgl1-mesa-dev \
libpulse-dev \
libudev-dev \
libwayland-dev \
libx11-dev \
libxcursor-dev \
libxext-dev \
libxfixes-dev \
libxi-dev \
libxkbcommon-dev \
libxrandr-dev \
libxss-dev \
lld \
ninja-build \
wayland-protocols
- name: Install Qt 6.10.3
uses: jurplel/install-qt-action@v4
with:
version: "6.10.3"
host: linux
target: desktop
arch: linux_gcc_64
cache: true
- name: Verify toolchain
shell: bash
run: |
git --version
cmake --version
ninja --version
clang++ --version
ld.lld --version
glslangValidator --version
echo "Qt6_DIR=$Qt6_DIR"
- name: Configure
shell: bash
run: |
mkdir -p _Build
cmake -S src -B _Build/linux \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR" 2>&1 | tee _Build/configure.log
exit "${PIPESTATUS[0]}"
- name: Verify SDL2 backends
shell: bash
run: |
status=0
for feature in SDL_ALSA SDL_PULSEAUDIO SDL_WAYLAND SDL_X11 SDL_LIBUDEV SDL_DBUS; do
if grep -qE "^-- ${feature} +\\(Wanted: ON\\): ON" _Build/configure.log; then
echo "ok ${feature}"
else
echo "FAIL ${feature} is not enabled"
status=1
fi
done
exit "$status"
- name: Build
shell: bash
run: |
cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \
--parallel
- name: Test
shell: bash
run: |
ctest --test-dir _Build/linux --output-on-failure \
-R '^(page_manager|memory_tracker)$'
- name: Install
shell: bash
run: |
cmake --install _Build/linux --prefix _Build/linux/install
- name: Verify artifacts
shell: bash
run: |
file _Build/linux/install/launcher
file _Build/linux/install/kyty_emulator
file _Build/linux/install/kyty_emulator | grep -q "ELF 64-bit LSB .*x86-64"
ldd _Build/linux/install/kyty_emulator > /dev/null
readelf -d _Build/linux/install/launcher | grep -q 'RPATH.*\$ORIGIN/lib'
while IFS= read -r -d '' binary; do
while read -r dependency; do
test -e "_Build/linux/install/lib/$dependency"
done < <(
readelf -d "$binary" |
sed -n 's/.*Shared library: \[\(libQt6[^]]*\|libicu[^]]*\)\].*/\1/p'
)
done < <(
find _Build/linux/install/launcher _Build/linux/install/plugins \
-type f \( -name launcher -o -name '*.so' \) -print0
)
_Build/linux/install/kyty_emulator --help > /dev/null
- name: Upload Linux artifacts
uses: actions/upload-artifact@v4
with:
name: KytyPS5-Linux-x86_64
path: _Build/linux/install/**
if-no-files-found: error
release:
name: Release KytyPS5
if: github.event_name == 'push'
needs: [windows, macos]
if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5'
needs: [windows, macos, linux]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -222,12 +349,16 @@ jobs:
run: |
windows_dir="artifacts/KytyPS5-Windows-x64"
macos_dir="artifacts/KytyPS5-macOS-x86_64"
linux_dir="artifacts/KytyPS5-Linux-x86_64"
test -d "$windows_dir"
test -d "$macos_dir"
test -d "$linux_dir"
chmod a+x \
"$macos_dir/launcher" \
"$macos_dir/kyty_emulator" \
"$macos_dir/libMoltenVK.dylib"
"$macos_dir/libMoltenVK.dylib" \
"$linux_dir/launcher" \
"$linux_dir/kyty_emulator"
(
cd "$windows_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
@@ -236,6 +367,8 @@ jobs:
cd "$macos_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
)
tar -C "$linux_dir" -czf \
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
- name: Create release
shell: bash
@@ -245,6 +378,7 @@ jobs:
assets=(
"$RELEASE_NAME-Windows-x64.zip"
"$RELEASE_NAME-macOS-x86_64.zip"
"$RELEASE_NAME-Linux-x86_64.tar.gz"
)
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release upload "$RELEASE_NAME" "${assets[@]}" \
+58 -11
View File
@@ -1,14 +1,14 @@
# KytyPS5
[![Build KytyPS5 (Windows)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Windows%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Build KytyPS5 (Linux)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml/badge.svg)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build-linux.yml)
[![Build KytyPS5 (Linux)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28Linux%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Build KytyPS5 (macOS)](https://img.shields.io/github/actions/workflow/status/KytyPS5/KytyPS5/build.yml?branch=main&event=push&label=Build%20KytyPS5%20%28macOS%29)](https://github.com/KytyPS5/KytyPS5/actions/workflows/build.yml)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20macOS%20x86__64-0078D4.svg)](#system-requirements)
[![Platform](https://img.shields.io/badge/platform-Windows%20x64%20%7C%20Linux%20x64%20%7C%20macOS%20x86__64-0078D4.svg)](#system-requirements)
[![Status](https://img.shields.io/badge/status-early%20development-orange.svg)](#current-status)
[![License](https://img.shields.io/badge/license-GPL--2.0-blue.svg)](LICENSE)
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows, with
experimental macOS support. It is based on a heavily modified version of
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
with experimental macOS support. It is based on a heavily modified version of
[Kyty](https://github.com/InoriRus/Kyty). The project is in an early stage of development, so
compatibility is limited and behavior may change significantly between builds.
@@ -24,7 +24,8 @@ KytyPS5 can boot 2D games and a selection of 3D games, including titles built wi
Development is focused on compatibility and boot reliability.
Linux support is planned. Windows remains the primary supported platform.
Windows is the primary platform and receives the most testing. Linux builds and runs; see
[Building on Linux](#building-on-linux).
macOS support is experimental. Compatibility with the same games on Windows and macOS has not yet
been tested.
@@ -64,8 +65,9 @@ graphical glitches, low compatibility, and poor performance.
Testing games and submitting detailed bug reports are useful ways to contribute. Search existing
issues first, then use the **Game Emulation Bug Report** template and attach the complete log file.
Code contributions should be focused, build successfully on Windows, and include relevant tests
where practical. Because KytyPS5 is still evolving quickly, consider opening an issue before
Code contributions should be focused, build successfully on the platforms they touch, and include
relevant tests where practical. Windows is the primary target, so a change that alters shared code
should not regress it; changes confined to a platform's own code paths only need to build there. Because KytyPS5 is still evolving quickly, consider opening an issue before
starting a large change.
### Formatting
@@ -100,11 +102,11 @@ the Vulkan/SPIR-V validation rules.
### System requirements
- Windows 10 version 1803
- Windows 10 version 1803, or a current Linux distribution
- A 64-bit x86 processor
- A Vulkan 1.3-capable GPU with current drivers
### Build requirements
### Build requirements (Windows)
- Git
- CMake 3.12 or newer
@@ -138,11 +140,48 @@ cmake --install _Build/windows --prefix _Build/windows/install
The finished application and its runtime dependencies will be placed in
`_Build/windows/install`.
### Building on Linux
Install the toolchain and the libraries the bundled SDL2 needs. Without the audio, Wayland and
udev development packages SDL2 quietly configures itself without those backends, and the resulting
build has no working sound and no gamepad hotplug:
```bash
sudo apt-get install --no-install-recommends \
clang lld ninja-build cmake git glslang-tools \
libgl1-mesa-dev libx11-dev libxcursor-dev libxext-dev libxfixes-dev \
libxi-dev libxrandr-dev libxss-dev libxkbcommon-dev \
libasound2-dev libpulse-dev libudev-dev libdbus-1-dev libwayland-dev wayland-protocols
```
Qt 6 (Concurrent, Network, Widgets) is also required — either the distribution packages
(`qt6-base-dev`) or an official Qt installation.
```bash
git submodule update --init --recursive
cmake -S src -B _Build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_PREFIX_PATH="$Qt6_DIR"
cmake --build _Build/linux --target launcher --parallel
cmake --install _Build/linux --prefix _Build/linux/install
```
The install step copies the Qt libraries and plugins next to the binaries, so
`_Build/linux/install` runs without a matching system Qt.
As on Windows, the MSVC compiler is not used; Clang is required. `cl.exe` is rejected at configure
time.
Note that the CMake source root is `src`, not the repository root.
### Visual Studio Code
A ready-made Visual Studio Code setup is included in [`.vscode`](.vscode). It configures CMake
Tools to build the project with Ninja and `clang-cl` and provides launch profiles for both
`launcher.exe` and `kyty_emulator.exe`.
`launcher.exe` and `kyty_emulator.exe`. It is Windows-only: VS Code settings cannot select a
compiler per platform, so on Linux configure from the command line as shown above.
Before using it:
@@ -164,6 +203,10 @@ To use the graphical launcher:
.\_Build\windows\install\launcher.exe
```
```bash
./_Build/linux/install/launcher
```
On first launch, add one or more game folders in the global settings. The launcher searches those
folders recursively for game directories containing `eboot.bin`. Select a detected game and run it
from the game list.
@@ -174,7 +217,11 @@ The emulator can also be started directly with a legally obtained game directory
.\_Build\windows\install\kyty_emulator.exe --game "D:\Games\ExampleGame"
```
Run `kyty_emulator.exe --help` to see the available graphics, logging, validation, profiling, and
```bash
./_Build/linux/install/kyty_emulator --game "/games/ExampleGame"
```
Run `kyty_emulator --help` to see the available graphics, logging, validation, profiling, and
debugging options.
### AI Use
+42 -10
View File
@@ -251,6 +251,26 @@ endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
# Linux system libraries required by the static FFmpeg archive.
if(LINUX)
find_package(Threads REQUIRED)
list(APPEND kyty_emulator_link_libraries m ${CMAKE_DL_LIBS} Threads::Threads)
# Optional FFmpeg dependencies.
find_package(ZLIB)
if(ZLIB_FOUND)
list(APPEND kyty_emulator_link_libraries ZLIB::ZLIB)
endif()
find_library(KYTY_BZ2_LIBRARY bz2)
if(KYTY_BZ2_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_BZ2_LIBRARY})
endif()
find_library(KYTY_LZMA_LIBRARY lzma)
if(KYTY_LZMA_LIBRARY)
list(APPEND kyty_emulator_link_libraries ${KYTY_LZMA_LIBRARY})
endif()
endif()
set(inc_headers
${CMAKE_CURRENT_SOURCE_DIR}
${KYTY_THIRD_PARTY_DIR}/SDL2/include
@@ -394,6 +414,14 @@ add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemo
target_compile_definitions(virtual_memory_allocation_tests PRIVATE
KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1)
# These tests use exceptions.
if(NOT KYTY_CLANG_CL)
foreach(kyty_exception_test scalar_provenance_tests resource_tracking_tests
virtual_memory_allocation_tests)
target_compile_options(${kyty_exception_test} PRIVATE -fexceptions)
endforeach()
endif()
if(BUILD_TESTING)
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
add_test(NAME memory_tracker COMMAND $<TARGET_FILE:memory_tracker_tests>)
@@ -410,23 +438,25 @@ if(BUILD_TESTING)
add_test(NAME gpu_tiler
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --gpu-tiler-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
if(WIN32)
# These tests still depend on the Windows multisample-depth path.
add_test(NAME texture_cache_image_overlap
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-overlap-only)
add_test(NAME texture_cache_htile_clear
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --htile-clear-only)
add_test(NAME texture_cache_layered_image
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --layered-image-only)
add_test(NAME texture_cache_image_views
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --image-view-cache-only)
add_test(NAME texture_cache_storage_sampled
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --storage-sampled-only)
add_test(NAME texture_cache_depth_readback
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --depth-readback-only)
add_test(NAME buffer_cache_ranges
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-range-only)
add_test(NAME buffer_cache_dirty_gc
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --buffer-cache-gc-only)
endif()
endif()
@@ -446,6 +476,8 @@ endif()
if (CLANG AND NOT KYTY_CLANG_CL)
target_link_libraries(kyty_emulator pthread)
endif()
# dlopen/dlsym/dladdr for RenderDoc.
target_link_libraries(kyty_emulator ${CMAKE_DL_LIBS})
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
+111 -28
View File
@@ -9,6 +9,11 @@
#elif defined(__APPLE__)
#include <csignal>
#include <sys/ucontext.h>
#else
#include <csignal>
#include <initializer_list>
#include <ucontext.h> // IWYU pragma: keep
#include <unistd.h>
#endif
// IWYU pragma: no_include <errhandlingapi.h>
@@ -19,7 +24,7 @@
namespace Common::HostException {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#if !defined(__APPLE__)
static std::atomic<Handler> g_handler {nullptr};
static std::atomic_uint32_t g_install_state {0};
@@ -33,7 +38,9 @@ static_assert(decltype(g_install_state)::is_always_lock_free);
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
#endif
std::_Exit(321);
}
@@ -51,6 +58,21 @@ public:
KYTY_CLASS_NO_COPY(FilterScope);
};
static Handler LoadInstalledHandler() noexcept {
if (g_install_state.load(std::memory_order_acquire) == 0) {
FailFast("host exception handler is not installed");
}
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
return handler;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
FilterScope filter_scope;
@@ -109,14 +131,7 @@ static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
info.r14 = exception->ContextRecord->R14;
info.r15 = exception->ContextRecord->R15;
if (g_install_state.load(std::memory_order_acquire) == 0) {
FailFast("host exception handler is not installed");
}
const auto handler = g_handler.load(std::memory_order_acquire);
if (handler == nullptr) {
FailFast("host exception callback is null");
}
const auto handler = LoadInstalledHandler();
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
}
@@ -213,10 +228,79 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
sigaction(sig, &dfl, nullptr);
}
#else
// x86-64 page-fault error bits.
constexpr uint64_t PAGE_FAULT_ERROR_WRITE = 0x02;
constexpr uint64_t PAGE_FAULT_ERROR_INSTRUCTION = 0x10;
// Let the kernel handle an unresolved fault on retry.
static void ChainToDefault(int signal_number) noexcept {
struct sigaction restore {};
restore.sa_handler = SIG_DFL;
sigemptyset(&restore.sa_mask);
restore.sa_flags = 0;
::sigaction(signal_number, &restore, nullptr);
}
static void SignalHandler(int signal_number, siginfo_t* signal_info, void* native_context) {
FilterScope filter_scope;
auto* context = static_cast<ucontext_t*>(native_context);
auto* gregs = context->uc_mcontext.gregs;
ExceptionInfo info {};
info.exception_address = static_cast<uint64_t>(gregs[REG_RIP]);
info.native_code = static_cast<uint32_t>(signal_number);
info.native_context = context;
if (signal_number == SIGSEGV || signal_number == SIGBUS) {
info.type = ExceptionType::AccessViolation;
const auto error_code = static_cast<uint64_t>(gregs[REG_ERR]);
if ((error_code & PAGE_FAULT_ERROR_INSTRUCTION) != 0) {
info.access_violation_type = AccessViolationType::Execute;
} else if ((error_code & PAGE_FAULT_ERROR_WRITE) != 0) {
info.access_violation_type = AccessViolationType::Write;
} else {
info.access_violation_type = AccessViolationType::Read;
}
info.access_violation_vaddr = reinterpret_cast<uint64_t>(signal_info->si_addr);
} else if (signal_number == SIGILL) {
info.type = ExceptionType::IllegalInstruction;
} else {
ChainToDefault(signal_number);
return;
}
info.rax = static_cast<uint64_t>(gregs[REG_RAX]);
info.rbx = static_cast<uint64_t>(gregs[REG_RBX]);
info.rcx = static_cast<uint64_t>(gregs[REG_RCX]);
info.rdx = static_cast<uint64_t>(gregs[REG_RDX]);
info.rsi = static_cast<uint64_t>(gregs[REG_RSI]);
info.rdi = static_cast<uint64_t>(gregs[REG_RDI]);
info.rbp = static_cast<uint64_t>(gregs[REG_RBP]);
info.rsp = static_cast<uint64_t>(gregs[REG_RSP]);
info.r8 = static_cast<uint64_t>(gregs[REG_R8]);
info.r9 = static_cast<uint64_t>(gregs[REG_R9]);
info.r10 = static_cast<uint64_t>(gregs[REG_R10]);
info.r11 = static_cast<uint64_t>(gregs[REG_R11]);
info.r12 = static_cast<uint64_t>(gregs[REG_R12]);
info.r13 = static_cast<uint64_t>(gregs[REG_R13]);
info.r14 = static_cast<uint64_t>(gregs[REG_R14]);
info.r15 = static_cast<uint64_t>(gregs[REG_R15]);
const auto handler = LoadInstalledHandler();
if (handler(info)) {
return;
}
ChainToDefault(signal_number);
}
#endif
bool InstallHandler(Handler handler) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (handler == nullptr) {
return false;
}
@@ -228,27 +312,14 @@ bool InstallHandler(Handler handler) {
g_handler.store(handler, std::memory_order_release);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("AddVectoredExceptionHandler() failed\n");
return false;
}
g_install_state.store(2, std::memory_order_release);
return true;
#elif defined(__APPLE__)
if (handler == nullptr) {
return false;
}
uint32_t expected_state = 0;
if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) {
return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler;
}
g_handler.store(handler, std::memory_order_release);
struct sigaction sa {};
sa.sa_sigaction = SignalHandler;
sa.sa_flags = SA_SIGINFO;
@@ -264,13 +335,25 @@ bool InstallHandler(Handler handler) {
printf("sigaction() failed to install the host fault handler\n");
return false;
}
#else
struct sigaction action {};
action.sa_sigaction = SignalHandler;
sigemptyset(&action.sa_mask);
// Fault resolution needs the normal thread stack.
action.sa_flags = SA_SIGINFO | SA_RESTART;
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
if (::sigaction(signal_number, &action, nullptr) != 0) {
g_handler.store(nullptr, std::memory_order_release);
g_install_state.store(0, std::memory_order_release);
printf("sigaction(%d) failed\n", signal_number);
return false;
}
}
#endif
g_install_state.store(2, std::memory_order_release);
return true;
#else
(void)handler;
return false;
#endif
}
} // namespace Common::HostException
+4
View File
@@ -23,6 +23,10 @@ struct sys_dbg_stack_info_t {
size_t commited_size;
size_t total_size;
size_t code_size;
// Full stack reservation reported by pthread.
uintptr_t reserved_addr;
size_t reserved_size;
#endif
};
+76 -4
View File
@@ -8,6 +8,8 @@
#include <cstdlib>
#include <cstring>
#include <execinfo.h>
#include <pthread.h>
#include <sys/param.h>
#include <sys/types.h>
#include <unistd.h>
@@ -15,8 +17,48 @@
#include <libgen.h> // POSIX basename() lives here on macOS, not in <cstring>
#endif
void SysStackWalk(void** /*stack*/, int* depth) {
*depth = 0;
// Avoid unwinding a guest-owned stack.
static bool OnOwnStack() {
const char* probe = reinterpret_cast<const char*>(&probe);
pthread_attr_t attr {};
#if defined(__APPLE__)
const auto* top = static_cast<const char*>(pthread_get_stackaddr_np(pthread_self()));
const auto size = pthread_get_stacksize_np(pthread_self());
(void)attr;
return top != nullptr && size != 0 && probe < top && probe >= top - size;
#else
if (pthread_getattr_np(pthread_self(), &attr) != 0) {
return false;
}
void* base = nullptr;
size_t size = 0;
const bool ok =
pthread_attr_getstack(&attr, &base, &size) == 0 && base != nullptr && size != 0;
pthread_attr_destroy(&attr);
if (!ok) {
return false;
}
const auto* low = static_cast<const char*>(base);
return probe >= low && probe < low + size;
#endif
}
void SysStackWalk(void** stack, int* depth) {
if (stack == nullptr || depth == nullptr || *depth <= 0) {
if (depth != nullptr) {
*depth = 0;
}
return;
}
if (!OnOwnStack()) {
*depth = 0;
return;
}
const int n = ::backtrace(stack, *depth);
*depth = (n < 0 ? 0 : n);
}
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
@@ -33,6 +75,33 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
[[maybe_unused]] int result = 0;
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
// Record the reservation before the Linux /proc walk.
{
pthread_attr_t self_attr {};
#if defined(__APPLE__)
void* stack_top = pthread_get_stackaddr_np(pthread_self());
const size_t stack_size = pthread_get_stacksize_np(pthread_self());
if (stack_top != nullptr && stack_size != 0) {
s.reserved_addr = reinterpret_cast<uintptr_t>(stack_top) - stack_size;
s.reserved_size = stack_size;
}
(void)self_attr;
#else
if (pthread_getattr_np(pthread_self(), &self_attr) == 0) {
void* stack_base = nullptr;
size_t stack_size = 0;
if (pthread_attr_getstack(&self_attr, &stack_base, &stack_size) == 0 &&
stack_base != nullptr && stack_size != 0) {
s.reserved_addr = reinterpret_cast<uintptr_t>(stack_base);
s.reserved_size = stack_size;
}
pthread_attr_destroy(&self_attr);
}
#endif
}
char str[1024];
char str2[1024];
result = sprintf(str, "/proc/%d/exe", static_cast<int>(pid));
@@ -46,8 +115,6 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
result = sprintf(str, "/proc/%d/maps", static_cast<int>(pid));
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
FILE* f = fopen(str, "r");
if (f == nullptr) {
@@ -121,6 +188,11 @@ void SysStackUsage(sys_dbg_stack_info_t& s) {
}
result = fclose(f);
if (s.reserved_addr == 0) {
s.reserved_addr = s.addr;
s.reserved_size = s.total_size;
}
}
#endif
+187 -39
View File
@@ -12,7 +12,10 @@
#include <cerrno>
#include <cstdlib>
#include <dirent.h>
#include <fcntl.h>
#include <filesystem>
#include <sys/stat.h>
#include <system_error>
#include <unistd.h>
#include <utime.h>
@@ -40,10 +43,43 @@ struct sys_file_t {
};
};
// Darwin uses BSD timestamp member names.
#if defined(__APPLE__)
#define KYTY_STAT_ATIME_NS(st) ((st).st_atimespec.tv_nsec)
#define KYTY_STAT_MTIME_NS(st) ((st).st_mtimespec.tv_nsec)
#else
#define KYTY_STAT_ATIME_NS(st) ((st).st_atim.tv_nsec)
#define KYTY_STAT_MTIME_NS(st) ((st).st_mtim.tv_nsec)
#endif
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
}
// Pass access-pattern hints to the host.
static void apply_cache_hint(FILE* f, sys_file_cache_type_t cache_type) {
if (f == nullptr) {
return;
}
#if !defined(__APPLE__)
int advice = POSIX_FADV_NORMAL;
switch (cache_type) {
case SYS_FILE_CACHE_RANDOM_ACCESS: advice = POSIX_FADV_RANDOM; break;
case SYS_FILE_CACHE_SEQUENTIAL_SCAN: advice = POSIX_FADV_SEQUENTIAL; break;
case SYS_FILE_CACHE_AUTO:
default: return;
}
::posix_fadvise(fileno(f), 0, 0, advice);
#else
if (cache_type == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
::fcntl(fileno(f), F_RDAHEAD, 1);
} else if (cache_type == SYS_FILE_CACHE_RANDOM_ACCESS) {
::fcntl(fileno(f), F_RDAHEAD, 0);
}
#endif
}
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
if (f.type == SYS_FILE_FILE) {
size_t w = fread(data, 1, size, f.f);
@@ -137,7 +173,7 @@ sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
}
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) {
sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
ret->type = SYS_FILE_FILE;
@@ -151,6 +187,8 @@ sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
ret->type = SYS_FILE_ERROR;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
@@ -181,7 +219,7 @@ sys_file_t* SysFileCreate() {
}
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) {
sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
@@ -195,13 +233,15 @@ sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
ret->type = SYS_FILE_FILE;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
}
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
sys_file_cache_type_t /*cache_type*/) {
sys_file_cache_type_t cache_type) {
auto* ret = new sys_file_t;
auto real_name = get_internal_name(file_name);
@@ -215,6 +255,8 @@ sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
ret->type = SYS_FILE_FILE;
}
apply_cache_hint(f, cache_type);
ret->f = f;
return ret;
@@ -240,11 +282,17 @@ uint64_t SysFileSize(sys_file_t& f) {
[[maybe_unused]] int result = 0;
if (f.type == SYS_FILE_FILE) {
uint32_t pos = ftell(f.f);
result = fseek(f.f, 0, SEEK_END);
uint32_t size = ftell(f.f);
result = fseek(f.f, pos, SEEK_SET);
return size;
// Preserve sizes above 4 GiB.
const off_t pos = ftello(f.f);
if (pos < 0) {
return 0;
}
if (fseeko(f.f, 0, SEEK_END) != 0) {
return 0;
}
const off_t size = ftello(f.f);
result = fseeko(f.f, pos, SEEK_SET);
return (size < 0 ? 0 : static_cast<uint64_t>(size));
}
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
@@ -261,8 +309,18 @@ uint64_t SysFileSize(const std::filesystem::path& file_name) {
return size;
}
bool SysFileTruncate(sys_file_t& /*f*/, uint64_t /*size*/) {
return false;
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
bool ok = false;
if (f.type == SYS_FILE_FILE) {
// Flush before resizing and restore the caller's position.
const auto position = ftell(f.f);
fflush(f.f);
ok = (ftruncate(fileno(f.f), static_cast<off_t>(size)) == 0);
if (position >= 0) {
fseek(f.f, position, SEEK_SET);
}
}
return ok;
}
bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) {
@@ -383,6 +441,7 @@ SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name)
} else {
r.is_invalid = false;
r.time = s.st_atime;
r.nanos = KYTY_STAT_ATIME_NS(s);
}
return r;
@@ -401,6 +460,7 @@ SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name)
} else {
r.is_invalid = false;
r.time = s.st_mtime;
r.nanos = KYTY_STAT_MTIME_NS(s);
}
return r;
@@ -420,13 +480,36 @@ void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysF
a.is_invalid = false;
w.is_invalid = false;
a.time = s.st_atime;
a.nanos = KYTY_STAT_ATIME_NS(s);
w.time = s.st_mtime;
w.nanos = KYTY_STAT_MTIME_NS(s);
}
}
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& /*f*/, SysFileTimeStruct& /*a*/,
SysFileTimeStruct& /*w*/) {
EXIT("not implemented\n");
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a,
SysFileTimeStruct& w) {
if (f.type == SYS_FILE_FILE) {
struct stat s {};
const bool ok = (0 == fstat(fileno(f.f), &s));
a.is_invalid = w.is_invalid = !ok;
if (ok) {
a.time = s.st_atime;
a.nanos = KYTY_STAT_ATIME_NS(s);
w.time = s.st_mtime;
w.nanos = KYTY_STAT_MTIME_NS(s);
}
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
// Memory-backed files use the current time.
SysTimeStruct t {};
SysGetSystemTimeUtc(t);
SysSystemToFileTimeUtc(t, a);
SysSystemToFileTimeUtc(t, w);
} else {
a.is_invalid = w.is_invalid = true;
}
}
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
@@ -532,45 +615,110 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
// }
}
void SysFileFindFiles(const std::filesystem::path& /*path*/,
std::vector<sys_file_find_t>& /*out*/) {
EXIT("not implemented\n");
}
// Recursively collect regular files.
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
auto real_path = get_internal_name(path);
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
DIR* dir = opendir(path.c_str());
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
sys_dir_entry_t r {};
r.name = entry->d_name;
if (entry->d_type == DT_REG) {
r.is_file = true;
} else if (entry->d_type == DT_DIR) {
r.is_file = false;
} else {
// DT_UNKNOWN / symlink: resolve with stat.
struct stat st {};
r.is_file = (stat((path / entry->d_name).c_str(), &st) == 0) ? S_ISREG(st.st_mode) : true;
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
const std::string file_name(entry->d_name);
if (file_name == "." || file_name == "..") {
continue;
}
auto child = real_path / file_name;
struct stat s {};
// lstat, so a symlink is never followed into a cycle during the recursive walk.
if (0 != lstat(child.string().c_str(), &s)) {
continue;
}
if (S_ISDIR(s.st_mode)) {
SysFileFindFiles(child, out);
} else if (S_ISREG(s.st_mode)) {
sys_file_find_t r {};
r.path_with_name = child;
r.size = static_cast<uint64_t>(s.st_size);
r.last_access_time.is_invalid = false;
r.last_access_time.time = s.st_atime;
r.last_access_time.nanos = KYTY_STAT_ATIME_NS(s);
r.last_write_time.is_invalid = false;
r.last_write_time.time = s.st_mtime;
r.last_write_time.nanos = KYTY_STAT_MTIME_NS(s);
out.push_back(r);
}
out.push_back(std::move(r));
}
closedir(dir);
}
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
EXIT("not implemented\n");
return false;
// Keep "." and ".." to match FindFirstFileW.
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
auto real_path = get_internal_name(path);
DIR* dir = opendir(real_path.string().c_str());
if (dir == nullptr) {
return;
}
for (const dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) {
sys_dir_entry_t r {};
r.name = entry->d_name;
if (entry->d_type == DT_UNKNOWN) {
// Some filesystems do not populate d_type.
struct stat s {};
r.is_file = 0 == lstat((real_path / r.name).string().c_str(), &s) && S_ISREG(s.st_mode);
} else {
r.is_file = entry->d_type != DT_DIR;
}
out.push_back(r);
}
closedir(dir);
}
bool SysFileMoveFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
EXIT("not implemented\n");
return false;
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
std::error_code error;
return std::filesystem::copy_file(get_internal_name(src), get_internal_name(dst),
std::filesystem::copy_options::overwrite_existing, error) &&
!error;
}
void SysFileRemoveReadonly(const std::filesystem::path& /*name*/) {
EXIT("not implemented\n");
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
auto real_src = get_internal_name(src);
auto real_dst = get_internal_name(dst);
// Match MoveFileW: fail when the destination exists.
std::error_code error;
if (std::filesystem::exists(real_dst, error)) {
return false;
}
return 0 == rename(real_src.string().c_str(), real_dst.string().c_str());
}
void SysFileRemoveReadonly(const std::filesystem::path& name) {
auto real_name = get_internal_name(name);
auto real_name_str = real_name.string();
struct stat s {};
if (0 != stat(real_name_str.c_str(), &s)) {
return;
}
chmod(real_name_str.c_str(), s.st_mode | S_IWUSR);
}
#endif
+125 -21
View File
@@ -8,9 +8,11 @@
#include "common/platform/sysVirtual.h"
#include "common/virtualMemory.h"
#include <atomic>
#include <map>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#if defined(__APPLE__)
#include <mach/mach.h>
@@ -81,6 +83,70 @@ static VirtualMemory::Mode get_protection_flag(int mode) {
}
}
// Keep automatic mappings inside the guest and GPU-addressable low window.
#ifdef KYTY_FIXED_NOREPLACE
static constexpr uintptr_t LOW_ARENA_LIMIT = 0x000000FC00000000ULL; // libc mspace window ceiling
static constexpr uintptr_t LOW_ARENA_FLOOR = 0x000000A000000000ULL; // 640 GiB
static constexpr uintptr_t LOW_ARENA_GRAIN = 0x0000000000010000ULL; // 64 KiB
static_assert(LOW_ARENA_LIMIT <= 0x0000010000000000ULL,
"arena must stay inside the GPU page tracker's 1<<40 window");
static_assert(LOW_ARENA_FLOOR < LOW_ARENA_LIMIT, "arena floor must sit below its ceiling");
static std::atomic<uintptr_t> g_low_arena_next {LOW_ARENA_LIMIT};
#endif
// Caller holds g_virtual_mutex.
static void record_alloc(uintptr_t addr, size_t size) {
auto next = g_allocs->upper_bound(addr);
if (next != g_allocs->begin()) {
auto it = std::prev(next);
const auto alloc_addr = it->first;
const auto alloc_end = alloc_addr + it->second;
if (alloc_addr <= addr && addr + size <= alloc_end) {
g_allocs->erase(it);
if (alloc_addr < addr) {
(*g_allocs)[alloc_addr] = addr - alloc_addr;
}
if (addr + size < alloc_end) {
(*g_allocs)[addr + size] = alloc_end - (addr + size);
}
}
}
(*g_allocs)[addr] = size;
}
#ifdef KYTY_FIXED_NOREPLACE
static uintptr_t align_up_to(uintptr_t addr, uint64_t alignment) {
return (addr + alignment - 1) & ~(alignment - 1);
}
#endif
// Freed arena addresses are not reused while GPU caches remain keyed by address.
static void* map_anonymous(uintptr_t addr, size_t size, int protect, int flags) {
if (addr != 0) {
return mmap(reinterpret_cast<void*>(addr), size, protect, flags, -1, 0); // NOLINT
}
#ifdef KYTY_FIXED_NOREPLACE
const auto step = align_up_to(size, LOW_ARENA_GRAIN);
for (int attempt = 0; attempt < 256; attempt++) {
const auto top = g_low_arena_next.fetch_sub(step, std::memory_order_relaxed);
if (top < step || top - step < LOW_ARENA_FLOOR) {
break;
}
const auto hint = (top - step) & ~(LOW_ARENA_GRAIN - 1);
void* ptr = mmap(reinterpret_cast<void*>(hint), size, protect,
flags | MAP_FIXED_NOREPLACE, -1, 0); // NOLINT
if (ptr != MAP_FAILED) {
return ptr;
}
}
#endif
return mmap(nullptr, size, protect, flags, -1, 0); // NOLINT
}
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
EXIT_IF(g_allocs == nullptr);
@@ -88,14 +154,13 @@ uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mo
int protect = get_protection_flag(mode);
void* ptr =
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -122,16 +187,15 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
auto addr = static_cast<uintptr_t>(address);
int protect = get_protection_flag(mode);
void* ptr =
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
void* ptr = map_anonymous(addr, size, protect, MAP_PRIVATE | MAP_ANON);
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
munmap(ptr, size);
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, protect,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
ptr = map_anonymous(addr, size + alignment, protect,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) {
#if defined(__APPLE__)
@@ -186,7 +250,7 @@ uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::
}
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -272,7 +336,7 @@ bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode m
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -303,16 +367,15 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
auto addr = static_cast<uintptr_t>(address);
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
void* ptr = map_anonymous(addr, size, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
munmap(ptr, size);
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
ptr = map_anonymous(addr, size + alignment, PROT_NONE,
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE);
ret_addr = reinterpret_cast<uintptr_t>(ptr);
if (ptr != MAP_FAILED) {
#if defined(__APPLE__)
@@ -368,7 +431,7 @@ uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alig
}
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -406,7 +469,7 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
if (ptr != MAP_FAILED) {
pthread_mutex_lock(&g_virtual_mutex);
(*g_allocs)[ret_addr] = size;
record_alloc(ret_addr, size);
uintptr_t page_start = ret_addr >> 12u;
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
for (uintptr_t page = page_start; page <= page_end; page++) {
@@ -421,7 +484,29 @@ bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
}
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
return SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess);
// Drop physical pages while preserving the reservation.
if (!SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess)) {
return false;
}
if (size != 0) {
#if defined(__APPLE__)
constexpr int RECLAIM_ADVICE = MADV_FREE;
#else
constexpr int RECLAIM_ADVICE = MADV_DONTNEED;
#endif
const auto page_size = static_cast<uintptr_t>(sysconf(_SC_PAGESIZE));
if (page_size != 0) {
// Do not discard pages outside the requested range.
const auto begin = (static_cast<uintptr_t>(address) + page_size - 1) & ~(page_size - 1);
const auto end = (static_cast<uintptr_t>(address) + size) & ~(page_size - 1);
if (end > begin) {
::madvise(reinterpret_cast<void*>(begin), end - begin, RECLAIM_ADVICE);
}
}
}
return true;
}
bool SysVirtualFree(uint64_t address) {
@@ -473,15 +558,34 @@ bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
auto it = std::prev(next);
const auto alloc_addr = it->first;
const auto alloc_end = alloc_addr + it->second;
if (addr < alloc_addr || end > alloc_end || munmap(reinterpret_cast<void*>(addr), size) != 0) {
// A reservation may have been split into several adjacent records.
auto first = std::prev(next);
const auto alloc_addr = first->first;
if (addr < alloc_addr || alloc_addr + first->second <= addr) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
g_allocs->erase(it);
auto last = first;
uintptr_t cursor = alloc_addr + first->second;
while (cursor < end) {
auto following = std::next(last);
if (following == g_allocs->end() || following->first != cursor) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
last = following;
cursor = following->first + following->second;
}
const auto alloc_end = cursor;
if (munmap(reinterpret_cast<void*>(addr), size) != 0) {
pthread_mutex_unlock(&g_virtual_mutex);
return false;
}
g_allocs->erase(first, std::next(last));
if (alloc_addr < addr) {
(*g_allocs)[alloc_addr] = addr - alloc_addr;
}
+11 -7
View File
@@ -27,7 +27,9 @@ struct SysFileTimeStruct {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
FILETIME time;
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
// Nanoseconds preserve sub-second file timestamps.
time_t time;
long nanos;
#endif
bool is_invalid;
};
@@ -139,7 +141,7 @@ inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t)
t.Hour = i.tm_hour;
t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0;
t.Milliseconds = static_cast<uint16_t>((f.nanos / 1000000) % 1000);
}
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
@@ -168,10 +170,11 @@ inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t)
// Retrieves the current local date and time.
inline void SysGetSystemTime(SysTimeStruct& t) {
time_t st {};
// Preserve millisecond precision.
timespec now {};
struct tm i {};
if (time(&st) == static_cast<time_t>(-1) || localtime_r(&st, &i) == nullptr) {
if (clock_gettime(CLOCK_REALTIME, &now) != 0 || localtime_r(&now.tv_sec, &i) == nullptr) {
t.is_invalid = true;
return;
}
@@ -183,15 +186,16 @@ inline void SysGetSystemTime(SysTimeStruct& t) {
t.Hour = i.tm_hour;
t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0;
t.Milliseconds = static_cast<uint16_t>((now.tv_nsec / 1000000) % 1000);
}
// Retrieves the current system date and time in Coordinated Universal Time (UTC).
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
time_t st {};
// Preserve millisecond precision.
timespec now {};
struct tm i {};
if (time(&st) == static_cast<time_t>(-1) || gmtime_r(&st, &i) == nullptr) {
if (clock_gettime(CLOCK_REALTIME, &now) != 0 || gmtime_r(&now.tv_sec, &i) == nullptr) {
t.is_invalid = true;
return;
}
@@ -203,7 +207,7 @@ inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
t.Hour = i.tm_hour;
t.Minute = i.tm_min;
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
t.Milliseconds = 0;
t.Milliseconds = static_cast<uint16_t>((now.tv_nsec / 1000000) % 1000);
}
inline void SysQueryPerformanceFrequency(uint64_t* freq) {
+47
View File
@@ -7,6 +7,7 @@
#include <atomic>
#include <chrono> // IWYU pragma: keep
#include <condition_variable> // IWYU pragma: keep
#include <cerrno>
#include <mutex>
#include <vector>
@@ -14,6 +15,12 @@
#define KYTY_WIN_CS
#endif
// macOS has no clock_nanosleep.
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && !defined(__APPLE__)
#define KYTY_POSIX_HIGH_RES_SLEEP
#include <ctime>
#endif
#include <sstream>
#include <string>
#include <thread>
@@ -121,6 +128,42 @@ static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
#endif
#ifdef KYTY_POSIX_HIGH_RES_SLEEP
// Spin for very short waits; use an absolute deadline for longer waits.
static void SleepHighResolutionNanos(uint64_t nanos) {
if (nanos == 0) {
return;
}
constexpr uint64_t NANOS_PER_SEC = 1000000000;
constexpr uint64_t SPIN_LIMIT_NS = 50000; // below this a context switch dominates
timespec deadline {};
if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) {
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
return;
}
auto target_nsec = static_cast<uint64_t>(deadline.tv_nsec) + nanos;
deadline.tv_sec += static_cast<time_t>(target_nsec / NANOS_PER_SEC);
deadline.tv_nsec = static_cast<long>(target_nsec % NANOS_PER_SEC);
if (nanos <= SPIN_LIMIT_NS) {
timespec now {};
do {
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
return;
}
} while (now.tv_sec < deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec < deadline.tv_nsec));
return;
}
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, nullptr) == EINTR) {
}
}
#endif
namespace Common {
using thread_id_t = std::thread::id;
@@ -249,6 +292,8 @@ void Thread::Sleep(uint32_t millis) {
void Thread::SleepMicro(uint32_t micros) {
#ifdef KYTY_WIN_CS
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(static_cast<uint64_t>(micros) * 1000);
#else
std::this_thread::sleep_for(std::chrono::microseconds(micros));
#endif
@@ -257,6 +302,8 @@ void Thread::SleepMicro(uint32_t micros) {
void Thread::SleepNano(uint64_t nanos) {
#ifdef KYTY_WIN_CS
SleepHighResolution100ns((nanos + 99) / 100);
#elif defined(KYTY_POSIX_HIGH_RES_SLEEP)
SleepHighResolutionNanos(nanos);
#else
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
#endif
+206 -23
View File
@@ -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;
+6
View File
@@ -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
+109 -11
View File
@@ -19,12 +19,16 @@
#include <windows.h>
#undef min
#undef max
#else
#include <dlfcn.h>
// RenderDoc uses Windows-style names in its cross-platform API.
#define __cdecl
using HMODULE = void*;
#endif
namespace Libs::Graphics {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
using RenderDocDevicePointer = void*;
using RenderDocWindowHandle = void*;
@@ -107,6 +111,8 @@ static RenderDocDevicePointer GetRenderDocDevicePointer(vk::Instance instance) {
return VulkanHandleToPointer(instance);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool BindRenderDocApi(HMODULE module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(module, "RENDERDOC_GetAPI"));
if (get_api == nullptr) {
@@ -147,10 +153,84 @@ static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
return info.info.win.window;
}
#else
static bool BindRenderDocApi(HMODULE module) {
auto* get_api = reinterpret_cast<pRENDERDOC_GetAPI>(::dlsym(module, "RENDERDOC_GetAPI"));
if (get_api == nullptr) {
return false;
}
void* api = nullptr;
if (get_api(eRENDERDOC_API_Version_1_4_2, &api) == 0 || api == nullptr) {
return false;
}
g_module = module;
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
static RenderDocInputButton capture_keys[] = {eRENDERDOC_Key_F1};
g_api->SetCaptureKeys(capture_keys, 1);
g_api->UnloadCrashHandler();
Dl_info info {};
if (::dladdr(reinterpret_cast<void*>(get_api), &info) != 0 && info.dli_fname != nullptr) {
LOGF("RenderDoc: bound API from %s\n", info.dli_fname);
} else {
LOGF("RenderDoc: bound API\n");
}
return true;
}
static RenderDocWindowHandle GetRenderDocWindowHandle(SDL_Window* window) {
if (window == nullptr) {
return nullptr;
}
SDL_SysWMinfo info {};
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info) != SDL_TRUE) {
return nullptr;
}
#if defined(SDL_VIDEO_DRIVER_X11)
if (info.subsystem == SDL_SYSWM_X11) {
// RenderDoc takes the raw xlib Window id in the pointer slot, not a Display*.
return reinterpret_cast<RenderDocWindowHandle>(
static_cast<uintptr_t>(info.info.x11.window));
}
#endif
// Wayland capture works without an active-window handle.
static std::atomic_bool logged = false;
if (!logged.exchange(true)) {
LOGF("RenderDoc: no native window handle for SDL subsystem %d (Wayland?); the in-app "
"overlay is unavailable, but --rd captures still work\n",
static_cast<int>(info.subsystem));
}
return nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr && g_window != nullptr;
}
#else
static bool IsAvailable() {
return g_api != nullptr && g_device != nullptr;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
@@ -187,6 +267,33 @@ void RenderDocInit() {
}
}
#else
void RenderDocInit() {
bool expected = false;
if (!g_init_done.compare_exchange_strong(expected, true)) {
return;
}
// Prefer an injected RenderDoc instance.
auto* module = ::dlopen("librenderdoc.so", RTLD_NOW | RTLD_NOLOAD);
if (module == nullptr) {
module = ::dlopen("librenderdoc.so", RTLD_NOW);
}
if (module == nullptr) {
LOGF("RenderDoc: librenderdoc.so was not found; in-app capture disabled\n");
return;
}
if (!BindRenderDocApi(module)) {
LOGF("RenderDoc: API 1.4.2 is not available; in-app capture disabled\n");
::dlclose(module);
return;
}
}
#endif
void RenderDocSetActiveWindow(vk::Instance instance, SDL_Window* window) {
if (g_api == nullptr) {
return;
@@ -274,13 +381,4 @@ void RenderDocOnPresent() {
}
}
#else
void RenderDocInit() {}
void RenderDocSetActiveWindow(vk::Instance /*instance*/, SDL_Window* /*window*/) {}
void RenderDocRequestCapture() {}
void RenderDocOnPresent() {}
#endif
} // namespace Libs::Graphics
+60
View File
@@ -21,6 +21,7 @@
#include <cstring>
#include <filesystem>
#include <random>
#include <system_error>
#include <vector>
namespace Libs::LibKernel::FileSystem {
@@ -153,6 +154,11 @@ void FileDescriptors::DeleteDescriptor(int d) {
EXIT_IF(m_files[index] == nullptr);
EXIT_IF(m_files[index]->opened);
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// Close host files opened before a failed descriptor setup.
m_files[index]->f.Close();
#endif
delete m_files[index];
m_files[index] = nullptr;
}
@@ -223,6 +229,52 @@ void MountPoints::Umount(const std::string& folder_or_point) {
}
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// Resolve guest paths case-insensitively on case-sensitive hosts.
static std::filesystem::path ResolvePathIgnoringCase(const std::filesystem::path& path) {
std::error_code ec;
if (std::filesystem::exists(path, ec)) {
return path;
}
// Preserve unmatched components for the caller's ENOENT path.
std::filesystem::path resolved = path.has_root_path() ? path.root_path() : std::filesystem::path(".");
bool matched = true;
for (const auto& component: path.relative_path()) {
if (component.empty()) {
continue;
}
auto candidate = resolved / component;
if (!matched || std::filesystem::exists(candidate, ec)) {
resolved = std::move(candidate);
continue;
}
bool found = false;
for (std::filesystem::directory_iterator entry(resolved, ec), end; entry != end;
entry.increment(ec)) {
if (ec) {
break;
}
if (Common::EqualNoCase(entry->path().filename().string(), component.string())) {
resolved = entry->path();
found = true;
break;
}
}
if (!found) {
resolved = std::move(candidate);
matched = false;
}
}
return resolved;
}
#endif
std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_file_name) {
Common::LockGuard lock(m_mutex);
@@ -239,7 +291,11 @@ std::filesystem::path MountPoints::GetRealFilename(const std::string& mounted_fi
while (Common::StartsWith(rel_path, '/')) {
rel_path = Common::RemoveFirst(rel_path, 1);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return p.dir / rel_path;
#else
return ResolvePathIgnoringCase(p.dir / rel_path);
#endif
}
return mounted_file_name;
@@ -260,7 +316,11 @@ std::filesystem::path MountPoints::GetRealDirectory(const std::string& mounted_d
while (Common::StartsWith(rel_path, '/')) {
rel_path = Common::RemoveFirst(rel_path, 1);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
return p.dir / rel_path;
#else
return ResolvePathIgnoringCase(p.dir / rel_path);
#endif
}
return mounted_directory;
+14
View File
@@ -242,6 +242,13 @@ static bool VirtualRangesOverlap(uint64_t left_start, uint64_t left_size, uint64
static bool CommitFixedHostRange(uint64_t start, uint64_t size, VirtualMemory::Mode mode) {
constexpr uint64_t PAGE_SIZE = 0x4000;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// Prefer one syscall, then fall back to the per-page path.
if (size > PAGE_SIZE && VirtualMemory::AllocFixed(start, size, mode)) {
return true;
}
#endif
for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {};
@@ -2992,6 +2999,13 @@ static bool ReserveFixedHostRange(uint64_t start, uint64_t size) {
g_test_host_reservation_pages_before_failure = UINT32_MAX;
#endif
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// Prefer one syscall, then fall back to the per-page path.
if (size > PAGE_SIZE && VirtualMemory::ReserveFixed(start, size)) {
return true;
}
#endif
bool host_mutated = false;
for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
+7 -1
View File
@@ -385,9 +385,12 @@ public:
m_maps.emplace(old.vaddr, old);
return false;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (preserve_placeholder && placeholder_preserved != nullptr) {
*placeholder_preserved = true;
}
#endif
// Only Windows preserves placeholders.
return true;
}
@@ -1079,7 +1082,10 @@ private:
#endif
}
static bool UnmapViewPreservePlaceholder(void* /*vaddr*/, uint64_t /*size*/) { return false; }
// POSIX has no placeholder equivalent.
static bool UnmapViewPreservePlaceholder(void* vaddr, uint64_t size) {
return munmap(vaddr, size) == 0;
}
int m_fd = -1;
#endif
+109 -35
View File
@@ -46,6 +46,17 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <fmt/format.h>
#include <pthread_time.h>
#elif !defined(__APPLE__)
#include <sys/syscall.h>
#include <unistd.h>
#endif
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
#include <csignal> // pthread_kill is declared here, not in <pthread.h>
#endif
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && (defined(_M_X64) || defined(__x86_64__))
#include <x86intrin.h>
#endif
#ifdef pthread_attr_getguardsize
@@ -99,8 +110,21 @@ static constexpr int KERNEL_PTHREAD_MUTEX_RECURSIVE = 2;
static constexpr int KERNEL_PTHREAD_MUTEX_NORMAL = 3;
static constexpr int KERNEL_PTHREAD_MUTEX_ADAPTIVE = 4;
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// OS-level thread id reported to the guest.
static uint64_t GetHostThreadId() {
#if defined(__APPLE__)
uint64_t tid = 0;
pthread_threadid_np(nullptr, &tid);
return tid;
#else
return static_cast<uint64_t>(::syscall(SYS_gettid));
#endif
}
#endif
static uint64_t KernelReadTscNative() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && (defined(_M_X64) || defined(__x86_64__))
#if defined(_M_X64) || defined(__x86_64__)
return __rdtsc();
#else
return Common::Timer::QueryPerformanceCounter();
@@ -109,7 +133,7 @@ static uint64_t KernelReadTscNative() {
static uint64_t KernelGetTscFrequencyNative() {
static const uint64_t frequency = [] {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && (defined(_M_X64) || defined(__x86_64__))
#if defined(_M_X64) || defined(__x86_64__)
const auto host_frequency = Common::Timer::QueryPerformanceFrequency();
if (host_frequency == 0) {
return uint64_t {1000000000};
@@ -784,7 +808,11 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
if (g_pthread_self != nullptr) {
g_pthread_self->guest_host_rbx = host_rbx;
#if defined(__APPLE__)
g_pthread_self->guest_host_rsp = host_rsp - (2u * sizeof(uint64_t));
#else
g_pthread_self->guest_host_rsp = host_rsp - (4u * sizeof(uint64_t));
#endif
g_pthread_self->guest_host_rbp = host_rbp;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uintptr_t host_gs8 = 0;
@@ -794,45 +822,14 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
: "=r"(host_gs8), "=r"(host_gs10)
:
: "memory");
g_pthread_self->guest_host_rsp -= 2u * sizeof(uint64_t);
g_pthread_self->guest_host_gs8 = host_gs8;
g_pthread_self->guest_host_gs10 = host_gs10;
#endif
}
// The guest ABI expects the entry argument in rdi and a 16-byte aligned stack before call.
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
asm volatile("pushq %%r12\n\t"
"pushq %%r13\n\t"
"pushq %%r14\n\t"
"pushq %%r15\n\t"
"movq %%gs:0x08, %%r14\n\t"
"movq %%gs:0x10, %%r15\n\t"
"xorq %%rcx, %%rcx\n\t"
"movq %%rcx, %%gs:0x08\n\t"
"movq %%rcx, %%gs:0x10\n\t"
"movq %%rsp, %%r12\n\t"
"movq %%rbp, %%r13\n\t"
"movq %[guest_rsp], %%rsp\n\t"
"movq %[guest_rbp], %%rbp\n\t"
"callq *%%rsi\n\t"
"movq %%r13, %%rbp\n\t"
"movq %%r12, %%rsp\n\t"
"movq %%r14, %%gs:0x08\n\t"
"movq %%r15, %%gs:0x10\n\t"
"popq %%r15\n\t"
"popq %%r14\n\t"
"popq %%r13\n\t"
"popq %%r12\n\t"
: "=a"(ret), "+D"(arg), "+S"(func)
: [guest_rsp] "r"(guest_rsp), [guest_rbp] "r"(guest_rbp)
: "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
"xmm12", "xmm13", "xmm14", "xmm15");
#else
// Pin the stack operands to registers the template never touches (r14/r15 are
// SysV callee-saved, so guest code preserves them): plain "r" constraints may
// allocate them into r12/r13, which the template overwrites before use.
#if defined(__APPLE__)
// Keep inputs out of r12/r13.
register uintptr_t guest_rsp_reg asm("r14") = guest_rsp;
register uintptr_t guest_rbp_reg asm("r15") = guest_rbp;
asm volatile("pushq %%r12\n\t"
@@ -851,6 +848,45 @@ static KYTY_SYSV_ABI void* RunOnGuestStack(void* arg, pthread_entry_func_t func,
: "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
"xmm12", "xmm13", "xmm14", "xmm15");
#else
// PthreadExit resumes at this frame, so all four saved registers stay on the host stack.
asm volatile("pushq %%r12\n\t"
"pushq %%r13\n\t"
"pushq %%r14\n\t"
"pushq %%r15\n\t"
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
"movq %%gs:0x08, %%r14\n\t"
"movq %%gs:0x10, %%r15\n\t"
"xorq %%rcx, %%rcx\n\t"
"movq %%rcx, %%gs:0x08\n\t"
"movq %%rcx, %%gs:0x10\n\t"
#endif
"movq %%rsp, %%r12\n\t"
"movq %%rbp, %%r13\n\t"
"movq %[guest_rsp], %%rsp\n\t"
"movq %[guest_rbp], %%rbp\n\t"
"callq *%%rsi\n\t"
"movq %%r13, %%rbp\n\t"
"movq %%r12, %%rsp\n\t"
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
"movq %%r14, %%gs:0x08\n\t"
"movq %%r15, %%gs:0x10\n\t"
#endif
"popq %%r15\n\t"
"popq %%r14\n\t"
"popq %%r13\n\t"
"popq %%r12\n\t"
: "=a"(ret), "+D"(arg), "+S"(func)
: [guest_rsp] "r"(guest_rsp), [guest_rbp] "r"(guest_rbp)
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
: "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
"xmm12", "xmm13", "xmm14", "xmm15");
#else
: "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11", "r12", "r13", "xmm0",
"xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10",
"xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
#endif
#endif
g_guest_entry_return_rsp = 0;
@@ -886,6 +922,29 @@ static void UpdateCurrentThreadStackAttr(PthreadAttr* attr) {
(*attr)->stack_size = high - low;
(*attr)->stack_user = true;
}
#elif defined(__APPLE__)
// macOS reports a stack top and size.
void* top = pthread_get_stackaddr_np(pthread_self());
const size_t size = pthread_get_stacksize_np(pthread_self());
if (top != nullptr && size != 0) {
(*attr)->stack_addr = static_cast<void*>(static_cast<uint8_t*>(top) - size);
(*attr)->stack_size = size;
(*attr)->stack_user = true;
}
#else
// Record the main thread's stack bounds.
pthread_attr_t self_attr {};
if (pthread_getattr_np(pthread_self(), &self_attr) == 0) {
void* base = nullptr;
size_t size = 0;
if (pthread_attr_getstack(&self_attr, &base, &size) == 0 && base != nullptr && size != 0) {
(*attr)->stack_addr = base;
(*attr)->stack_size = size;
(*attr)->stack_user = true;
}
pthread_attr_destroy(&self_attr);
}
#endif
}
@@ -926,6 +985,8 @@ void PthreadInitSelfForMainThread() {
uint64_t os_thread_id = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
os_thread_id = static_cast<uint64_t>(GetCurrentThreadId());
#else
os_thread_id = GetHostThreadId();
#endif
g_pthread_self->host_thread_id = os_thread_id;
g_pthread_main = g_pthread_self;
@@ -3118,6 +3179,17 @@ uint64_t PthreadGetHostThreadId(Pthread thread) {
return thread != nullptr ? thread->host_thread_id : 0;
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// Raise a host signal on another guest thread.
bool PthreadKillHost(Pthread thread, int host_signal) {
if (thread == nullptr || thread->free) {
return false;
}
return ::pthread_kill(thread->p, host_signal) == 0;
}
#endif
void PthreadQueuePendingSignal(Pthread thread, int signum) {
if (thread == nullptr || signum < 0 || signum >= 64) {
return;
@@ -3215,6 +3287,8 @@ static void* RunThread(void* arg) {
uint64_t os_thread_id = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
os_thread_id = static_cast<uint64_t>(GetCurrentThreadId());
#else
os_thread_id = GetHostThreadId();
#endif
thread->host_thread_id = os_thread_id;
+3
View File
@@ -112,6 +112,9 @@ void PthreadQueuePendingSignal(Pthread thread, int signum);
bool PthreadHasPendingSignal(Pthread thread, int signum);
bool PthreadTakePendingSignal(Pthread thread, int signum);
bool PthreadGetGuestStack(Pthread thread, uint64_t* stack_addr, uint64_t* stack_size);
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
bool PthreadKillHost(Pthread thread, int host_signal);
#endif
int PthreadGetPriorityForKernel(Pthread thread);
int PthreadGetCurrentPriorityForKernel();
+59
View File
@@ -32,6 +32,9 @@ set_target_properties(launcher PROPERTIES AUTOUIC ON AUTOMOC ON AUTORCC ON)
if(WIN32 AND NOT KYTY_CLANG_CL)
set_target_properties(launcher PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
elseif(LINUX)
# Use the same linker as kyty_emulator.
set_target_properties(launcher PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS}")
endif()
add_dependencies(launcher KytyGitVersion)
@@ -79,6 +82,62 @@ if(WIN32)
--no-translations
)
")
elseif(LINUX)
# Bundle the Qt runtime and plugins.
set_target_properties(launcher PROPERTIES
INSTALL_RPATH "$ORIGIN/lib"
BUILD_WITH_INSTALL_RPATH FALSE
LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,--disable-new-dtags")
# Support distribution and official Qt layouts.
get_target_property(kyty_qt_core_lib Qt6::Core IMPORTED_LOCATION_RELEASE)
if(NOT kyty_qt_core_lib)
get_target_property(kyty_qt_core_lib Qt6::Core IMPORTED_LOCATION)
endif()
get_filename_component(kyty_qt_libdir "${kyty_qt_core_lib}" DIRECTORY)
set(kyty_qt_plugins_dir "")
foreach(kyty_candidate
"${kyty_qt_libdir}/qt6/plugins"
"${kyty_qt_libdir}/../plugins"
"${Qt6_DIR}/../../../plugins")
if(IS_DIRECTORY "${kyty_candidate}/platforms")
get_filename_component(kyty_qt_plugins_dir "${kyty_candidate}" ABSOLUTE)
break()
endif()
endforeach()
if(kyty_qt_plugins_dir)
message(STATUS "Qt plugins for launcher deployment: ${kyty_qt_plugins_dir}")
set(kyty_qt_plugin_files "")
foreach(kyty_qt_plugin platforms tls imageformats xcbglintegrations wayland-shell-integration)
if(IS_DIRECTORY "${kyty_qt_plugins_dir}/${kyty_qt_plugin}")
file(GLOB kyty_qt_plugin_group
"${kyty_qt_plugins_dir}/${kyty_qt_plugin}/*.so"
"${kyty_qt_plugins_dir}/${kyty_qt_plugin}/*.so.*")
list(APPEND kyty_qt_plugin_files ${kyty_qt_plugin_group})
install(DIRECTORY "${kyty_qt_plugins_dir}/${kyty_qt_plugin}" DESTINATION "plugins")
endif()
endforeach()
install(CODE "
set(kyty_plugin_files \"${kyty_qt_plugin_files}\")
file(GET_RUNTIME_DEPENDENCIES
EXECUTABLES \"${CMAKE_CURRENT_BINARY_DIR}/${launcher_name}\"
LIBRARIES \${kyty_plugin_files}
RESOLVED_DEPENDENCIES_VAR kyty_resolved
POST_INCLUDE_REGEXES \"libQt6\" \"libicu\"
POST_EXCLUDE_REGEXES \".*\")
foreach(kyty_dep \${kyty_resolved})
file(INSTALL DESTINATION \"\${CMAKE_INSTALL_PREFIX}/lib\"
TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES \"\${kyty_dep}\")
endforeach()
")
install(CODE "
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/qt.conf\" \"[Paths]\\nPlugins = plugins\\n\")
")
else()
message(WARNING "Qt plugin directory not found; the installed launcher will need a system Qt")
endif()
endif()
+70 -4
View File
@@ -45,8 +45,6 @@ constexpr char EMULATOR_EXE[] = "kyty_emulator";
#if defined(_WIN32)
constexpr char CMD_EXE[] = "cmd.exe";
#elif defined(__linux__)
constexpr char GNOME[] = "gnome-terminal";
constexpr char XTERM[] = "xterm";
constexpr char KYTY_BASH_FILE[] = "kyty_run.sh";
#endif
#if defined(_WIN32)
@@ -265,6 +263,57 @@ static bool CreateBashScript(const QString& interpreter, const QStringList& args
}
return false;
}
// Find a terminal and its command separator.
static bool FindTerminal(QString* program, QStringList* prefix) {
struct TerminalSpec {
const char* executable;
const char* separator; // nullptr when the command follows immediately
};
static const TerminalSpec candidates[] = {
{"x-terminal-emulator", "-e"}, {"gnome-terminal", "--"}, {"konsole", "-e"},
{"xfce4-terminal", "-x"}, {"mate-terminal", "--"}, {"tilix", "-e"},
{"alacritty", "-e"}, {"kitty", nullptr}, {"foot", nullptr},
{"wezterm", "-e"}, {"urxvt", "-e"}, {"xterm", "-e"},
};
const auto try_candidate = [program, prefix](const QString& executable, const char* separator) {
const auto resolved = QStandardPaths::findExecutable(executable);
if (resolved.isEmpty()) {
return false;
}
*program = resolved;
prefix->clear();
if (separator != nullptr) {
*prefix << QString::fromLatin1(separator);
}
return true;
};
if (const auto from_env = qEnvironmentVariable("TERMINAL"); !from_env.isEmpty()) {
// Reuse the known separator for an explicit terminal.
const auto env_name = QFileInfo(from_env).fileName();
const char* separator = "-e";
for (const auto& candidate: candidates) {
if (env_name == QLatin1String(candidate.executable)) {
separator = candidate.separator;
break;
}
}
if (try_candidate(from_env, separator)) {
return true;
}
}
for (const auto& candidate: candidates) {
if (try_candidate(QString::fromLatin1(candidate.executable), candidate.separator)) {
return true;
}
}
return false;
}
#endif
void MainDialog::RunInterpreter(QProcess* process, const Configuration& info) {
@@ -289,8 +338,16 @@ void MainDialog::RunInterpreter(QProcess* process, const Configuration& info) {
}
{
process->setProgram(GNOME);
process->setArguments({"--", "bash", "-c", bash_file_name});
QString terminal;
QStringList terminal_prefix;
if (FindTerminal(&terminal, &terminal_prefix)) {
process->setProgram(terminal);
process->setArguments(terminal_prefix + QStringList {"bash", "-c", bash_file_name});
} else {
// Run without a terminal as a fallback.
process->setProgram(QStringLiteral("bash"));
process->setArguments({QStringLiteral("-c"), bash_file_name});
}
}
#elif defined(_WIN32)
{
@@ -319,6 +376,15 @@ void MainDialog::RunInterpreter(QProcess* process, const Configuration& info) {
});
#endif
process->start();
#if !defined(_WIN32)
// Report immediate launch failures.
if (!process->waitForStarted(5000)) {
QMessageBox::critical(this, tr("Error"),
tr("Failed to start:\n%1\n\n%2")
.arg(process->program(), process->errorString()));
return;
}
#endif
process->waitForFinished(100);
}
+10 -1
View File
@@ -192,7 +192,8 @@ static void PrintAbortPointerArrayCandidate(const char* name, uint64_t addr) {
uint64_t rbp = 0;
uint64_t rsp = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
// This assembly is x86-64-specific, not Windows-specific.
#if defined(__x86_64__) || defined(_M_X64)
asm volatile("movq %%rbp, %0" : "=r"(rbp));
asm volatile("movq %%rsp, %0" : "=r"(rsp));
#endif
@@ -419,7 +420,15 @@ static KYTY_SYSV_ABI size_t libc_strftime(char* str, size_t count, const char* f
return 0;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
// Guest-created tm values do not initialize glibc's tm_zone pointer.
std::tm sanitized = *timeptr;
sanitized.tm_zone = nullptr;
return std::strftime(str, count, format, &sanitized);
#else
return std::strftime(str, count, format, timeptr);
#endif
}
static KYTY_SYSV_ABI void catchReturnFromMain(int status) {
+1 -4
View File
@@ -135,11 +135,8 @@ static void KYTY_SYSV_ABI asan_init() {
sys_dbg_stack_info_t s {};
SysStackUsage(s);
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
AllocShadow(s.addr, reinterpret_cast<uintptr_t>(&s));
#else
// Cover the full stack reservation.
AllocShadow(s.reserved_addr, reinterpret_cast<uintptr_t>(&s));
#endif
LibKernel::Memory::RegisterCallbacks(KernelAlloc, KernelFree);
}
+220
View File
@@ -40,6 +40,9 @@
#define NOMINMAX
#endif
#include <windows.h>
#elif !defined(__APPLE__)
#include <csignal>
#include <ucontext.h>
#endif
namespace Libs {
@@ -630,6 +633,75 @@ static SignalUcontext CreateSignalUcontext(
ctx.uc_mcontext.mc_rsp = reinterpret_cast<uint64_t>(&stack_marker);
ctx.uc_mcontext.mc_rbp = reinterpret_cast<uint64_t>(__builtin_frame_address(0));
ctx.uc_mcontext.mc_rip = reinterpret_cast<uint64_t>(__builtin_return_address(0));
#if defined(__x86_64__)
// Capture the remaining guest-visible registers.
uint64_t rax = 0;
uint64_t rbx = 0;
uint64_t rcx = 0;
uint64_t rdx = 0;
uint64_t rsi = 0;
uint64_t rdi = 0;
asm volatile("movq %%rax, %0\n\t"
"movq %%rbx, %1\n\t"
"movq %%rcx, %2\n\t"
"movq %%rdx, %3\n\t"
"movq %%rsi, %4\n\t"
"movq %%rdi, %5\n\t"
: "=m"(rax), "=m"(rbx), "=m"(rcx), "=m"(rdx), "=m"(rsi), "=m"(rdi)
:
: "memory");
uint64_t r8 = 0;
uint64_t r9 = 0;
uint64_t r10 = 0;
uint64_t r11 = 0;
uint64_t r12 = 0;
uint64_t r13 = 0;
uint64_t r14 = 0;
uint64_t r15 = 0;
asm volatile("movq %%r8, %0\n\t"
"movq %%r9, %1\n\t"
"movq %%r10, %2\n\t"
"movq %%r11, %3\n\t"
"movq %%r12, %4\n\t"
"movq %%r13, %5\n\t"
"movq %%r14, %6\n\t"
"movq %%r15, %7\n\t"
: "=m"(r8), "=m"(r9), "=m"(r10), "=m"(r11), "=m"(r12), "=m"(r13), "=m"(r14),
"=m"(r15)
:
: "memory");
uint64_t rflags = 0;
uint64_t cs = 0;
uint64_t ss = 0;
asm volatile("pushfq\n\t"
"popq %0\n\t"
"movq %%cs, %1\n\t"
"movq %%ss, %2\n\t"
: "=r"(rflags), "=r"(cs), "=r"(ss)
:
: "memory");
ctx.uc_mcontext.mc_rax = rax;
ctx.uc_mcontext.mc_rbx = rbx;
ctx.uc_mcontext.mc_rcx = rcx;
ctx.uc_mcontext.mc_rdx = rdx;
ctx.uc_mcontext.mc_rsi = rsi;
ctx.uc_mcontext.mc_rdi = rdi;
ctx.uc_mcontext.mc_r8 = r8;
ctx.uc_mcontext.mc_r9 = r9;
ctx.uc_mcontext.mc_r10 = r10;
ctx.uc_mcontext.mc_r11 = r11;
ctx.uc_mcontext.mc_r12 = r12;
ctx.uc_mcontext.mc_r13 = r13;
ctx.uc_mcontext.mc_r14 = r14;
ctx.uc_mcontext.mc_r15 = r15;
ctx.uc_mcontext.mc_rflags = rflags;
ctx.uc_mcontext.mc_cs = cs;
ctx.uc_mcontext.mc_ss = ss;
#endif
#endif
return ctx;
@@ -765,6 +837,123 @@ static void ApplySignalUcontext(CONTEXT* dst_ctx, const SignalUcontext& src_ctx)
}
#endif
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS && !defined(__APPLE__) && defined(__x86_64__)
static SignalUcontext CreateSignalUcontextFromHost(const ucontext_t* host_ctx) {
SignalUcontext ctx = {};
if (host_ctx == nullptr) {
return ctx;
}
const auto* gregs = host_ctx->uc_mcontext.gregs;
ctx.uc_mcontext.mc_rdi = static_cast<uint64_t>(gregs[REG_RDI]);
ctx.uc_mcontext.mc_rsi = static_cast<uint64_t>(gregs[REG_RSI]);
ctx.uc_mcontext.mc_rdx = static_cast<uint64_t>(gregs[REG_RDX]);
ctx.uc_mcontext.mc_rcx = static_cast<uint64_t>(gregs[REG_RCX]);
ctx.uc_mcontext.mc_r8 = static_cast<uint64_t>(gregs[REG_R8]);
ctx.uc_mcontext.mc_r9 = static_cast<uint64_t>(gregs[REG_R9]);
ctx.uc_mcontext.mc_rax = static_cast<uint64_t>(gregs[REG_RAX]);
ctx.uc_mcontext.mc_rbx = static_cast<uint64_t>(gregs[REG_RBX]);
ctx.uc_mcontext.mc_rbp = static_cast<uint64_t>(gregs[REG_RBP]);
ctx.uc_mcontext.mc_r10 = static_cast<uint64_t>(gregs[REG_R10]);
ctx.uc_mcontext.mc_r11 = static_cast<uint64_t>(gregs[REG_R11]);
ctx.uc_mcontext.mc_r12 = static_cast<uint64_t>(gregs[REG_R12]);
ctx.uc_mcontext.mc_r13 = static_cast<uint64_t>(gregs[REG_R13]);
ctx.uc_mcontext.mc_r14 = static_cast<uint64_t>(gregs[REG_R14]);
ctx.uc_mcontext.mc_r15 = static_cast<uint64_t>(gregs[REG_R15]);
ctx.uc_mcontext.mc_rip = static_cast<uint64_t>(gregs[REG_RIP]);
ctx.uc_mcontext.mc_rsp = static_cast<uint64_t>(gregs[REG_RSP]);
ctx.uc_mcontext.mc_rflags = static_cast<uint64_t>(gregs[REG_EFL]);
// Linux packs cs/gs/fs into one greg.
const auto csgsfs = static_cast<uint64_t>(gregs[REG_CSGSFS]);
ctx.uc_mcontext.mc_cs = csgsfs & 0xffffu;
ctx.uc_mcontext.mc_gs = static_cast<uint16_t>((csgsfs >> 16u) & 0xffffu);
ctx.uc_mcontext.mc_fs = static_cast<uint16_t>((csgsfs >> 32u) & 0xffffu);
ctx.uc_mcontext.mc_len = sizeof(SignalMcontext);
return ctx;
}
static void ApplySignalUcontextToHost(ucontext_t* dst_ctx, const SignalUcontext& src_ctx) {
if (dst_ctx == nullptr) {
return;
}
auto* gregs = dst_ctx->uc_mcontext.gregs;
gregs[REG_RDI] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rdi);
gregs[REG_RSI] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rsi);
gregs[REG_RDX] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rdx);
gregs[REG_RCX] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rcx);
gregs[REG_R8] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r8);
gregs[REG_R9] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r9);
gregs[REG_RAX] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rax);
gregs[REG_RBX] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rbx);
gregs[REG_RBP] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rbp);
gregs[REG_R10] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r10);
gregs[REG_R11] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r11);
gregs[REG_R12] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r12);
gregs[REG_R13] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r13);
gregs[REG_R14] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r14);
gregs[REG_R15] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_r15);
gregs[REG_RIP] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rip);
gregs[REG_RSP] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rsp);
gregs[REG_EFL] = static_cast<greg_t>(src_ctx.uc_mcontext.mc_rflags);
// The kernel validates packed segment selectors on sigreturn.
}
static int SignalDispatchHostSignal() {
static const int host_signal = SIGRTMIN + 3;
return host_signal;
}
static void HostSignalDispatchHandler(int /*host_signal*/, siginfo_t* /*info*/,
void* native_context) {
Pthread current = PthreadSelfOrNull();
if (current == nullptr) {
return;
}
auto* host_ctx = static_cast<ucontext_t*>(native_context);
for (int signum = 0; signum < static_cast<int>(std::size(g_exception_handlers)); signum++) {
if (!TakePendingSignal(current, signum)) {
continue;
}
auto* handler = reinterpret_cast<exception_handler_func_t>(g_exception_handlers[signum]);
if (handler != nullptr) {
SignalDispatchScope scope;
auto ctx = CreateSignalUcontextFromHost(host_ctx);
if (IsGuestCodeAddress(ctx.uc_mcontext.mc_rip)) {
handler(signum, &ctx);
ApplySignalUcontextToHost(host_ctx, ctx);
} else {
// Do not expose or restore a host frame.
SanitizeNonGuestSignalUcontext(&ctx, current);
handler(signum, &ctx);
}
}
return;
}
}
static bool EnsureHostSignalDispatchInstalled() {
static const bool installed = [] {
struct sigaction action {};
action.sa_sigaction = HostSignalDispatchHandler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO | SA_RESTART;
return ::sigaction(SignalDispatchHostSignal(), &action, nullptr) == 0;
}();
return installed;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
enum KytyQueueUserApcFlags : ULONG_PTR {
KytyQueueUserApcFlagsNone = 0,
@@ -985,6 +1174,31 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
}
CloseHandle(target_thread);
return OK;
#elif !defined(__APPLE__) && defined(__x86_64__)
// Deliver on the target thread.
if (thread == PthreadSelfOrNull()) {
SignalDispatchScope scope;
auto ctx = CreateCurrentGuestCallSignalUcontext(
reinterpret_cast<uint64_t>(__builtin_return_address(0)));
handler(signum, &ctx);
return OK;
}
if (!EnsureHostSignalDispatchInstalled()) {
return KERNEL_ERROR_EINVAL;
}
QueuePendingSignal(thread, signum);
if (!PthreadKillHost(thread, SignalDispatchHostSignal())) {
TakePendingSignal(thread, signum);
LOGF("\t pthread_kill failed for target thread\n");
return KERNEL_ERROR_EINVAL;
}
Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread);
WaitForSignalDispatch(thread, signum);
return OK;
#else
auto ctx = CreateSignalUcontext();
handler(signum, &ctx);
@@ -1293,6 +1507,11 @@ static bool DecodeEhFramePointer(const uint8_t* data, const uint8_t* end, uint8_
return true;
}
// Kyty does not create guest signal-return trampoline frames.
int KYTY_SYSV_ABI KernelIsSignalReturn(uint64_t /*pc*/) {
return 0;
}
int KYTY_SYSV_ABI KernelGetModuleInfoForUnwind(uint64_t addr, int flags,
ModuleInfoForUnwind* info) {
if (flags >= 3) {
@@ -3044,6 +3263,7 @@ LIB_DEFINE(InitLibKernel_1) {
LIB_FUNC("AqBioC2vF3I", LibKernel::read);
LIB_FUNC("f7KBOafysXo", LibKernel::KernelGetModuleInfoFromAddr);
LIB_FUNC("RpQJJVKTiFM", LibKernel::KernelGetModuleInfoForUnwind);
LIB_FUNC("crb5j7mkk1c", LibKernel::KernelIsSignalReturn); // _is_signal_return
LIB_FUNC("Fjc4-n1+y2g", LibKernel::elf_phdr_match_addr);
LIB_FUNC("FxVZqBAA7ks", LibKernel::write);
LIB_FUNC("FJmglmTMdr4", LibKernel::getargv);
+126 -39
View File
@@ -36,6 +36,12 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <dlfcn.h>
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
#include <sys/uio.h>
#include <unistd.h>
#endif
#endif
namespace Libs::LibKernel {
@@ -354,6 +360,7 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
register uintptr_t guest_rbp_reg asm("r15") = guest_rbp;
#endif
#if defined(__APPLE__) || KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
asm volatile(
"pushq %%r12\n\t"
"pushq %%r13\n\t"
@@ -377,6 +384,22 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12",
"xmm13", "xmm14", "xmm15");
#else
// Clobbers prevent inputs from being allocated to r12/r13.
asm volatile("movq %%rsp, %%r12\n\t"
"movq %%rbp, %%r13\n\t"
"movq %[guest_rsp], %%rsp\n\t"
"movq %[guest_rbp], %%rbp\n\t"
"callq *%[func]\n\t"
"movq %%r13, %%rbp\n\t"
"movq %%r12, %%rsp\n\t"
:
: [func] "r"(func), "D"(params),
"S"(atexit_func), [guest_rsp] "r"(guest_rsp), [guest_rbp] "r"(guest_rbp)
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "r12", "r13",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8",
"xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
#endif
return;
}
@@ -387,6 +410,7 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
register uintptr_t guest_rbp_reg asm("r14") = reinterpret_cast<uintptr_t>(guest_root_frame);
#endif
#if defined(__APPLE__) || KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
asm volatile("pushq %%r12\n\t"
"pushq %%r13\n\t"
"movq %%rbp, %%r12\n\t"
@@ -406,6 +430,19 @@ static KYTY_SYSV_ABI void RunEntry(uint64_t addr, EntryParams* params, atexit_fu
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "xmm0", "xmm1",
"xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11",
"xmm12", "xmm13", "xmm14", "xmm15");
#else
// Keep inputs out of r12.
asm volatile("movq %%rbp, %%r12\n\t"
"movq %[guest_rbp], %%rbp\n\t"
"callq *%[func]\n\t"
"movq %%r12, %%rbp\n\t"
:
: [func] "r"(func), "D"(params),
"S"(atexit_func), [guest_rbp] "r"(guest_root_frame)
: "cc", "memory", "rax", "rcx", "rdx", "r8", "r9", "r10", "r11", "r12", "xmm0",
"xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10",
"xmm11", "xmm12", "xmm13", "xmm14", "xmm15");
#endif
#else
(void)stack_top;
reinterpret_cast<entry_func_t>(addr)(params, atexit_func);
@@ -518,6 +555,69 @@ void KYTY_SYSV_ABI SysStackWalkX86(uint64_t rbp, void** stack, int* depth) {
SysStackWalkX86(rbp, rbp, stack, depth);
}
// Probe diagnostic ranges without raising another fault.
static bool IsReadableRange(uint64_t addr, uint64_t size) {
if (addr == 0 || size == 0) {
return false;
}
const uint64_t end = addr + size;
if (end < addr) {
return false;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t current = addr;
while (current < end) {
MEMORY_BASIC_INFORMATION mbi {};
if (VirtualQuery(reinterpret_cast<const void*>(current), &mbi, sizeof(mbi)) == 0 ||
mbi.State != MEM_COMMIT || (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
return false;
}
const auto region_end = reinterpret_cast<uint64_t>(mbi.BaseAddress) + mbi.RegionSize;
if (region_end <= current) {
return false;
}
current = std::min(region_end, end);
}
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
const auto page_size = static_cast<uint64_t>(sysconf(_SC_PAGESIZE));
if (page_size == 0) {
return false;
}
for (uint64_t current = addr; current < end;) {
uint8_t probe = 0;
iovec local {&probe, sizeof(probe)};
iovec remote {reinterpret_cast<void*>(current), sizeof(probe)};
if (process_vm_readv(getpid(), &local, 1, &remote, 1, 0) !=
static_cast<ssize_t>(sizeof(probe))) {
return false;
}
const uint64_t next = (current & ~(page_size - 1)) + page_size;
if (next <= current) { // wrapped at the top of the address space
break;
}
current = next;
}
#else
(void)end;
#endif
return true;
}
static bool IsDumpableRange(uint64_t addr, uint64_t size) {
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX && !defined(__APPLE__)
return IsReadableRange(addr, size);
#else
(void)size;
return addr != 0;
#endif
}
static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exception_info) {
const auto* info = &exception_info;
@@ -563,6 +663,12 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
LOGF("exception module: %s\n", module_name);
}
}
#else
Dl_info module_info {};
if (::dladdr(reinterpret_cast<void*>(info->exception_address), &module_info) != 0 &&
module_info.dli_fname != nullptr) {
LOGF("exception module: %s\n", module_info.dli_fname);
}
#endif
if (info->exception_address != 0) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
@@ -586,12 +692,18 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
LOGF("code-32: unavailable\n");
}
#else
LOGF("code-32:");
for (uint64_t i = 0; i < 64; i++) {
LOGF(" %02" PRIx32, static_cast<uint32_t>(*reinterpret_cast<const uint8_t*>(
info->exception_address + i - 32)));
const auto fault_addr = info->exception_address;
const auto dump_start = (fault_addr >= 32 ? fault_addr - 32 : fault_addr);
if (IsReadableRange(dump_start, 64)) {
auto* dump_ptr = reinterpret_cast<const uint8_t*>(dump_start);
LOGF("code-32:");
for (uint32_t i = 0; i < 64; i++) {
LOGF(" %02" PRIx32, static_cast<uint32_t>(dump_ptr[i]));
}
LOGF("\n");
} else {
LOGF("code-32: unavailable\n");
}
LOGF("\n");
#endif
} else {
LOGF("code: unavailable\n");
@@ -609,36 +721,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
LOGF("regs: r12=%016" PRIx64 " r13=%016" PRIx64 " r14=%016" PRIx64 " r15=%016" PRIx64 "\n",
info->r12, info->r13, info->r14, info->r15);
auto is_readable_range = [](uint64_t addr, uint64_t size) {
if (addr == 0 || size == 0) {
return false;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t current = addr;
uint64_t end = addr + size;
if (end < addr) {
return false;
}
while (current < end) {
MEMORY_BASIC_INFORMATION mbi {};
if (VirtualQuery(reinterpret_cast<const void*>(current), &mbi, sizeof(mbi)) == 0 ||
mbi.State != MEM_COMMIT || (mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
return false;
}
const auto region_end = reinterpret_cast<uint64_t>(mbi.BaseAddress) + mbi.RegionSize;
if (region_end <= current) {
return false;
}
current = std::min(region_end, end);
}
#else
(void)addr;
(void)size;
#endif
return true;
};
if (is_readable_range(info->rsp, 16u * sizeof(uint64_t))) {
if (IsReadableRange(info->rsp, 16u * sizeof(uint64_t))) {
auto* stack = reinterpret_cast<const uint64_t*>(info->rsp);
LOGF("stack:");
for (uint64_t i = 0; i < 16; i++) {
@@ -669,6 +752,9 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
#else
auto* dump_ptr = reinterpret_cast<const uint8_t*>(addr >= 16 ? addr - 16 : addr);
const auto dump_size = 32u;
if (!IsReadableRange(reinterpret_cast<uint64_t>(dump_ptr), dump_size)) {
return;
}
#endif
LOGF("%s code: addr=%016" PRIx64 ", off=%016" PRIx64 ", module=%s:", name, addr,
@@ -684,7 +770,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
dump_guest_code("guest rbx[0]", info->rbx);
dump_guest_code("guest rcx[0]", info->rcx);
dump_guest_code("guest rsi[0]", info->rsi);
if (info->rsp != 0) {
if (IsDumpableRange(info->rsp, 16u * sizeof(uint64_t))) {
auto* stack = reinterpret_cast<const uint64_t*>(info->rsp);
for (uint64_t i = 0; i < 16; i++) {
char name[32] {};
@@ -713,7 +799,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
}
}
auto dump_guest_qwords = [&is_readable_range](const char* name, uint64_t addr) {
auto dump_guest_qwords = [](const char* name, uint64_t addr) {
if (addr == 0) {
LOGF("%s = 0\n", name);
return;
@@ -728,7 +814,7 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
}
#endif
if (!is_readable_range(addr, 8u * sizeof(uint64_t))) {
if (!IsReadableRange(addr, 8u * sizeof(uint64_t))) {
LOGF("%s = %016" PRIx64 " (unmapped)\n", name, addr);
return;
}
@@ -753,7 +839,8 @@ static bool KytyExceptionHandler(const Common::HostException::ExceptionInfo& exc
dump_guest_qwords("guest r14", info->r14);
dump_guest_qwords("guest r15", info->r15);
if (info->exception_address == 0x000000090064364e && info->rbx != 0) {
if (info->exception_address == 0x000000090064364e &&
IsDumpableRange(info->rbx, sizeof(uint64_t))) {
auto* local = reinterpret_cast<const uint64_t*>(info->rbx);
dump_guest_qwords("vorbis obj", local[0]);
dump_guest_qwords("vorbis len", info->rcx);
+126 -10
View File
@@ -4,20 +4,13 @@
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#include <windows.h> // IWYU pragma: keep
#elif !defined(__APPLE__)
#include <sched.h>
#include <ucontext.h>
#endif
namespace Loader::X64InstructionEmulator {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static M128A* GetContextXmm(PCONTEXT context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
return &context->Xmm0 + index;
}
static uint64_t ExtractBitField(uint64_t value, uint32_t length, uint32_t index) {
length &= 0x3fu;
index &= 0x3fu;
@@ -63,6 +56,16 @@ static uint64_t InsertBitField(uint64_t dst, uint64_t src, uint32_t length, uint
return (dst & ~shifted) | src_shifted;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static M128A* GetContextXmm(PCONTEXT context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
return &context->Xmm0 + index;
}
static bool TryEmulateSse4a(PCONTEXT context) {
if (context == nullptr) {
return false;
@@ -140,12 +143,125 @@ static bool TryEmulateMonitorxMwaitx(PCONTEXT context) {
return true;
}
#elif !defined(__APPLE__)
// Linux signal contexts expose registers through ucontext_t.
static uint32_t* GetContextXmm(ucontext_t* context, uint8_t index) {
if (context == nullptr || index >= 16) {
return nullptr;
}
auto* fpregs = context->uc_mcontext.fpregs;
if (fpregs == nullptr) {
return nullptr;
}
return static_cast<uint32_t*>(fpregs->_xmm[index].element);
}
static uint64_t GetXmmLow(const uint32_t* xmm) {
return static_cast<uint64_t>(xmm[0]) | (static_cast<uint64_t>(xmm[1]) << 32u);
}
static void SetXmmLow(uint32_t* xmm, uint64_t value) {
xmm[0] = static_cast<uint32_t>(value);
xmm[1] = static_cast<uint32_t>(value >> 32u);
}
static void SetXmmHigh(uint32_t* xmm, uint64_t value) {
xmm[2] = static_cast<uint32_t>(value);
xmm[3] = static_cast<uint32_t>(value >> 32u);
}
static bool TryEmulateSse4a(ucontext_t* context) {
if (context == nullptr) {
return false;
}
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
const uint8_t prefix = rip[0];
if (prefix != 0x66 && prefix != 0xf2) {
return false;
}
size_t offset = 1;
uint8_t rex = 0;
if ((rip[offset] & 0xf0u) == 0x40u) {
rex = rip[offset];
offset++;
}
if (rip[offset] != 0x0f || rip[offset + 1] != 0x78) {
return false;
}
auto modrm = rip[offset + 2];
if ((modrm & 0xc0u) != 0xc0u) {
return false;
}
const uint8_t reg = ((modrm >> 3u) & 0x07u) | ((rex & 0x04u) << 1u);
const uint8_t rm = (modrm & 0x07u) | ((rex & 0x01u) << 3u);
const uint8_t length = rip[offset + 3];
const uint8_t index = rip[offset + 4];
// AMD SSE4a immediate-form EXTRQ/INSERTQ.
if (prefix == 0x66) {
auto* dst = GetContextXmm(context, rm);
if (dst == nullptr) {
return false;
}
SetXmmLow(dst, ExtractBitField(GetXmmLow(dst), length, index));
SetXmmHigh(dst, 0);
rip_reg += static_cast<greg_t>(offset + 5);
return true;
}
auto* dst = GetContextXmm(context, reg);
auto* src = GetContextXmm(context, rm);
if (dst == nullptr || src == nullptr) {
return false;
}
SetXmmLow(dst, InsertBitField(GetXmmLow(dst), GetXmmLow(src), length, index));
rip_reg += static_cast<greg_t>(offset + 5);
return true;
}
static bool TryEmulateMonitorxMwaitx(ucontext_t* context) {
if (context == nullptr) {
return false;
}
auto& rip_reg = context->uc_mcontext.gregs[REG_RIP];
const auto* rip = reinterpret_cast<const uint8_t*>(rip_reg);
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
return false;
}
// Approximate AMD MONITORX/MWAITX as no-op/yield.
if (rip[2] == 0xfb) {
::sched_yield();
}
rip_reg += 3;
return true;
}
#endif
bool TryEmulate(void* native_context) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
auto* context = static_cast<PCONTEXT>(native_context);
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context);
#elif !defined(__APPLE__)
auto* context = static_cast<ucontext_t*>(native_context);
return TryEmulateMonitorxMwaitx(context) || TryEmulateSse4a(context);
#else
(void)native_context;
return false;
+12 -4
View File
@@ -1,20 +1,28 @@
# Keep the existing Linux IWYU warnings non-fatal.
set(KYTY_IWYU_COMMON "-Xiwyu;--cxx17ns;-Qunused-arguments")
if (LINUX)
set(KYTY_IWYU_STRICT "")
else()
set(KYTY_IWYU_STRICT ";-Werror")
endif()
function(include_what_you_use target dirs)
if (NOT LINUX AND CLANG AND ("${target}" IN_LIST KYTY_IWYU))
if (CLANG AND ("${target}" IN_LIST KYTY_IWYU))
find_program (CLANG_IWYU_EXE NAMES "include-what-you-use")
if (CLANG_IWYU_EXE)
set_target_properties(${target} PROPERTIES CXX_INCLUDE_WHAT_YOU_USE "${CLANG_IWYU_EXE};-Xiwyu;--cxx17ns;-Qunused-arguments;-Werror")
set_target_properties(${target} PROPERTIES CXX_INCLUDE_WHAT_YOU_USE "${CLANG_IWYU_EXE};${KYTY_IWYU_COMMON}${KYTY_IWYU_STRICT}")
endif()
endif()
endfunction()
function(include_what_you_use_with_mappings target dirs mappings)
if (NOT LINUX AND CLANG AND ("${target}" IN_LIST KYTY_IWYU))
if (CLANG AND ("${target}" IN_LIST KYTY_IWYU))
find_program (CLANG_IWYU_EXE NAMES "include-what-you-use")
if (CLANG_IWYU_EXE)
foreach(map ${mappings})
list(APPEND mapdirs ";-Xiwyu;--mapping_file=${map}")
endforeach()
set_target_properties(${target} PROPERTIES CXX_INCLUDE_WHAT_YOU_USE "${CLANG_IWYU_EXE};${mapdirs};-Xiwyu;--cxx17ns;-Qunused-arguments;-Werror")
set_target_properties(${target} PROPERTIES CXX_INCLUDE_WHAT_YOU_USE "${CLANG_IWYU_EXE};${mapdirs};${KYTY_IWYU_COMMON}${KYTY_IWYU_STRICT}")
endif()
endif()
endfunction()
+185 -1
View File
@@ -18,6 +18,13 @@
#include <windows.h>
#undef min
#undef max
#else
#include <csignal>
#include <map>
#include <sys/mman.h>
#include <sys/wait.h>
#include <ucontext.h>
#include <unistd.h>
#endif
namespace {
@@ -39,6 +46,123 @@ void Check(bool value, const char *text) {
}
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// POSIX shims for the shared test body.
using DWORD = uint32_t;
constexpr uint32_t PAGE_NOACCESS = 1;
constexpr uint32_t PAGE_READONLY = 2;
constexpr uint32_t PAGE_READWRITE = 3;
constexpr uint32_t MEM_RESERVE = 0;
constexpr uint32_t MEM_COMMIT = 0;
constexpr uint32_t MEM_RELEASE = 0;
int ToHostProt(uint32_t protection) {
switch (protection) {
case PAGE_NOACCESS:
return PROT_NONE;
case PAGE_READONLY:
return PROT_READ;
default:
return PROT_READ | PROT_WRITE;
}
}
uint32_t Protection(const void *address) {
const auto addr = reinterpret_cast<uintptr_t>(address);
std::FILE *maps = std::fopen("/proc/self/maps", "r");
Check(maps != nullptr, "open /proc/self/maps failed");
char line[512];
uint32_t result = 0; // 0 => not mapped at all
while (std::fgets(line, sizeof(line), maps) != nullptr) {
unsigned long start = 0;
unsigned long end = 0;
char perms[8]{};
if (std::sscanf(line, "%lx-%lx %7s", &start, &end, perms) != 3) {
continue;
}
if (addr >= start && addr < end) {
result = perms[1] == 'w' ? PAGE_READWRITE
: perms[0] == 'r' ? PAGE_READONLY
: PAGE_NOACCESS;
break;
}
}
std::fclose(maps);
return result;
}
bool IsWritable(const void *address) { return Protection(address) == PAGE_READWRITE; }
// munmap needs a length where VirtualFree's callers pass 0, so sizes are remembered here.
std::map<void *, size_t> &AllocationSizes() {
static std::map<void *, size_t> sizes;
return sizes;
}
void *VirtualAlloc(void *address, size_t size, DWORD /*type*/, uint32_t protection) {
const int extra = address != nullptr ? MAP_FIXED_NOREPLACE : 0;
void *raw = ::mmap(address, size, ToHostProt(protection),
MAP_PRIVATE | MAP_ANONYMOUS | extra, -1, 0);
if (raw == MAP_FAILED) {
return nullptr;
}
AllocationSizes()[raw] = size;
return raw;
}
int VirtualFree(void *address, size_t /*size*/, DWORD /*type*/) {
auto &sizes = AllocationSizes();
auto it = sizes.find(address);
if (it == sizes.end()) {
return 0;
}
const int ok = ::munmap(address, it->second) == 0 ? 1 : 0;
sizes.erase(it);
return ok;
}
[[maybe_unused]] int VirtualProtect(void *address, size_t size, uint32_t protection,
DWORD *old_protection) {
if (old_protection != nullptr) {
*old_protection = Protection(address);
}
return ::mprotect(address, size, ToHostProt(protection)) == 0 ? 1 : 0;
}
// Two views of one anonymous shared object.
class SharedPage final {
public:
SharedPage(uintptr_t address, uint64_t size) : size_(static_cast<size_t>(size)) {
fd_ = static_cast<int>(::syscall(SYS_memfd_create, "kyty-shared-page", 0u));
Check(fd_ >= 0, "memfd_create failed");
Check(::ftruncate(fd_, static_cast<off_t>(size)) == 0, "ftruncate failed");
guest = static_cast<uint8_t *>(::mmap(reinterpret_cast<void *>(address), size_,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_FIXED_NOREPLACE, fd_, 0));
Check(guest == reinterpret_cast<void *>(address), "fixed shared view failed");
backing = static_cast<uint8_t *>(
::mmap(nullptr, size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0));
Check(backing != MAP_FAILED && backing != nullptr, "shared backing view failed");
}
~SharedPage() {
Check(::munmap(backing, size_) == 0, "shared backing unmap failed");
Check(::munmap(guest, size_) == 0, "shared guest unmap failed");
Check(::close(fd_) == 0, "shared mapping close failed");
}
SharedPage(const SharedPage &) = delete;
SharedPage &operator=(const SharedPage &) = delete;
uint8_t *guest = nullptr;
uint8_t *backing = nullptr;
private:
size_t size_ = 0;
int fd_ = -1;
};
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool IsWritable(const void *address) {
MEMORY_BASIC_INFORMATION info{};
@@ -84,6 +208,8 @@ class SharedPage final {
private:
HANDLE mapping_ = nullptr;
};
#endif
#if 1
bool DummyFault(void *, PageFaultAccess, uint64_t, uint64_t, PageFaultPhase) noexcept {
return true;
@@ -246,6 +372,7 @@ void UnmapContended() noexcept {
g_unmap_contended.store(true, std::memory_order_release);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
LONG CALLBACK NativeTrackerFaultHandler(EXCEPTION_POINTERS *exception) {
if (exception == nullptr || exception->ExceptionRecord == nullptr ||
exception->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) {
@@ -266,6 +393,45 @@ LONG CALLBACK NativeTrackerFaultHandler(EXCEPTION_POINTERS *exception) {
? EXCEPTION_CONTINUE_EXECUTION
: EXCEPTION_CONTINUE_SEARCH;
}
#else
// SIGSEGV stands in for the vectored exception handler.
void NativeTrackerFaultHandler(int signal_number, siginfo_t *info, void *native_context) {
auto *context = static_cast<ucontext_t *>(native_context);
const auto error_code = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_ERR]);
const auto access = (error_code & 0x10u) != 0 ? PageFaultAccess::Execute
: (error_code & 0x02u) != 0 ? PageFaultAccess::Write
: PageFaultAccess::Read;
auto *page_manager = g_native_page_manager.load(std::memory_order_acquire);
if (page_manager != nullptr) {
g_native_fault_entered.store(true, std::memory_order_release);
if (page_manager->HandleFault(access, reinterpret_cast<uint64_t>(info->si_addr))) {
return;
}
}
struct sigaction restore {};
restore.sa_handler = SIG_DFL;
sigemptyset(&restore.sa_mask);
::sigaction(signal_number, &restore, nullptr);
}
struct sigaction g_saved_segv_action {};
void *AddVectoredExceptionHandler(unsigned long /*first*/,
void (*handler)(int, siginfo_t *, void *)) {
struct sigaction action {};
action.sa_sigaction = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO;
if (::sigaction(SIGSEGV, &action, &g_saved_segv_action) != 0) {
return nullptr;
}
return reinterpret_cast<void *>(handler);
}
int RemoveVectoredExceptionHandler(void * /*token*/) {
return ::sigaction(SIGSEGV, &g_saved_segv_action, nullptr) == 0 ? 1 : 0;
}
#endif
void TestPendingFaultBlocksUploadConsumption() {
constexpr uintptr_t base = 0x0000000200010000ull;
@@ -978,14 +1144,17 @@ void TestCrossRegionUpload() {
}
void TestFatalPaths() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
char path[MAX_PATH]{};
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
"GetModuleFileName failed");
#endif
for (const char *name : {"gpu-dirty-fault", "gpu-dirty-read", "virtual-gpu-read",
"gpu-dirty-explicit-cpu",
"unmapped", "reentrant-upload",
"writable-upload-race", "gpu-dirty-unmap-race",
"missing-download-bytes"}) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::string command = std::string("\"") + path + "\" --death " + name;
std::vector<char> mutable_command(command.begin(), command.end());
mutable_command.push_back('\0');
@@ -1004,6 +1173,21 @@ void TestFatalPaths() {
"MemoryTracker death path used the wrong exit");
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
#else
const pid_t pid = ::fork();
Check(pid >= 0, "fork failed");
if (pid == 0) {
::execl("/proc/self/exe", "MemoryTrackerTests", "--death", name, nullptr);
std::_Exit(0x7e);
}
int status = 0;
Check(::waitpid(pid, &status, 0) == pid, "waitpid failed");
// Exit status carries only the low 8 bits.
const bool fatal_exit = WIFEXITED(status) && (WEXITSTATUS(status) == (321 & 0xff) ||
WEXITSTATUS(status) == (322 & 0xff));
Check(fatal_exit || WIFSIGNALED(status),
"MemoryTracker death path used the wrong exit");
#endif
}
}
#endif
@@ -1011,7 +1195,7 @@ void TestFatalPaths() {
} // namespace
int main(int argc, char **argv) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#if 1
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
RunDeathCase(argv[2]);
}
+150 -1
View File
@@ -17,6 +17,13 @@
#include <windows.h>
#undef min
#undef max
#else
#include <csignal>
#include <map>
#include <sys/mman.h>
#include <sys/wait.h>
#include <ucontext.h>
#include <unistd.h>
#endif
namespace {
@@ -32,6 +39,76 @@ void Check(bool value, const char *text) {
}
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
// POSIX shims for the shared test body.
using DWORD = uint32_t;
constexpr uint32_t PAGE_NOACCESS = 1;
constexpr uint32_t PAGE_READONLY = 2;
constexpr uint32_t PAGE_READWRITE = 3;
constexpr uint32_t MEM_RELEASE = 0;
int ToHostProt(uint32_t protection) {
switch (protection) {
case PAGE_NOACCESS:
return PROT_NONE;
case PAGE_READONLY:
return PROT_READ;
default:
return PROT_READ | PROT_WRITE;
}
}
uint32_t Protection(const void *address) {
const auto addr = reinterpret_cast<uintptr_t>(address);
std::FILE *maps = std::fopen("/proc/self/maps", "r");
Check(maps != nullptr, "open /proc/self/maps failed");
char line[512];
uint32_t result = 0; // 0 => not mapped at all
while (std::fgets(line, sizeof(line), maps) != nullptr) {
unsigned long start = 0;
unsigned long end = 0;
char perms[8]{};
if (std::sscanf(line, "%lx-%lx %7s", &start, &end, perms) != 3) {
continue;
}
if (addr >= start && addr < end) {
result = perms[1] == 'w' ? PAGE_READWRITE
: perms[0] == 'r' ? PAGE_READONLY
: PAGE_NOACCESS;
break;
}
}
std::fclose(maps);
return result;
}
bool IsWritable(const void *address) { return Protection(address) == PAGE_READWRITE; }
// munmap needs the length that VirtualFree's callers pass as 0, so sizes are remembered here.
std::map<void *, size_t> &AllocationSizes() {
static std::map<void *, size_t> sizes;
return sizes;
}
int VirtualFree(void *address, size_t /*size*/, DWORD /*type*/) {
auto &sizes = AllocationSizes();
auto it = sizes.find(address);
if (it == sizes.end()) {
return 0;
}
const int ok = ::munmap(address, it->second) == 0 ? 1 : 0;
sizes.erase(it);
return ok;
}
int VirtualProtect(void *address, size_t size, uint32_t protection, DWORD *old_protection) {
if (old_protection != nullptr) {
*old_protection = Protection(address);
}
return ::mprotect(address, size, ToHostProt(protection)) == 0 ? 1 : 0;
}
#endif
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool IsWritable(const void *address) {
MEMORY_BASIC_INFORMATION info{};
@@ -44,6 +121,8 @@ uint32_t Protection(const void *address) {
Check(VirtualQuery(address, &info, sizeof(info)) != 0, "VirtualQuery failed");
return info.Protect;
}
#endif
#if 1
struct FaultContext {
PageManager *manager = nullptr;
@@ -61,6 +140,7 @@ std::atomic_bool g_delay_native_fault{false};
std::atomic_bool g_native_fault_entered{false};
std::atomic_bool g_release_native_fault{false};
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
LONG CALLBACK NativeFaultHandler(EXCEPTION_POINTERS *exception) {
if (exception == nullptr || exception->ExceptionRecord == nullptr ||
exception->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION) {
@@ -86,6 +166,50 @@ LONG CALLBACK NativeFaultHandler(EXCEPTION_POINTERS *exception) {
? EXCEPTION_CONTINUE_EXECUTION
: EXCEPTION_CONTINUE_SEARCH;
}
#else
// SIGSEGV stands in for the vectored exception handler.
void NativeFaultHandler(int signal_number, siginfo_t *info, void *native_context) {
auto *context = static_cast<ucontext_t *>(native_context);
const auto error_code = static_cast<uint64_t>(context->uc_mcontext.gregs[REG_ERR]);
const auto access = (error_code & 0x10u) != 0 ? PageFaultAccess::Execute
: (error_code & 0x02u) != 0 ? PageFaultAccess::Write
: PageFaultAccess::Read;
auto *manager = g_native_fault_manager.load(std::memory_order_acquire);
if (manager != nullptr) {
if (g_delay_native_fault.load(std::memory_order_acquire)) {
g_native_fault_entered.store(true, std::memory_order_release);
while (!g_release_native_fault.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
}
if (manager->HandleFault(access, reinterpret_cast<uint64_t>(info->si_addr))) {
return;
}
}
struct sigaction restore {};
restore.sa_handler = SIG_DFL;
sigemptyset(&restore.sa_mask);
::sigaction(signal_number, &restore, nullptr);
}
struct sigaction g_saved_segv_action {};
void *AddVectoredExceptionHandler(unsigned long /*first*/,
void (*handler)(int, siginfo_t *, void *)) {
struct sigaction action {};
action.sa_sigaction = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_SIGINFO;
if (::sigaction(SIGSEGV, &action, &g_saved_segv_action) != 0) {
return nullptr;
}
return reinterpret_cast<void *>(handler);
}
int RemoveVectoredExceptionHandler(void * /*token*/) {
return ::sigaction(SIGSEGV, &g_saved_segv_action, nullptr) == 0 ? 1 : 0;
}
#endif
bool InvalidateFault(void *context, Libs::Graphics::PageFaultAccess, uint64_t vaddr, uint64_t size,
Libs::Graphics::PageFaultPhase phase) noexcept {
@@ -112,11 +236,20 @@ bool InvalidateFault(void *context, Libs::Graphics::PageFaultAccess, uint64_t va
uint8_t *Allocate(uint64_t size, uint32_t protection = PAGE_READWRITE) {
constexpr uintptr_t test_address = 0x0000000200010000ull;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
auto *memory = static_cast<uint8_t *>(
VirtualAlloc(reinterpret_cast<void *>(test_address), size,
MEM_RESERVE | MEM_COMMIT, protection));
Check(memory == reinterpret_cast<void *>(test_address),
"fixed low VirtualAlloc failed");
#else
// Do not overwrite a leaked mapping from an earlier case.
void *raw = ::mmap(reinterpret_cast<void *>(test_address), size, ToHostProt(protection),
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
Check(raw == reinterpret_cast<void *>(test_address), "fixed low mmap failed");
auto *memory = static_cast<uint8_t *>(raw);
AllocationSizes()[raw] = static_cast<size_t>(size);
#endif
return memory;
}
@@ -497,6 +630,7 @@ void TestCrossRegionRange() {
}
void CheckDeathCase(const char *name) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
char path[MAX_PATH]{};
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0,
"GetModuleFileName failed");
@@ -518,6 +652,21 @@ void CheckDeathCase(const char *name) {
"death case did not use the PageManager fatal exit");
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
#else
const pid_t pid = ::fork();
Check(pid >= 0, "fork failed");
if (pid == 0) {
::execl("/proc/self/exe", "PageManagerTests", "--death", name, nullptr);
std::_Exit(0x7e);
}
int status = 0;
Check(::waitpid(pid, &status, 0) == pid, "waitpid failed");
// Exit status carries only the low 8 bits.
const bool fatal_exit = WIFEXITED(status) && WEXITSTATUS(status) == (322 & 0xff);
const bool fatal_signal = WIFSIGNALED(status);
Check(fatal_exit || fatal_signal,
"death case did not use the PageManager fatal exit");
#endif
}
void TestFatalPaths() {
@@ -631,7 +780,7 @@ void TestGpuAccessPermissions() {
} // namespace
int main(int argc, char **argv) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#if 1
if (argc == 3 && std::strcmp(argv[1], "--death") == 0) {
RunDeathCase(argv[2]);
}
+49 -42
View File
@@ -1507,7 +1507,7 @@ public:
ordered_finished.load(),
"submission barrier did not drain prior PM4 work");
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
constexpr uintptr_t fault_base = 0x0000000200500000ull;
constexpr uint64_t fault_size = 0x10000;
int64_t fault_direct_offset = -1;
@@ -1689,7 +1689,7 @@ public:
Libs::LibKernel::Memory::KernelReleaseDirectMemory(
fault_direct_offset, fault_size) == 0,
"processor-fault direct-memory allocation release failed");
#endif
std::binary_semaphore command_entered{0};
std::binary_semaphore release_command{0};
@@ -1896,7 +1896,7 @@ public:
std::printf("[host] %-32s ok\n", name);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void CheckBufferCacheDirtyGarbageCollection() {
constexpr const char *name = "BufferCacheDirtyGarbageCollection";
constexpr uintptr_t base = 0x0000000200700000ull;
@@ -6117,7 +6117,7 @@ public:
"descriptor discovery direct-memory allocation release failed");
std::printf("[host] %-32s ok\n", name);
}
#endif
Buffer CreateStorageBuffer(const char *shader_name,
const std::vector<u32> &initial,
@@ -15071,7 +15071,6 @@ void CheckEmbeddedFetchVertexOffset() {
std::printf("[host] %-32s ok\n", "EmbeddedFetchVertexOffset");
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
struct CacheFaultContext {
TextureCache *texture = nullptr;
};
@@ -15088,6 +15087,7 @@ bool CacheFault(void *opaque, PageFaultAccess access, uint64_t vaddr,
std::_Exit(0x7f);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void CheckReverseRenderTargetFormatContract() {
const auto format = TextureGetRenderTargetFormat(12u, 7u, 2u);
Require("ReverseRenderTarget", "exact format",
@@ -15129,6 +15129,7 @@ void CheckReverseRenderTargetFormatContract() {
"adjacent unproven render-target tuple did not retain the fatal guard");
std::printf("[host] %-32s ok\n", "ReverseRenderTargetFormat");
}
#endif
[[noreturn]] void RunImageViewDeathCase(const char *kind) {
if (std::strcmp(kind, "sampled-invalid-selector") == 0) {
@@ -15211,6 +15212,7 @@ void CheckReverseRenderTargetFormatContract() {
std::_Exit(0x7f);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void CheckSampledColorViews() {
Require("SampledColorViews", "identity",
SelectSampledColorView(vk::Format::eR8G8B8A8Unorm,
@@ -15446,6 +15448,7 @@ void CheckSampledColorViews() {
}
std::printf("[host] %-32s ok\n", "SampledColorRenderTargetViews");
}
#endif
void CheckSampledDepthResource() {
ShaderRecompiler::IR::ImageResource resource{};
@@ -16209,6 +16212,7 @@ ShaderTextureResource BasicUintVolumeStorageTextureDescriptor() {
std::_Exit(0x7f);
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void CheckBasicStorageTextureDescriptor() {
const auto descriptor = BasicStorageTextureDescriptor();
Require("BasicStorageTexture", "descriptor",
@@ -16567,6 +16571,7 @@ void CheckBasicStorageTextureDescriptor() {
}
std::printf("[host] %-32s ok\n", "BasicStorageTextureDescriptor");
}
#endif
void CheckStorageTextureLinearUploadLayout() {
constexpr uint32_t format =
@@ -16835,6 +16840,7 @@ void CheckStandard64RenderTargetTileRoundTrip() {
std::printf("[host] %-32s ok\n", "Standard64RenderTarget");
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void CheckStorageTextureGpuOwnedRebindState() {
constexpr uintptr_t base = 0x0000000200200000ull;
constexpr uint64_t size = 0x10000;
@@ -16904,6 +16910,7 @@ void CheckStorageTextureGpuOwnedRebindState() {
VirtualFree(memory, 0, MEM_RELEASE) != 0, "VirtualFree failed");
std::printf("[host] %-32s ok\n", "StorageTextureGpuOwnedRebind");
}
#endif
void CheckNativeMsaaState() {
Require("NativeMsaaState", "sample encoding",
@@ -17407,7 +17414,7 @@ void CheckSharedFenceResourceLifetime() {
std::printf("[host] %-32s ok\n", "SharedFenceResourceLifetime");
}
#endif
void CheckEmbeddedFetchLaneSpill() {
std::vector<u32> code;
@@ -17683,18 +17690,6 @@ int main(int argc, char **argv) {
vulkan.CheckGpuCommandLane();
return 0;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-death") == 0) {
RunReverseRenderTargetDeathCase();
}
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-only") == 0) {
CheckReverseRenderTargetFormatContract();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--standard64-rt-only") == 0) {
CheckStandard64RenderTargetTileRoundTrip();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-overlap-only") == 0) {
CheckDepthAttachmentWrites();
CheckDynamicRenderingState();
@@ -17714,17 +17709,48 @@ int main(int argc, char **argv) {
vulkan.CheckUnifiedImageViewCache();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-view-cache-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedImageViewCache();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--storage-sampled-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedImageViewCache();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--depth-readback-only") == 0) {
CheckDepthTargetFootprints();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-range-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedTextureCacheFlow();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-gc-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckBufferCacheDirtyGarbageCollection();
return 0;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-death") == 0) {
RunReverseRenderTargetDeathCase();
}
if (argc == 2 && std::strcmp(argv[1], "--reverse-rt-only") == 0) {
CheckReverseRenderTargetFormatContract();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--standard64-rt-only") == 0) {
CheckStandard64RenderTargetTileRoundTrip();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-view-only") == 0) {
VulkanHarness vulkan;
CheckSampledColorViews();
CheckSampledVideoOutView(vulkan.RuntimeRenderer());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-view-cache-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedImageViewCache();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--image-transition-only") == 0) {
VulkanHarness vulkan;
CheckImageTransitionState(vulkan.RuntimeRenderer());
@@ -17736,16 +17762,6 @@ int main(int argc, char **argv) {
CheckSampledDepthDescriptor(vulkan.RuntimeRenderer());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-range-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedTextureCacheFlow();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--buffer-cache-gc-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckBufferCacheDirtyGarbageCollection();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--storage-bgra-only") == 0) {
CheckSampledColorViews();
CheckBasicStorageTextureDescriptor();
@@ -17762,15 +17778,6 @@ int main(int argc, char **argv) {
RunCase(&vulkan, ImageStoreYzwxUsesInverseSwizzle());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--storage-sampled-only") == 0) {
VulkanHarness vulkan;
vulkan.CheckUnifiedImageViewCache();
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--depth-readback-only") == 0) {
CheckDepthTargetFootprints();
return 0;
}
if (argc == 3 && std::strcmp(argv[1], "--image-view-death") == 0) {
RunImageViewDeathCase(argv[2]);
}