Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/windows-msys2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Windows MSYS2

on:
schedule:
- cron: '23 8 * * *'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: windows-msys2-${{ github.ref }}
cancel-in-progress: true

env:
CONFIGURE: >-
-G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
jobs:
windows-msys2:
runs-on: windows-latest
timeout-minutes: 45
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.sha }}
persist-credentials: false
- id: submodules
name: Key the submodules on their commits
run: |
echo "key=$(git ls-files -s | awk '$1 == "160000" { print $2, $4 }' | cat - .gitmodules | sha256sum | cut -c1-16)" >> "$GITHUB_OUTPUT"
echo "ffmpeg=$(git rev-parse HEAD:3rdparty/ffmpeg-core)" >> "$GITHUB_OUTPUT"
- id: submodule-cache
uses: actions/cache/restore@v6
with:
path: ${{ runner.temp }}/submodule-gitdirs.tar
key: submodule-gitdirs-${{ runner.os }}-${{ steps.submodules.outputs.key }}
- name: Check out the submodules
run: |
if [ -f "$RUNNER_TEMP/submodule-gitdirs.tar" ]; then tar --force-local -xf "$RUNNER_TEMP/submodule-gitdirs.tar"; fi
git submodule update --init --recursive --depth 1 --jobs 8
- if: steps.submodule-cache.outputs.cache-hit != 'true'
run: tar --force-local -cf "$RUNNER_TEMP/submodule-gitdirs.tar" .git/modules
- if: steps.submodule-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
continue-on-error: true
with:
path: ${{ runner.temp }}/submodule-gitdirs.tar
key: submodule-gitdirs-${{ runner.os }}-${{ steps.submodules.outputs.key }}
- name: Use the CA bundle of Git for Windows
run: |
for prefix in ucrt64 mingw64; do
bundle="C:/Program Files/Git/$prefix/etc/ssl/certs/ca-bundle.crt"
if [ -f "$bundle" ]; then echo "SSL_CERT_FILE=$bundle" >> "$GITHUB_ENV"; exit 0; fi
done
echo "::error::No CA bundle found in Git for Windows"; exit 1
- id: msys2
uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: >-
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-ninja
- shell: pwsh
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
run: Add-Content -LiteralPath $env:GITHUB_PATH -Value "$env:MSYS2_LOCATION\ucrt64\bin"
- uses: hendrikmuhs/ccache-action@v1.2.24
with:
key: windows-msys2
save: ${{ github.ref == 'refs/heads/main' }}
evict-old-files: job
- id: ffmpeg-cache
uses: actions/cache/restore@v6
with:
path: build/externals
key: ffmpeg-${{ runner.os }}-${{ steps.submodules.outputs.ffmpeg }}
- shell: cmd
run: cmake -S . -B build %CONFIGURE% "-DANYPS5_AGC_EXECUTION_TESTS=F16ToF32Unpack;F64FmaSubnormals;RegistrationPrepareShutdown"
- if: steps.ffmpeg-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v6
continue-on-error: true
with:
path: build/externals
key: ${{ steps.ffmpeg-cache.outputs.cache-primary-key }}
- name: Check that every test executable is registered with ctest
shell: cmd
run: python tools/check_test_registration.py build
- shell: cmd
run: cmake --build build
- shell: cmd
run: cmake --build build --target libs
- shell: cmd
working-directory: build
run: ctest --output-on-failure --timeout 120 --parallel 4 --output-log ctest.log --output-junit ctest.xml
- run: python tools/check_skips.py build/ctest.xml --job windows
- name: Report tests slower than 30 s
if: ${{ !cancelled() }}
run: |
[ -f build/ctest.log ] || exit 0
awk '/Test +#[0-9]+: / && $NF == "sec" { for (i = 1; i <= NF; i++) if ($i ~ /^#[0-9]+:$/) name = $(i + 1); t = $(NF - 1) + 0; if (t > max[name]) max[name] = t }
END { for (name in max) if (max[name] > 30) printf "::warning title=Slow test::%s took %.1f s on %s; tests must finish in 30 s (CONTRIBUTING.md)\n", name, max[name], ENVIRON["RUNNER_OS"] }' build/ctest.log
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: windows-msys2-tests
path: |
build/ctest.log
build/ctest.xml
if-no-files-found: ignore
16 changes: 13 additions & 3 deletions core/libs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@ if(MINGW)
check_cxx_source_compiles("#ifdef __USING_SJLJ_EXCEPTIONS__\n#error\n#endif\nint main() { return 0; }" ANYPS5_MINGW_DWARF_EXCEPTIONS)
unset(CMAKE_REQUIRED_FLAGS)
if(NOT ANYPS5_MINGW_DWARF_EXCEPTIONS)
message(FATAL_ERROR "${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} selects SjLj exceptions with -fno-asynchronous-unwind-tables; the libraries need DWARF unwinding. Use the MinGW-w64 toolchain listed in docs/dev/BUILD.md.")
check_cxx_source_compiles("#if !defined(__SEH__) || defined(__USING_SJLJ_EXCEPTIONS__)\n#error\n#endif\n[[gnu::sysv_abi, gnu::noinline]] void Throw() { throw 1; }\n[[gnu::sysv_abi, gnu::noinline]] int Catch() { try { Throw(); } catch (int value) { return value; } return 0; }\nint main() { return Catch() != 1; }" ANYPS5_MINGW_SYSV_SEH_EXCEPTIONS)
if(NOT ANYPS5_MINGW_SYSV_SEH_EXCEPTIONS)
message(FATAL_ERROR "The Windows libraries require DWARF exceptions or GCC with System V ABI SEH support. Use the toolchains listed in docs/dev/BUILD.md.")
endif()
add_library(windows_seh_personalities OBJECT ${CMAKE_CURRENT_SOURCE_DIR}/prx/libc/src/specifics/windows/SehPersonalities.cpp)
target_link_options(windows_seh_personalities INTERFACE LINKER:--undefined=LibcSehPersonalities_nid_no_patch)
link_libraries(windows_seh_personalities)
else()
add_compile_options(-fno-asynchronous-unwind-tables)
endif()
add_compile_options(-fno-asynchronous-unwind-tables)
endif()

