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