function(configure_windows_unwind target)
Expand All @@ -50,7 +57,7 @@ function(configure_windows_unwind target)
message(FATAL_ERROR "Windows DWARF unwinding requires objcopy")
endif()
add_custom_command(TARGET ${target} POST_BUILD
COMMAND "${CMAKE_OBJCOPY}" --rename-section .eh_frame=.ehfram "$<TARGET_FILE:${target}>"
COMMAND "${CMAKE_OBJCOPY}" --rename-section .eh_frame=.ehfram --rename-section .eh_fram=.ehfram "$<TARGET_FILE:${target}>"
VERBATIM
)
endif()
Expand Down Expand Up @@ -327,6 +334,9 @@ if(BUILD_TESTING)

if(WIN32)
add_test_executable(undeliverable_host_exception_tests tests/UndeliverableHostException.cpp)
if(MINGW)
target_compile_options(undeliverable_host_exception_tests PRIVATE -fno-asynchronous-unwind-tables -fno-exceptions)
endif()
add_dependencies(undeliverable_host_exception_tests libc)
add_test(NAME undeliverable_host_exception COMMAND undeliverable_host_exception_tests $<TARGET_FILE:libc>)
set_tests_properties(undeliverable_host_exception PROPERTIES
Expand Down
1 change: 1 addition & 0 deletions core/libs/prx/libSceAjm.native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ configure_windows_unwind(libSceAjm.native)
if(BUILD_TESTING)
add_test_executable(atrac9_gradient_tests tests/Atrac9Gradient.c ${LIBATRAC9_DIR}/bit_reader.c)
target_include_directories(atrac9_gradient_tests PRIVATE ${LIBATRAC9_PATCHED_DIR} ${LIBATRAC9_DIR})
target_link_libraries(atrac9_gradient_tests PRIVATE atrac9)
target_compile_options(atrac9_gradient_tests PRIVATE -ffunction-sections -fdata-sections)
if(APPLE)
target_link_options(atrac9_gradient_tests PRIVATE LINKER:-dead_strip)
Expand Down
39 changes: 39 additions & 0 deletions core/libs/prx/libSceLibcInternal/UserContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,51 @@ constexpr std::uint64_t MinimumStackSize = 2048;

extern "C" {

#if defined(_WIN32) && defined(__SEH__)
[[noreturn]] void APS5_VABI Aps5MakecontextStart_nid_no_patch(UserUcontext* context, ContextFunction function, const std::uint64_t* arguments);

[[noreturn]] void APS5_VABI Aps5MakecontextFinish_nid_no_patch(UserUcontext* context) {
if (context->link == nullptr) LibcExit_nid_no_patch(0);
setcontext_nid_postfix(context->link);
std::abort();
}

asm(R"(
.text
.globl Aps5MakecontextStart_nid_no_patch
.def Aps5MakecontextStart_nid_no_patch; .scl 2; .type 32; .endef
.seh_proc Aps5MakecontextStart_nid_no_patch
Aps5MakecontextStart_nid_no_patch:
pushq %rbp
.seh_pushreg %rbp
subq $48, %rsp
.seh_stackalloc 48
leaq 48(%rsp), %rbp
.seh_setframe %rbp, 48
.seh_endprologue
movq %rdi, -8(%rbp)
movq %rsi, %rax
movq %rdx, %r10
movq (%r10), %rdi
movq 8(%r10), %rsi
movq 16(%r10), %rdx
movq 24(%r10), %rcx
movq 32(%r10), %r8
movq 40(%r10), %r9
call *%rax
movq -8(%rbp), %rdi
call Aps5MakecontextFinish_nid_no_patch
ud2
.seh_endproc
)");
#else
[[noreturn]] void APS5_VABI Aps5MakecontextStart_nid_no_patch(UserUcontext* context, ContextFunction function, const std::uint64_t* arguments) {
function(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]);
if (context->link == nullptr) LibcExit_nid_no_patch(0);
setcontext_nid_postfix(context->link);
std::abort();
}
#endif

void APS5_VABI makecontext_nid_postfix(UserUcontext* context, void (APS5_VABI* start)(), int count, ...) {
if (context == nullptr || context->mcontext.len != sizeof(UserMcontext)) return;
Expand Down
2 changes: 1 addition & 1 deletion core/libs/prx/libc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ if(MINGW)
target_include_directories(guest_message_destructor_abi_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_message_destructor_abi_tests PRIVATE libc)
configure_windows_unwind(guest_message_destructor_abi_tests)
foreach(mode IN ITEMS control decoy zero release regex-decoy regex-zero
foreach(mode IN ITEMS control hybrid decoy zero release regex-decoy regex-zero
runtime-decoy runtime-zero doraise-decoy doraise-zero
unshared-decoy unshared-zero regex-unshared-decoy regex-unshared-zero)
add_test(NAME guest_message_destructor_${mode} COMMAND guest_message_destructor_abi_tests ${mode})
Expand Down
49 changes: 38 additions & 11 deletions core/libs/prx/libc/src/exception/Unwind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,21 @@ extern "C" void _Unwind_DeleteException(_Unwind_Exception*);
#endif

namespace LibcUnwind {
_Unwind_Reason_Code CallPersonality(Word personality, _Unwind_Action actions, _Unwind_Exception* exception, _Unwind_Context* context) {
#ifdef _WIN32
const auto* code = reinterpret_cast<const Byte*>(personality);
static Word ResolveImport(Word address) {
const auto* code = reinterpret_cast<const Byte*>(address);
if (code[0] == 0xff && code[1] == 0x25) {
std::int32_t displacement;
std::memcpy(&displacement, code + 2, 4);
std::memcpy(&personality, code + 6 + displacement, sizeof(personality));
std::memcpy(&displacement, code + 2, sizeof(displacement));
std::memcpy(&address, code + 6 + displacement, sizeof(address));
}
return address;
}
#endif

_Unwind_Reason_Code CallPersonality(Word personality, _Unwind_Action actions, _Unwind_Exception* exception, _Unwind_Context* context) {
#ifdef _WIN32
personality = ResolveImport(personality);
#endif
if (personality == reinterpret_cast<Word>(__gxx_personality_v0_nid_postfix) || personality == reinterpret_cast<Word>(__gxx_personality_v0))
return __gxx_personality_v0_nid_postfix(1, actions, exception->exception_class, exception, context);
Expand Down Expand Up @@ -257,14 +264,20 @@ const IMAGE_NT_HEADERS64* ImageHeaders(Word pc, const Byte*& base) {
return nt->Signature == IMAGE_NT_SIGNATURE ? nt : nullptr;
}

bool VirtualUnwind(const _Unwind_Context& context, _Unwind_Context& caller) {
bool VirtualUnwind(_Unwind_Context& context, Frame& frame) {
const Word pc = context.registers[16];
const Byte* base = nullptr;
const auto* nt = ImageHeaders(pc, base);
if (!nt) return false;
const auto* sections = IMAGE_FIRST_SECTION(nt);
for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i)
if (std::memcmp(sections[i].Name, ".ehmeta", 8) == 0 || std::memcmp(sections[i].Name, ".ehfram", 8) == 0) return false;
const PEXCEPTION_ROUTINE* personalities = nullptr;
bool dwarf = false;
for (unsigned i = 0; i < nt->FileHeader.NumberOfSections; ++i) {
if (std::memcmp(sections[i].Name, ".ehmeta", 8) == 0 || std::memcmp(sections[i].Name, ".ehfram", 8) == 0) dwarf = true;
if (std::memcmp(sections[i].Name, ".sehgcc", 8) == 0 && sections[i].Misc.VirtualSize >= 2 * sizeof(PEXCEPTION_ROUTINE))
personalities = reinterpret_cast<const PEXCEPTION_ROUTINE*>(base + sections[i].VirtualAddress);
}
if (dwarf && !personalities) return false;
DWORD64 imageBase = 0;
auto* function = RtlLookupFunctionEntry(pc, &imageBase, nullptr);
if (!function) return false;
Expand All @@ -275,11 +288,27 @@ bool VirtualUnwind(const _Unwind_Context& context, _Unwind_Context& caller) {
for (unsigned i = 0; i < 16; ++i) std::memcpy(&state.FltSave.XmmRegisters[i], context.vectorRegisters[i], 16);
void* handlerData = nullptr;
DWORD64 establisher = 0;
if (RtlVirtualUnwind(UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER, imageBase, pc, function, &state, &handlerData, &establisher, nullptr)) return false;
const auto handler = RtlVirtualUnwind(UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER, imageBase, pc, function, &state, &handlerData, &establisher, nullptr);
frame.start = imageBase + function->BeginAddress;
if (handler) {
if (!personalities) return false;
const Word resolved = ResolveImport(reinterpret_cast<Word>(handler));
if (resolved == ResolveImport(reinterpret_cast<Word>(personalities[0]))) frame.personality = reinterpret_cast<Word>(__gxx_personality_v0_nid_postfix);
else if (resolved == ResolveImport(reinterpret_cast<Word>(personalities[1]))) frame.personality = reinterpret_cast<Word>(__gcc_personality_v0_nid_postfix);
else return false;
frame.lsda = reinterpret_cast<Word>(handlerData);
}
auto& caller = frame.caller;
caller = context;
for (unsigned i = 0; i < 17; ++i) caller.registers[i] = *registers[i];
for (unsigned i = 0; i < 16; ++i) std::memcpy(caller.vectorRegisters[i], &state.FltSave.XmmRegisters[i], 16);
caller.signalFrame = false;
context.cfa = caller.registers[7];
context.region = frame.start;
context.lsda = frame.lsda;
context.personality = frame.personality;
context.textBase = imageBase;
context.dataBase = 0;
return true;
}
#endif
Expand Down Expand Up @@ -414,10 +443,8 @@ bool GetRules(_Unwind_Context& context, Frame& frame, Rules& rules) {
if (!DecodeFrame(context, frame)) {
#ifdef _WIN32
frame = {};
if (!VirtualUnwind(context, frame.caller)) return false;
if (!VirtualUnwind(context, frame)) return false;
frame.foreign = true;
context.cfa = frame.caller.registers[7];
context.region = context.lsda = context.personality = context.textBase = context.dataBase = 0;
return true;
#else
return false;
Expand Down
14 changes: 10 additions & 4 deletions core/libs/prx/libc/src/specifics/windows/CrashReport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ constexpr DWORD GccException = 0x20474343u;
constexpr std::uint64_t GccExceptionClass = 0x474e5543432b2b00ull;
constexpr std::uint64_t CxaExceptionTypeOffset = 80;

PWSTR ThreadDescription(HANDLE thread) {
using GetDescription = HRESULT (WINAPI *)(HANDLE, PWSTR*);
static const auto getDescription = reinterpret_cast<GetDescription>(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetThreadDescription"));
PWSTR description = nullptr;
if (!getDescription || FAILED(getDescription(thread, &description))) return nullptr;
return description;
}

std::uint64_t FrameWithoutUnwindData(const CONTEXT& start) {
const HMODULE runtime[] = {GetModuleHandleA("KERNELBASE.dll"), GetModuleHandleA("libgcc_s_seh-1.dll"), GetModuleHandleA("libstdc++-6.dll")};
CONTEXT context = start;
Expand Down Expand Up @@ -363,8 +371,7 @@ LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) {
const auto* context = info->ContextRecord;
char line[MAX_PATH + 64];
char threadName[128] = "";
PWSTR description = nullptr;
if (SUCCEEDED(GetThreadDescription(GetCurrentThread(), &description)) && description) {
if (PWSTR description = ThreadDescription(GetCurrentThread())) {
WideCharToMultiByte(CP_UTF8, 0, description, -1, threadName, sizeof(threadName), nullptr, nullptr);
LocalFree(description);
}
Expand Down Expand Up @@ -582,8 +589,7 @@ void ReportAllThreads() {
const std::size_t candidateCount = haveContext && frameCount < 2 ? CaptureStackCandidates(context.Rsp, candidates, sizeof(candidates) / sizeof(candidates[0])) : 0;
ResumeThread(thread);
char name[128] = "";
PWSTR description = nullptr;
if (SUCCEEDED(GetThreadDescription(thread, &description)) && description) {
if (PWSTR description = ThreadDescription(thread)) {
WideCharToMultiByte(CP_UTF8, 0, description, -1, name, sizeof(name), nullptr, nullptr);
LocalFree(description);
}
Expand Down
11 changes: 11 additions & 0 deletions core/libs/prx/libc/src/specifics/windows/SehPersonalities.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <windows.h>

extern "C" {
EXCEPTION_DISPOSITION __gxx_personality_seh0(EXCEPTION_RECORD*, void*, CONTEXT*, void*);
EXCEPTION_DISPOSITION __gcc_personality_seh0(EXCEPTION_RECORD*, void*, CONTEXT*, void*);

[[gnu::section(".sehgcc"), gnu::used]] extern const PEXCEPTION_ROUTINE LibcSehPersonalities_nid_no_patch[] {
__gxx_personality_seh0,
__gcc_personality_seh0,
};
}
Loading
Loading