diff --git a/CMakeLists.txt b/CMakeLists.txt index 98a1b94b4..8308a7613 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,10 @@ set(SDL_HAPTIC OFF CACHE BOOL "" FORCE) set(SDL_HIDAPI OFF CACHE BOOL "" FORCE) set(SDL_SENSOR OFF CACHE BOOL "" FORCE) set(SDL_STATIC_PIC ON CACHE BOOL "" FORCE) +set(ANYPS5_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") +set(CMAKE_EXE_LINKER_FLAGS "") add_subdirectory(3rdparty/SDL2) +set(CMAKE_EXE_LINKER_FLAGS "${ANYPS5_EXE_LINKER_FLAGS}") add_subdirectory(core/shader/recompiler) add_subdirectory(core/libs) diff --git a/README.md b/README.md index e7efe9009..13abea598 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Releases will be published after the first full successful launch of at least on Execution reaches `_start`, [stack unwinding](core/libs/prx/libc/src/exception/Unwind.cpp) and exception handling tables are built, reaches main. Unsupported or unexpected states strictly throw `std::runtime_error`. `what()` is printed to stderr and the process terminates. -The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces validated via [Spirv-Tools](3rdparty/SPIRV-Tools) SPIR-V. +The [shader recompiler](core/shader/recompiler/Recompiler.cpp) successfully produces SPIR-V, validated via [Spirv-Tools](3rdparty/SPIRV-Tools) when built with `ANYPS5_ENABLE_SPIRV_TOOLS`. The real game reaches the logo, main menu, and [gameplay](https://gist.github.com/user-attachments/assets/81d28e9b-c237-4545-b2ca-720071129816) with audio. diff --git a/core/libs/CMakeLists.txt b/core/libs/CMakeLists.txt index a76a7dbfe..b0572b5bf 100644 --- a/core/libs/CMakeLists.txt +++ b/core/libs/CMakeLists.txt @@ -1,3 +1,8 @@ +if(NOT WIN32) + string(REPLACE "-static " "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ") + string(STRIP "${CMAKE_EXE_LINKER_FLAGS}" CMAKE_EXE_LINKER_FLAGS) +endif() + add_executable(nid_patcher ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/Sha1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nid/src/NidCompute.cpp @@ -103,6 +108,255 @@ endforeach() add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS}) +if(BUILD_TESTING) + add_executable(guest_rendering_wait_tests tests/GuestRenderingWait.cpp) + target_include_directories(guest_rendering_wait_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_rendering_wait_tests PRIVATE libSceAgcDriver libc) + configure_windows_unwind(guest_rendering_wait_tests) + add_test(NAME guest_rendering_wait COMMAND guest_rendering_wait_tests) + if(WIN32) + set_tests_properties(guest_rendering_wait PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_buffer_reuse_tests tests/GuestBufferReuse.cpp) + target_include_directories(guest_buffer_reuse_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_buffer_reuse_tests PRIVATE libc) + configure_windows_unwind(guest_buffer_reuse_tests) + add_test(NAME guest_buffer_reuse COMMAND guest_buffer_reuse_tests) + if(WIN32) + set_tests_properties(guest_buffer_reuse PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_shader_link_tests tests/GuestShaderLink.cpp) + target_include_directories(guest_shader_link_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_shader_link_tests PRIVATE libSceAgc libc) + configure_windows_unwind(guest_shader_link_tests) + add_test(NAME guest_shader_link COMMAND guest_shader_link_tests) + if(WIN32) + set_tests_properties(guest_shader_link PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_system_exit_tests tests/GuestSystemExit.cpp) + target_include_directories(guest_system_exit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_system_exit_tests PRIVATE libSceSystemService libc) + configure_windows_unwind(guest_system_exit_tests) + add_test(NAME guest_system_exit_parameters COMMAND guest_system_exit_tests) + add_test(NAME guest_system_exit COMMAND guest_system_exit_tests --exit) + add_test(NAME guest_immediate_exit COMMAND guest_system_exit_tests --immediate) + set_tests_properties(guest_system_exit PROPERTIES + PASS_REGULAR_EXPRESSION "Guest shutdown and atexit completed") + if(WIN32) + set_tests_properties(guest_system_exit guest_system_exit_parameters guest_immediate_exit PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_character_tests tests/GuestCharacter.cpp) + target_include_directories(guest_character_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_character_tests PRIVATE libc) + configure_windows_unwind(guest_character_tests) + add_test(NAME guest_character COMMAND guest_character_tests) + if(WIN32) + set_tests_properties(guest_character PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_math_tests tests/GuestMath.cpp) + target_include_directories(guest_math_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_math_tests PRIVATE libc) + configure_windows_unwind(guest_math_tests) + add_test(NAME guest_math COMMAND guest_math_tests) + if(WIN32) + set_tests_properties(guest_math PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_working_directory_tests tests/GuestWorkingDirectory.cpp) + target_include_directories(guest_working_directory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_working_directory_tests PRIVATE libc) + configure_windows_unwind(guest_working_directory_tests) + add_test(NAME guest_working_directory COMMAND guest_working_directory_tests) + if(WIN32) + set_tests_properties(guest_working_directory PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_library(guest_module_fixture SHARED tests/GuestModuleFixture.cpp) + target_include_directories(guest_module_fixture PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + add_custom_command(TARGET guest_module_fixture POST_BUILD + COMMAND $ guest_module_fixture $ + VERBATIM) + add_dependencies(guest_module_fixture nid_patcher) + add_executable(guest_dynamic_loader_tests tests/GuestDynamicLoader.cpp) + target_include_directories(guest_dynamic_loader_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_dynamic_loader_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_dynamic_loader_tests) + add_dependencies(guest_dynamic_loader_tests guest_module_fixture) + add_test(NAME guest_dynamic_loader COMMAND guest_dynamic_loader_tests $) + set_tests_properties(guest_dynamic_loader PROPERTIES + WORKING_DIRECTORY "$") + if(WIN32) + set_tests_properties(guest_dynamic_loader PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_filesystem_tests tests/GuestFilesystem.cpp) + target_include_directories(guest_filesystem_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_filesystem_tests PRIVATE libc) + configure_windows_unwind(guest_filesystem_tests) + add_test(NAME guest_filesystem COMMAND guest_filesystem_tests) + if(WIN32) + set_tests_properties(guest_filesystem PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_directory_tests tests/GuestDirectory.cpp) + target_include_directories(guest_directory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_directory_tests PRIVATE libc) + configure_windows_unwind(guest_directory_tests) + add_test(NAME guest_directory COMMAND guest_directory_tests) + if(WIN32) + set_tests_properties(guest_directory PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_environment_tests tests/GuestEnvironment.cpp) + target_include_directories(guest_environment_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_environment_tests PRIVATE libc) + configure_windows_unwind(guest_environment_tests) + add_test(NAME guest_environment COMMAND guest_environment_tests) + if(WIN32) + set_tests_properties(guest_environment PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_standard_stream_tests tests/GuestStandardStreams.cpp) + target_include_directories(guest_standard_stream_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_standard_stream_tests PRIVATE libc) + configure_windows_unwind(guest_standard_stream_tests) + add_test(NAME guest_standard_streams COMMAND guest_standard_stream_tests) + if(WIN32) + set_tests_properties(guest_standard_streams PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_once_tests tests/GuestOnce.cpp) + target_include_directories(guest_once_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_once_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_once_tests) + add_test(NAME guest_once COMMAND guest_once_tests) + set_tests_properties(guest_once PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_once PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_signal_tests tests/GuestSignals.cpp) + target_include_directories(guest_signal_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_signal_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_signal_tests) + add_test(NAME guest_signals COMMAND guest_signal_tests) + if(WIN32) + set_tests_properties(guest_signals PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_system_configuration_tests tests/GuestSystemConfiguration.cpp) + target_include_directories(guest_system_configuration_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_system_configuration_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_system_configuration_tests) + add_test(NAME guest_system_configuration COMMAND guest_system_configuration_tests) + if(WIN32) + set_tests_properties(guest_system_configuration PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_udp_tests tests/GuestUdp.cpp) + target_include_directories(guest_udp_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_udp_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_udp_tests) + add_test(NAME guest_udp COMMAND guest_udp_tests) + set_tests_properties(guest_udp PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_udp PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_address_tests tests/GuestAddressConversion.cpp) + target_include_directories(guest_address_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_address_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_address_tests) + add_test(NAME guest_addresses COMMAND guest_address_tests) + if(WIN32) + set_tests_properties(guest_addresses PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_error_tests tests/GuestErrors.cpp) + target_include_directories(guest_error_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_error_tests PRIVATE libc) + configure_windows_unwind(guest_error_tests) + add_test(NAME guest_errors COMMAND guest_error_tests) + if(WIN32) + set_tests_properties(guest_errors PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_random_tests tests/GuestRandom.cpp) + target_include_directories(guest_random_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_random_tests PRIVATE libSceRandom) + add_test(NAME guest_random COMMAND guest_random_tests) + if(WIN32) + set_tests_properties(guest_random PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_resolver_tests tests/GuestResolver.cpp) + target_include_directories(guest_resolver_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_resolver_tests PRIVATE libScePosixForWebKit) + add_test(NAME guest_resolver COMMAND guest_resolver_tests) + if(WIN32) + set_tests_properties(guest_resolver PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_rtc_tests tests/GuestRtc.cpp) + target_include_directories(guest_rtc_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_rtc_tests PRIVATE libSceRtc) + add_test(NAME guest_rtc COMMAND guest_rtc_tests) + if(WIN32) + set_tests_properties(guest_rtc PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(mspace_tests tests/Mspace.cpp) + target_include_directories(mspace_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(mspace_tests PRIVATE libc) + configure_windows_unwind(mspace_tests) + add_test(NAME mspace COMMAND mspace_tests) + if(WIN32) + set_tests_properties(mspace PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_memory_tests tests/GuestMemory.cpp) + target_include_directories(guest_memory_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_memory_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_memory_tests) + add_test(NAME guest_memory COMMAND guest_memory_tests) + if(WIN32) + set_tests_properties(guest_memory PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_search_tests tests/GuestSearch.cpp) + target_include_directories(guest_search_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_search_tests PRIVATE libc) + configure_windows_unwind(guest_search_tests) + add_test(NAME guest_search COMMAND guest_search_tests) + if(WIN32) + set_tests_properties(guest_search PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(guest_string_tests tests/GuestStrings.cpp) + target_include_directories(guest_string_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(guest_string_tests PRIVATE libc libScePosixForWebKit) + configure_windows_unwind(guest_string_tests) + add_test(NAME guest_strings COMMAND guest_string_tests) + if(WIN32) + set_tests_properties(guest_strings PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + add_executable(exception_runtime_tests tests/ExceptionRuntime.cpp) + target_include_directories(exception_runtime_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(exception_runtime_tests PRIVATE libc) + configure_windows_unwind(exception_runtime_tests) + add_test(NAME exception_runtime COMMAND exception_runtime_tests) + if(WIN32) + set_tests_properties(exception_runtime PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() + add_executable(application_heap_tests EXCLUDE_FROM_ALL tests/ApplicationHeap.cpp prx/libc/src/ApplicationHeap.cpp) target_include_directories(application_heap_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(application_heap_tests PROPERTIES CXX_EXTENSIONS OFF) @@ -115,11 +369,6 @@ set_target_properties(guest_locale_tests PROPERTIES CXX_EXTENSIONS OFF) target_link_libraries(guest_locale_tests PRIVATE libc) configure_windows_unwind(guest_locale_tests) -add_executable(guest_rtc_tests EXCLUDE_FROM_ALL tests/GuestRtc.cpp) -target_include_directories(guest_rtc_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -set_target_properties(guest_rtc_tests PROPERTIES CXX_EXTENSIONS OFF) -target_link_libraries(guest_rtc_tests PRIVATE libSceRtc) - if(MINGW) foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static) string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index 3b149bce3..51b0a9caf 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -173,9 +173,9 @@ using PthreadCondattr = PthreadCondattrPrivate*; using PthreadKey = int; using pthread_entry_func_t = void* (*)(void*); using pthread_key_destructor_func_t = void (*)(void*); -using thread_dtors_func_t = void (*)(); -using get_thread_atexit_count_func_t = int (*)(KernelModule); -using thread_atexit_report_func_t = void (*)(KernelModule); +using thread_dtors_func_t = void (APS5_VABI *)(); +using get_thread_atexit_count_func_t = int (APS5_VABI *)(KernelModule); +using thread_atexit_report_func_t = void (APS5_VABI *)(KernelModule); struct FileStat { std::uint32_t st_dev; diff --git a/core/libs/nid/include/nid/NidPatcherUtils.hpp b/core/libs/nid/include/nid/NidPatcherUtils.hpp index 886296fef..f46775612 100644 --- a/core/libs/nid/include/nid/NidPatcherUtils.hpp +++ b/core/libs/nid/include/nid/NidPatcherUtils.hpp @@ -44,7 +44,7 @@ inline std::string StripNidPostfix(const std::string& name) { std::string result = name; if (result.size() >= kNidPostfixLen && result.compare(result.size() - kNidPostfixLen, kNidPostfixLen, kNidPostfix) == 0) { - result = result.substr(0u, result.size() - kNidPostfixLen); + result.resize(result.size() - kNidPostfixLen); } const auto disambigPos = result.rfind(kNidDisambigMarker); if (disambigPos != std::string::npos) { @@ -54,7 +54,7 @@ inline std::string StripNidPostfix(const std::string& name) { [](unsigned char c) { return std::isdigit(c) != 0; } ); if (allDigits && suffixStart < result.size()) - result = result.substr(0u, disambigPos); + result.resize(disambigPos); } return result; } diff --git a/core/libs/prx/libRenoirCore.PS5/Export.cpp b/core/libs/prx/libRenoirCore.PS5/Export.cpp new file mode 100644 index 000000000..c0cf955a3 --- /dev/null +++ b/core/libs/prx/libRenoirCore.PS5/Export.cpp @@ -0,0 +1,145 @@ +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +extern "C" { + +APS5_EXPORT("0DsDTr534gc", renoirStub00); +uint64_t APS5_VABI renoirStub00(void) { + NotImplemented_nid_no_patch("0DsDTr534gc"); + return 0; +} + +APS5_EXPORT("2T56HgbPW08", renoirStub01); +uint64_t APS5_VABI renoirStub01(void) { + NotImplemented_nid_no_patch("2T56HgbPW08"); + return 0; +} + +APS5_EXPORT("3HnCozh8Az8", renoirStub02); +uint64_t APS5_VABI renoirStub02(void) { + NotImplemented_nid_no_patch("3HnCozh8Az8"); + return 0; +} + +APS5_EXPORT("5Ysce+s-CWU", renoirStub03); +uint64_t APS5_VABI renoirStub03(void) { + NotImplemented_nid_no_patch("5Ysce+s-CWU"); + return 0; +} + +APS5_EXPORT("AdlSqA4lGS8", renoirStub04); +uint64_t APS5_VABI renoirStub04(void) { + NotImplemented_nid_no_patch("AdlSqA4lGS8"); + return 0; +} + +APS5_EXPORT("bC37hk7rHqs", renoirStub05); +uint64_t APS5_VABI renoirStub05(void) { + NotImplemented_nid_no_patch("bC37hk7rHqs"); + return 0; +} + +APS5_EXPORT("dfPK9R6M0qM", renoirStub06); +uint64_t APS5_VABI renoirStub06(void) { + NotImplemented_nid_no_patch("dfPK9R6M0qM"); + return 0; +} + +APS5_EXPORT("fZiBbVa2ZDU", renoirStub07); +uint64_t APS5_VABI renoirStub07(void) { + NotImplemented_nid_no_patch("fZiBbVa2ZDU"); + return 0; +} + +APS5_EXPORT("hkWBH8-WfW4", renoirStub08); +uint64_t APS5_VABI renoirStub08(void) { + NotImplemented_nid_no_patch("hkWBH8-WfW4"); + return 0; +} + +APS5_EXPORT("hUxvGdi64Io", renoirStub09); +uint64_t APS5_VABI renoirStub09(void) { + NotImplemented_nid_no_patch("hUxvGdi64Io"); + return 0; +} + +APS5_EXPORT("NxqsZxGisrA", renoirStub10); +uint64_t APS5_VABI renoirStub10(void) { + NotImplemented_nid_no_patch("NxqsZxGisrA"); + return 0; +} + +APS5_EXPORT("OgZVIkaNmb0", renoirStub11); +uint64_t APS5_VABI renoirStub11(void) { + NotImplemented_nid_no_patch("OgZVIkaNmb0"); + return 0; +} + +APS5_EXPORT("ojLQdRUnVgA", renoirStub12); +uint64_t APS5_VABI renoirStub12(void) { + NotImplemented_nid_no_patch("ojLQdRUnVgA"); + return 0; +} + +APS5_EXPORT("s0qDfgsAgVg", renoirStub13); +uint64_t APS5_VABI renoirStub13(void) { + NotImplemented_nid_no_patch("s0qDfgsAgVg"); + return 0; +} + +APS5_EXPORT("sgrd-r1Nw60", renoirStub14); +uint64_t APS5_VABI renoirStub14(void) { + NotImplemented_nid_no_patch("sgrd-r1Nw60"); + return 0; +} + +APS5_EXPORT("sjTRb8YD7dQ", renoirStub15); +uint64_t APS5_VABI renoirStub15(void) { + NotImplemented_nid_no_patch("sjTRb8YD7dQ"); + return 0; +} + +APS5_EXPORT("tG86XGc2VtE", renoirStub16); +uint64_t APS5_VABI renoirStub16(void) { + NotImplemented_nid_no_patch("tG86XGc2VtE"); + return 0; +} + +APS5_EXPORT("umlGxOcNzgc", renoirStub17); +uint64_t APS5_VABI renoirStub17(void) { + NotImplemented_nid_no_patch("umlGxOcNzgc"); + return 0; +} + +APS5_EXPORT("vPxA9OFW0os", renoirStub18); +uint64_t APS5_VABI renoirStub18(void) { + NotImplemented_nid_no_patch("vPxA9OFW0os"); + return 0; +} + +APS5_EXPORT("X2qEBlOLkNo", renoirStub19); +uint64_t APS5_VABI renoirStub19(void) { + NotImplemented_nid_no_patch("X2qEBlOLkNo"); + return 0; +} + +APS5_EXPORT("ynh4E+BIaqY", renoirStub20); +uint64_t APS5_VABI renoirStub20(void) { + NotImplemented_nid_no_patch("ynh4E+BIaqY"); + return 0; +} + +APS5_EXPORT("YXe6ABbHZPs", renoirStub21); +uint64_t APS5_VABI renoirStub21(void) { + NotImplemented_nid_no_patch("YXe6ABbHZPs"); + return 0; +} + +APS5_EXPORT("z0jsXZVz75I", renoirStub22); +uint64_t APS5_VABI renoirStub22(void) { + NotImplemented_nid_no_patch("z0jsXZVz75I"); + return 0; +} + +} diff --git a/core/libs/prx/libSceAgc/CMakeLists.txt b/core/libs/prx/libSceAgc/CMakeLists.txt index d1d99b23c..34eb04fb1 100644 --- a/core/libs/prx/libSceAgc/CMakeLists.txt +++ b/core/libs/prx/libSceAgc/CMakeLists.txt @@ -44,6 +44,7 @@ add_library(libSceAgc SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Predication/src/Predication.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/CreateShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/InterpolantMapping.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/LinkShaders.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/PrimState.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Shader/src/ShaderUtils.cpp ) diff --git a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp index f418119c5..473c39dae 100644 --- a/core/libs/prx/libSceAgc/Cb/src/Sync.cpp +++ b/core/libs/prx/libSceAgc/Cb/src/Sync.cpp @@ -39,6 +39,8 @@ std::uint32_t* APS5_VABI sceAgcCbReleaseMem(CommandBuffer* buf, std::uint8_t act } else if (dataSelect == 5) { Agc::Command::Require(data == 0, __func__, "GDS release cannot use immediate data"); value = gdsOffset | (static_cast(gdsSize) << 16u); + } else { + Agc::Command::Require(gdsOffset == 0 && gdsSize <= 1, __func__, "GDS parameters supplied for a non-GDS release"); } // A null label is accepted: games emit releases whose destination is never read. if (dataSelect != 0 && interrupt != 4 && guestAddress != 0) { diff --git a/core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp b/core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp index 4866a9186..820fb9a77 100644 --- a/core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp +++ b/core/libs/prx/libSceAgc/Misc/src/RegisterDefaults.cpp @@ -9,6 +9,11 @@ extern "C" { +void* APS5_VABI sceAgcGetRegisterDefaults() { + // The legacy API has no version argument; use the baseline public table. + return Agc::Command::GetRegisterDefaults(0, false, __func__); +} + void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t version) { return Agc::Command::GetRegisterDefaults(version, false, __func__); } diff --git a/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp b/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp new file mode 100644 index 000000000..8f0b7e266 --- /dev/null +++ b/core/libs/prx/libSceAgc/Shader/src/LinkShaders.cpp @@ -0,0 +1,30 @@ +#include "SceShaders.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" +#include +#include + +extern "C" int APS5_VABI sceAgcCreatePrimState(ShaderRegister*, ShaderRegister*, const Shader*, const Shader*, std::uint32_t); +extern "C" int APS5_VABI sceAgcCreateInterpolantMapping(ShaderRegister*, const Shader*, const Shader*); + +extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister* context, ShaderRegister* primitive, + const void* reserved, const Shader* vertex, const Shader* pixel, std::uint32_t primitiveType) { + using namespace ShaderRegs; + if (!context || !primitive || reserved || !vertex || + vertex->type != static_cast(ShaderBinaryType::Gs) || + (vertex->num_output_semantics && !vertex->output_semantics) || + (pixel && (pixel->type != static_cast(ShaderBinaryType::Ps) || + pixel->num_input_semantics > 32 || + (pixel->num_input_semantics && !pixel->input_semantics)))) + return GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM; + + // The simple vertex/pixel pipeline combines two primitive context registers + // and 32 interpolant registers. Publish only after both helpers succeed. + std::array contextValues{}; + std::array primitiveValues{}; + sceAgcCreatePrimState(contextValues.data(), primitiveValues.data(), nullptr, vertex, primitiveType); + sceAgcCreateInterpolantMapping(contextValues.data() + 2, vertex, pixel); + std::copy(contextValues.begin(), contextValues.end(), context); + std::copy(primitiveValues.begin(), primitiveValues.end(), primitive); + return 0; +} diff --git a/core/libs/prx/libSceAgcDriver/CMakeLists.txt b/core/libs/prx/libSceAgcDriver/CMakeLists.txt index 71f2dad5d..e97930867 100644 --- a/core/libs/prx/libSceAgcDriver/CMakeLists.txt +++ b/core/libs/prx/libSceAgcDriver/CMakeLists.txt @@ -218,6 +218,8 @@ add_executable(agc_driver_graphics_tests EXCLUDE_FROM_ALL tests/GuestTextureResource.cpp tests/GuestSamplerResource.cpp tests/TextureDetiler.cpp + Graphics/src/Draw.cpp + Graphics/src/Pipeline.cpp Graphics/src/State.cpp Graphics/src/ColorTargetLayout.cpp Graphics/src/ColorTargetTransfer.cpp @@ -253,6 +255,7 @@ target_include_directories(agc_driver_graphics_tests PRIVATE ) target_link_libraries(agc_driver_graphics_tests PRIVATE + shader_recompiler_agc_driver libc ) @@ -278,6 +281,18 @@ target_link_libraries(agc_driver_bda_device_tests PRIVATE SDL2-static libc shade set_target_properties(agc_driver_bda_device_tests PROPERTIES CXX_EXTENSIONS OFF) configure_windows_unwind(agc_driver_bda_device_tests) +if(BUILD_TESTING) + foreach(agcTest IN ITEMS agc_driver_tests agc_shader_memory_tests agc_driver_flip_tests agc_driver_pm4_tests) + string(REGEX REPLACE "_tests$" "" agcTestName "${agcTest}") + set_target_properties(${agcTest} PROPERTIES EXCLUDE_FROM_ALL OFF) + add_test(NAME ${agcTestName} COMMAND ${agcTest}) + if(WIN32) + set_tests_properties(${agcTestName} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endforeach() +endif() + option(AGC_BUILD_VISUAL_TEST "Enable the standalone AGC SPIR-V visual test" OFF) if(AGC_BUILD_VISUAL_TEST) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp index f5b6ab74a..79f96d8c1 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VideoOutput.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace AgcDriver { @@ -11,6 +12,15 @@ class FrameTiming; inline constexpr std::uint32_t FlipPacketHeader = 0xc004105cu; inline constexpr std::uint32_t FlipPacketWords = 6; +// AnyPS5's internal encoding for the driver-generated rendering wait. +inline constexpr std::uint32_t RenderingWaitPacketHeader = 0xc0021018u; +inline constexpr std::uint32_t RenderingWaitPacketWords = 4; + +class IRenderingWait { +public: + virtual ~IRenderingWait() = default; + virtual void Wait() = 0; +}; struct FlipInfo { std::uint32_t handle; @@ -30,6 +40,9 @@ class IVideoOutput { public: virtual ~IVideoOutput() = default; virtual std::shared_ptr Reserve(const FlipInfo& info) = 0; + virtual std::shared_ptr CaptureRenderingWait(std::uint32_t index) { + throw std::runtime_error("VideoOut: rendering waits are unsupported by this output"); + } virtual void Fail(std::exception_ptr error) noexcept = 0; }; diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp index 697d37b5b..84699adb2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver.cpp @@ -194,6 +194,7 @@ struct Submission { // copies it first when a submission still holds it). std::shared_ptr shaders; std::map> flips; + std::map> renderingWaits; bool suspend = false; // Record-order stamp (Driver::eventSerial) taken when the game submitted: a WAIT_REG_MEM of this // submission trusts only labels the recorder noted with a newer stamp (see Recorder::NoteLabel). @@ -508,6 +509,13 @@ public: require(accepted != std::numeric_limits::max(), "submission serial overflow"); for (std::size_t cursor = 0; cursor < submission.commands.size();) { const auto* words = submission.commands.data() + cursor; + if (words[0] == RenderingWaitPacketHeader) { + const auto output = outputs.find(words[1]); + require(output != outputs.end(), "rendering wait references an unregistered video output"); + auto wait = output->second->CaptureRenderingWait(words[2]); + require(wait != nullptr, "video output returned a null rendering wait"); + submission.renderingWaits.emplace(cursor, std::move(wait)); + } if (words[0] == FlipPacketHeader) { const auto output = outputs.find(words[1]); require(output != outputs.end(), "flip references an unregistered video output"); @@ -5870,7 +5878,7 @@ private: } traceLabel(packet, submission.queue); // Waits do not count as progress; every other packet does, including while it runs. - const bool waitPacket = opcode == 0x3c || opcode == 0x93; + const bool waitPacket = opcode == 0x3c || opcode == 0x93 || header == RenderingWaitPacketHeader; struct Progress { Driver& driver; bool counted; @@ -5882,7 +5890,9 @@ private: } progress{*this, !waitPacket}; if (!waitPacket) ++packetsInFlight; recent.Record(cursor); - if (header == FlipPacketHeader) { + if (header == RenderingWaitPacketHeader) { + timed(&WorkerProfile::waitMs, [&] { submission.renderingWaits.at(cursor)->Wait(); }); + } else if (header == FlipPacketHeader) { CheckFailure(); std::uint64_t batchesAtFlip = 0, unsignaledAtFlip = 0; if (!drains) { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp index cc8578d61..21dd47ae2 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Pm4.cpp @@ -131,8 +131,7 @@ std::string_view UnsupportedReason(std::uint32_t header) { const auto opcode = (header >> 8u) & 0xffu; if (opcode == 0x10) { switch ((header >> 2u) & 0x3fu) { - case 0: case 0x09: case 0x0b: case 0x0c: case 0x17: case 0x1a: return {}; - case 0x06: return "VideoOut buffer-completion wait interface is not implemented"; + case 0: case 0x06: case 0x09: case 0x0b: case 0x0c: case 0x17: case 0x1a: return {}; case 0x14: case 0x18: return "guest cache actions and GPU release events are not implemented"; default: return "custom packet has no implemented contract in the reference dispatch table"; } @@ -187,6 +186,7 @@ void Validate(std::span packet, std::uint32_t queue) { require((packet[1] & 0xffff0000u) != 0x68750000u, "typed user-data and legacy flip markers are not implemented"); break; case 0x09: size(2); break; + case 0x06: graphics(); size(4); require(packet[3] == 0, "unsupported rendering wait mode"); break; case 0x0b: { const auto data = std::as_bytes(packet.subspan(1)); require(std::find(data.begin(), data.end(), std::byte{}) != data.end(), "unterminated marker text"); diff --git a/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp b/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp index 6c217c0b4..48da441e6 100644 --- a/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp +++ b/core/libs/prx/libSceAgcDriver/Submit/src/Dcb.cpp @@ -1,5 +1,9 @@ #include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" #include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp" +#include +#include #include #include @@ -8,6 +12,22 @@ extern "C" { +std::uint32_t APS5_VABI sceAgcDriverGetWaitRenderingPacketSizeInDwords() { + return AgcDriver::RenderingWaitPacketWords; +} + +std::uint32_t APS5_VABI sceAgcDriverWaitUntilSafeForRendering(std::uint32_t** command, + std::uint32_t capacity, std::uint32_t mode, std::uint32_t handle, int index) { + if (!command || capacity < AgcDriver::RenderingWaitPacketWords || mode != 0 || index < 0) + throw std::invalid_argument("AGC driver: invalid rendering wait arguments"); + AgcDriver::GuestMemory::CheckRange(command, sizeof(*command), alignof(std::uint32_t*), true); + AgcDriver::GuestMemory::CheckRange(*command, AgcDriver::RenderingWaitPacketWords * sizeof(std::uint32_t), alignof(std::uint32_t), true); + const std::uint32_t words[] = {AgcDriver::RenderingWaitPacketHeader, handle, static_cast(index), mode}; + std::copy(std::begin(words), std::end(words), *command); + *command += AgcDriver::RenderingWaitPacketWords; + return 0; +} + int APS5_VABI sceAgcDriverSubmitDcb(const Packet* packet) { AgcDriver::Submit(packet, 0); return 0; diff --git a/core/libs/prx/libSceLibcInternal/Export.cpp b/core/libs/prx/libSceLibcInternal/Export.cpp index 7541f8ed1..6597f5f5b 100644 --- a/core/libs/prx/libSceLibcInternal/Export.cpp +++ b/core/libs/prx/libSceLibcInternal/Export.cpp @@ -1,14 +1,9 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include "prx/libc/include/HeapDiagnostics.hpp" extern "C" { - void APS5_VABI sceLibcHeapGetTraceInfo_nid_postfix(Info* info) { - (void)info; - NotImplemented_nid_no_patch(__func__); + LibcHeapTraceInfo_nid_no_patch(info); } } diff --git a/core/libs/prx/libScePosixForWebKit/CMakeLists.txt b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt new file mode 100644 index 000000000..f5e668f80 --- /dev/null +++ b/core/libs/prx/libScePosixForWebKit/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(libScePosixForWebKit SHARED EXCLUDE_FROM_ALL Export.cpp Resolver.cpp) +target_include_directories(libScePosixForWebKit PRIVATE ${LIBS_INCLUDE_DIR}) +if(WIN32) + target_link_libraries(libScePosixForWebKit PRIVATE ws2_32) +endif() +configure_windows_unwind(libScePosixForWebKit) diff --git a/core/libs/prx/libScePosixForWebKit/Export.cpp b/core/libs/prx/libScePosixForWebKit/Export.cpp new file mode 100644 index 000000000..71f7b26f6 --- /dev/null +++ b/core/libs/prx/libScePosixForWebKit/Export.cpp @@ -0,0 +1,22 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include + +extern "C" { + +char* APS5_VABI strcasestr_nid_postfix(const char* text, const char* needle) { + if (*needle == '\0') return const_cast(text); + for (; *text != '\0'; ++text) { + const char* candidate = text; + const char* match = needle; + while (*candidate != '\0' && *match != '\0' && + std::tolower(static_cast(*candidate)) == + std::tolower(static_cast(*match))) { + ++candidate; + ++match; + } + if (*match == '\0') return const_cast(text); + } + return nullptr; +} + +} diff --git a/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp b/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp new file mode 100644 index 000000000..d14d4524e --- /dev/null +++ b/core/libs/prx/libScePosixForWebKit/GuestResolver.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +namespace GuestResolver { +struct AddressInfo { + std::int32_t flags; + std::int32_t family; + std::int32_t socketType; + std::int32_t protocol; + std::uint32_t addressLength; + char* canonicalName; + void* address; + AddressInfo* next; +}; +static_assert(sizeof(AddressInfo) == 48); +static_assert(offsetof(AddressInfo, canonicalName) == 24); +static_assert(offsetof(AddressInfo, address) == 32); +} diff --git a/core/libs/prx/libScePosixForWebKit/Resolver.cpp b/core/libs/prx/libScePosixForWebKit/Resolver.cpp new file mode 100644 index 000000000..5952e6276 --- /dev/null +++ b/core/libs/prx/libScePosixForWebKit/Resolver.cpp @@ -0,0 +1,184 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#include +#include +#endif +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include "GuestResolver.hpp" + +namespace { +bool Ready() { +#ifdef _WIN32 + static const int status = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }(); + return status == 0; +#else + return true; +#endif +} +int GuestError(int error) { + if (error == 0) return 0; + if (error == EAI_AGAIN) return 2; + if (error == EAI_BADFLAGS) return 3; + if (error == EAI_FAMILY) return 5; + if (error == EAI_MEMORY) return 6; + if (error == EAI_NONAME) return 8; + if (error == EAI_SERVICE) return 9; + if (error == EAI_SOCKTYPE) return 10; +#ifdef _WIN32 + if (error == WSAEFAULT) return 14; +#else + if (error == EAI_OVERFLOW) return 14; +#endif + return 4; +} +} + +extern "C" { +void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo* first) { + while (first) { + auto* next = first->next; + std::free(first->canonicalName); + std::free(first->address); + std::free(first); + first = next; + } +} + +int APS5_VABI getaddrinfo_nid_postfix(const char* node, const char* service, + const GuestResolver::AddressInfo* hints, GuestResolver::AddressInfo** output) { + if (!output) return 4; + *output = nullptr; + addrinfo nativeHints{}; + if (hints) { + if (hints->addressLength || hints->address || hints->canonicalName || hints->next) return 12; + if ((hints->flags & ~0xd0f) != 0) return 3; + if (hints->family != 0 && hints->family != 2 && hints->family != 28) return 5; + if (hints->socketType != 0 && hints->socketType != 1 && hints->socketType != 2) return 10; + nativeHints.ai_family = hints->family == 28 ? AF_INET6 : hints->family == 2 ? AF_INET : AF_UNSPEC; + nativeHints.ai_socktype = hints->socketType == 1 ? SOCK_STREAM : hints->socketType == 2 ? SOCK_DGRAM : 0; + nativeHints.ai_protocol = hints->protocol; + if (hints->flags & 1) nativeHints.ai_flags |= AI_PASSIVE; + if (hints->flags & 2) nativeHints.ai_flags |= AI_CANONNAME; + if (hints->flags & 4) nativeHints.ai_flags |= AI_NUMERICHOST; + if (hints->flags & 8) nativeHints.ai_flags |= AI_NUMERICSERV; + if (hints->flags & 0x100) nativeHints.ai_flags |= AI_ALL; + if (hints->flags & 0x400) nativeHints.ai_flags |= AI_ADDRCONFIG; + if (hints->flags & 0x800) nativeHints.ai_flags |= AI_V4MAPPED; + } + if (!Ready()) return 4; + addrinfo* native = nullptr; + const auto error = ::getaddrinfo(node, service, &nativeHints, &native); + if (error) return GuestError(error); + GuestResolver::AddressInfo* first = nullptr; + auto** tail = &first; + int failure = 0; + for (auto* item = native; item; item = item->ai_next) { + if (item->ai_family != AF_INET && item->ai_family != AF_INET6) { failure = 5; break; } + auto* converted = static_cast(std::calloc(1, sizeof(GuestResolver::AddressInfo))); + if (!converted) { failure = 6; break; } + *tail = converted; + tail = &converted->next; + converted->flags = hints ? hints->flags : 0; + converted->family = item->ai_family == AF_INET ? 2 : 28; + converted->socketType = item->ai_socktype == SOCK_STREAM ? 1 : item->ai_socktype == SOCK_DGRAM ? 2 : 0; + converted->protocol = item->ai_protocol; + converted->addressLength = item->ai_family == AF_INET ? 16 : 28; + auto* bytes = static_cast(std::calloc(1, converted->addressLength)); + converted->address = bytes; + if (!bytes) { failure = 6; break; } + bytes[0] = static_cast(converted->addressLength); + bytes[1] = static_cast(converted->family); + if (item->ai_family == AF_INET) { + const auto* v4 = reinterpret_cast(item->ai_addr); + std::memcpy(bytes + 2, &v4->sin_port, 2); + std::memcpy(bytes + 4, &v4->sin_addr, 4); + } else { + const auto* v6 = reinterpret_cast(item->ai_addr); + std::memcpy(bytes + 2, &v6->sin6_port, 2); + std::memcpy(bytes + 4, &v6->sin6_flowinfo, 4); + std::memcpy(bytes + 8, &v6->sin6_addr, 16); + std::memcpy(bytes + 24, &v6->sin6_scope_id, 4); + } + if (item->ai_canonname) { + const auto size = std::strlen(item->ai_canonname) + 1; + converted->canonicalName = static_cast(std::malloc(size)); + if (!converted->canonicalName) { failure = 6; break; } + std::memcpy(converted->canonicalName, item->ai_canonname, size); + } + } + ::freeaddrinfo(native); + if (failure) { freeaddrinfo_nid_postfix(first); return failure; } + *output = first; + return 0; +} + +int APS5_VABI getnameinfo_nid_postfix(const void* address, std::uint32_t length, + char* host, std::uint32_t hostLength, char* service, std::uint32_t serviceLength, int flags) { + // PS5 sockaddr begins with byte-sized length and family. The host's does not. + if (!address || length < 2) return 5; + if ((!host && hostLength) || (!service && serviceLength)) return 4; + if ((flags & ~0x1f) != 0) return 3; + const auto* bytes = static_cast(address); + sockaddr_storage native{}; + socklen_t nativeLength; + if (bytes[1] == 2) { + if (length < 16 || bytes[0] != 16) return 5; + auto& v4 = reinterpret_cast(native); + v4.sin_family = AF_INET; + std::memcpy(&v4.sin_port, bytes + 2, 2); + std::memcpy(&v4.sin_addr, bytes + 4, 4); + nativeLength = sizeof(v4); + } else if (bytes[1] == 28) { + if (length < 28 || bytes[0] != 28) return 5; + auto& v6 = reinterpret_cast(native); + v6.sin6_family = AF_INET6; + std::memcpy(&v6.sin6_port, bytes + 2, 2); + std::memcpy(&v6.sin6_flowinfo, bytes + 4, 4); + std::memcpy(&v6.sin6_addr, bytes + 8, 16); + std::memcpy(&v6.sin6_scope_id, bytes + 24, 4); + nativeLength = sizeof(v6); + } else return 5; + int nativeFlags = 0; + if (flags & 1) nativeFlags |= NI_NOFQDN; + if (flags & 2) nativeFlags |= NI_NUMERICHOST; + if (flags & 4) nativeFlags |= NI_NAMEREQD; + if (flags & 8) nativeFlags |= NI_NUMERICSERV; + if (flags & 16) nativeFlags |= NI_DGRAM; + if (!Ready()) return 4; + // Stage results so host-specific short-buffer errors do not leak into the + // guest ABI, and neither guest output is partially modified on overflow. + char resolvedHost[NI_MAXHOST]{}; + char resolvedService[NI_MAXSERV]{}; + const bool wantHost = host && hostLength; + const bool wantService = service && serviceLength; + const int error = ::getnameinfo(reinterpret_cast(&native), nativeLength, + wantHost ? resolvedHost : nullptr, wantHost ? sizeof(resolvedHost) : 0, + wantService ? resolvedService : nullptr, wantService ? sizeof(resolvedService) : 0, nativeFlags); + if (error) return GuestError(error); + if ((wantHost && std::strlen(resolvedHost) >= hostLength) || + (wantService && std::strlen(resolvedService) >= serviceLength)) return 14; + if (wantHost) std::memcpy(host, resolvedHost, std::strlen(resolvedHost) + 1); + if (wantService) std::memcpy(service, resolvedService, std::strlen(resolvedService) + 1); + return 0; +} + +const char* APS5_VABI gai_strerror_nid_postfix(int error) { + static const char* messages[] = { + "Success", "Address family for hostname not supported", "Temporary failure in name resolution", + "Invalid flags", "Name resolution failed", "Address family not supported", + "Memory allocation failed", "No address for hostname", "Name or service not known", + "Service not supported for socket type", "Socket type not supported", "System error", + "Invalid hints", "Unknown protocol", "Buffer too small" + }; + if (error < 0 || error >= static_cast(sizeof(messages) / sizeof(messages[0]))) + return "Unknown address resolution error"; + return messages[error]; +} +} diff --git a/core/libs/prx/libSceRandom/Export.cpp b/core/libs/prx/libSceRandom/Export.cpp index 4ab56faf2..4e9ef2674 100644 --- a/core/libs/prx/libSceRandom/Export.cpp +++ b/core/libs/prx/libSceRandom/Export.cpp @@ -1,15 +1,29 @@ #include #include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +namespace { + +constexpr int SCE_RANDOM_ERROR_INVALID = static_cast(0x817C0016); +constexpr std::size_t SCE_RANDOM_MAX_SIZE = 64; + +} + extern "C" { int APS5_VABI sceRandomGetRandomNumber(void* buf, size_t size) { - (void)buf; - (void)size; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!buf || size > SCE_RANDOM_MAX_SIZE) return SCE_RANDOM_ERROR_INVALID; + static thread_local std::random_device device; + auto* bytes = static_cast(buf); + for (std::size_t offset = 0; offset < size; offset += sizeof(unsigned int)) { + const unsigned int value = device(); + std::memcpy(bytes + offset, &value, std::min(sizeof(value), size - offset)); + } + return 0; } } diff --git a/core/libs/prx/libSceSysmodule/Export.cpp b/core/libs/prx/libSceSysmodule/Export.cpp index 9bb667574..b0457018b 100644 --- a/core/libs/prx/libSceSysmodule/Export.cpp +++ b/core/libs/prx/libSceSysmodule/Export.cpp @@ -55,8 +55,8 @@ bool fillModuleInfoForUnwind(std::uint64_t addr, ModuleInfoForUnwind* info) { std::uint64_t end = 0; char perms[8] = {}; std::uint64_t offset = 0; - int devMajor = 0; - int devMinor = 0; + unsigned int devMajor = 0; + unsigned int devMinor = 0; std::uint64_t inode = 0; char path[4096] = {}; int parsed = std::sscanf( diff --git a/core/libs/prx/libSceSystemService/Export.cpp b/core/libs/prx/libSceSystemService/Export.cpp index 38277e0d9..9ff7183b3 100644 --- a/core/libs/prx/libSceSystemService/Export.cpp +++ b/core/libs/prx/libSceSystemService/Export.cpp @@ -1,12 +1,24 @@ #include #include #include +#include +#include "prx/libc/include/Shutdown.hpp" #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "prx/libSceSystemService/SystemService.hpp" extern "C" { +int APS5_VABI sceSystemServiceLoadExec(const char* path, const char* const* arguments) { + if (!path || !*path) return SYSTEM_SERVICE_ERROR_PARAMETER; + if (std::strcmp(path, "exit") != 0) { + NotImplemented_nid_no_patch("sceSystemServiceLoadExec: executable replacement"); + } + (void)arguments; + LibcRunShutdown_nid_postfix(); + std::exit(0); +} + int APS5_VABI sceSystemServiceDisableNoticeScreenSkipFlagAutoSet(void) { NotImplemented_nid_no_patch(__func__); return 0; @@ -95,9 +107,4 @@ int APS5_VABI sceSystemServiceSetNoticeScreenSkipFlag(void) { return 0; } -int APS5_VABI sceSystemServiceLoadExec() { - NotImplemented_nid_no_patch(__func__); - return 0; -} - } diff --git a/core/libs/prx/libSceVideoOut/include/BufferReuseTracker.hpp b/core/libs/prx/libSceVideoOut/include/BufferReuseTracker.hpp new file mode 100644 index 000000000..da5c4bf26 --- /dev/null +++ b/core/libs/prx/libSceVideoOut/include/BufferReuseTracker.hpp @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include +#include + +// Caller serializes access with the owning VideoOutConfig mutex. +class BufferReuseTracker { + std::uint64_t _issued = 0; + std::set _pending; +public: + std::uint64_t Reserve() { + if (_issued == std::numeric_limits::max()) + throw std::overflow_error("VideoOut: buffer reservation sequence exhausted"); + const auto ticket = _issued + 1; + _pending.insert(ticket); + _issued = ticket; + return ticket; + } + std::uint64_t Capture() const { return _issued; } + bool IsComplete(std::uint64_t through) const { + if (through > _issued) throw std::invalid_argument("VideoOut: unissued buffer fence"); + return _pending.empty() || *_pending.begin() > through; + } + void Complete(std::uint64_t ticket) noexcept { + if (_pending.erase(ticket) != 1) std::terminate(); + } +}; diff --git a/core/libs/prx/libSceVideoOut/include/DisplayWindow.hpp b/core/libs/prx/libSceVideoOut/include/DisplayWindow.hpp index 1c7130a78..18cfc77f1 100644 --- a/core/libs/prx/libSceVideoOut/include/DisplayWindow.hpp +++ b/core/libs/prx/libSceVideoOut/include/DisplayWindow.hpp @@ -29,7 +29,7 @@ private: void removeSubclass() noexcept; void applyAspectRatio(void* hwnd, std::uintptr_t edge, void* rect) const; - static std::intptr_t __stdcall windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData); + static std::intptr_t windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData); SDL_Window* window = nullptr; std::uint32_t aspectWidth = 0; diff --git a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp index 608b3caf8..05a53f833 100644 --- a/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp +++ b/core/libs/prx/libSceVideoOut/include/VideoOutDriver.hpp @@ -16,6 +16,7 @@ #include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" #include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp" #include "prx/libSceVideoOut/include/DisplayWindow.hpp" +#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp" #include "SDL.h" #include "SceTypes.hpp" @@ -111,6 +112,7 @@ struct VideoOutConfig { std::array buffers{}; std::array bufferPending{}; + std::array bufferReuse; std::array groups{}; void Check() const { @@ -122,6 +124,7 @@ struct VideoOutConfig { struct FlipQueue; struct FlipRequest final : AgcDriver::IFlipRequest, std::enable_shared_from_this { + std::uint64_t reuseTicket = 0; std::shared_ptr cfg; std::shared_ptr queue; uint64_t generation = 0; diff --git a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp index cced33f9a..6717efe30 100644 --- a/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp +++ b/core/libs/prx/libSceVideoOut/src/DisplayWindow.cpp @@ -158,7 +158,7 @@ void DisplayWindow::applyAspectRatio(void* hwnd, std::uintptr_t edge, void* rect #endif } -std::intptr_t __stdcall DisplayWindow::windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData) { +std::intptr_t DisplayWindow::windowProc(void* hwnd, unsigned int message, std::uintptr_t wParam, std::intptr_t lParam, std::uintptr_t subclassId, std::uintptr_t referenceData) { #ifdef _WIN32 static_cast(subclassId); if (message == WM_SIZING) { diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index c0036f286..a929e098b 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -29,10 +29,34 @@ void checkConfig(const VideoOutConfig& cfg) { cfg.Check(); } +class RenderingWait final : public AgcDriver::IRenderingWait { + std::shared_ptr _config; + std::uint32_t _index; + std::uint64_t _ticket; +public: + RenderingWait(std::shared_ptr config, std::uint32_t index, std::uint64_t ticket) + : _config(std::move(config)), _index(index), _ticket(ticket) {} + void Wait() override { + std::unique_lock lock(_config->mutex); + _config->vblankCond.wait(lock, [&] { + return _config->failure || _config->closing || !_config->opened || + _config->bufferReuse[_index].IsComplete(_ticket); + }); + checkConfig(*_config); + } +}; + class VideoOutput final : public AgcDriver::IVideoOutput { public: VideoOutput(std::shared_ptr config, std::shared_ptr requests) : cfg(std::move(config)), queue(std::move(requests)) {} + std::shared_ptr CaptureRenderingWait(std::uint32_t index) override { + std::lock_guard lock(cfg->mutex); + checkConfig(*cfg); + require(index < VIDEO_OUT_BUFFER_NUM_MAX && cfg->buffers[index].Occupied(), "wait buffer is not registered"); + return std::make_shared(cfg, index, cfg->bufferReuse[index].Capture()); + } + std::shared_ptr Reserve(const AgcDriver::FlipInfo& info) override { // All flip modes are presented at the next vsync. require(info.mode >= VIDEO_OUT_FLIP_MODE_VSYNC && info.mode <= 6, "unsupported flip mode"); @@ -76,6 +100,7 @@ public: } request->generation = cfg->generation; request->flipRate = cfg->flipRate; + if (info.index >= 0) request->reuseTicket = cfg->bufferReuse[info.index].Reserve(); ++queue->reservations; ++cfg->flipStatus.flipPendingNum; if (info.index >= 0) ++cfg->bufferPending[info.index]; @@ -110,7 +135,10 @@ FlipRequest::~FlipRequest() { if (!terminal) { --cfg->flipStatus.flipPendingNum; --queue->reservations; - if (index >= 0) --cfg->bufferPending[index]; + if (index >= 0) { + --cfg->bufferPending[index]; + cfg->bufferReuse[index].Complete(reuseTicket); + } cfg->vblankCond.notify_all(); } } @@ -154,7 +182,10 @@ void FlipRequest::Fail(std::exception_ptr error) noexcept { if (reserved && !terminal) { --cfg->flipStatus.flipPendingNum; --queue->reservations; - if (index >= 0) --cfg->bufferPending[index]; + if (index >= 0) { + --cfg->bufferPending[index]; + cfg->bufferReuse[index].Complete(reuseTicket); + } terminal = true; } cfg->vblankCond.notify_all(); @@ -421,7 +452,10 @@ void VideoOutDriver::processFlip(FlipRequest& req) { req.cfg->height = req.height; --req.cfg->flipStatus.flipPendingNum; --req.queue->reservations; - if (req.index >= 0) --req.cfg->bufferPending[req.index]; + if (req.index >= 0) { + --req.cfg->bufferPending[req.index]; + req.cfg->bufferReuse[req.index].Complete(req.reuseTicket); + } req.terminal = true; req.cfg->vblankCond.notify_all(); timing.Mark("notify_game"); diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 872f8dfb6..ba4ae9c93 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -5,13 +5,17 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/windows/CrashReport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/linux/DlIteratePhdr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Strings.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/StringExtensions.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Math.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/FileAndHeap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/StdioExtras.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/StandardStreams.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestAllocations.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestArena.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/GuestHeap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/HeapDiagnostics.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Mspace.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationHeap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/ApplicationProcessParameters.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/JumpBuffer.cpp @@ -19,6 +23,10 @@ target_sources(libc PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/RuntimeSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/LocaleSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/Process.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Environment.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Directory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Filesystem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/Errors.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/CxxAbiSupport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/exception/Exports.cpp ) diff --git a/core/libs/prx/libc/Export.cpp b/core/libs/prx/libc/Export.cpp index 76c3e114b..ec1701d74 100644 --- a/core/libs/prx/libc/Export.cpp +++ b/core/libs/prx/libc/Export.cpp @@ -2,6 +2,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/HeapDiagnostics.hpp" uint32_t Need_sceLibc = 1; @@ -39,8 +40,7 @@ extern "C" { } void APS5_VABI LibcHeapGetTraceInfo_nid_postfix(LibcHeapInfo* info) { - (void)info; - NotImplemented_nid_no_patch(__func__); + LibcHeapTraceInfo_nid_no_patch(info); } int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*destructor)(void*), void* object, void* module_id) { diff --git a/core/libs/prx/libc/include/FileStream.hpp b/core/libs/prx/libc/include/FileStream.hpp index 40289ad23..00c9ebcb9 100644 --- a/core/libs/prx/libc/include/FileStream.hpp +++ b/core/libs/prx/libc/include/FileStream.hpp @@ -4,6 +4,28 @@ #include #include #include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +struct GuestFilePrefix { + unsigned char* position = nullptr; + std::int32_t readRemaining = 0; + std::int32_t writeRemaining = 0; + std::int16_t flags = 0; + std::int16_t descriptor = -1; + unsigned char* buffer = nullptr; + std::int32_t bufferSize = 0; + std::int32_t bufferPadding = 0; + std::int32_t lineBufferSize = 0; +}; +static_assert(offsetof(GuestFilePrefix, flags) == 16); +static_assert(offsetof(GuestFilePrefix, descriptor) == 18); +static_assert(offsetof(GuestFilePrefix, buffer) == 24); +static_assert(offsetof(GuestFilePrefix, lineBufferSize) == 40); static constexpr const char* FOPEN_EXT_VERT = ".vert"; static constexpr const char* FOPEN_MSG_NULL_ARG = "null argument"; @@ -11,12 +33,23 @@ static constexpr const char* FOPEN_MSG_NOT_FOUND = "file not found"; static constexpr const char* FOPEN_MSG_OPEN_FAILED = "open failed"; class FileStream { + // Only the macro-accessed FreeBSD prefix is exposed. The remaining guest + // FILE area is reserved; host state lives beyond it and is never a guest FILE*. + GuestFilePrefix _guest{}; + std::byte _reserved[256 - sizeof(GuestFilePrefix)]{}; std::FILE* _handle; bool _dynamic; public: explicit FileStream(std::FILE* handle, bool dynamic = false) : _handle(handle), _dynamic(dynamic) { if (!_handle) throw std::runtime_error("FileStream: null handle"); + _guest.flags = handle == stdin ? 4 : handle == stdout || handle == stderr ? 8 : 0x10; +#ifdef _WIN32 + const int descriptor = _fileno(handle); +#else + const int descriptor = ::fileno(handle); +#endif + _guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast(descriptor) : -1; } FileStream(const FileStream&) = delete; @@ -31,11 +64,36 @@ public: return _dynamic; } + GuestFilePrefix& GuestState() { return _guest; } + bool Reopen(const char* filename, const char* mode) { + auto* previous = GetHandle(); + _guest = {}; + _handle = std::freopen(filename, mode, previous); + if (!_handle) return false; + _guest.flags = 0x10; +#ifdef _WIN32 + const int descriptor = _fileno(_handle); +#else + const int descriptor = ::fileno(_handle); +#endif + _guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast(descriptor) : -1; + return true; + } + void SyncStatus() { + _guest.readRemaining = 0; + _guest.writeRemaining = 0; + _guest.flags = static_cast((_guest.flags & ~0x60) | + (std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) ? 0x40 : 0)); + } + void Close() { GetHandle(); if (std::fclose(std::exchange(_handle, nullptr)) != 0) throw std::runtime_error("FileStream: close failed"); + _guest.flags = 0; + _guest.descriptor = -1; } }; +static_assert(std::is_standard_layout_v); inline std::FILE* GetNativeStream(FileStream* stream) { if (!stream) throw std::runtime_error("FileStream: null stream"); diff --git a/core/libs/prx/libc/include/GuestAllocations.hpp b/core/libs/prx/libc/include/GuestAllocations.hpp index 4a3475148..c58dfc3b5 100644 --- a/core/libs/prx/libc/include/GuestAllocations.hpp +++ b/core/libs/prx/libc/include/GuestAllocations.hpp @@ -24,9 +24,7 @@ using Lease = std::vector>; extern "C" { void* GuestAllocationsBegin_nid_postfix(); -#ifdef _WIN32 void GuestAllocationsRegisterMainImage_nid_postfix(void* mutation); -#endif void GuestAllocationsEnd_nid_postfix(void* mutation) noexcept; void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t bytes, bool readable, bool writable); void GuestAllocationsRequireUnpinned_nid_postfix(void* mutation, const void* pointer, std::size_t bytes); @@ -57,9 +55,7 @@ public: ~Mutation() { GuestAllocationsEnd_nid_postfix(handle); } Mutation(const Mutation&) = delete; Mutation& operator=(const Mutation&) = delete; -#ifdef _WIN32 void RegisterMainImage() { GuestAllocationsRegisterMainImage_nid_postfix(handle); } -#endif void Add(void* pointer, std::size_t bytes, bool readable, bool writable) { GuestAllocationsAdd_nid_postfix(handle, pointer, bytes, readable, writable); } void RequireUnpinned(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireUnpinned_nid_postfix(handle, pointer, bytes); } void RequireAvailable(const void* pointer, std::size_t bytes) const { GuestAllocationsRequireAvailable_nid_postfix(handle, pointer, bytes); } diff --git a/core/libs/prx/libc/include/GuestDirectory.hpp b/core/libs/prx/libc/include/GuestDirectory.hpp new file mode 100644 index 000000000..532db73fb --- /dev/null +++ b/core/libs/prx/libc/include/GuestDirectory.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include + +// FreeBSD 11 ABI used by the PS5 SDK: names start at byte 8. +struct GuestDirectoryEntry { + std::uint32_t fileNumber; + std::uint16_t recordLength; + std::uint8_t type; + std::uint8_t nameLength; + char name[256]; +}; +static_assert(offsetof(GuestDirectoryEntry, name) == 8); +static_assert(sizeof(GuestDirectoryEntry) == 264); diff --git a/core/libs/prx/libc/include/HeapDiagnostics.hpp b/core/libs/prx/libc/include/HeapDiagnostics.hpp new file mode 100644 index 000000000..7d3d62454 --- /dev/null +++ b/core/libs/prx/libc/include/HeapDiagnostics.hpp @@ -0,0 +1,8 @@ +#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_HEAPDIAGNOSTICS_HPP +#define CORE_LIBS_PRX_LIBC_INCLUDE_HEAPDIAGNOSTICS_HPP + +#include "SceTypes.hpp" + +extern "C" void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info); + +#endif diff --git a/core/libs/prx/libc/include/Shutdown.hpp b/core/libs/prx/libc/include/Shutdown.hpp index 048505884..c1ff16604 100644 --- a/core/libs/prx/libc/include/Shutdown.hpp +++ b/core/libs/prx/libc/include/Shutdown.hpp @@ -3,5 +3,6 @@ extern "C" void LibcRegisterShutdown_nid_postfix(void (*callback)()); extern "C" void LibcRunShutdown_nid_postfix(); +extern "C" [[noreturn]] void LibcExit_nid_no_patch(int code); #endif diff --git a/core/libs/prx/libc/src/Directory.cpp b/core/libs/prx/libc/src/Directory.cpp new file mode 100644 index 000000000..0cc8ae972 --- /dev/null +++ b/core/libs/prx/libc/src/Directory.cpp @@ -0,0 +1,102 @@ +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include + +namespace { +int DirectoryError(int error) { + switch (error) { + case ENOENT: return 2; + case EACCES: return 13; + case ENOTDIR: return 20; + case EINVAL: return 22; + case EMFILE: return 24; + case ENFILE: return 23; + case ENOMEM: return 12; + case ENAMETOOLONG: return 63; + default: return 5; + } +} +struct Directory { + DIR* native = nullptr; + std::filesystem::path path; + GuestDirectoryEntry entry{}; + std::mutex mutex; + ~Directory() { if (native) ::closedir(native); } +}; +std::uint8_t DirectoryType(const std::filesystem::path& path) { + std::error_code error; + switch (std::filesystem::symlink_status(path, error).type()) { + case std::filesystem::file_type::regular: return 8; + case std::filesystem::file_type::directory: return 4; + case std::filesystem::file_type::symlink: return 10; + case std::filesystem::file_type::block: return 6; + case std::filesystem::file_type::character: return 2; + case std::filesystem::file_type::fifo: return 1; + case std::filesystem::file_type::socket: return 12; + default: return 0; + } +} +} + +extern "C" { +void* APS5_VABI opendir_nid_postfix(const char* path) { + if (!path) { errno = 14; return nullptr; } + if (!*path) { errno = 2; return nullptr; } + try { + auto directory = std::make_unique(); + directory->path = ResolvePath_nid_no_patch(path); + directory->native = ::opendir(directory->path.string().c_str()); + if (!directory->native) { errno = DirectoryError(errno); return nullptr; } + return directory.release(); + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } + catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; } +} + +GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void* handle) { + if (!handle) { errno = 9; return nullptr; } + auto& directory = *static_cast(handle); + std::lock_guard lock(directory.mutex); + const int savedError = errno; + errno = 0; + auto* entry = ::readdir(directory.native); + if (!entry) { + errno = errno ? DirectoryError(errno) : savedError; + return nullptr; + } + const auto length = std::strlen(entry->d_name); + if (length > 255) { errno = 63; return nullptr; } + try { + directory.entry = {}; + // MinGW reports no inode identity (zero); do not fabricate one. + const auto inode = static_cast(entry->d_ino); + directory.entry.fileNumber = inode <= UINT32_MAX ? static_cast(inode) : 0; + directory.entry.recordLength = static_cast(8 + ((length + 1 + 3) & ~3)); + directory.entry.nameLength = static_cast(length); + directory.entry.type = DirectoryType(directory.path / entry->d_name); + std::memcpy(directory.entry.name, entry->d_name, length + 1); + errno = savedError; + return &directory.entry; + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } + catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; } +} + +int APS5_VABI closedir_nid_postfix(void* handle) { + if (!handle) { errno = 9; return -1; } + std::unique_ptr directory(static_cast(handle)); + const int result = ::closedir(directory->native); + directory->native = nullptr; + if (result) errno = DirectoryError(errno); + return result; +} + +void APS5_VABI rewinddir_nid_postfix(void* handle) { + if (!handle) { errno = 9; return; } + auto& directory = *static_cast(handle); + std::lock_guard lock(directory.mutex); + ::rewinddir(directory.native); +} +} diff --git a/core/libs/prx/libc/src/Environment.cpp b/core/libs/prx/libc/src/Environment.cpp new file mode 100644 index 000000000..080c8b110 --- /dev/null +++ b/core/libs/prx/libc/src/Environment.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "prx/libc/include/general/VabiMacros.hpp" + +#ifndef _WIN32 +extern char** environ; +#endif + +namespace { +struct EnvironmentEntry { + std::string owned; + char* borrowed = nullptr; + char* Data() { return borrowed ? borrowed : owned.data(); } + bool Matches(std::string_view name) { + const char* data = Data(); + const char* separator = std::strchr(data, '='); + return separator && std::string_view(data, separator - data) == name; + } +}; +std::mutex environmentMutex; +std::vector& Environment() { + // Seed once from the host. Guest mutations remain local to the guest runtime. + static std::vector entries = [] { + std::vector result; +#ifdef _WIN32 + char** source = _environ; +#else + char** source = environ; +#endif + if (source) for (; *source; ++source) { + if (**source && **source != '=' && std::strchr(*source, '=')) + result.push_back({*source, nullptr}); + } + return result; + }(); + return entries; +} +bool ValidEnvironmentName(const char* name) { + return name && *name && !std::strchr(name, '='); +} +int EnvironmentError(int error) { errno = error; return -1; } +} + +extern "C" { +char* APS5_VABI getenv_nid_postfix(const char* name) { + if (!ValidEnvironmentName(name)) return nullptr; + try { + std::lock_guard lock(environmentMutex); + for (auto& entry : Environment()) + if (entry.Matches(name)) return entry.Data() + std::strlen(name) + 1; + return nullptr; + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } +} + +int APS5_VABI setenv_nid_postfix(const char* name, const char* value, int overwrite) { + if (!ValidEnvironmentName(name) || !value) return EnvironmentError(22); + try { + std::lock_guard lock(environmentMutex); + auto& entries = Environment(); + auto found = std::find_if(entries.begin(), entries.end(), + [name](auto& entry) { return entry.Matches(name); }); + if (found != entries.end() && !overwrite) return 0; + EnvironmentEntry replacement{std::string(name) + '=' + value, nullptr}; + if (found != entries.end()) *found = std::move(replacement); + else entries.push_back(std::move(replacement)); + return 0; + } catch (const std::bad_alloc&) { return EnvironmentError(12); } +} + +int APS5_VABI unsetenv_nid_postfix(const char* name) { + if (!ValidEnvironmentName(name)) return EnvironmentError(22); + try { + std::lock_guard lock(environmentMutex); + auto& entries = Environment(); + const std::string key(name); + std::erase_if(entries, [&key](auto& entry) { return entry.Matches(key); }); + return 0; + } catch (const std::bad_alloc&) { return EnvironmentError(12); } +} + +int APS5_VABI putenv_nid_postfix(char* text) { + const char* separator = text ? std::strchr(text, '=') : nullptr; + if (!separator || separator == text) return EnvironmentError(22); + try { + std::lock_guard lock(environmentMutex); + auto& entries = Environment(); + const std::string_view name(text, separator - text); + auto found = std::find_if(entries.begin(), entries.end(), + [name](auto& entry) { return entry.Matches(name); }); + EnvironmentEntry replacement{{}, text}; + if (found != entries.end()) *found = std::move(replacement); + else entries.push_back(std::move(replacement)); + return 0; + } catch (const std::bad_alloc&) { return EnvironmentError(12); } +} +} diff --git a/core/libs/prx/libc/src/Errors.cpp b/core/libs/prx/libc/src/Errors.cpp new file mode 100644 index 000000000..963191cf6 --- /dev/null +++ b/core/libs/prx/libc/src/Errors.cpp @@ -0,0 +1,75 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); + +namespace { +// Guest FreeBSD errno numbering, not the host CRT's numbering. +constexpr const char* messages[] = { + "No error", "Operation not permitted", "No such file or directory", "No such process", + "Interrupted system call", "Input/output error", "Device not configured", "Argument list too long", + "Exec format error", "Bad file descriptor", "No child processes", "Resource deadlock avoided", + "Cannot allocate memory", "Permission denied", "Bad address", "Block device required", + "Device busy", "File exists", "Cross-device link", "Operation not supported by device", + "Not a directory", "Is a directory", "Invalid argument", "Too many open files in system", + "Too many open files", "Inappropriate ioctl for device", "Text file busy", "File too large", + "No space left on device", "Illegal seek", "Read-only filesystem", "Too many links", "Broken pipe", + "Numerical argument out of domain", "Result too large", "Resource temporarily unavailable", + "Operation now in progress", "Operation already in progress", "Socket operation on non-socket", + "Destination address required", "Message too long", "Protocol wrong type for socket", + "Protocol not available", "Protocol not supported", "Socket type not supported", + "Operation not supported", "Protocol family not supported", "Address family not supported", + "Address already in use", "Cannot assign requested address", "Network is down", "Network is unreachable", + "Network connection reset", "Connection aborted", "Connection reset by peer", "No buffer space available", + "Socket is already connected", "Socket is not connected", "Socket is shut down", "Too many references", + "Operation timed out", "Connection refused", "Too many symbolic links", "File name too long", + "Host is down", "No route to host", "Directory not empty", "Too many processes", "Too many users", + "Disk quota exceeded", "Stale file handle", "Remote path has too many levels", "Invalid RPC structure", + "RPC version mismatch", "RPC program unavailable", "Program version mismatch", "Invalid RPC procedure", + "No locks available", "Function not implemented", "Inappropriate file type", "Authentication error", + "Authentication required", "Identifier removed", "No message of requested type", "Value too large", + "Operation canceled", "Invalid byte sequence", "Attribute not found", "Programming error", + "Bad message", "Multihop attempted", "Link severed", "Protocol error", "Insufficient capabilities", + "Operation not permitted in capability mode", "State not recoverable", "Previous owner died" +}; +static_assert(sizeof(messages) / sizeof(messages[0]) == 97); +bool Known(int error) { return error >= 0 && error < 97; } +} + +extern "C" { +char* APS5_VABI strerror_nid_postfix(int error) { + thread_local char buffer[128]; + const int saved = *__error_nid_postfix(); + if (Known(error)) std::snprintf(buffer, sizeof(buffer), "%s", messages[error]); + else std::snprintf(buffer, sizeof(buffer), "Unknown error: %d", error); + *__error_nid_postfix() = Known(error) ? saved : 22; + return buffer; +} + +int APS5_VABI strerror_r_nid_postfix(int error, char* buffer, std::size_t length) { + const int saved = *__error_nid_postfix(); + char temporary[128]; + if (Known(error)) std::snprintf(temporary, sizeof(temporary), "%s", messages[error]); + else std::snprintf(temporary, sizeof(temporary), "Unknown error: %d", error); + const auto required = std::strlen(temporary) + 1; + int result = Known(error) ? 0 : 22; + if (!buffer || length < required) result = 34; + if (buffer && length) { + const auto count = required <= length ? required - 1 : length - 1; + std::memcpy(buffer, temporary, count); + buffer[count] = '\0'; + } + *__error_nid_postfix() = saved; + return result; +} + +void APS5_VABI perror_nid_postfix(const char* prefix) { + const int saved = *__error_nid_postfix(); + char message[128]; + strerror_r_nid_postfix(saved, message, sizeof(message)); + if (prefix && *prefix) std::fprintf(stderr, "%s: %s\n", prefix, message); + else std::fprintf(stderr, "%s\n", message); + *__error_nid_postfix() = saved; +} +} diff --git a/core/libs/prx/libc/src/FileAndHeap.cpp b/core/libs/prx/libc/src/FileAndHeap.cpp index a4785f8e7..b3e3ed183 100644 --- a/core/libs/prx/libc/src/FileAndHeap.cpp +++ b/core/libs/prx/libc/src/FileAndHeap.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/ApplicationHeap.hpp" @@ -15,6 +16,25 @@ extern "C" { [[noreturn]] void APS5_VABI _ZSt11_Xbad_allocv_nid_postfix(); +FileStream* APS5_VABI freopen_nid_postfix(const char* filename, const char* mode, FileStream* stream) { + if (!stream || !mode) { errno = 22; return nullptr; } + if (!filename) { errno = 45; return nullptr; } // Mode-only reopening is not supported. + const char* supported[] = {"r", "w", "a", "rb", "wb", "ab", "r+", "w+", "a+", + "rb+", "wb+", "ab+", "r+b", "w+b", "a+b"}; + bool valid = false; + for (const auto* candidate : supported) if (std::strcmp(mode, candidate) == 0) valid = true; + if (!valid) { errno = 22; return nullptr; } + try { + const auto path = *filename ? ResolvePath_nid_no_patch(filename).string() : std::string{}; + if (stream->Reopen(path.c_str(), mode)) return stream; + const int error = errno; + if (stream->IsDynamic()) delete stream; + errno = error; + return nullptr; + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } + catch (const std::filesystem::filesystem_error&) { errno = 5; return nullptr; } +} + FileStream* APS5_VABI fopen_nid_postfix(const char* filename, const char* mode) { if (!filename || !mode) throw std::runtime_error(std::string(__func__) + ": " + FOPEN_MSG_NULL_ARG); const std::filesystem::path fpath = ResolvePath_nid_no_patch(filename); @@ -55,6 +75,7 @@ size_t APS5_VABI fread_nid_postfix(void* buffer, size_t size, size_t count, File if (size == 0 || count == 0) return 0; if (!buffer) throw std::runtime_error("fread: null buffer"); const auto result = std::fread(buffer, size, count, handle); + stream->SyncStatus(); if (std::ferror(handle)) throw std::runtime_error("fread: read failed"); return result; } @@ -64,22 +85,42 @@ size_t APS5_VABI fwrite_nid_postfix(const void* buffer, size_t size, size_t coun if (size == 0 || count == 0) return 0; if (!buffer) throw std::runtime_error("fwrite: null buffer"); const auto result = std::fwrite(buffer, size, count, handle); + stream->SyncStatus(); if (result != count || std::ferror(handle)) throw std::runtime_error("fwrite: write failed"); return result; } -int APS5_VABI fseek_nid_postfix(FileStream* stream, long offset, int origin) { - if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) throw std::runtime_error("fseek: invalid origin"); - if (std::fseek(GetNativeStream(stream), offset, origin) != 0) throw std::runtime_error("fseek: seek failed"); - return 0; -} - -long APS5_VABI ftell_nid_postfix(FileStream* stream) { - const auto result = std::ftell(GetNativeStream(stream)); - if (result == -1L) throw std::runtime_error("ftell: position query failed"); +int APS5_VABI fseeko_nid_postfix(FileStream* stream, std::int64_t offset, int origin) { + if (origin != SEEK_SET && origin != SEEK_CUR && origin != SEEK_END) { errno = 22; return -1; } +#ifdef _WIN32 + const int result = _fseeki64(GetNativeStream(stream), offset, origin); +#else + static_assert(sizeof(off_t) == 8); + const int result = ::fseeko(GetNativeStream(stream), offset, origin); +#endif + const int nativeError = errno; + stream->SyncStatus(); + if (result) errno = nativeError == EOVERFLOW ? 84 : nativeError; return result; } +std::int64_t APS5_VABI ftello_nid_postfix(FileStream* stream) { +#ifdef _WIN32 + const auto result = _ftelli64(GetNativeStream(stream)); +#else + static_assert(sizeof(off_t) == 8); + const auto result = ::ftello(GetNativeStream(stream)); +#endif + if (result == -1 && errno == EOVERFLOW) errno = 84; + return result; +} + +int APS5_VABI fseek_nid_postfix(FileStream* stream, std::int64_t offset, int origin) { + return fseeko_nid_postfix(stream, offset, origin); +} + +std::int64_t APS5_VABI ftell_nid_postfix(FileStream* stream) { return ftello_nid_postfix(stream); } + int APS5_VABI fputs_nid_postfix(const char* str, FileStream* stream) { if (!str) throw std::runtime_error("fputs: null string"); const int result = std::fputs(str, GetNativeStream(stream)); @@ -116,6 +157,27 @@ int APS5_VABI posix_memalign_nid_postfix(void** pointer, size_t alignment, size_ return ApplicationHeapPosixAlign_nid_no_patch(pointer, alignment, size); } +void* APS5_VABI bsearch_nid_postfix(const void* key, const void* base, size_t count, + size_t size, int (APS5_VABI *compare)(const void*, const void*)) { + if (count == 0) return nullptr; + if (!key || !base || !compare || size == 0) + throw std::invalid_argument("bsearch: invalid arguments"); + if (count > std::numeric_limits::max() / size) + throw std::overflow_error("bsearch: array size overflow"); + const auto* bytes = static_cast(base); + size_t first = 0; + while (count != 0) { + const size_t half = count / 2; + const size_t middle = first + half; + const auto* element = bytes + middle * size; + const int result = compare(key, element); + if (result == 0) return const_cast(element); + if (result < 0) count = half; + else { first = middle + 1; count -= half + 1; } + } + return nullptr; +} + void APS5_VABI qsort_nid_postfix(void* base, size_t count, size_t size, int (APS5_VABI *compare)(const void*, const void*)) { if (!compare) throw std::invalid_argument("qsort: null comparator"); if (size == 0) throw std::invalid_argument("qsort: zero element size"); diff --git a/core/libs/prx/libc/src/Filesystem.cpp b/core/libs/prx/libc/src/Filesystem.cpp new file mode 100644 index 000000000..60a4c02c6 --- /dev/null +++ b/core/libs/prx/libc/src/Filesystem.cpp @@ -0,0 +1,77 @@ +#include "prx/libc/include/General.hpp" +#include +#ifdef _WIN32 +#define NOMINMAX +#include +#endif + +namespace { +int FilesystemError(const std::error_code& error) { + if (error == std::errc::no_such_file_or_directory) return 2; + if (error == std::errc::permission_denied) return 13; + if (error == std::errc::operation_not_permitted) return 1; + if (error == std::errc::not_a_directory) return 20; + if (error == std::errc::is_a_directory) return 21; + if (error == std::errc::directory_not_empty) return 66; + if (error == std::errc::device_or_resource_busy) return 16; + if (error == std::errc::read_only_file_system) return 30; + if (error == std::errc::filename_too_long) return 63; + if (error == std::errc::too_many_symbolic_link_levels) return 62; + if (error == std::errc::not_enough_memory) return 12; + if (error == std::errc::invalid_argument) return 22; + if (error == std::errc::cross_device_link) return 18; + if (error == std::errc::file_exists) return 17; + if (error == std::errc::no_space_on_device) return 28; + return 5; +} +} + +extern "C" int APS5_VABI rename_nid_postfix(const char* from, const char* to) { + if (!from || !to) { errno = 14; return -1; } + if (!*from || !*to) { errno = 2; return -1; } + try { + const auto source = ResolvePath_nid_no_patch(from); + const auto destination = ResolvePath_nid_no_patch(to); + std::error_code error; + std::filesystem::rename(source, destination, error); + if (error) { errno = FilesystemError(error); return -1; } + return 0; + } catch (const std::bad_alloc&) { errno = 12; return -1; } + catch (const std::filesystem::filesystem_error& error) { + errno = FilesystemError(error.code()); + return -1; + } +} + +extern "C" int APS5_VABI remove_nid_postfix(const char* path) { + if (!path) { errno = 14; return -1; } + if (!*path) { errno = 2; return -1; } + try { + const auto resolved = ResolvePath_nid_no_patch(path); + std::error_code error; +#ifdef _WIN32 + const DWORD attributes = GetFileAttributesW(resolved.c_str()); + bool removed = false; + if (attributes != INVALID_FILE_ATTRIBUTES) { + removed = (attributes & FILE_ATTRIBUTE_DIRECTORY) ? + RemoveDirectoryW(resolved.c_str()) != 0 : DeleteFileW(resolved.c_str()) != 0; + } + if (!removed) { + const DWORD nativeError = GetLastError(); + if (nativeError == ERROR_DIR_NOT_EMPTY) { errno = 66; return -1; } + error = std::error_code(static_cast(nativeError), std::system_category()); + } +#else + const bool removed = std::filesystem::remove(resolved, error); +#endif + if (error || !removed) { + errno = error ? FilesystemError(error) : 2; + return -1; + } + return 0; + } catch (const std::bad_alloc&) { errno = 12; return -1; } + catch (const std::filesystem::filesystem_error& error) { + errno = FilesystemError(error.code()); + return -1; + } +} diff --git a/core/libs/prx/libc/src/Formatting.cpp b/core/libs/prx/libc/src/Formatting.cpp index b85e0eab2..207069071 100644 --- a/core/libs/prx/libc/src/Formatting.cpp +++ b/core/libs/prx/libc/src/Formatting.cpp @@ -4,6 +4,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/VarArgsAbi.hpp" +#include "prx/libc/include/FileStream.hpp" #ifdef _WIN32 #include "prx/libc/include/WindowsFormatting.hpp" @@ -11,6 +12,37 @@ extern "C" { +int APS5_VABI vfprintf_nid_postfix(FileStream* stream, const char* format, VaList* args) { + auto* native = GetNativeStream(stream); +#ifdef _WIN32 + std::string buffer; + const int count = LibcDetail::FormatWindows(nullptr, 0, format, args, &buffer); + const int result = std::fwrite(buffer.data(), 1, static_cast(count), native) == + static_cast(count) ? count : -1; +#else + const int result = std::vfprintf(native, format, *reinterpret_cast(args)); +#endif + stream->SyncStatus(); + return result; +} + +int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfprintf_nid_postfix(stream, format, reinterpret_cast(args)); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} + #ifdef _WIN32 int APS5_VABI printf_nid_postfix(const char* format, ...) { @@ -120,6 +152,14 @@ int APS5_VABI vprintf_nid_postfix(const char* str, VaList* c) { #endif } +int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args) { +#ifdef _WIN32 + return LibcDetail::FormatWindows(str, SIZE_MAX, format, args); +#else + return std::vsprintf(str, format, *reinterpret_cast(args)); +#endif +} + int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) { #ifdef _WIN32 return LibcDetail::FormatWindows(str, size, format, c); diff --git a/core/libs/prx/libc/src/General.cpp b/core/libs/prx/libc/src/General.cpp index cfe33fcd0..5a3364b95 100644 --- a/core/libs/prx/libc/src/General.cpp +++ b/core/libs/prx/libc/src/General.cpp @@ -4,11 +4,13 @@ #include #include #include - +#include +#include +#include #include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestHeap.hpp" namespace { - // Guest prefixes (without leading slashes) mapped to host directories, e.g. save-data mount points: // the PS5 hands the title a short mount point ("/_sm/0") whose files live under _sd/. struct PathAliases { @@ -34,6 +36,47 @@ std::string TrimSlashes(const char* path) { return s.substr(start, end - start); } +std::optional ResolveAlias(const std::string& guestPath) { + const auto relative = TrimSlashes(guestPath.c_str()); + auto& aliases = Aliases(); + std::lock_guard lock(aliases.mutex); + for (const auto& [prefix, host] : aliases.entries) { + if (relative.size() < prefix.size() || relative.compare(0, prefix.size(), prefix) != 0) continue; + if (relative.size() == prefix.size()) return std::filesystem::path(host).make_preferred(); + if (relative[prefix.size()] != '/') continue; + std::filesystem::path result = std::filesystem::path(host) / std::filesystem::path(relative.substr(prefix.size() + 1)); + return result.make_preferred(); + } + return std::nullopt; +} + +struct WorkingDirectory { + std::mutex mutex; + const std::filesystem::path root = std::filesystem::canonical(std::filesystem::current_path()); + std::filesystem::path current = root; +}; +WorkingDirectory& Directories() { static WorkingDirectory state; return state; } +std::filesystem::path Resolve(WorkingDirectory& state, const char* path) { + std::string text(path); + for (auto& character : text) if (character == '\\') character = '/'; + std::filesystem::path input(text); +#ifdef _WIN32 + // Preserve the existing ability to pass explicit native drive paths. + if (input.has_root_name()) return input; +#endif + auto guest = (std::filesystem::path("/") / state.current.lexically_relative(state.root)); + guest = (input.is_absolute() ? input : guest / input).lexically_normal(); + if (auto aliased = ResolveAlias(guest.relative_path().generic_string())) return *aliased; + return (state.root / guest.relative_path()).make_preferred(); +} +int DirectoryFailure(const std::error_code& error) { + if (error == std::errc::permission_denied) return 13; + if (error == std::errc::not_a_directory) return 20; + if (error == std::errc::no_such_file_or_directory) return 2; + if (error == std::errc::filename_too_long) return 63; + if (error == std::errc::too_many_symbolic_link_levels) return 62; + return 5; +} } extern "C" void AddPathAlias_nid_no_patch(const char* guestPrefix, const char* hostPath) { @@ -63,23 +106,50 @@ extern "C" void RemovePathAlias_nid_no_patch(const char* guestPrefix) { } extern "C" std::filesystem::path ResolvePath_nid_no_patch(const char* path) { - if (path == nullptr) { - APS5_INVALID_ARG_EX; - } - const auto relative = TrimSlashes(path); - { - auto& aliases = Aliases(); - std::lock_guard lock(aliases.mutex); - for (const auto& [prefix, host] : aliases.entries) { - if (relative.size() < prefix.size() || relative.compare(0, prefix.size(), prefix) != 0) continue; - if (relative.size() == prefix.size()) return std::filesystem::path(host).make_preferred(); - if (relative[prefix.size()] != '/' && relative[prefix.size()] != '\\') continue; - std::filesystem::path result = std::filesystem::path(host) / std::filesystem::path(relative.substr(prefix.size() + 1)); - return result.make_preferred(); + if (!path) { APS5_INVALID_ARG_EX; } + auto& state = Directories(); + std::lock_guard lock(state.mutex); + return Resolve(state, path); +} + +extern "C" int APS5_VABI chdir_nid_postfix(const char* path) { + if (!path) { errno = 14; return -1; } + if (!*path) { errno = 2; return -1; } + try { + auto& state = Directories(); + std::lock_guard lock(state.mutex); + std::error_code error; + const auto resolved = std::filesystem::canonical(Resolve(state, path), error); + if (error) { errno = DirectoryFailure(error); return -1; } + if (!std::filesystem::is_directory(resolved, error)) { + errno = error ? DirectoryFailure(error) : 20; return -1; } - } - std::filesystem::path result = std::filesystem::current_path() / std::filesystem::path(relative); - return result.make_preferred(); + const auto relative = resolved.lexically_relative(state.root); + if (relative.empty() || *relative.begin() == "..") { errno = 45; return -1; } + state.current = resolved; + return 0; + } catch (const std::bad_alloc&) { errno = 12; return -1; } + catch (const std::filesystem::filesystem_error& error) { errno = DirectoryFailure(error.code()); return -1; } +} + +extern "C" char* APS5_VABI getcwd_nid_postfix(char* buffer, std::size_t size) { + if (buffer && size == 0) { errno = 22; return nullptr; } + try { + auto& state = Directories(); + std::lock_guard lock(state.mutex); + std::error_code error; + if (!std::filesystem::is_directory(state.current, error)) { + errno = error ? DirectoryFailure(error) : 2; return nullptr; + } + const auto relative = state.current.lexically_relative(state.root); + const auto path = relative == "." ? std::string("/") : "/" + relative.generic_string(); + const auto required = path.size() + 1; + if ((buffer || size) && size < required) { errno = 34; return nullptr; } + if (!buffer) buffer = static_cast(GuestHeap::GuestHeapAllocate_nid_postfix(size ? size : required)); + std::memcpy(buffer, path.c_str(), required); + return buffer; + } catch (const std::bad_alloc&) { errno = 12; return nullptr; } + catch (const std::filesystem::filesystem_error& error) { errno = DirectoryFailure(error.code()); return nullptr; } } extern "C" void NotImplemented_nid_no_patch(const char* funcName) { diff --git a/core/libs/prx/libc/src/GuestAllocations.cpp b/core/libs/prx/libc/src/GuestAllocations.cpp index 571d25743..b0f022aaf 100644 --- a/core/libs/prx/libc/src/GuestAllocations.cpp +++ b/core/libs/prx/libc/src/GuestAllocations.cpp @@ -12,6 +12,9 @@ #define NOMINMAX #endif #include +#else +#include +#include #endif namespace GuestAllocations { @@ -116,6 +119,51 @@ void GuestAllocationsRegisterMainImage_nid_postfix(void*) { state.ranges.swap(replacement); state.mainImageRegistered = true; } +#else +void GuestAllocationsRegisterMainImage_nid_postfix(void*) { + auto& state = registry(); + if (state.mainImageRegistered) return; + struct Page { + bool readable = false; + bool writable = false; + }; + std::map pages; + const auto pageSize = static_cast(::sysconf(_SC_PAGESIZE)); + std::pair*, std::uint64_t> collection{&pages, pageSize}; + dl_iterate_phdr([](dl_phdr_info* image, std::size_t, void* data) { + auto& collected = *static_cast*, std::uint64_t>*>(data); + for (int index = 0; index < image->dlpi_phnum; ++index) { + const auto& header = image->dlpi_phdr[index]; + if (header.p_type != PT_LOAD || header.p_memsz == 0) continue; + const auto start = (image->dlpi_addr + header.p_vaddr) & ~(collected.second - 1); + const auto end = (image->dlpi_addr + header.p_vaddr + header.p_memsz + collected.second - 1) & ~(collected.second - 1); + for (auto page = start; page < end; page += collected.second) { + auto& entry = (*collected.first)[page]; + entry.readable = entry.readable || (header.p_flags & (PF_R | PF_W)) != 0; + entry.writable = entry.writable || (header.p_flags & PF_W) != 0; + } + } + return 1; + }, &collection); + require(!pages.empty(), "main guest image has no loadable segments"); + auto replacement = state.ranges; + for (auto page = pages.begin(); page != pages.end();) { + auto last = page; + while (std::next(last) != pages.end() && std::next(last)->first == last->first + pageSize && std::next(last)->second.readable == page->second.readable && std::next(last)->second.writable == page->second.writable) ++last; + const auto address = page->first; + const auto bytes = static_cast(last->first + pageSize - address); + const auto next = replacement.lower_bound(address); + require(next == replacement.end() || address + bytes <= next->first, "guest image overlaps a registered allocation"); + if (next != replacement.begin()) { + const auto& previous = *std::prev(next)->second; + require(previous.address + previous.bytes <= address, "guest image overlaps a registered allocation"); + } + replacement.emplace(address, std::make_shared(Range{address, bytes, page->second.readable, page->second.writable, address, bytes, false})); + page = std::next(last); + } + state.ranges.swap(replacement); + state.mainImageRegistered = true; +} #endif void GuestAllocationsAdd_nid_postfix(void* mutation, void* pointer, std::size_t bytes, bool readable, bool writable) { diff --git a/core/libs/prx/libc/src/HeapDiagnostics.cpp b/core/libs/prx/libc/src/HeapDiagnostics.cpp new file mode 100644 index 000000000..39aaf3951 --- /dev/null +++ b/core/libs/prx/libc/src/HeapDiagnostics.cpp @@ -0,0 +1,45 @@ +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/HeapDiagnostics.hpp" +#include +#include +#include +#include + +namespace { + +std::uint64_t mspaceAtomicIdMask = 0; +std::array mstateTable{}; + +static_assert(sizeof(LibcHeapInfo) == 32); +static_assert(offsetof(LibcHeapInfo, mspace_atomic_id_mask) == 16); +static_assert(offsetof(LibcHeapInfo, mstate_table) == 24); + +} + +extern "C" { + +void LibcHeapTraceInfo_nid_no_patch(LibcHeapInfo* info) { + if (info == nullptr) + throw std::invalid_argument("heap trace info: null output"); + if (info->size != sizeof(LibcHeapInfo)) + throw std::invalid_argument("heap trace info: unsupported structure size"); + info->unknown2 = 0; + info->mspace_atomic_id_mask = &mspaceAtomicIdMask; + info->mstate_table = mstateTable.data(); +} + +// unknown signature +void APS5_VABI sceLibcInternalBacktraceForGame_nid_postfix(const char* heapName) { + (void)heapName; + NotImplemented_nid_no_patch(__func__); +} + +// unknown signature +void APS5_VABI sceLibcInternalHeapErrorReportForGame_nid_postfix(void* heap, void* block, std::uint32_t error) { + (void)heap; + (void)block; + (void)error; + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libc/src/LocaleSupport.cpp b/core/libs/prx/libc/src/LocaleSupport.cpp index 66a128c39..4c52e8ad2 100644 --- a/core/libs/prx/libc/src/LocaleSupport.cpp +++ b/core/libs/prx/libc/src/LocaleSupport.cpp @@ -1,8 +1,11 @@ +#include #include +#include #include #include #include #include +#include #include #include #include @@ -100,10 +103,43 @@ constexpr auto g_classificationTable = MakeClassificationTable(); constexpr auto g_lowerTable = MakeCaseTable(false); constexpr auto g_upperTable = MakeCaseTable(true); +void ValidateCharacter(int value) { + if (value != EOF && (value < 0 || value > UCHAR_MAX)) throw std::invalid_argument("Invalid character value"); +} + +int ClassifyCharacter(int value, std::ctype_base::mask mask) { + ValidateCharacter(value); + if (value == EOF) return 0; + return std::use_facet>(std::locale::classic()).is(mask, static_cast(value)); +} + +int ConvertCharacter(int value, bool upper) { + ValidateCharacter(value); + if (value == EOF) return EOF; + const auto& facet = std::use_facet>(std::locale::classic()); + const auto character = static_cast(value); + return static_cast(upper ? facet.toupper(character) : facet.tolower(character)); +} + } extern "C" { +int APS5_VABI isupper_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::upper); } +int APS5_VABI islower_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::lower); } +int APS5_VABI isalpha_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alpha); } +int APS5_VABI isdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::digit); } +int APS5_VABI isalnum_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::alnum); } +int APS5_VABI isspace_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::space); } +int APS5_VABI isblank_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::blank); } +int APS5_VABI iscntrl_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::cntrl); } +int APS5_VABI isprint_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::print); } +int APS5_VABI isgraph_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::graph); } +int APS5_VABI ispunct_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::punct); } +int APS5_VABI isxdigit_nid_postfix(int c) { return ClassifyCharacter(c, std::ctype_base::xdigit); } +int APS5_VABI toupper_nid_postfix(int c) { return ConvertCharacter(c, true); } +int APS5_VABI tolower_nid_postfix(int c) { return ConvertCharacter(c, false); } + std::uint64_t _ZNSt5ctypeIcE2idE_nid_postfix = 0; std::uint64_t _ZNSt5ctypeIwE2idE_nid_postfix = 0; std::uint64_t _ZNSt7collateIwE2idE_nid_postfix = 0; diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 48968b726..2aca8a4e5 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -1,11 +1,33 @@ #include #include #include +#include #include "prx/libc/include/General.hpp" extern "C" { +float APS5_VABI fmodf_nid_postfix(float x, float y) { return std::fmod(x, y); } +float APS5_VABI asinf_nid_postfix(float x) { return std::asin(x); } +float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } +float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } +float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } +float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } +double APS5_VABI exp2_nid_postfix(double x) { return std::exp2(x); } +double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); } +double APS5_VABI scalbn_nid_postfix(double x, int exponent) { return std::scalbn(x, exponent); } +float APS5_VABI scalbnf_nid_postfix(float x, int exponent) { return std::scalbn(x, exponent); } +double APS5_VABI frexp_nid_postfix(double x, int* exponent) { return std::frexp(x, exponent); } +float APS5_VABI frexpf_nid_postfix(float x, int* exponent) { return std::frexp(x, exponent); } +// Guest long is 64-bit, including on Windows where native long is 32-bit. +std::int64_t APS5_VABI lround_nid_postfix(double x) { return std::llround(x); } +std::int64_t APS5_VABI lroundf_nid_postfix(float x) { return std::llround(x); } +std::int64_t APS5_VABI llround_nid_postfix(double x) { return std::llround(x); } +int APS5_VABI __isfinitef_nid_postfix(float x) { return std::isfinite(x) ? 1 : 0; } +int APS5_VABI __isnormal_nid_postfix(double x) { return std::isnormal(x) ? 1 : 0; } +int APS5_VABI __isnormalf_nid_postfix(float x) { return std::isnormal(x) ? 1 : 0; } +int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; } + double APS5_VABI cbrt_nid_postfix(double x) { return std::cbrt(x); } double APS5_VABI asin_nid_postfix(double x) { return std::asin(x); } double APS5_VABI acos_nid_postfix(double x) { return std::acos(x); } @@ -51,21 +73,11 @@ int APS5_VABI __signbit_nid_postfix(double x) { return std::signbit(x) ? 1 : 0; double APS5_VABI modf_nid_postfix(double x, double* integral) { return std::modf(x, integral); } float APS5_VABI modff_nid_postfix(float x, float* integral) { return std::modf(x, integral); } -double APS5_VABI frexp_nid_postfix(double x, int* exponent) { return std::frexp(x, exponent); } -float APS5_VABI frexpf_nid_postfix(float x, int* exponent) { return std::frexp(x, exponent); } -double APS5_VABI ldexp_nid_postfix(double x, int exponent) { return std::ldexp(x, exponent); } -float APS5_VABI fmodf_nid_postfix(float x, float y) { return std::fmod(x, y); } -float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } -float APS5_VABI asinf_nid_postfix(float x) { return std::asin(x); } -float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } -float APS5_VABI tanf_nid_postfix(float x) { return std::tan(x); } double APS5_VABI tanh_nid_postfix(double x) { return std::tanh(x); } float APS5_VABI tanhf_nid_postfix(float x) { return std::tanh(x); } -float APS5_VABI log10f_nid_postfix(float x) { return std::log10(x); } float APS5_VABI _FSinh_nid_postfix(float x, float y) { return y * std::sinh(x); } float APS5_VABI _FCosh_nid_postfix(float x, float y) { return y * std::cosh(x); } int APS5_VABI __isnanf_nid_postfix(float x) { return std::isnan(x) ? 1 : 0; } -int APS5_VABI __isinff_nid_postfix(float x) { return std::isinf(x) ? 1 : 0; } int APS5_VABI __signbitf_nid_postfix(float x) { return std::signbit(x) ? 1 : 0; } static std::mutex g_randLock; diff --git a/core/libs/prx/libc/src/Mspace.cpp b/core/libs/prx/libc/src/Mspace.cpp new file mode 100644 index 000000000..5e1119fa5 --- /dev/null +++ b/core/libs/prx/libc/src/Mspace.cpp @@ -0,0 +1,177 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); + +namespace { +struct alignas(16) Block { + std::size_t capacity; + Block* next; + void* pointer; + std::size_t used; +}; +struct alignas(16) Arena { + Arena* next; + Block* first; + std::uintptr_t end; +}; +std::mutex arenaMutex; +Arena* arenas = nullptr; + +void Error(int value) { *__error_nid_postfix() = value; } +Arena* Find(void* handle) { + for (auto* arena = arenas; arena; arena = arena->next) + if (arena == handle) return arena; + Error(22); + return nullptr; +} +Block* FindBlock(Arena* arena, const void* pointer) { + if (arena && pointer) + for (auto* block = arena->first; block; block = block->next) + if (block->pointer == pointer) return block; + Error(22); + return nullptr; +} +void* Allocate(Arena* arena, std::size_t size, std::size_t alignment) { + if (!arena) return nullptr; + size = std::max(size, 1); + for (auto* block = arena->first; block; block = block->next) { + if (block->pointer) continue; + const auto start = reinterpret_cast(block + 1); + if (start > std::numeric_limits::max() - (alignment - 1)) continue; + const auto aligned = (start + alignment - 1) & ~(alignment - 1); + const auto padding = aligned - start; + if (padding > block->capacity || size > block->capacity - padding) continue; + auto consumed = padding + size; + // Splitting preserves header alignment and avoids small unusable fragments. + if (consumed <= std::numeric_limits::max() - 15) { + const auto rounded = (consumed + 15) & ~std::size_t{15}; + if (rounded <= block->capacity && block->capacity - rounded >= sizeof(Block) + 16) { + auto* tail = reinterpret_cast(start + rounded); + *tail = {block->capacity - rounded - sizeof(Block), block->next, nullptr, 0}; + block->capacity = rounded; + block->next = tail; + } + } + block->pointer = reinterpret_cast(aligned); + block->used = size; + return block->pointer; + } + Error(12); + return nullptr; +} +void Release(Arena* arena, Block* released) { + released->pointer = nullptr; + released->used = 0; + for (auto* block = arena->first; block && block->next;) { + if (!block->pointer && !block->next->pointer) { + block->capacity += sizeof(Block) + block->next->capacity; + block->next = block->next->next; + } else block = block->next; + } +} +} + +extern "C" { +void* APS5_VABI sceLibcMspaceCreate_nid_postfix(const char* name, void* base, + std::size_t size, unsigned flags) { + (void)name; + const auto start = reinterpret_cast(base); + if (!base || (start & 15) || size < sizeof(Arena) + sizeof(Block) + 16 || + size > std::numeric_limits::max() - start || flags != 0) { + Error(22); + return nullptr; + } + std::lock_guard lock(arenaMutex); + for (auto* arena = arenas; arena; arena = arena->next) { + if (start < arena->end && reinterpret_cast(arena) < start + size) { + Error(22); + return nullptr; + } + } + auto* arena = static_cast(base); + auto* block = reinterpret_cast(arena + 1); + *block = {size - sizeof(Arena) - sizeof(Block), nullptr, nullptr, 0}; + *arena = {arenas, block, start + size}; + arenas = arena; + return arena; +} + +int APS5_VABI sceLibcMspaceDestroy_nid_postfix(void* handle) { + std::lock_guard lock(arenaMutex); + for (auto** entry = &arenas; *entry; entry = &(*entry)->next) { + if (*entry == handle) { + *entry = (*entry)->next; + return 0; + } + } + Error(22); + return -1; +} + +void* APS5_VABI sceLibcMspaceMalloc_nid_postfix(void* handle, std::size_t size) { + std::lock_guard lock(arenaMutex); + return Allocate(Find(handle), size, 16); +} + +void APS5_VABI sceLibcMspaceFree_nid_postfix(void* handle, void* pointer) { + if (!pointer) return; + std::lock_guard lock(arenaMutex); + auto* arena = Find(handle); + if (auto* block = FindBlock(arena, pointer)) Release(arena, block); +} + +void* APS5_VABI sceLibcMspaceCalloc_nid_postfix(void* handle, std::size_t count, std::size_t size) { + if (size && count > std::numeric_limits::max() / size) { + Error(12); + return nullptr; + } + std::lock_guard lock(arenaMutex); + void* result = Allocate(Find(handle), count * size, 16); + if (result) std::memset(result, 0, count * size); + return result; +} + +void* APS5_VABI sceLibcMspaceRealloc_nid_postfix(void* handle, void* pointer, std::size_t size) { + std::lock_guard lock(arenaMutex); + auto* arena = Find(handle); + if (!pointer) return Allocate(arena, size, 16); + auto* block = FindBlock(arena, pointer); + if (!block) return nullptr; + if (!size) { Release(arena, block); return nullptr; } + const auto padding = reinterpret_cast(pointer) - reinterpret_cast(block + 1); + if (size <= block->capacity - padding) { block->used = size; return pointer; } + void* result = Allocate(arena, size, 16); + if (result) { + std::memcpy(result, pointer, block->used); + Release(arena, block); + } + return result; +} + +int APS5_VABI sceLibcMspacePosixMemalign_nid_postfix(void* handle, void** result, + std::size_t alignment, std::size_t size) { + if (!result || alignment < sizeof(void*) || (alignment & (alignment - 1))) return 22; + std::lock_guard lock(arenaMutex); + auto* arena = Find(handle); + if (!arena) return 22; + void* pointer = Allocate(arena, size, std::max(alignment, 16)); + if (!pointer) return 12; + *result = pointer; + return 0; +} + +std::size_t APS5_VABI sceLibcMspaceMallocUsableSize_nid_postfix(const void* pointer) { + if (!pointer) return 0; + std::lock_guard lock(arenaMutex); + for (auto* arena = arenas; arena; arena = arena->next) + for (auto* block = arena->first; block; block = block->next) + if (block->pointer == pointer) return block->used; + Error(22); + return 0; +} +} diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index 622d60321..7a8dcc613 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -58,14 +58,27 @@ extern "C" void LibcRunShutdown_nid_postfix() { extern "C" { +[[noreturn]] void APS5_VABI _Exit_nid_postfix(int code) { + static const bool trace = std::getenv("APS5_TRACE_EXIT") != nullptr; + if (trace) { + std::fprintf(stderr, "[libc] _Exit(%d) called from %p\n", code, __builtin_return_address(0)); + std::fflush(stderr); + } + std::_Exit(code); +} + +[[noreturn]] void LibcExit_nid_no_patch(int code) { + LibcRunShutdown_nid_postfix(); + std::exit(code); +} + void APS5_VABI exit_nid_postfix(int code) { static const bool trace = std::getenv("APS5_TRACE_EXIT") != nullptr; if (trace) { std::fprintf(stderr, "[libc] exit(%d) called from %p\n", code, __builtin_return_address(0)); std::fflush(stderr); } - LibcRunShutdown_nid_postfix(); - std::exit(code); + LibcExit_nid_no_patch(code); } [[noreturn]] void abort_nid_postfix( @@ -98,5 +111,3 @@ int APS5_VABI atexit_nid_postfix(atexit_func_t func) { } } - -#include "prx/libc/src/specifics/linux/PosixProcess.cpp" diff --git a/core/libs/prx/libc/src/StandardStreams.cpp b/core/libs/prx/libc/src/StandardStreams.cpp new file mode 100644 index 000000000..ac9f10191 --- /dev/null +++ b/core/libs/prx/libc/src/StandardStreams.cpp @@ -0,0 +1,68 @@ +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace { FileStream input{stdin}; } +extern "C" { +FileStream* __stdinp_nid_postfix = &input; +FileStream* __stdoutp_nid_postfix = &_Stdout_nid_postfix; +FileStream* __stderrp_nid_postfix = &_Stderr_nid_postfix; +int __isthreaded_nid_postfix = 1; + +int APS5_VABI fgetc_nid_postfix(FileStream* stream) { + const int result = std::fgetc(GetNativeStream(stream)); + stream->SyncStatus(); + return result; +} +int APS5_VABI getc_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(stream); } +int APS5_VABI __srget_nid_postfix(FileStream* stream) { return fgetc_nid_postfix(stream); } +int APS5_VABI getchar_nid_postfix() { return fgetc_nid_postfix(__stdinp_nid_postfix); } +int APS5_VABI fputc_nid_postfix(int value, FileStream* stream) { + const int result = std::fputc(value, GetNativeStream(stream)); + stream->SyncStatus(); + return result; +} +int APS5_VABI putc_nid_postfix(int value, FileStream* stream) { return fputc_nid_postfix(value, stream); } +int APS5_VABI __swbuf_nid_postfix(int value, FileStream* stream) { return fputc_nid_postfix(value, stream); } +int APS5_VABI putchar_nid_postfix(int value) { return fputc_nid_postfix(value, __stdoutp_nid_postfix); } +int APS5_VABI ungetc_nid_postfix(int value, FileStream* stream) { + const int result = std::ungetc(value, GetNativeStream(stream)); + stream->SyncStatus(); + return result; +} +char* APS5_VABI fgets_nid_postfix(char* buffer, int size, FileStream* stream) { + auto* result = std::fgets(buffer, size, GetNativeStream(stream)); + stream->SyncStatus(); + return result; +} +int APS5_VABI feof_nid_postfix(FileStream* stream) { + stream->SyncStatus(); + return (stream->GuestState().flags & 0x20) != 0; +} +int APS5_VABI ferror_nid_postfix(FileStream* stream) { + stream->SyncStatus(); + return (stream->GuestState().flags & 0x40) != 0; +} +void APS5_VABI clearerr_nid_postfix(FileStream* stream) { + std::clearerr(GetNativeStream(stream)); + stream->SyncStatus(); +} +int APS5_VABI fileno_nid_postfix(FileStream* stream) { +#ifdef _WIN32 + const int descriptor = _fileno(GetNativeStream(stream)); +#else + const int descriptor = ::fileno(GetNativeStream(stream)); +#endif + stream->GuestState().descriptor = static_cast(descriptor); + return descriptor; +} +int APS5_VABI setvbuf_nid_postfix(FileStream* stream, char* buffer, int mode, std::size_t size) { + if (mode < 0 || mode > 2) return -1; + const int native = mode == 0 ? _IOFBF : mode == 1 ? _IOLBF : _IONBF; + return std::setvbuf(GetNativeStream(stream), buffer, native, size); +} +} diff --git a/core/libs/prx/libc/src/StdioExtras.cpp b/core/libs/prx/libc/src/StdioExtras.cpp index e9c989660..90a21b057 100644 --- a/core/libs/prx/libc/src/StdioExtras.cpp +++ b/core/libs/prx/libc/src/StdioExtras.cpp @@ -1,11 +1,8 @@ -#include #include #include #include #include #include -#include -#include #include "prx/libc/include/FileStream.hpp" #include "prx/libc/include/GuestHeap.hpp" @@ -18,100 +15,16 @@ namespace { -using GuestCompare = int (APS5_VABI*)(const void*, const void*); using GuestNewHandler = void (APS5_VABI*)(); GuestNewHandler g_newHandler = nullptr; -#ifdef _WIN32 -// Formats with a guest (SysV) va_list into a host string. -std::string FormatGuest(const char* format, const void* args) { - std::string text; - LibcDetail::FormatWindows(nullptr, 0, format, args, &text); - return text; -} -#endif - } extern "C" { -void* APS5_VABI bsearch_nid_postfix(const void* key, const void* base, size_t count, size_t size, GuestCompare compare) { - const auto* bytes = static_cast(base); - size_t low = 0; - size_t high = count; - while (low < high) { - const size_t middle = low + (high - low) / 2; - const void* element = bytes + middle * size; - const int order = compare(key, element); - if (order == 0) return const_cast(element); - if (order < 0) high = middle; - else low = middle + 1; - } - return nullptr; -} - -void APS5_VABI clearerr_nid_postfix(FileStream* stream) { - std::clearerr(GetNativeStream(stream)); -} - -int APS5_VABI feof_nid_postfix(FileStream* stream) { - return std::feof(GetNativeStream(stream)); -} - -int APS5_VABI ferror_nid_postfix(FileStream* stream) { - return std::ferror(GetNativeStream(stream)); -} - -int APS5_VABI fputc_nid_postfix(int character, FileStream* stream) { - return std::fputc(character, GetNativeStream(stream)); -} - -int APS5_VABI getc_nid_postfix(FileStream* stream) { - return std::getc(GetNativeStream(stream)); -} - -int APS5_VABI ungetc_nid_postfix(int character, FileStream* stream) { - return std::ungetc(character, GetNativeStream(stream)); -} - -char* APS5_VABI fgets_nid_postfix(char* buffer, int count, FileStream* stream) { - return std::fgets(buffer, count, GetNativeStream(stream)); -} - -int APS5_VABI putchar_nid_postfix(int character) { - return std::putchar(character); -} - -void APS5_VABI perror_nid_postfix(const char* prefix) { - if (prefix && *prefix) std::fprintf(stderr, "%s: %s\n", prefix, std::strerror(errno)); - else std::fprintf(stderr, "%s\n", std::strerror(errno)); -} - -int APS5_VABI remove_nid_postfix(const char* path) { - std::error_code error; - return std::filesystem::remove(ResolvePath_nid_no_patch(path), error) ? 0 : -1; -} - -// FreeBSD and the host CRT agree on the classic errno values the messages are looked up by. -char* APS5_VABI strerror_nid_postfix(int error) { - return std::strerror(error); -} - #ifdef _WIN32 -int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) { - __builtin_sysv_va_list args; - __builtin_sysv_va_start(args, format); - const auto text = FormatGuest(format, args); - __builtin_sysv_va_end(args); - return static_cast(std::fwrite(text.data(), 1, text.size(), GetNativeStream(stream))); -} - -int APS5_VABI vsprintf_nid_postfix(char* buffer, const char* format, VaList* args) { - return LibcDetail::FormatWindows(buffer, SIZE_MAX, format, args); -} - int APS5_VABI vsprintf_s_nid_postfix(char* buffer, size_t size, const char* format, VaList* args) { return LibcDetail::FormatWindows(buffer, size, format, args); } @@ -132,13 +45,6 @@ int APS5_VABI sprintf_s_nid_postfix(char* buffer, size_t size, const char* forma std::abort(); } -[[noreturn]] void APS5_VABI _Exit_nid_postfix(int status) { - static const bool trace = std::getenv("APS5_TRACE_EXIT") != nullptr; - if (trace) std::fprintf(stderr, "[libc] _Exit(%d) called from %p\n", status, __builtin_return_address(0)); - std::fflush(nullptr); - std::_Exit(status); -} - // Only the "C" locale exists. const char* APS5_VABI setlocale_nid_postfix(int category, const char* locale) { (void)category; diff --git a/core/libs/prx/libc/src/StringExtensions.cpp b/core/libs/prx/libc/src/StringExtensions.cpp new file mode 100644 index 000000000..9533fd26b --- /dev/null +++ b/core/libs/prx/libc/src/StringExtensions.cpp @@ -0,0 +1,72 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { + +char* APS5_VABI basename_nid_postfix(const char* path) { + thread_local char buffer[1024]; + std::string_view name = path && *path ? path : "."; + while (name.size() > 1 && name.back() == '/') name.remove_suffix(1); + if (name != "/") { + const auto separator = name.find_last_of('/'); + if (separator != std::string_view::npos) name.remove_prefix(separator + 1); + } + if (name.size() >= sizeof(buffer)) { errno = 63; return nullptr; } + std::memcpy(buffer, name.data(), name.size()); + buffer[name.size()] = '\0'; + return buffer; +} + +std::size_t APS5_VABI strnlen_nid_postfix(const char* text, std::size_t limit) { + std::size_t length = 0; + while (length < limit && text[length] != '\0') ++length; + return length; +} + +char* APS5_VABI strncat_nid_postfix(char* destination, const char* source, std::size_t limit) { + return std::strncat(destination, source, limit); +} + +char* APS5_VABI strpbrk_nid_postfix(const char* text, const char* accept) { + return const_cast(std::strpbrk(text, accept)); +} + +std::size_t APS5_VABI strcspn_nid_postfix(const char* text, const char* reject) { + return std::strcspn(text, reject); +} + +std::size_t APS5_VABI strlcat_nid_postfix(char* destination, const char* source, std::size_t capacity) { + const auto destinationLength = strnlen_nid_postfix(destination, capacity); + const auto sourceLength = std::strlen(source); + if (destinationLength < capacity) { + const auto remaining = capacity - destinationLength - 1; + const auto count = sourceLength < remaining ? sourceLength : remaining; + std::memcpy(destination + destinationLength, source, count); + destination[destinationLength + count] = '\0'; + } + return destinationLength + sourceLength; +} + +char* APS5_VABI strtok_r_nid_postfix(char* text, const char* delimiters, char** state) { + if (text == nullptr) text = *state; + if (text == nullptr) return nullptr; + text += std::strspn(text, delimiters); + if (*text == '\0') { + *state = text; + return nullptr; + } + char* end = text + std::strcspn(text, delimiters); + if (*end != '\0') *end++ = '\0'; + *state = end; + return text; +} + +char* APS5_VABI strtok_nid_postfix(char* text, const char* delimiters) { + thread_local char* state = nullptr; + return strtok_r_nid_postfix(text, delimiters, &state); +} + +} diff --git a/core/libs/prx/libc/src/Strings.cpp b/core/libs/prx/libc/src/Strings.cpp index f46cee049..c3fdec2d6 100644 --- a/core/libs/prx/libc/src/Strings.cpp +++ b/core/libs/prx/libc/src/Strings.cpp @@ -97,6 +97,13 @@ double APS5_VABI strtod_nid_postfix(const char* str, char** endptr) { return std::strtod(str, endptr); } +double APS5_VABI atof_nid_postfix(const char* str) { return std::atof(str); } +float APS5_VABI strtof_nid_postfix(const char* str, char** endptr) { return std::strtof(str, endptr); } +long double APS5_VABI strtold_nid_postfix(const char* str, char** endptr) { + static_assert(sizeof(long double) == 16, "Guest long double requires x87 extended precision storage"); + return std::strtold(str, endptr); +} + int APS5_VABI atoi_nid_postfix(const char* str) { return std::atoi(str); } @@ -158,18 +165,6 @@ size_t APS5_VABI strspn_nid_postfix(const char* s, const char* accept) { return std::strspn(s, accept); } -size_t APS5_VABI strcspn_nid_postfix(const char* s, const char* reject) { - return std::strcspn(s, reject); -} - -const char* APS5_VABI strpbrk_nid_postfix(const char* s, const char* accept) { - return std::strpbrk(s, accept); -} - -char* APS5_VABI strncat_nid_postfix(char* dest, const char* src, size_t n) { - return std::strncat(dest, src, n); -} - int APS5_VABI strcoll_nid_postfix(const char* s1, const char* s2) { return std::strcmp(s1, s2); } @@ -193,14 +188,6 @@ int APS5_VABI strncpy_s_nid_postfix(char* dest, size_t destsz, const char* src, return 0; } -float APS5_VABI strtof_nid_postfix(const char* str, char** endptr) { - return std::strtof(str, endptr); -} - -double APS5_VABI atof_nid_postfix(const char* str) { - return std::atof(str); -} - unsigned long long APS5_VABI _Stoull_nid_postfix(const char* str, char** endptr, int base) { return std::strtoull(str, endptr, base); } diff --git a/core/libs/prx/libc/src/exception/Exports.cpp b/core/libs/prx/libc/src/exception/Exports.cpp index 2c7f8be9d..462c2c479 100644 --- a/core/libs/prx/libc/src/exception/Exports.cpp +++ b/core/libs/prx/libc/src/exception/Exports.cpp @@ -20,9 +20,25 @@ GCC_GLOBAL_ALIAS(__cxa_end_catch_nid_no_patch_cut, __cxa_end_catch_nid_postfix); GCC_GLOBAL_ALIAS(__cxa_rethrow_nid_no_patch_cut, __cxa_rethrow_nid_postfix); GCC_GLOBAL_ALIAS(__gxx_personality_v0_nid_no_patch_cut, __gxx_personality_v0_nid_postfix); GCC_GLOBAL_ALIAS(_Unwind_Resume_nid_no_patch_cut, _Unwind_Resume_nid_postfix); -GCC_LOCAL_ALIAS(__cxa_free_exception, __cxa_free_exception_nid_postfix); -GCC_LOCAL_ALIAS(_ZSt9terminatev, _ZSt9terminatev_nid_postfix); -GCC_LOCAL_ALIAS(__cxa_call_terminate, __cxa_call_terminate_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_free_exception, __cxa_free_exception_nid_postfix); +GCC_GLOBAL_ALIAS(_ZSt9terminatev, _ZSt9terminatev_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_call_terminate, __cxa_call_terminate_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_allocate_exception, __cxa_allocate_exception_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_throw, __cxa_throw_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_init_primary_exception, __cxa_init_primary_exception_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_begin_catch, __cxa_begin_catch_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_end_catch, __cxa_end_catch_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_rethrow, __cxa_rethrow_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_get_exception_ptr, __cxa_get_exception_ptr_nid_postfix); +GCC_GLOBAL_ALIAS(__cxa_current_exception_type, __cxa_current_exception_type_nid_postfix); +GCC_GLOBAL_ALIAS(_ZSt13set_terminatePFvvE, _ZSt13set_terminatePFvvE_nid_postfix); +GCC_GLOBAL_ALIAS(_ZSt13get_terminatev, _ZSt13get_terminatev_nid_postfix); +GCC_GLOBAL_ALIAS(_ZSt18uncaught_exceptionv, _ZSt18uncaught_exceptionv_nid_postfix); +GCC_GLOBAL_ALIAS(_ZSt19uncaught_exceptionsv, _ZSt19uncaught_exceptionsv_nid_postfix); +GCC_GLOBAL_ALIAS(_Unwind_Resume, _Unwind_Resume_nid_postfix); +GCC_GLOBAL_ALIAS(_Unwind_RaiseException, _Unwind_RaiseException_nid_postfix); +GCC_GLOBAL_ALIAS(_Unwind_Resume_or_Rethrow, _Unwind_Resume_or_Rethrow_nid_postfix); +GCC_GLOBAL_ALIAS(_Unwind_DeleteException, _Unwind_DeleteException_nid_postfix); #ifdef _WIN32 extern "C" { diff --git a/core/libs/prx/libc/src/exception/Runtime.cpp b/core/libs/prx/libc/src/exception/Runtime.cpp index c04c5a1cf..411edbf91 100644 --- a/core/libs/prx/libc/src/exception/Runtime.cpp +++ b/core/libs/prx/libc/src/exception/Runtime.cpp @@ -246,6 +246,18 @@ void APS5_VABI __cxa_free_exception_nid_postfix(void* object) { InvokeTerminate(header->terminate); } +LibcException::Header* __cxa_init_primary_exception_nid_postfix(void* object, std::type_info* type, void (*destructor)(void*)) { + using namespace LibcException; + auto* header = FromObject(object); + header->type = type; + header->destructor = destructor; + header->terminate = terminateHandler.load(std::memory_order_acquire); + header->adjusted = object; + header->unwind.exception_class = PrimaryClass; + header->unwind.exception_cleanup = Cleanup; + return header; +} + void* APS5_VABI __cxa_begin_catch_nid_postfix(void* exception) { using namespace LibcException; auto* unwind = static_cast<_Unwind_Exception*>(exception); diff --git a/core/libs/prx/libc/src/exception/Unwind.cpp b/core/libs/prx/libc/src/exception/Unwind.cpp index f37d0a524..95690e1f1 100644 --- a/core/libs/prx/libc/src/exception/Unwind.cpp +++ b/core/libs/prx/libc/src/exception/Unwind.cpp @@ -8,7 +8,7 @@ #if defined(__linux__) || defined(_WIN32) -#ifdef _WIN32 +#if defined(_WIN32) || defined(__linux__) extern "C" _Unwind_Reason_Code __gxx_personality_v0(int, _Unwind_Action, std::uint64_t, _Unwind_Exception*, _Unwind_Context*); #endif @@ -23,7 +23,7 @@ bool OwnPersonality(Word personality) { } #endif if (personality == reinterpret_cast(__gxx_personality_v0_nid_postfix)) return true; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__linux__) if (personality == reinterpret_cast(__gxx_personality_v0)) return true; #endif return false; diff --git a/core/libs/prx/libc/src/specifics/linux/PosixProcess.cpp b/core/libs/prx/libc/src/specifics/linux/PosixProcess.cpp deleted file mode 100644 index a362f9f48..000000000 --- a/core/libs/prx/libc/src/specifics/linux/PosixProcess.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include -#include -#include - -extern "C" { - -int APS5_VABI setenv_nid_postfix(const char* name, const char* value, int overwrite) { - if (overwrite == 0 && std::getenv(name) != nullptr) { - return 0; - } - const size_t nameLength = std::strlen(name); - const size_t valueLength = std::strlen(value); - char* entry = static_cast(std::malloc(nameLength + valueLength + 2)); - if (entry == nullptr) { - errno = ENOMEM; - return -1; - } - std::memcpy(entry, name, nameLength); - entry[nameLength] = '='; - std::memcpy(entry + nameLength + 1, value, valueLength); - entry[nameLength + 1 + valueLength] = '\0'; - return ::putenv(entry); -} - -} diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index fc0208d6b..801440466 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -9,6 +9,7 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Semaphore/src/Semaphore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/DirectMemory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/PosixMemory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/MemoryPool.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DirectMemory/MemoryPoolExport.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/src/Mutex.cpp @@ -23,17 +24,25 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Mutex.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Rwlock.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Thread.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Once.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Tsd.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Pthread/Posix/Sem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Socket.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/Udp.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Socket/src/AddressConversion.cpp ${CMAKE_CURRENT_SOURCE_DIR}/EventFlag/src/EventFlag.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Module/src/Module.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Module/src/DynamicLoader.cpp + ${LIBS_INCLUDE_DIR}/nid/src/NidCompute.cpp + ${LIBS_INCLUDE_DIR}/nid/src/Sha1.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Aio/src/Aio.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Coredump.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Exception.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Sanitizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Rtld.cpp ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Process.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/SystemConfiguration.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/Signals.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/Time.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Time/TimedWait.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AppMetadata/src/AppMetadata.cpp @@ -41,8 +50,11 @@ add_library(libkernel SHARED EXCLUDE_FROM_ALL ) target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR}) +target_include_directories(libkernel PRIVATE ${LIBS_INCLUDE_DIR}/nid/include) +target_link_libraries(libkernel PRIVATE ${CMAKE_DL_LIBS}) target_link_libraries(libkernel PRIVATE libc) if(WIN32) + target_link_libraries(libkernel PRIVATE ws2_32) target_compile_definitions(libkernel PRIVATE _WIN32_WINNT=0x0602) endif() configure_windows_unwind(libkernel) diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 9ff2811c0..2d0569a6e 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -159,9 +159,9 @@ int LinuxProtFromSce(int prot) { throw std::invalid_argument("Unsupported memory protection bits: " + std::to_string(prot)); } int result = PROT_NONE; - if (prot & 1) result |= PROT_READ; - if (prot & 2) result |= PROT_WRITE; - if (prot & 4) result |= PROT_EXEC; + if (prot & 0x13) result |= PROT_READ; + if (prot & 0x22) result |= PROT_READ | PROT_WRITE; + if (prot & 4) result |= PROT_READ | PROT_EXEC; return result; } @@ -320,6 +320,8 @@ int DoMprotect(const void* addr, size_t len, int prot) { if (memory.AllocationBase != GetModuleHandleW(nullptr)) throw std::invalid_argument("Memory protection of a foreign image is not supported"); mutation.RegisterMainImage(); } +#else + mutation.RegisterMainImage(); #endif mutation.Protect(pointer, bytes, (prot & 3) != 0, (prot & 2) != 0, [&] { if (mprotect(const_cast(pointer), bytes, nativeProtection) != 0) throw std::system_error(errno, std::generic_category(), "mprotect failed"); diff --git a/core/libs/prx/libkernel/DirectMemory/Export.cpp b/core/libs/prx/libkernel/DirectMemory/Export.cpp index fd263732a..14d1333d8 100644 --- a/core/libs/prx/libkernel/DirectMemory/Export.cpp +++ b/core/libs/prx/libkernel/DirectMemory/Export.cpp @@ -315,3 +315,14 @@ int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry* entries, int num_entries, } } + +extern "C" { + +int APS5_VABI sceKernelMlock_nid_postfix(void* address, std::uint64_t length) { + (void)address; + (void)length; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp b/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp new file mode 100644 index 000000000..e3d2f1796 --- /dev/null +++ b/core/libs/prx/libkernel/DirectMemory/PosixMemory.cpp @@ -0,0 +1,81 @@ +#include "DirectMemory.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); + +namespace { +// FreeBSD/PS5 ABI values, independent of the host's errno and mmap constants. +constexpr int GuestInvalid = 22; +constexpr int GuestNoMemory = 12; +constexpr int GuestNotSupported = 45; +constexpr int GuestPrivate = 0x2; +constexpr int GuestAnonymous = 0x1000; + +bool RoundLength(std::size_t length, std::size_t& rounded) { + constexpr auto mask = PS5_PAGE_SIZE - 1; + if (length == 0 || length > std::numeric_limits::max() - mask) + return false; + rounded = (length + mask) & ~mask; + return true; +} + +void SetError(int error) { + *__error_nid_postfix() = error; +} +} + +extern "C" { + +void* APS5_VABI mmap_nid_postfix(void* address, std::size_t length, int protection, + int flags, int descriptor, std::int64_t offset) noexcept { + const auto failed = [](int error) -> void* { + SetError(error); + return reinterpret_cast(static_cast(-1)); + }; + std::size_t rounded; + if (!RoundLength(length, rounded) || (protection & ~7) != 0) + return failed(GuestInvalid); + if (flags != (GuestPrivate | GuestAnonymous)) + return failed(GuestNotSupported); + if (descriptor != -1 || offset != 0) + return failed(GuestInvalid); + // A non-fixed address is a hint; the kernel may choose another address. + (void)address; + void* mapped = nullptr; + try { + const auto result = DoMapAnon(&mapped, rounded, protection, 0); + if (result != 0) return failed(GuestNoMemory); + return mapped; + } catch (const std::bad_alloc&) { + return failed(GuestNoMemory); + } catch (const std::exception&) { + return failed(GuestNoMemory); + } +} + +int APS5_VABI munmap_nid_postfix(void* address, std::size_t length) noexcept { + const auto failed = [](int error) { + SetError(error); + return -1; + }; + std::size_t rounded; + const auto start = reinterpret_cast(address); + if (!RoundLength(length, rounded) || start == 0 || + (start & (PS5_PAGE_SIZE - 1)) != 0 || + rounded > std::numeric_limits::max() - start) + return failed(GuestInvalid); + try { + if (DoMunmap(address, rounded) != 0) return failed(GuestInvalid); + return 0; + } catch (const std::bad_alloc&) { + return failed(GuestNoMemory); + } catch (const std::exception&) { + // The allocation tracker rejects foreign, pinned, and noncontiguous ranges. + return failed(GuestInvalid); + } +} + +} diff --git a/core/libs/prx/libkernel/File/include/NativeStat.hpp b/core/libs/prx/libkernel/File/include/NativeStat.hpp index 7a8460499..f5b8642f5 100644 --- a/core/libs/prx/libkernel/File/include/NativeStat.hpp +++ b/core/libs/prx/libkernel/File/include/NativeStat.hpp @@ -7,6 +7,7 @@ namespace File { void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb); +void FillFileStat(int nativeDescriptor, FileStat* sb); bool FillFileStatFromDescriptor(int fd, FileStat* sb); } diff --git a/core/libs/prx/libkernel/File/src/NativeStat.cpp b/core/libs/prx/libkernel/File/src/NativeStat.cpp index 8791dc71f..f9f195b81 100644 --- a/core/libs/prx/libkernel/File/src/NativeStat.cpp +++ b/core/libs/prx/libkernel/File/src/NativeStat.cpp @@ -24,26 +24,7 @@ static int DoFstat(int fd, NativeStat* st) { } #endif -namespace File { - -static void FillFromNative(const NativeStat& st, FileStat* sb); - -void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) { - NativeStat st{}; - if (DoStat(nativePath, &st) != 0) { - throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string()); - } - FillFromNative(st, sb); -} - -bool FillFileStatFromDescriptor(int fd, FileStat* sb) { - NativeStat st{}; - if (DoFstat(fd, &st) != 0) return false; - FillFromNative(st, sb); - return true; -} - -static void FillFromNative(const NativeStat& st, FileStat* sb) { +static void CopyNativeStat(const NativeStat& st, FileStat* sb) { *sb = FileStat{}; sb->st_mode = static_cast(st.st_mode); sb->st_size = static_cast(st.st_size); @@ -91,4 +72,29 @@ static void FillFromNative(const NativeStat& st, FileStat* sb) { #endif } +namespace File { + +void FillFileStat(const std::filesystem::path& nativePath, FileStat* sb) { + NativeStat st{}; + if (DoStat(nativePath, &st) != 0) { + throw std::runtime_error(std::string("FillFileStat: stat failed for ") + nativePath.string()); + } + CopyNativeStat(st, sb); +} + +void FillFileStat(int nativeDescriptor, FileStat* sb) { + NativeStat st{}; + if (DoFstat(nativeDescriptor, &st) != 0) { + throw std::runtime_error(std::string("FillFileStat: fstat failed for fd ") + std::to_string(nativeDescriptor)); + } + CopyNativeStat(st, sb); +} + +bool FillFileStatFromDescriptor(int fd, FileStat* sb) { + NativeStat st{}; + if (DoFstat(fd, &st) != 0) return false; + CopyNativeStat(st, sb); + return true; +} + } diff --git a/core/libs/prx/libkernel/File/src/Stdio.cpp b/core/libs/prx/libkernel/File/src/Stdio.cpp index 14f9a91bd..30ee4ccf7 100644 --- a/core/libs/prx/libkernel/File/src/Stdio.cpp +++ b/core/libs/prx/libkernel/File/src/Stdio.cpp @@ -1,11 +1,105 @@ #include #include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/File/include/File.hpp" +#include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/File/include/NativeStat.hpp" +#include "prx/libkernel/Socket/include/SocketRuntime.hpp" #include +#include #include #include +#include +#ifdef _WIN32 +#include +#include +#include +#include +static int NativeRmdir(const std::filesystem::path& path) { + return ::_wrmdir(path.wstring().c_str()); +} +static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) { + (void)mode; + return ::_wmkdir(path.wstring().c_str()); +} +static int NativeChmod(const std::filesystem::path& path, int mode) { + return ::_wchmod(path.wstring().c_str(), mode); +} +static int NativeFtruncate(int descriptor, std::int64_t length) { + return static_cast(::_chsize_s(descriptor, length)); +} +static int NativeFlock(int descriptor, int operation) { + HANDLE handle = reinterpret_cast(::_get_osfhandle(descriptor)); + if (handle == INVALID_HANDLE_VALUE) { + return -1; + } + OVERLAPPED overlapped{}; + if (operation & 8) { + return ::UnlockFileEx(handle, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1; + } + DWORD flags = 0; + if (operation & 2) flags |= LOCKFILE_EXCLUSIVE_LOCK; + if (operation & 4) flags |= LOCKFILE_FAIL_IMMEDIATELY; + return ::LockFileEx(handle, flags, 0, MAXDWORD, MAXDWORD, &overlapped) ? 0 : -1; +} +static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) { + if (nbytes > static_cast(std::numeric_limits::max())) { + throw std::runtime_error("NativePread: nbytes exceeds platform limit"); + } + std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR); + if (saved < 0) { + return -1; + } + if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) { + return -1; + } + int n = ::_read(descriptor, buf, static_cast(nbytes)); + ::_lseeki64(descriptor, saved, SEEK_SET); + return n; +} +static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) { + if (nbytes > static_cast(std::numeric_limits::max())) { + throw std::runtime_error("NativePwrite: nbytes exceeds platform limit"); + } + std::int64_t saved = ::_lseeki64(descriptor, 0, SEEK_CUR); + if (saved < 0) { + return -1; + } + if (::_lseeki64(descriptor, offset, SEEK_SET) < 0) { + return -1; + } + int n = ::_write(descriptor, buf, static_cast(nbytes)); + ::_lseeki64(descriptor, saved, SEEK_SET); + return n; +} +#else +#include +#include +#include +static int NativeRmdir(const std::filesystem::path& path) { + return ::rmdir(path.c_str()); +} +static int NativeMkdir(const std::filesystem::path& path, std::uint16_t mode) { + return ::mkdir(path.c_str(), static_cast(mode)); +} +static int NativeChmod(const std::filesystem::path& path, int mode) { + return ::chmod(path.c_str(), static_cast(mode)); +} +static int NativeFtruncate(int descriptor, std::int64_t length) { + return ::ftruncate(descriptor, static_cast(length)); +} +static int NativeFlock(int descriptor, int operation) { + return ::flock(descriptor, operation); +} +static std::int64_t NativePread(int descriptor, void* buf, std::size_t nbytes, std::int64_t offset) { + return static_cast(::pread(descriptor, buf, nbytes, static_cast(offset))); +} +static std::int64_t NativePwrite(int descriptor, const void* buf, std::size_t nbytes, std::int64_t offset) { + return static_cast(::pwrite(descriptor, buf, nbytes, static_cast(offset))); +} +#endif static constexpr int GUEST_ENOENT = 2; static constexpr int GUEST_EIO = 5; @@ -20,101 +114,155 @@ static int SceErrorFromErrno(int error) { extern "C" { int APS5_VABI chmod_nid_postfix(const char* path, int mode) { - (void)path; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; + if (path == nullptr) { + APS5_INVALID_ARG_EX; + } + auto native = ResolvePath_nid_no_patch(path); + if (NativeChmod(native, mode) != 0) { + throw std::runtime_error(std::string(__func__) + ": chmod failed for " + native.string() + ", errno=" + std::to_string(errno)); + } + return 0; } int APS5_VABI close_nid_postfix(int d) { - (void)d; - NotImplemented_nid_no_patch(__func__); - return 0; + if (d >= GuestSockets::FirstDescriptor) return GuestSockets::Close(d); +#ifdef _WIN32 + return _close(d); +#else + return ::close(d); +#endif +} + +int APS5_VABI _close_nid_postfix(int descriptor) { + return close_nid_postfix(descriptor); } int APS5_VABI flock_nid_postfix(int d, int operation) { - (void)d; - (void)operation; - NotImplemented_nid_no_patch(__func__); - return 0; + if (NativeFlock(d, operation) != 0) { +#ifdef _WIN32 + throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", error=" + std::to_string(::GetLastError())); +#else + throw std::runtime_error(std::string(__func__) + ": flock failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); +#endif + } + return 0; } int64_t APS5_VABI fstat_nid_disambig1_nid_postfix(int d, FileStat* sb) { - (void)d; - (void)sb; - NotImplemented_nid_no_patch(__func__); - return 0; + if (sb == nullptr) { + APS5_INVALID_ARG_EX; + } + File::FillFileStat(d, sb); + return 0; } int APS5_VABI ftruncate_nid_postfix(int d, int64_t length) { - (void)d; - (void)length; - NotImplemented_nid_no_patch(__func__); - return 0; + if (length < 0) { + APS5_INVALID_ARG_EX; + } +#ifdef _WIN32 + int error = NativeFtruncate(d, length); + if (error != 0) { + throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", error=" + std::to_string(error)); + } +#else + if (NativeFtruncate(d, length) != 0) { + throw std::runtime_error(std::string(__func__) + ": ftruncate failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); + } +#endif + return 0; } int64_t APS5_VABI lseek_nid_postfix(int d, int64_t offset, int whence) { - (void)d; - (void)offset; - (void)whence; - NotImplemented_nid_no_patch(__func__); - return 0; + return static_cast(sceKernelLseek(d, offset, whence)); } int APS5_VABI mkdir_nid_postfix(const char* path, uint16_t mode) { - (void)path; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; + if (path == nullptr) { + APS5_INVALID_ARG_EX; + } + auto native = ResolvePath_nid_no_patch(path); + if (NativeMkdir(native, mode) != 0) { + throw std::runtime_error(std::string(__func__) + ": mkdir failed for " + native.string() + ", errno=" + std::to_string(errno)); + } + return 0; } int APS5_VABI open_nid_postfix(const char* path, int flags, int mode) { - (void)path; - (void)flags; - (void)mode; - NotImplemented_nid_no_patch(__func__); - return 0; + return sceKernelOpen(path, flags, static_cast(mode)); +} + +int APS5_VABI _open_nid_postfix(const char* path, int flags, ...) { + std::uint16_t mode = 0; + if (flags & SCE_KERNEL_O_CREAT) { +#ifdef _WIN32 + __builtin_sysv_va_list arguments; + __builtin_sysv_va_start(arguments, flags); + mode = static_cast(__builtin_va_arg(arguments, int)); + __builtin_sysv_va_end(arguments); +#else + std::va_list arguments; + va_start(arguments, flags); + mode = static_cast(va_arg(arguments, int)); + va_end(arguments); +#endif + } + return sceKernelOpen(path, flags, mode); } int64_t APS5_VABI pread_nid_postfix(int d, void* buf, size_t nbytes, int64_t offset) { - (void)d; - (void)buf; - (void)nbytes; - (void)offset; - NotImplemented_nid_no_patch(__func__); - return 0; + if (buf == nullptr) { + APS5_INVALID_ARG_EX; + } + if (offset < 0) { + APS5_INVALID_ARG_EX; + } + auto n = NativePread(d, buf, nbytes, offset); + if (n < 0) { + throw std::runtime_error(std::string(__func__) + ": pread failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); + } + return n; } int64_t APS5_VABI pwrite_nid_disambig1_nid_postfix(int d, const void* buf, size_t nbytes, int64_t offset) { - (void)d; - (void)buf; - (void)nbytes; - (void)offset; - NotImplemented_nid_no_patch(__func__); - return 0; + if (buf == nullptr) { + APS5_INVALID_ARG_EX; + } + if (offset < 0) { + APS5_INVALID_ARG_EX; + } + auto n = NativePwrite(d, buf, nbytes, offset); + if (n < 0) { + throw std::runtime_error(std::string(__func__) + ": pwrite failed, fd=" + std::to_string(d) + ", errno=" + std::to_string(errno)); + } + return n; } int64_t APS5_VABI read_nid_postfix(int d, void* buf, uint64_t nbytes) { - (void)d; - (void)buf; - (void)nbytes; - NotImplemented_nid_no_patch(__func__); - return 0; + return sceKernelRead(d, buf, static_cast(nbytes)); +} + +std::int64_t APS5_VABI _read_nid_postfix(int descriptor, void* buffer, std::size_t count) { + return sceKernelRead(descriptor, buffer, count); } int64_t APS5_VABI write_nid_postfix(int d, const char* str, int64_t size) { - (void)d; - (void)str; - (void)size; - NotImplemented_nid_no_patch(__func__); - return 0; + if (size < 0) { + APS5_INVALID_ARG_EX; + } + return sceKernelWrite(d, str, static_cast(size)); +} + +std::int64_t APS5_VABI _write_nid_postfix(int descriptor, const void* buffer, std::size_t count) { + return sceKernelWrite(descriptor, buffer, count); } int APS5_VABI stat_nid_postfix(const char* path, FileStat* sb) { - (void)path; - (void)sb; - NotImplemented_nid_no_patch(__func__); - return 0; + return sceKernelStat(path, sb); +} + +int APS5_VABI unlink_nid_postfix(const char* path) { + return sceKernelUnlink(path); } int APS5_VABI sceKernelCheckReachability(const char* path) { @@ -201,4 +349,33 @@ int APS5_VABI sceKernelRmdir(const char* path) { return 0; } +int APS5_VABI rmdir_nid_postfix(const char* path) { + return sceKernelRmdir(path); +} + +} + +extern "C" { + +int APS5_VABI sceKernelChmod_nid_postfix(const char* path, std::uint16_t mode) { + (void)path; + (void)mode; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceKernelTruncate_nid_postfix(const char* path, std::int64_t length) { + (void)path; + (void)length; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval* times) { + (void)path; + (void)times; + NotImplemented_nid_no_patch(__func__); + return 0; +} + } diff --git a/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp new file mode 100644 index 000000000..e1091def8 --- /dev/null +++ b/core/libs/prx/libkernel/Module/src/DynamicLoader.cpp @@ -0,0 +1,126 @@ +#include "prx/libc/include/General.hpp" +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define NOMINMAX +#include +#else +#include +#endif + +namespace { +thread_local std::array loaderError{}; +thread_local bool pendingError = false; +void Error(const char* message) { + std::snprintf(loaderError.data(), loaderError.size(), "%s", message); + pendingError = true; +} +struct Module { + void* native = nullptr; + bool owned = true; + bool global = false; + ~Module() { + if (owned && native) { +#ifdef _WIN32 + FreeLibrary(static_cast(native)); +#else + ::dlclose(native); +#endif + } + } +}; +std::mutex modulesMutex; +std::map> modules; +std::uintptr_t nextHandle = 0x20000000; +void* Symbol(Module& module, const char* name) { +#ifdef _WIN32 + return reinterpret_cast(GetProcAddress(static_cast(module.native), name)); +#else + return ::dlsym(module.native, name); +#endif +} +void* FindSymbol(Module& module, const char* name) { + if (auto* symbol = Symbol(module, name)) return symbol; + const auto nid = Nid::ComputeNid(name, ""); + return Symbol(module, nid.c_str()); +} +} + +extern "C" { +char* APS5_VABI dlerror_nid_postfix() { + if (!pendingError) return nullptr; + pendingError = false; + return loaderError.data(); +} +void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) { + if ((flags & ~0x103) || (flags & 3) == 0 || (flags & 3) == 3) { + Error("dlopen: unsupported flags"); return nullptr; + } + try { + auto module = std::make_shared(); + module->global = (flags & 0x100) != 0 || !path; +#ifdef _WIN32 + if (!path) { + module->native = GetModuleHandleW(nullptr); + module->owned = false; + } else { + if (!*path) { Error("dlopen: empty module path"); return nullptr; } + const auto resolved = ResolvePath_nid_no_patch(path); + module->native = LoadLibraryExW(resolved.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); + } + if (!module->native) { + char message[128]; + std::snprintf(message, sizeof(message), "dlopen: Windows loader error %lu (module must be host-compatible)", GetLastError()); + Error(message); return nullptr; + } +#else + const auto resolved = path ? ResolvePath_nid_no_patch(path).string() : std::string{}; + const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) | + ((flags & 0x100) ? RTLD_GLOBAL : RTLD_LOCAL); + module->native = ::dlopen(path ? resolved.c_str() : nullptr, nativeFlags); + if (!module->native) { Error(::dlerror()); return nullptr; } +#endif + std::lock_guard lock(modulesMutex); + const auto handle = nextHandle++; + modules.emplace(handle, std::move(module)); + return reinterpret_cast(handle); + } catch (const std::exception& error) { Error(error.what()); return nullptr; } +} +void* APS5_VABI dlsym_nid_postfix(void* handle, const char* name) { + if (!name || !*name) { Error("dlsym: empty symbol name"); return nullptr; } + try { + std::vector> search; + { + std::lock_guard lock(modulesMutex); + if (handle == reinterpret_cast(static_cast(-2))) { + for (const auto& [key, module] : modules) if (module->global) search.push_back(module); + } else { + auto found = modules.find(reinterpret_cast(handle)); + if (found == modules.end()) { Error("dlsym: invalid or unsupported module handle"); return nullptr; } + search.push_back(found->second); + } + } + for (const auto& module : search) if (auto* result = FindSymbol(*module, name)) return result; + Error("dlsym: symbol not found in supported module scope"); + return nullptr; + } catch (const std::exception& error) { Error(error.what()); return nullptr; } +} +int APS5_VABI dlclose_nid_postfix(void* handle) { + std::shared_ptr module; + { + std::lock_guard lock(modulesMutex); + auto found = modules.find(reinterpret_cast(handle)); + if (found == modules.end()) { Error("dlclose: invalid module handle"); return -1; } + module = std::move(found->second); + modules.erase(found); + } + // Unload outside the registry lock: module destructors may call loader APIs. + module.reset(); + return 0; +} +} diff --git a/core/libs/prx/libkernel/Module/src/Module.cpp b/core/libs/prx/libkernel/Module/src/Module.cpp index c34c4577a..4b0f12136 100644 --- a/core/libs/prx/libkernel/Module/src/Module.cpp +++ b/core/libs/prx/libkernel/Module/src/Module.cpp @@ -52,3 +52,21 @@ int APS5_VABI sceKernelStopUnloadModule(KernelModule handle, size_t args, const } } + +extern "C" { + +int APS5_VABI __elf_phdr_match_addr_nid_postfix(ModuleInfo* module, std::uint64_t address) { + (void)module; + (void)address; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +// unknown signature +std::int32_t APS5_VABI sceKernelInternalMemoryGetModuleSegmentInfo_nid_postfix(void* result) { + (void)result; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp index 7055cae0c..4a77cf00f 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Cond.cpp @@ -1,75 +1,58 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" -#include "../include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" #include "Common.hpp" -#include +#include #include -#include -extern "C" { -int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr); -int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr); -int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); -int APS5_VABI scePthreadCondSignal(PthreadCond* cond); -int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond); -int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec); +namespace { + +int toPosix(int result) { + if (result == 0) + return 0; + const auto error = static_cast(result); + if ((error & 0xffff0000u) != 0x80020000u) + throw std::runtime_error("Unexpected SCE condition variable error"); + return static_cast(error & 0xffffu); } -static void InitializeStatic(PthreadCond* cond, const char* funcName) { - if (!cond) throw std::runtime_error(std::string(funcName) + ": null cond"); - std::atomic_ref slot(*cond); - PthreadCond current = slot.load(std::memory_order_acquire); - if (current != nullptr) return; - auto* created = new PthreadCondPrivate(); - if (!slot.compare_exchange_strong(current, created, std::memory_order_acq_rel, std::memory_order_acquire)) - delete created; } extern "C" { int APS5_VABI pthread_cond_broadcast_nid_postfix(PthreadCond* cond) { - InitializeStatic(cond, __func__); - return PosixThread::ToErrno(scePthreadCondBroadcast(cond)); + return toPosix(scePthreadCondBroadcast(cond)); } int APS5_VABI pthread_cond_init_nid_postfix(PthreadCond* cond, const PthreadCondattr* attr) { - return PosixThread::ToErrno(scePthreadCondInit(cond, attr, nullptr)); + return toPosix(scePthreadCondInit(cond, attr, nullptr)); } int APS5_VABI pthread_cond_signal_nid_postfix(PthreadCond* cond) { - InitializeStatic(cond, __func__); - return PosixThread::ToErrno(scePthreadCondSignal(cond)); + return toPosix(scePthreadCondSignal(cond)); } int APS5_VABI pthread_cond_timedwait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) { - InitializeStatic(cond, __func__); - KernelUseconds usec = 0; - if (!PosixThread::RelativeMicroseconds((*cond)->_clockid, abstime, &usec)) return PosixThread::GUEST_EINVAL; - return PosixThread::ToErrno(scePthreadCondTimedwait(cond, mutex, usec)); + if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return PosixThread::GUEST_EINVAL; + return toPosix(CondOperations::AbsoluteTimedwait(cond, mutex, abstime)); } int APS5_VABI pthread_cond_wait_nid_postfix(PthreadCond* cond, PthreadMutex* mutex) { - InitializeStatic(cond, __func__); - return PosixThread::ToErrno(scePthreadCondWait(cond, mutex)); + return toPosix(scePthreadCondWait(cond, mutex)); } int APS5_VABI pthread_condattr_destroy_nid_postfix(PthreadCondattr* attr) { if (!attr || !*attr) return PosixThread::GUEST_EINVAL; - return PosixThread::ToErrno(scePthreadCondattrDestroy(attr)); + return toPosix(scePthreadCondattrDestroy(attr)); } int APS5_VABI pthread_condattr_init_nid_postfix(PthreadCondattr* attr) { if (!attr) return PosixThread::GUEST_EINVAL; - return PosixThread::ToErrno(scePthreadCondattrInit(attr)); + return toPosix(scePthreadCondattrInit(attr)); } int APS5_VABI pthread_condattr_setclock_nid_postfix(PthreadCondattr* attr, KernelClockid clock_id) { if (!attr || !*attr) return PosixThread::GUEST_EINVAL; - (*attr)->_clockid = clock_id; - return 0; + return toPosix(scePthreadCondattrSetclock(attr, clock_id)); } } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp index 2f56763b1..3e2ed7c68 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp @@ -3,22 +3,13 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "../include/Pthread.hpp" +#include "../include/Mutex.hpp" +#include "Common.hpp" #include #include #include #include - -extern "C" { -int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); -int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); -int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec); -int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex); -} +#include namespace { @@ -27,10 +18,6 @@ constexpr int POSIX_MUTEX_ADAPTIVE_NP = 4; constexpr int POSIX_PRIO_PROTECT = 2; constexpr std::uintptr_t POSIX_ADAPTIVE_MUTEX_INITIALIZER = 1; -int _toErrno(int sceResult) { - return sceResult == 0 ? 0 : static_cast(static_cast(sceResult) & 0xFFFFu); -} - bool _isStaticInitializer(PthreadMutex mutex) { const auto value = reinterpret_cast(mutex); return value == 0 || value == POSIX_ADAPTIVE_MUTEX_INITIALIZER; @@ -57,18 +44,18 @@ int APS5_VABI pthread_mutex_destroy_nid_postfix(PthreadMutex* mutex) { *mutex = nullptr; return 0; } - return _toErrno(scePthreadMutexDestroy(mutex)); + return PosixThread::ToErrno(scePthreadMutexDestroy(mutex)); } int APS5_VABI pthread_mutex_init_nid_postfix(PthreadMutex* mutex, const PthreadMutexattr* attr) { const int result = scePthreadMutexInit(mutex, attr, nullptr); if (result == 0 && (!attr || !*attr)) (*mutex)->_type = MutexType::ErrorCheck; - return _toErrno(result); + return PosixThread::ToErrno(result); } int APS5_VABI pthread_mutex_lock_nid_postfix(PthreadMutex* mutex) { _initializeStatic(mutex, __func__); - return _toErrno(scePthreadMutexLock(mutex)); + return PosixThread::ToErrno(scePthreadMutexLock(mutex)); } int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime) { @@ -79,27 +66,27 @@ int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const Ker const auto now = std::chrono::system_clock::now().time_since_epoch(); const auto remaining = std::chrono::duration_cast(deadline - now).count(); const auto usec = remaining <= 0 ? 0 : remaining >= std::numeric_limits::max() ? std::numeric_limits::max() : static_cast(remaining); - return _toErrno(scePthreadMutexTimedlock(mutex, usec)); + return PosixThread::ToErrno(scePthreadMutexTimedlock(mutex, usec)); } int APS5_VABI pthread_mutex_trylock_nid_postfix(PthreadMutex* mutex) { _initializeStatic(mutex, __func__); - return _toErrno(scePthreadMutexTrylock(mutex)); + return PosixThread::ToErrno(scePthreadMutexTrylock(mutex)); } int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex) { _initializeStatic(mutex, __func__); - return _toErrno(scePthreadMutexUnlock(mutex)); + return PosixThread::ToErrno(scePthreadMutexUnlock(mutex)); } int APS5_VABI pthread_mutexattr_destroy_nid_postfix(PthreadMutexattr* attr) { - return _toErrno(scePthreadMutexattrDestroy(attr)); + return PosixThread::ToErrno(scePthreadMutexattrDestroy(attr)); } int APS5_VABI pthread_mutexattr_init_nid_postfix(PthreadMutexattr* attr) { const int result = scePthreadMutexattrInit(attr); if (result == 0) (*attr)->type = MutexType::ErrorCheck; - return _toErrno(result); + return PosixThread::ToErrno(result); } int APS5_VABI pthread_mutexattr_setprotocol_nid_postfix(PthreadMutexattr* attr, int protocol) { @@ -114,7 +101,7 @@ int APS5_VABI pthread_mutexattr_settype_nid_postfix(PthreadMutexattr* attr, int (*attr)->type = MutexType::Normal; return 0; } - return _toErrno(scePthreadMutexattrSettype(attr, type)); + return PosixThread::ToErrno(scePthreadMutexattrSettype(attr, type)); } } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Once.cpp b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp new file mode 100644 index 000000000..8702a17a0 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/Posix/Once.cpp @@ -0,0 +1,57 @@ +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +namespace { +// FreeBSD guest ABI: PTHREAD_ONCE_INIT is {0, nullptr}; completed state is 1. +struct GuestOnce { + std::int32_t state; + void* mutex; +}; +static_assert(offsetof(GuestOnce, mutex) == 8 && sizeof(GuestOnce) == 16); +std::mutex onceMutex; +std::condition_variable onceChanged; +} + +extern "C" int APS5_VABI pthread_once_nid_postfix( + GuestOnce* control, void (APS5_VABI *initialize)()) { + if (!control || !initialize) return 22; + std::unique_lock lock(onceMutex); + onceChanged.wait(lock, [&] { return control->state != 2; }); + if (control->state == 1) return 0; + if (control->state != 0) return 22; + control->state = 2; + lock.unlock(); + // Initializers may initialize other once controls. Never run guest code + // under the shared lock. The lock publishes all initializer writes. + try { + initialize(); + } catch (...) { + lock.lock(); + control->state = 0; + lock.unlock(); + onceChanged.notify_all(); + throw; + } + lock.lock(); + control->state = 1; + lock.unlock(); + onceChanged.notify_all(); + return 0; +} + +using OnceRoutine = void (APS5_VABI *)(); + +extern "C" { + +int APS5_VABI scePthreadOnce_nid_postfix(void* onceControl, OnceRoutine initRoutine) { + (void)onceControl; + (void)initRoutine; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp index c15248a60..0e85208eb 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp @@ -3,22 +3,23 @@ #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" #include "../include/Pthread.hpp" +#include "../include/Rwlock.hpp" #include #include - -extern "C" { -int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name); -int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock); -int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock); -} +#include namespace { -int _toErrno(int sceResult) { - return sceResult == 0 ? 0 : static_cast(static_cast(sceResult) & 0xFFFFu); +int toPosix(int result) { + if (result == 0) + return 0; + const auto error = static_cast(result); + if ((error & 0xffff0000u) != 0x80020000u) + throw std::runtime_error("Unexpected SCE rwlock error"); + return static_cast(error & 0xffffu); } -void _initializeStatic(PthreadRwlock* rwlock, const char* funcName) { +void initializeStatic(PthreadRwlock* rwlock, const char* funcName) { if (!rwlock) throw std::runtime_error(std::string(funcName) + ": null rwlock"); std::atomic_ref slot(*rwlock); PthreadRwlock current = slot.load(std::memory_order_acquire); @@ -35,16 +36,26 @@ extern "C" { int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock) { if (!rwlock) throw std::runtime_error("pthread_rwlock_destroy: null rwlock"); if (*rwlock == nullptr) return 0; - return _toErrno(scePthreadRwlockDestroy(rwlock)); + return toPosix(scePthreadRwlockDestroy(rwlock)); } int APS5_VABI pthread_rwlock_init_nid_postfix(PthreadRwlock* rwlock, const PthreadRwlockattr* attr) { - return _toErrno(scePthreadRwlockInit(rwlock, attr, nullptr)); + return toPosix(scePthreadRwlockInit(rwlock, attr, nullptr)); +} + +int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock) { + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockRdlock(rwlock)); +} + +int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock) { + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockUnlock(rwlock)); } int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock) { - _initializeStatic(rwlock, __func__); - return _toErrno(scePthreadRwlockWrlock(rwlock)); + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockWrlock(rwlock)); } } diff --git a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp index fc7b245e7..f1d43aa31 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Thread.cpp @@ -1,6 +1,7 @@ #include #include #include "SceTypes.hpp" +#include "../include/ThreadLifecycle.hpp" #include "prx/libc/include/General.hpp" #include "../include/Pthread.hpp" #include "Common.hpp" diff --git a/core/libs/prx/libkernel/Pthread/include/Cond.hpp b/core/libs/prx/libkernel/Pthread/include/Cond.hpp new file mode 100644 index 000000000..936b95714 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/Cond.hpp @@ -0,0 +1,26 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_COND_HPP +#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_COND_HPP + +#include "SceTypes.hpp" + +class CondOperations { +public: + static int AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime); +}; + +extern "C" { + +int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr); +int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr); +int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId); +int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name); +int APS5_VABI scePthreadCondDestroy(PthreadCond* cond); +int APS5_VABI scePthreadCondSignal(PthreadCond* cond); +int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond); +int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread); +int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex); +int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec); + +} + +#endif diff --git a/core/libs/prx/libkernel/Pthread/include/Mutex.hpp b/core/libs/prx/libkernel/Pthread/include/Mutex.hpp new file mode 100644 index 000000000..ca734c603 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/Mutex.hpp @@ -0,0 +1,26 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_MUTEX_HPP +#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_MUTEX_HPP + +#include "SceTypes.hpp" + +class MutexOperations { +public: + static int Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime); +}; + +extern "C" { + +int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr); +int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr); +int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type); +int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol); +int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name); +int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex); +int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec); +int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex); + +} + +#endif diff --git a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp index c87009f13..cd3946ffd 100644 --- a/core/libs/prx/libkernel/Pthread/include/Pthread.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Pthread.hpp @@ -30,7 +30,7 @@ struct PthreadMutexPrivate { std::atomic _owner; int _count; - PthreadMutexPrivate() : _type(MutexType::Normal), _count(0) {} + PthreadMutexPrivate() : _type(MutexType::Normal), _owner(std::thread::id{}), _count(0) {} }; struct PthreadRwlockattrPrivate { diff --git a/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp b/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp new file mode 100644 index 000000000..0c9668003 --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp @@ -0,0 +1,21 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_RWLOCK_HPP +#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_RWLOCK_HPP + +#include "SceTypes.hpp" + +extern "C" { + +int APS5_VABI scePthreadRwlockattrInit(PthreadRwlockattr* attr); +int APS5_VABI scePthreadRwlockattrDestroy(PthreadRwlockattr* attr); +int APS5_VABI scePthreadRwlockattrSettype(PthreadRwlockattr* attr, int type); +int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr, const char* name); +int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock); + +} + +#endif diff --git a/core/libs/prx/libkernel/Pthread/include/ThreadLifecycle.hpp b/core/libs/prx/libkernel/Pthread/include/ThreadLifecycle.hpp new file mode 100644 index 000000000..f342bb0ca --- /dev/null +++ b/core/libs/prx/libkernel/Pthread/include/ThreadLifecycle.hpp @@ -0,0 +1,15 @@ +#ifndef CORE_LIBS_PRX_LIBKERNEL_PTHREAD_THREADLIFECYCLE_HPP +#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_THREADLIFECYCLE_HPP + +#include "SceTypes.hpp" + +class ThreadLifecycle { +public: + static void SetThreadDtors(thread_dtors_func_t callback); + static void SetThreadAtexitCount(get_thread_atexit_count_func_t callback); + static void SetThreadAtexitReport(thread_atexit_report_func_t callback); +}; + +extern "C" void APS5_VABI scePthreadExit(void* retval); + +#endif diff --git a/core/libs/prx/libkernel/Pthread/src/Attr.cpp b/core/libs/prx/libkernel/Pthread/src/Attr.cpp index 5de4fe8d7..11fb3c38f 100644 --- a/core/libs/prx/libkernel/Pthread/src/Attr.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Attr.cpp @@ -1,9 +1,10 @@ -#include "../include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" #include "prx/libc/include/General.hpp" #include #include static constexpr int SCE_OK = 0; +static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016; static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C; static constexpr std::size_t DEFAULT_STACK_SIZE = 1u << 20; @@ -19,7 +20,7 @@ static constexpr int SCHED_FIFO_PS5 = 1; extern "C" { int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) { - if (!attr) throw std::runtime_error("scePthreadAttrInit: null attr"); + if (!attr) throw std::runtime_error(std::string(__func__) + ": null attr"); auto* p = new (std::nothrow) PthreadAttrPrivate{}; if (!p) return SCE_KERNEL_ERROR_ENOMEM; p->_stacksize = DEFAULT_STACK_SIZE; @@ -32,35 +33,35 @@ int APS5_VABI scePthreadAttrInit(PthreadAttr* attr) { } int APS5_VABI scePthreadAttrDestroy(PthreadAttr* attr) { - if (!attr || !*attr) throw std::runtime_error("scePthreadAttrDestroy: null attr"); + if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr"); delete *attr; *attr = nullptr; return SCE_OK; } int APS5_VABI scePthreadAttrSetdetachstate(PthreadAttr* attr, int detachstate) { - if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetdetachstate: null attr"); + if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr"); if (detachstate != DETACH_JOINABLE && detachstate != DETACH_DETACHED) - throw std::runtime_error("scePthreadAttrSetdetachstate: invalid state"); + throw std::runtime_error(std::string(__func__) + ": invalid state"); (*attr)->_detachstate = detachstate; return SCE_OK; } int APS5_VABI scePthreadAttrSetschedparam(PthreadAttr* attr, const KernelSchedParam* param) { if (!attr || !*attr || !param) - throw std::runtime_error("scePthreadAttrSetschedparam: null arg"); + throw std::runtime_error(std::string(__func__) + ": null arg"); (*attr)->_schedpriority = param->sched_priority; return SCE_OK; } int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksize) { - if (!attr || !*attr) throw std::runtime_error("scePthreadAttrSetstacksize: null attr"); - if (stacksize < 16384) throw std::runtime_error("scePthreadAttrSetstacksize: too small"); + if (!attr || !*attr) throw std::runtime_error(std::string(__func__) + ": null attr"); + if (stacksize < 16384) throw std::runtime_error(std::string(__func__) + ": too small"); #ifdef _WIN32 SYSTEM_INFO system{}; GetSystemInfo(&system); if (stacksize % system.dwPageSize != 0 || stacksize > std::numeric_limits::max()) - throw std::runtime_error("scePthreadAttrSetstacksize: invalid Windows stack size"); + throw std::runtime_error(std::string(__func__) + ": invalid Windows stack size"); #endif (*attr)->_stacksize = stacksize; return SCE_OK; @@ -68,7 +69,7 @@ int APS5_VABI scePthreadAttrSetstacksize(PthreadAttr* attr, std::size_t stacksiz int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr, std::size_t* stacksize) { if (!attr || !*attr || !stackaddr || !stacksize) - throw std::runtime_error("scePthreadAttrGetstack: null arg"); + throw std::runtime_error(std::string(__func__) + ": null arg"); *stackaddr = (*attr)->stackAddress; *stacksize = (*attr)->_stacksize; return SCE_OK; @@ -76,7 +77,7 @@ int APS5_VABI scePthreadAttrGetstack(const PthreadAttr* attr, void** stackaddr, int APS5_VABI scePthreadAttrGet(Pthread thread, PthreadAttr* attr) { if (!thread || !attr || !*attr) - throw std::runtime_error("scePthreadAttrGet: null arg"); + return SCE_KERNEL_ERROR_EINVAL; (*attr)->_stacksize = thread->stackSize; (*attr)->stackAddress = thread->stackAddress; (*attr)->_detachstate = thread->_detached ? DETACH_DETACHED : DETACH_JOINABLE; @@ -165,18 +166,17 @@ int APS5_VABI scePthreadAttrSetsolosched(PthreadAttr* attr, int solosched) { } int APS5_VABI scePthreadAttrSetstack(PthreadAttr* attr, void* addr, size_t size) { - (void)attr; - (void)addr; - (void)size; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg"); + if (size < 16384) throw std::runtime_error(std::string(__func__) + ": too small"); + (*attr)->stackAddress = addr; + (*attr)->_stacksize = size; + return SCE_OK; } int APS5_VABI scePthreadAttrSetstackaddr(PthreadAttr* attr, void* addr) { - (void)attr; - (void)addr; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!attr || !*attr || !addr) throw std::runtime_error(std::string(__func__) + ": null arg"); + (*attr)->stackAddress = addr; + return SCE_OK; } } diff --git a/core/libs/prx/libkernel/Pthread/src/Cond.cpp b/core/libs/prx/libkernel/Pthread/src/Cond.cpp index 23a7263dc..cfeec60be 100644 --- a/core/libs/prx/libkernel/Pthread/src/Cond.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Cond.cpp @@ -1,103 +1,154 @@ -#include "../include/Pthread.hpp" -#include "prx/libc/include/General.hpp" +#include "prx/libkernel/Pthread/include/Pthread.hpp" +#include "prx/libkernel/Pthread/include/Mutex.hpp" +#include "prx/libkernel/Pthread/include/Cond.hpp" +#include "prx/libkernel/Pthread/Posix/Common.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include #include +#include #include -#include #include +#include -static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C; -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C; +namespace { -static int WaitOn(PthreadCondPrivate* cond, PthreadMutexPrivate* mutex, std::optional deadlineNanos, const void* caller) { - const auto self = std::this_thread::get_id(); +constexpr int sceTimedOut = static_cast(0x8002003cu); +std::mutex condInitializationMutex; + +PthreadCond destroyedCond() { + return reinterpret_cast(std::uintptr_t{2}); +} + +PthreadCond resolveCond(PthreadCond* cond) { + if (!cond) + throw std::invalid_argument("Condition variable pointer is null"); + std::lock_guard lock(condInitializationMutex); + if (*cond == destroyedCond()) + throw std::runtime_error("Condition variable has been destroyed"); + if (!*cond) + *cond = new PthreadCondPrivate(); + return *cond; +} + +PthreadMutex lockedMutex(PthreadMutex* mutex) { + if (!mutex || !*mutex) + throw std::invalid_argument("Mutex pointer is null"); + auto* current = *mutex; + if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id()) + throw std::runtime_error("Condition wait mutex is not owned by the current thread"); + return current; +} + +int waitUntil(PthreadCond* cond, PthreadMutex* mutex, std::optional deadlineNanos, const void* caller) { + auto* c = resolveCond(cond); + auto* m = lockedMutex(mutex); bool timedOut = false; const auto waitStart = std::chrono::steady_clock::now(); struct Trace { const void* caller; const bool& timedOut; std::chrono::steady_clock::time_point start; ~Trace() { KernelTraceWait_nid_postfix("cond", caller, static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()), timedOut); } } trace{caller, timedOut, waitStart}; - mutex->_owner.store(std::thread::id{}, std::memory_order_relaxed); - if (mutex->_type == MutexType::Recursive) { - std::unique_lock lock(mutex->_rmtx, std::adopt_lock); - if (deadlineNanos) timedOut = !cond->_cv.WaitUntil(lock, *deadlineNanos); - else cond->_cv.Wait(lock); - lock.release(); - } else { - std::unique_lock lock(mutex->_mtx, std::adopt_lock); - if (deadlineNanos) timedOut = !cond->_cv.WaitUntil(lock, *deadlineNanos); - else cond->_cv.Wait(lock); + if (m->_type == MutexType::Recursive) { + std::unique_lock lock(m->_rmtx, std::adopt_lock); + const auto previousCount = m->_count; + m->_count = 0; + m->_owner.store(std::thread::id{}, std::memory_order_release); + if (deadlineNanos) timedOut = !c->_cv.WaitUntil(lock, *deadlineNanos); + else c->_cv.Wait(lock); + m->_owner.store(std::this_thread::get_id(), std::memory_order_release); + m->_count = previousCount; lock.release(); + return timedOut ? sceTimedOut : 0; } - mutex->_owner.store(self, std::memory_order_relaxed); - return timedOut ? SCE_KERNEL_ERROR_ETIMEDOUT : SCE_OK; + std::unique_lock lock(m->_mtx, std::adopt_lock); + m->_owner.store(std::thread::id{}, std::memory_order_release); + if (deadlineNanos) timedOut = !c->_cv.WaitUntil(lock, *deadlineNanos); + else c->_cv.Wait(lock); + m->_owner.store(std::this_thread::get_id(), std::memory_order_release); + lock.release(); + return timedOut ? sceTimedOut : 0; +} + +} + +int CondOperations::AbsoluteTimedwait(PthreadCond* cond, PthreadMutex* mutex, const KernelTimespec* abstime) { + KernelUseconds usec = 0; + if (!PosixThread::RelativeMicroseconds(resolveCond(cond)->_clockid, abstime, &usec)) + throw std::invalid_argument("Invalid absolute condition variable timeout"); + return waitUntil(cond, mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0)); } extern "C" { int APS5_VABI scePthreadCondattrInit(PthreadCondattr* attr) { - if (!attr) throw std::runtime_error("scePthreadCondattrInit: null attr"); - auto* p = new (std::nothrow) PthreadCondattrPrivate{0}; - if (!p) return SCE_KERNEL_ERROR_ENOMEM; - *attr = p; - return SCE_OK; + if (!attr) + throw std::invalid_argument("Condition attribute pointer is null"); + *attr = new PthreadCondattrPrivate{0}; + return 0; } int APS5_VABI scePthreadCondattrDestroy(PthreadCondattr* attr) { - if (!attr || !*attr) throw std::runtime_error("scePthreadCondattrDestroy: null attr"); + if (!attr || !*attr) + throw std::invalid_argument("Condition attributes are not initialized"); delete *attr; *attr = nullptr; - return SCE_OK; + return 0; +} + +int APS5_VABI scePthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clockId) { + if (!attr || !*attr) + throw std::invalid_argument("Condition attributes are not initialized"); + (*attr)->_clockid = static_cast(clockId); + return 0; } int APS5_VABI scePthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char*) { - if (!cond) throw std::runtime_error("scePthreadCondInit: null cond"); - auto* p = new (std::nothrow) PthreadCondPrivate{}; - if (!p) return SCE_KERNEL_ERROR_ENOMEM; - if (attr && *attr) p->_clockid = (*attr)->_clockid; - *cond = p; - return SCE_OK; + if (!cond) + throw std::invalid_argument("Condition variable pointer is null"); + if (attr && !*attr) + throw std::invalid_argument("Condition attributes are not initialized"); + auto replacement = std::make_unique(); + if (attr) + replacement->_clockid = (*attr)->_clockid; + std::lock_guard lock(condInitializationMutex); + *cond = replacement.release(); + return 0; } int APS5_VABI scePthreadCondDestroy(PthreadCond* cond) { - if (!cond || !*cond) throw std::runtime_error("scePthreadCondDestroy: null cond"); + if (!cond) + throw std::invalid_argument("Condition variable pointer is null"); + std::lock_guard lock(condInitializationMutex); + if (*cond == destroyedCond()) + throw std::runtime_error("Condition variable has already been destroyed"); delete *cond; - *cond = nullptr; - return SCE_OK; + *cond = destroyedCond(); + return 0; } int APS5_VABI scePthreadCondSignal(PthreadCond* cond) { - if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignal: null cond"); - (*cond)->_cv.NotifyOne(); - return SCE_OK; + resolveCond(cond)->_cv.NotifyOne(); + return 0; } int APS5_VABI scePthreadCondBroadcast(PthreadCond* cond) { - if (!cond || !*cond) throw std::runtime_error("scePthreadCondBroadcast: null cond"); - (*cond)->_cv.NotifyAll(); - return SCE_OK; -} - -int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, unsigned int usec) { - if (!cond || !*cond || !mutex || !*mutex) - throw std::runtime_error("scePthreadCondTimedwait: null arg"); - return WaitOn(*cond, *mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0)); + resolveCond(cond)->_cv.NotifyAll(); + return 0; } int APS5_VABI scePthreadCondSignalto(PthreadCond* cond, Pthread thread) { (void)thread; - if (!cond || !*cond) throw std::runtime_error("scePthreadCondSignalto: null cond"); - (*cond)->_cv.NotifyAll(); - return SCE_OK; + resolveCond(cond)->_cv.NotifyAll(); + return 0; } int APS5_VABI scePthreadCondWait(PthreadCond* cond, PthreadMutex* mutex) { - if (!cond || !*cond || !mutex || !*mutex) - throw std::runtime_error("scePthreadCondWait: null arg"); - return WaitOn(*cond, *mutex, std::nullopt, __builtin_return_address(0)); + return waitUntil(cond, mutex, std::nullopt, __builtin_return_address(0)); +} + +int APS5_VABI scePthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec) { + return waitUntil(cond, mutex, TimedWait::DeadlineNanos(usec), __builtin_return_address(0)); } } diff --git a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp index b88b85a30..d37a9161b 100644 --- a/core/libs/prx/libkernel/Pthread/src/Mutex.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Mutex.cpp @@ -1,142 +1,174 @@ #include "../include/Pthread.hpp" -#include "prx/libc/include/General.hpp" +#include "../include/Mutex.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" -#include #include #include +#include +#include #include -#include -static constexpr int SCE_OK = 0; -static constexpr int SCE_KERNEL_ERROR_ENOMEM = 0x8002000C; -static constexpr int SCE_KERNEL_ERROR_EDEADLK = 0x8002000B; -static constexpr int SCE_KERNEL_ERROR_EPERM = 0x80020001; -static constexpr int SCE_KERNEL_ERROR_EBUSY = 0x80020010; -static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = 0x8002003C; +namespace { + +constexpr int sceNotPermitted = static_cast(0x80020001u); +constexpr int sceDeadlock = static_cast(0x8002000bu); +constexpr int sceBusy = static_cast(0x80020010u); +constexpr int sceTimedOut = static_cast(0x8002003cu); +std::mutex initializationMutex; + +PthreadMutex destroyedMutex() { + return reinterpret_cast(std::uintptr_t{2}); +} + +PthreadMutex resolveMutex(PthreadMutex* mutex, bool initialize) { + if (!mutex) + throw std::invalid_argument("Mutex pointer is null"); + std::lock_guard lock(initializationMutex); + if (*mutex == destroyedMutex()) + throw std::runtime_error("Mutex has been destroyed"); + if (reinterpret_cast(*mutex) == 1) + throw std::runtime_error("Adaptive mutex initializer is unsupported"); + if (!*mutex) { + if (!initialize) + throw std::runtime_error("Mutex is not initialized"); + *mutex = new PthreadMutexPrivate(); + } + return *mutex; +} + +template +int acquireMutex(PthreadMutex mutex, TAcquire acquire, int unavailable, bool tryOnly) { + const auto thread = std::this_thread::get_id(); + const bool owned = mutex->_owner.load(std::memory_order_acquire) == thread; + if (owned && mutex->_type != MutexType::Recursive) { + if (tryOnly) + return sceBusy; + if (mutex->_type == MutexType::ErrorCheck) + return sceDeadlock; + throw std::runtime_error("Mutex is already owned by the current thread"); + } + if (owned && mutex->_count == std::numeric_limits::max()) + throw std::overflow_error("Recursive mutex lock count overflow"); + if (mutex->_type == MutexType::Recursive) { + if (!acquire(mutex->_rmtx)) + return unavailable; + ++mutex->_count; + } else { + if (!acquire(mutex->_mtx)) + return unavailable; + } + mutex->_owner.store(thread, std::memory_order_release); + return 0; +} + +} + +int MutexOperations::Timedlock(PthreadMutex* mutex, const KernelTimespec* abstime) { + if (!abstime || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) + throw std::invalid_argument("Invalid absolute mutex timeout"); + const auto maximum = std::chrono::nanoseconds::max().count(); + if (abstime->tv_sec > (maximum - abstime->tv_nsec) / 1000000000) + throw std::overflow_error("Absolute mutex timeout exceeds the host clock range"); + const auto duration = std::chrono::nanoseconds(abstime->tv_sec * 1000000000 + abstime->tv_nsec); + if (std::chrono::duration(duration) >= std::chrono::duration(std::chrono::system_clock::duration::max())) + throw std::overflow_error("Absolute mutex timeout exceeds the host clock range"); + const auto deadline = std::chrono::system_clock::time_point(std::chrono::duration_cast(duration)); + return acquireMutex(resolveMutex(mutex, true), [&](auto& native) { return native.try_lock_until(deadline); }, sceTimedOut, false); +} extern "C" { int APS5_VABI scePthreadMutexattrInit(PthreadMutexattr* attr) { - if (!attr) throw std::runtime_error("scePthreadMutexattrInit: null attr"); - auto* p = new (std::nothrow) PthreadMutexattrPrivate{MutexType::Normal}; - if (!p) return SCE_KERNEL_ERROR_ENOMEM; - *attr = p; - return SCE_OK; + if (!attr) + throw std::invalid_argument("Mutex attribute pointer is null"); + *attr = new PthreadMutexattrPrivate{MutexType::Normal}; + return 0; } int APS5_VABI scePthreadMutexattrDestroy(PthreadMutexattr* attr) { - if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrDestroy: null attr"); + if (!attr || !*attr) + throw std::invalid_argument("Mutex attributes are not initialized"); delete *attr; *attr = nullptr; - return SCE_OK; + return 0; } int APS5_VABI scePthreadMutexattrSettype(PthreadMutexattr* attr, int type) { - if (!attr || !*attr) throw std::runtime_error("scePthreadMutexattrSettype: null attr"); + if (!attr || !*attr) + throw std::invalid_argument("Mutex attributes are not initialized"); switch (type) { case 1: (*attr)->type = MutexType::ErrorCheck; break; case 2: (*attr)->type = MutexType::Recursive; break; case 3: (*attr)->type = MutexType::Normal; break; case 4: (*attr)->type = MutexType::Normal; break; - default: throw std::runtime_error("scePthreadMutexattrSettype: invalid type " + std::to_string(type)); + default: throw std::invalid_argument("Invalid mutex type"); } - return SCE_OK; -} - -int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) { - if (!mutex) throw std::runtime_error("scePthreadMutexInit: null mutex"); - MutexType t = MutexType::Normal; - if (attr && *attr) t = (*attr)->type; - auto* p = new (std::nothrow) PthreadMutexPrivate(); - if (!p) return SCE_KERNEL_ERROR_ENOMEM; - p->_type = t; - *mutex = p; - return SCE_OK; -} - -int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) { - if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexDestroy: null mutex"); - delete *mutex; - *mutex = nullptr; - return SCE_OK; -} - -int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) { - if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexLock: null mutex"); - auto* m = *mutex; - const auto tid = std::this_thread::get_id(); - if (m->_type == MutexType::Recursive) { - m->_rmtx.lock(); - m->_owner.store(tid, std::memory_order_relaxed); - ++m->_count; - return SCE_OK; - } - if (m->_type == MutexType::ErrorCheck) { - if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK; - } - m->_mtx.lock(); - m->_owner.store(tid, std::memory_order_relaxed); - return SCE_OK; -} - -int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) { - if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexUnlock: null mutex"); - auto* m = *mutex; - if (m->_type == MutexType::ErrorCheck || m->_type == MutexType::Normal) { - if (m->_owner.load(std::memory_order_acquire) != std::this_thread::get_id()) - return SCE_KERNEL_ERROR_EPERM; - } - if (m->_type == MutexType::Recursive) { - if (--m->_count == 0) m->_owner.store(std::thread::id{}, std::memory_order_relaxed); - m->_rmtx.unlock(); - return SCE_OK; - } - m->_owner.store(std::thread::id{}, std::memory_order_relaxed); - m->_mtx.unlock(); - return SCE_OK; -} - -int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) { - if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexTimedlock: null mutex"); - auto* m = *mutex; - const auto tid = std::this_thread::get_id(); - const std::uint64_t deadline = TimedWait::DeadlineNanos(usec); - if (m->_type == MutexType::Recursive) { - const bool locked = TimedWait::AcquireUntil(deadline, [&] { return m->_rmtx.try_lock(); }, [&](std::uint64_t micros) { return m->_rmtx.try_lock_for(std::chrono::microseconds(micros)); }); - if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT; - m->_owner.store(tid, std::memory_order_relaxed); - ++m->_count; - return SCE_OK; - } - if (m->_type == MutexType::ErrorCheck) { - if (m->_owner.load(std::memory_order_acquire) == tid) return SCE_KERNEL_ERROR_EDEADLK; - } - const bool locked = TimedWait::AcquireUntil(deadline, [&] { return m->_mtx.try_lock(); }, [&](std::uint64_t micros) { return m->_mtx.try_lock_for(std::chrono::microseconds(micros)); }); - if (!locked) return SCE_KERNEL_ERROR_ETIMEDOUT; - m->_owner.store(tid, std::memory_order_relaxed); - return SCE_OK; -} - -int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) { - if (!mutex || !*mutex) throw std::runtime_error("scePthreadMutexTrylock: null mutex"); - auto* m = *mutex; - const auto tid = std::this_thread::get_id(); - if (m->_type == MutexType::Recursive) { - if (!m->_rmtx.try_lock()) return SCE_KERNEL_ERROR_EBUSY; - m->_owner.store(tid, std::memory_order_relaxed); - ++m->_count; - return SCE_OK; - } - if (!m->_mtx.try_lock()) return SCE_KERNEL_ERROR_EBUSY; - m->_owner.store(tid, std::memory_order_relaxed); - return SCE_OK; + return 0; } int APS5_VABI scePthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol) { - (void)attr; - (void)protocol; - NotImplemented_nid_no_patch(__func__); - return 0; + if (!attr || !*attr) + throw std::invalid_argument("Mutex attributes are not initialized"); + if (protocol != 0) + throw std::invalid_argument("Mutex priority inheritance and protection are unsupported"); + return 0; +} + +int APS5_VABI scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char*) { + if (!mutex) + throw std::invalid_argument("Mutex pointer is null"); + if (attr && !*attr) + throw std::invalid_argument("Mutex attributes are not initialized"); + auto replacement = std::make_unique(); + if (attr) + replacement->_type = (*attr)->type; + std::lock_guard lock(initializationMutex); + *mutex = replacement.release(); + return 0; +} + +int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex) { + if (!mutex) + throw std::invalid_argument("Mutex pointer is null"); + std::lock_guard lock(initializationMutex); + if (*mutex == destroyedMutex()) + throw std::runtime_error("Mutex has already been destroyed"); + if (reinterpret_cast(*mutex) == 1) + throw std::runtime_error("Adaptive mutex initializer is unsupported"); + if (*mutex && (*mutex)->_owner.load(std::memory_order_acquire) != std::thread::id{}) + throw std::runtime_error("Cannot destroy a locked mutex"); + delete *mutex; + *mutex = destroyedMutex(); + return 0; +} + +int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex) { + return acquireMutex(resolveMutex(mutex, true), [](auto& native) { native.lock(); return true; }, 0, false); +} + +int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex) { + auto* current = resolveMutex(mutex, false); + if (current->_owner.load(std::memory_order_acquire) != std::this_thread::get_id()) + return sceNotPermitted; + if (current->_type == MutexType::Recursive) { + if (--current->_count == 0) + current->_owner.store(std::thread::id{}, std::memory_order_release); + current->_rmtx.unlock(); + } else { + current->_owner.store(std::thread::id{}, std::memory_order_release); + current->_mtx.unlock(); + } + return 0; +} + +int APS5_VABI scePthreadMutexTimedlock(PthreadMutex* mutex, KernelUseconds usec) { + const auto deadline = TimedWait::DeadlineNanos(usec); + return acquireMutex(resolveMutex(mutex, true), [=](auto& native) { + return TimedWait::AcquireUntil(deadline, [&] { return native.try_lock(); }, [&](std::uint64_t micros) { return native.try_lock_for(std::chrono::microseconds(micros)); }); + }, sceTimedOut, false); +} + +int APS5_VABI scePthreadMutexTrylock(PthreadMutex* mutex) { + return acquireMutex(resolveMutex(mutex, true), [](auto& native) { return native.try_lock(); }, sceBusy, true); } } diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index 9b4bb1713..ff81a2cc9 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -1,4 +1,5 @@ #include "../include/Pthread.hpp" +#include "../include/Rwlock.hpp" #include "prx/libc/include/General.hpp" #include "prx/libkernel/Time/include/TimedWait.hpp" #include diff --git a/core/libs/prx/libkernel/Pthread/src/Thread.cpp b/core/libs/prx/libkernel/Pthread/src/Thread.cpp index a7cfe767e..b2a884a47 100644 --- a/core/libs/prx/libkernel/Pthread/src/Thread.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Thread.cpp @@ -1,4 +1,5 @@ #include "../include/Pthread.hpp" +#include "../include/ThreadLifecycle.hpp" #include "prx/libc/include/General.hpp" #include "prx/libc/include/CpuTopology.hpp" #include @@ -35,7 +36,44 @@ struct ThreadArgs { PthreadPrivate* self; }; -static void FinishThread(PthreadPrivate* self, void* retval) { +static std::atomic threadDtors{nullptr}; +static std::atomic threadAtexitCount{nullptr}; +static std::atomic threadAtexitReport{nullptr}; +static thread_local PthreadPrivate* currentThread = nullptr; +static thread_local bool threadFinishing = false; + +void ThreadLifecycle::SetThreadDtors(thread_dtors_func_t callback) { + if (!callback) + throw std::runtime_error("Thread destructor callback is null"); + thread_dtors_func_t expected = nullptr; + if (!threadDtors.compare_exchange_strong(expected, callback)) + throw std::runtime_error("Thread destructor callback is already registered"); +} + +void ThreadLifecycle::SetThreadAtexitCount(get_thread_atexit_count_func_t callback) { + if (!callback) + throw std::runtime_error("Thread atexit count callback is null"); + get_thread_atexit_count_func_t expected = nullptr; + if (!threadAtexitCount.compare_exchange_strong(expected, callback)) + throw std::runtime_error("Thread atexit count callback is already registered"); +} + +void ThreadLifecycle::SetThreadAtexitReport(thread_atexit_report_func_t callback) { + if (!callback) + throw std::runtime_error("Thread atexit report callback is null"); + thread_atexit_report_func_t expected = nullptr; + if (!threadAtexitReport.compare_exchange_strong(expected, callback)) + throw std::runtime_error("Thread atexit report callback is already registered"); +} + +static void finishThread(PthreadPrivate* self, void* retval) { + if (!self || self != currentThread) + throw std::runtime_error("Finishing an unregistered guest thread"); + if (threadFinishing) + throw std::runtime_error("Guest thread is already finishing"); + threadFinishing = true; + if (const auto callback = threadDtors.load()) + callback(); { std::unique_lock lk(self->_join_mtx); self->_retval = retval; @@ -49,13 +87,13 @@ static void RunThread(std::unique_ptr args) { const auto entry = args->entry; void* arg = args->arg; PthreadPrivate* self = args->self; + currentThread = self; args.reset(); - FinishThread(self, entry(arg)); + finishThread(self, entry(arg)); + currentThread = nullptr; } #ifdef _WIN32 -static thread_local PthreadPrivate* currentThread = nullptr; - static void ReleaseThread(PthreadPrivate* thread) { if (thread->references.fetch_sub(1, std::memory_order_acq_rel) != 1) return; @@ -229,18 +267,18 @@ int APS5_VABI scePthreadDetach(Pthread thread) { } void APS5_VABI scePthreadExit(void* retval) { -#ifdef _WIN32 if (!currentThread) throw std::runtime_error("scePthreadExit: current thread is not registered"); auto* self = currentThread; - FinishThread(self, retval); + finishThread(self, retval); currentThread = nullptr; +#ifdef _WIN32 ReleaseThread(self); _endthreadex(0); #else pthread_exit(retval); #endif - __builtin_unreachable(); + throw std::runtime_error("Native thread exit returned"); } Pthread APS5_VABI scePthreadSelf() { @@ -256,10 +294,8 @@ Pthread APS5_VABI scePthreadSelf() { adopted->references.store(1, std::memory_order_relaxed); currentThread = adopted.release(); } - return currentThread; -#else - return nullptr; #endif + return currentThread; } void APS5_VABI scePthreadYield() { @@ -361,3 +397,12 @@ int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)) { } } + +extern "C" { + +void APS5_VABI __pthread_cxa_finalize_nid_postfix(void* argument) { + (void)argument; + NotImplemented_nid_no_patch(__func__); +} + +} diff --git a/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp b/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp new file mode 100644 index 000000000..ee0899030 --- /dev/null +++ b/core/libs/prx/libkernel/Socket/include/SocketRuntime.hpp @@ -0,0 +1,5 @@ +#pragma once +namespace GuestSockets { +constexpr int FirstDescriptor = 0x10000000; +int Close(int descriptor); +} diff --git a/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp b/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp new file mode 100644 index 000000000..b10fb9633 --- /dev/null +++ b/core/libs/prx/libkernel/Socket/src/AddressConversion.cpp @@ -0,0 +1,47 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#endif +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); + +extern "C" { +int APS5_VABI __inet_pton_nid_postfix(int family, const char* text, void* destination) { + if (family != 2 && family != 28) { *__error_nid_postfix() = 47; return -1; } + if (!text || !destination) { *__error_nid_postfix() = 14; return -1; } + unsigned char address[16]{}; + const int nativeFamily = family == 2 ? AF_INET : AF_INET6; +#ifdef _WIN32 + const int result = InetPtonA(nativeFamily, text, address); +#else + const int result = ::inet_pton(nativeFamily, text, address); +#endif + if (result == 1) std::memcpy(destination, address, family == 2 ? 4 : 16); + else if (result == -1) *__error_nid_postfix() = 22; + return result; +} + +const char* APS5_VABI __inet_ntop_nid_postfix(int family, const void* source, + char* destination, std::uint32_t capacity) { + if (family != 2 && family != 28) { *__error_nid_postfix() = 47; return nullptr; } + if (!source || !destination) { *__error_nid_postfix() = 14; return nullptr; } + char text[INET6_ADDRSTRLEN]{}; + const int nativeFamily = family == 2 ? AF_INET : AF_INET6; +#ifdef _WIN32 + const auto* result = InetNtopA(nativeFamily, const_cast(source), text, sizeof(text)); +#else + const auto* result = ::inet_ntop(nativeFamily, source, text, sizeof(text)); +#endif + if (!result) { *__error_nid_postfix() = 22; return nullptr; } + const auto size = std::strlen(text) + 1; + if (size > capacity) { *__error_nid_postfix() = 28; return nullptr; } + std::memcpy(destination, text, size); + return destination; +} +} diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 40ad59a0a..89b612603 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -13,13 +13,6 @@ int APS5_VABI accept_nid_postfix(int s, void* addr, uint32_t* addrlen) { return 0; } -int APS5_VABI bind_nid_postfix(int s, const void* addr, uint32_t addrlen) { - (void)s; - (void)addr; - (void)addrlen; - NotImplemented_nid_no_patch(__func__); - return 0; -} int APS5_VABI connect_nid_postfix(int s, const void* addr, uint32_t addrlen) { (void)s; @@ -36,41 +29,9 @@ int APS5_VABI listen_nid_postfix(int s, int backlog) { return 0; } -int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { - (void)family; - (void)type; - (void)protocol; - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI getsockname_nid_postfix(int s, void* addr, uint32_t* addrlen) { - (void)s; - (void)addr; - (void)addrlen; - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI getsockopt_nid_postfix(int s, int level, int optname, void* optval, uint32_t* optlen) { - (void)s; - (void)level; - (void)optname; - (void)optval; - (void)optlen; - NotImplemented_nid_no_patch(__func__); - return 0; -} -int APS5_VABI setsockopt_nid_postfix(int s, int level, int optname, const void* optval, uint32_t optlen) { - (void)s; - (void)level; - (void)optname; - (void)optval; - (void)optlen; - NotImplemented_nid_no_patch(__func__); - return 0; -} int64_t APS5_VABI send_nid_postfix(int s, const void* buf, uint64_t len, int flags) { (void)s; @@ -81,16 +42,6 @@ int64_t APS5_VABI send_nid_postfix(int s, const void* buf, uint64_t len, int fla return 0; } -int64_t APS5_VABI sendto_nid_postfix(int s, const void* buf, uint64_t len, int flags, const void* addr, uint32_t addrlen) { - (void)s; - (void)buf; - (void)len; - (void)flags; - (void)addr; - (void)addrlen; - NotImplemented_nid_no_patch(__func__); - return 0; -} int64_t APS5_VABI recv_nid_postfix(int s, void* buf, uint64_t len, int flags) { (void)s; @@ -101,16 +52,6 @@ int64_t APS5_VABI recv_nid_postfix(int s, void* buf, uint64_t len, int flags) { return 0; } -int64_t APS5_VABI recvfrom_nid_postfix(int s, void* buf, uint64_t len, int flags, void* addr, uint32_t* addrlen) { - (void)s; - (void)buf; - (void)len; - (void)flags; - (void)addr; - (void)addrlen; - NotImplemented_nid_no_patch(__func__); - return 0; -} const char* APS5_VABI inet_ntop_nid_postfix(int af, const void* src, char* dst, uint32_t size) { (void)af; diff --git a/core/libs/prx/libkernel/Socket/src/Udp.cpp b/core/libs/prx/libkernel/Socket/src/Udp.cpp new file mode 100644 index 000000000..386c95ed5 --- /dev/null +++ b/core/libs/prx/libkernel/Socket/src/Udp.cpp @@ -0,0 +1,287 @@ +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#include +#include +#include +#include +#endif +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/Socket/include/SocketRuntime.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); +namespace { +#ifdef _WIN32 +using NativeSocket = SOCKET; +constexpr auto Invalid = INVALID_SOCKET; +#else +using NativeSocket = int; +constexpr auto Invalid = -1; +#endif +int Fail(int error) { *__error_nid_postfix() = error; return -1; } +int NativeError() { +#ifdef _WIN32 + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: return 35; + case WSAEADDRINUSE: return 48; + case WSAEADDRNOTAVAIL: return 49; + case WSAEACCES: return 13; + case WSAEMSGSIZE: return 40; + case WSAENETUNREACH: return 51; + case WSAECONNRESET: return 54; + case WSAENOBUFS: return 55; + case WSAETIMEDOUT: return 60; + case WSAECONNREFUSED: return 61; + case WSAEINTR: return 4; + case WSAEINVAL: return 22; + default: return 5; + } +#else + switch (errno) { + case EAGAIN: return 35; + case EADDRINUSE: return 48; + case EADDRNOTAVAIL: return 49; + case EMSGSIZE: return 40; + case ENETUNREACH: return 51; + case ECONNREFUSED: return 61; + default: return 5; + } +#endif +} +struct Socket { + NativeSocket value; + std::mutex modeMutex; + bool nonblocking = false; + explicit Socket(NativeSocket value) : value(value) {} + ~Socket() { + if (value == Invalid) return; +#ifdef _WIN32 + closesocket(value); +#else + ::close(value); +#endif + } +}; +std::mutex socketsMutex; +std::map> sockets; +int nextDescriptor = GuestSockets::FirstDescriptor; +std::shared_ptr Lookup(int descriptor) { + std::lock_guard lock(socketsMutex); + const auto found = sockets.find(descriptor); + if (found != sockets.end()) return found->second; + Fail(9); + return {}; +} +int Option(int guest) { + switch (guest) { + case 0x4: return SO_REUSEADDR; + case 0x20: return SO_BROADCAST; + case 0x1001: return SO_SNDBUF; + case 0x1002: return SO_RCVBUF; + default: return -1; + } +} +bool Address(const void* input, std::uint32_t length, sockaddr_storage& native, socklen_t& size) { + if (!input || length < 2) { Fail(14); return false; } + const auto* bytes = static_cast(input); + if (bytes[1] == 2 && length >= 16 && bytes[0] == 16) { + auto& v4 = reinterpret_cast(native); + v4.sin_family = AF_INET; + std::memcpy(&v4.sin_port, bytes + 2, 2); + std::memcpy(&v4.sin_addr, bytes + 4, 4); + size = sizeof(v4); + return true; + } + if (bytes[1] == 28 && length >= 28 && bytes[0] == 28) { + auto& v6 = reinterpret_cast(native); + v6.sin6_family = AF_INET6; + std::memcpy(&v6.sin6_port, bytes + 2, 2); + std::memcpy(&v6.sin6_flowinfo, bytes + 4, 4); + std::memcpy(&v6.sin6_addr, bytes + 8, 16); + std::memcpy(&v6.sin6_scope_id, bytes + 24, 4); + size = sizeof(v6); + return true; + } + Fail(47); + return false; +} +void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* length) { + unsigned char bytes[28]{}; + bytes[0] = native.ss_family == AF_INET ? 16 : 28; + bytes[1] = native.ss_family == AF_INET ? 2 : 28; + if (native.ss_family == AF_INET) { + const auto& v4 = reinterpret_cast(native); + std::memcpy(bytes + 2, &v4.sin_port, 2); + std::memcpy(bytes + 4, &v4.sin_addr, 4); + } else { + const auto& v6 = reinterpret_cast(native); + std::memcpy(bytes + 2, &v6.sin6_port, 2); + std::memcpy(bytes + 4, &v6.sin6_flowinfo, 4); + std::memcpy(bytes + 8, &v6.sin6_addr, 16); + std::memcpy(bytes + 24, &v6.sin6_scope_id, 4); + } + std::memcpy(output, bytes, std::min(*length, bytes[0])); + *length = bytes[0]; +} +} + +int GuestSockets::Close(int descriptor) { + std::lock_guard lock(socketsMutex); + return sockets.erase(descriptor) ? 0 : Fail(9); +} + +extern "C" { +int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + std::lock_guard lock(socket->modeMutex); + if (command == 3) return 2 | (socket->nonblocking ? 4 : 0); // F_GETFL, O_RDWR + if (command != 4) return Fail(22); +#ifdef _WIN32 + __builtin_sysv_va_list arguments; + __builtin_sysv_va_start(arguments, command); + const int flags = __builtin_va_arg(arguments, int); + __builtin_sysv_va_end(arguments); +#else + std::va_list arguments; + va_start(arguments, command); + const int flags = va_arg(arguments, int); + va_end(arguments); +#endif + if ((flags & ~7) != 0) return Fail(45); +#ifdef _WIN32 + unsigned long enabled = (flags & 4) != 0; + if (ioctlsocket(socket->value, FIONBIO, &enabled)) return Fail(NativeError()); +#else + int enabled = (flags & 4) != 0; + if (::ioctl(socket->value, FIONBIO, &enabled)) return Fail(NativeError()); +#endif + socket->nonblocking = enabled != 0; + return 0; +} +int APS5_VABI setsockopt_nid_postfix(int descriptor, int level, int option, + const void* value, std::uint32_t length) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + const int nativeOption = Option(option); + if (level != 0xffff || nativeOption == -1) return Fail(42); + if (!value || length != sizeof(int)) return Fail(22); + return ::setsockopt(socket->value, SOL_SOCKET, nativeOption, + static_cast(value), sizeof(int)) ? Fail(NativeError()) : 0; +} +int APS5_VABI getsockopt_nid_postfix(int descriptor, int level, int option, + void* value, std::uint32_t* length) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + const int nativeOption = Option(option); + if (level != 0xffff || nativeOption == -1) return Fail(42); + if (!value || !length || *length < sizeof(int)) return Fail(22); + int result = 0; + socklen_t size = sizeof(result); + if (::getsockopt(socket->value, SOL_SOCKET, nativeOption, reinterpret_cast(&result), &size)) + return Fail(NativeError()); + std::memcpy(value, &result, sizeof(result)); + *length = sizeof(result); + return 0; +} +int APS5_VABI socket_nid_postfix(int family, int type, int protocol) { + if (family != 2 && family != 28) return Fail(47); + if (type != 2 || (protocol != 0 && protocol != 17)) return Fail(43); +#ifdef _WIN32 + static const int startup = [] { WSADATA data{}; return WSAStartup(MAKEWORD(2, 2), &data); }(); + if (startup) return Fail(5); +#endif + const auto native = ::socket(family == 2 ? AF_INET : AF_INET6, SOCK_DGRAM, protocol); + if (native == Invalid) return Fail(NativeError()); + Socket guard(native); + try { + auto socket = std::make_shared(native); + guard.value = Invalid; + std::lock_guard lock(socketsMutex); + if (nextDescriptor == INT_MAX) return Fail(24); + const int descriptor = nextDescriptor++; + sockets.emplace(descriptor, std::move(socket)); + return descriptor; + } catch (const std::bad_alloc&) { + return Fail(12); + } +} +int APS5_VABI bind_nid_postfix(int descriptor, const void* address, std::uint32_t length) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + sockaddr_storage native{}; + socklen_t size; + if (!Address(address, length, native, size)) return -1; + return ::bind(socket->value, reinterpret_cast(&native), size) ? Fail(NativeError()) : 0; +} +int APS5_VABI getsockname_nid_postfix(int descriptor, void* address, std::uint32_t* length) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + if (!address || !length) return Fail(14); + sockaddr_storage native{}; + socklen_t size = sizeof(native); + if (::getsockname(socket->value, reinterpret_cast(&native), &size)) return Fail(NativeError()); + GuestAddress(native, address, length); + return 0; +} +int APS5_VABI ioctl_nid_postfix(int descriptor, std::uint64_t request, void* argument) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + if (!argument) return Fail(14); + if (request != 0x8004667e && request != 0x4004667f) return Fail(25); + std::lock_guard lock(socket->modeMutex); + unsigned long value = 0; + if (request == 0x8004667e) value = *static_cast(argument) != 0; +#ifdef _WIN32 + const auto result = ioctlsocket(socket->value, request == 0x8004667e ? FIONBIO : FIONREAD, &value); +#else + int nativeValue = static_cast(value); + const auto result = ::ioctl(socket->value, request == 0x8004667e ? FIONBIO : FIONREAD, &nativeValue); + value = static_cast(nativeValue); +#endif + if (result) return Fail(NativeError()); + if (request == 0x8004667e) socket->nonblocking = value != 0; + if (request == 0x4004667f) *static_cast(argument) = static_cast(value); + return 0; +} +std::int64_t APS5_VABI sendto_nid_postfix(int descriptor, const void* buffer, std::uint64_t length, + int flags, const void* address, std::uint32_t addressLength) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + if (flags != 0) return Fail(45); + if (length > INT_MAX) return Fail(40); + if (!buffer && length) return Fail(14); + sockaddr_storage native{}; + socklen_t size; + if (!Address(address, addressLength, native, size)) return -1; + const auto result = ::sendto(socket->value, static_cast(buffer), static_cast(length), 0, + reinterpret_cast(&native), size); + return result < 0 ? Fail(NativeError()) : result; +} +std::int64_t APS5_VABI recvfrom_nid_postfix(int descriptor, void* buffer, std::uint64_t length, + int flags, void* address, std::uint32_t* addressLength) { + const auto socket = Lookup(descriptor); + if (!socket) return -1; + if ((flags & ~2) != 0) return Fail(45); + if (length > INT_MAX) return Fail(40); + if ((!buffer && length) || (address && !addressLength)) return Fail(14); + sockaddr_storage native{}; + socklen_t size = sizeof(native); + const auto result = ::recvfrom(socket->value, static_cast(buffer), static_cast(length), + flags & 2 ? MSG_PEEK : 0, reinterpret_cast(&native), &size); + if (result < 0) return Fail(NativeError()); + if (address) GuestAddress(native, address, addressLength); + return result; +} +} diff --git a/core/libs/prx/libkernel/System/src/Process.cpp b/core/libs/prx/libkernel/System/src/Process.cpp index 3b453878d..7755b1543 100644 --- a/core/libs/prx/libkernel/System/src/Process.cpp +++ b/core/libs/prx/libkernel/System/src/Process.cpp @@ -1,91 +1,320 @@ -#include -#include +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#else +#include +#include +#endif + #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libkernel/DirectMemory/DirectMemory.hpp" +#include +#include +#include +#include +#include +#include #include +#include +#include +#include #include #include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif -static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast(0x80020016); +namespace { + +constexpr int sceInvalidArgument = static_cast(0x80020016u); + +std::atomic gpoBits{0}; +constexpr std::array openPsId{'A', 'n', 'y', 'P', 'S', '5', 'O', 'p', 'e', 'n', 'P', 's', 'I', 'd', 0, 1}; + +class ProcessArguments { +public: + ProcessArguments() { +#ifdef _WIN32 + std::array path{}; + const auto size = GetModuleFileNameA(nullptr, path.data(), static_cast(path.size())); + if (size == 0) + throw std::system_error(GetLastError(), std::system_category(), "Reading executable path"); + if (size >= path.size()) + throw std::runtime_error("Executable path exceeds the guest argument buffer"); + arguments.emplace_back(path.data(), size); +#else + std::ifstream stream("/proc/self/cmdline", std::ios::binary); + if (!stream) + throw std::runtime_error("Cannot read process arguments"); + std::string argument; + while (std::getline(stream, argument, '\0')) { + if (stream.eof()) + throw std::runtime_error("Unterminated process argument"); + arguments.push_back(argument); + } + if (stream.bad() || !stream.eof()) + throw std::runtime_error("Reading process arguments failed"); +#endif + if (arguments.empty() || arguments.size() > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Invalid process argument count"); + for (const auto& argument : arguments) + pointers.push_back(argument.c_str()); + pointers.push_back(nullptr); + } + + int GetCount() const { return static_cast(arguments.size()); } + const char** GetValues() { return pointers.data(); } + +private: + std::vector arguments; + std::vector pointers; +}; + +ProcessArguments& getProcessArguments() { + static ProcessArguments arguments; + return arguments; +} + +void validateSchedulingPolicy(int policy) { + if (policy != 1 && policy != 3) + throw std::invalid_argument("Unsupported guest scheduling policy"); +} + +#ifdef _WIN32 +void syncVolumes() { + std::array name{}; + const auto first = FindFirstVolumeW(name.data(), static_cast(name.size())); + if (first == INVALID_HANDLE_VALUE) + throw std::system_error(GetLastError(), std::system_category(), "Enumerating volumes for sync"); + const std::unique_ptr search(first, &FindVolumeClose); + for (;;) { + DWORD flags = 0; + if (!GetVolumeInformationW(name.data(), nullptr, 0, nullptr, nullptr, &flags, nullptr, 0)) + throw std::system_error(GetLastError(), std::system_category(), "Reading volume properties for sync"); + if ((flags & FILE_READ_ONLY_VOLUME) == 0) { + std::wstring path(name.data()); + if (path.empty() || path.back() != L'\\') + throw std::runtime_error("Invalid volume path for sync"); + path.pop_back(); + const auto native = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); + if (native == INVALID_HANDLE_VALUE) + throw std::system_error(GetLastError(), std::system_category(), "Opening volume for sync"); + const std::unique_ptr volume(native, &CloseHandle); + if (!FlushFileBuffers(volume.get())) + throw std::system_error(GetLastError(), std::system_category(), "Flushing volume"); + } + if (FindNextVolumeW(search.get(), name.data(), static_cast(name.size()))) + continue; + const auto error = GetLastError(); + if (error != ERROR_NO_MORE_FILES) + throw std::system_error(error, std::system_category(), "Enumerating volumes for sync"); + break; + } +} +#endif + +} + +struct GuestResourceUsage { + KernelTimeval ru_utime; + KernelTimeval ru_stime; + std::int64_t ru_maxrss; + std::int64_t ru_ixrss; + std::int64_t ru_idrss; + std::int64_t ru_isrss; + std::int64_t ru_minflt; + std::int64_t ru_majflt; + std::int64_t ru_nswap; + std::int64_t ru_inblock; + std::int64_t ru_oublock; + std::int64_t ru_msgsnd; + std::int64_t ru_msgrcv; + std::int64_t ru_nsignals; + std::int64_t ru_nvcsw; + std::int64_t ru_nivcsw; +}; extern "C" { +// unknown data +const char* __progname_nid_postfix = "eboot.bin"; + int APS5_VABI getargc_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return getProcessArguments().GetCount(); } const char** APS5_VABI getargv_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return nullptr; + return getProcessArguments().GetValues(); } int APS5_VABI getpagesize_nid_postfix(void) { - return 0x4000; + return PS5_PAGE_SIZE; } int APS5_VABI getpid_nid_postfix(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +#ifdef _WIN32 + const auto pid = GetCurrentProcessId(); +#else + const auto pid = ::getpid(); +#endif + if (pid == 0 || static_cast(pid) > static_cast(std::numeric_limits::max())) + throw std::runtime_error("Process identifier is outside the guest range"); + return static_cast(pid); } void APS5_VABI exit_nid_postfix(int code) { - (void)code; - NotImplemented_nid_no_patch(__func__); + LibcExit_nid_no_patch(code); +} + +[[noreturn]] void APS5_VABI _exit_nid_postfix(int status) { + std::_Exit(status); } int APS5_VABI sceKernelGetCurrentCpu(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +#ifdef _WIN32 + PROCESSOR_NUMBER processor{}; + GetCurrentProcessorNumberEx(&processor); + unsigned index = processor.Number; + for (WORD group = 0; group < processor.Group; ++group) { + const auto count = GetActiveProcessorCount(group); + if (count == 0) + throw std::system_error(GetLastError(), std::system_category(), "Reading processor group size"); + index += count; + } + return static_cast(index); +#else + const int cpu = ::sched_getcpu(); + if (cpu < 0) + throw std::system_error(errno, std::generic_category(), "Reading current processor"); + return cpu; +#endif } -uint64_t APS5_VABI sceKernelGetGPI(void) { +std::uint64_t APS5_VABI sceKernelGetGPI(void) { + return gpoBits.load(std::memory_order_relaxed); +} + +void APS5_VABI sceKernelSetGPO(std::uint32_t bits) { + gpoBits.store(bits, std::memory_order_relaxed); +} + +int APS5_VABI sceKernelGetOpenPsId(void* openPsIdOutput) { + if (!openPsIdOutput) + throw std::invalid_argument("sceKernelGetOpenPsId: null output"); + std::memcpy(openPsIdOutput, openPsId.data(), openPsId.size()); return 0; } -void APS5_VABI sceKernelSetGPO(uint32_t bits) { - (void)bits; -} - -int APS5_VABI sceKernelGetOpenPsId(void* open_ps_id) { - (void)open_ps_id; - NotImplemented_nid_no_patch(__func__); - return 0; -} - void* APS5_VABI sceKernelGetProcParam(void) { - NotImplemented_nid_no_patch(__func__); - return nullptr; + return const_cast(ApplicationProcessParameters_nid_no_patch()); } -int APS5_VABI sceKernelUuidCreate(uint8_t* uuid) { - if (!uuid) return SCE_KERNEL_ERROR_EINVAL; - static std::mutex lock; - static std::mt19937_64 generator{std::random_device{}()}; - std::lock_guard guard(lock); - for (int index = 0; index < 16; index += 8) { - const uint64_t bits = generator(); - std::memcpy(uuid + index, &bits, 8); - } - uuid[6] = static_cast((uuid[6] & 0x0F) | 0x40); - uuid[8] = static_cast((uuid[8] & 0x3F) | 0x80); +int APS5_VABI sceKernelUuidCreate(std::uint32_t* uuid) { + if (!uuid) return sceInvalidArgument; + static thread_local std::random_device device; + std::uniform_int_distribution distribution; + std::array value; + for (auto& word : value) + word = distribution(device); + value[1] = (value[1] & 0x0fffffffu) | 0x40000000u; + value[2] = (value[2] & 0xffffff3fu) | 0x80u; + std::memcpy(uuid, value.data(), sizeof(value)); return 0; } void APS5_VABI sceKernelSync(void) { - NotImplemented_nid_no_patch(__func__); +#ifdef _WIN32 + syncVolumes(); +#else + ::sync(); +#endif } int APS5_VABI sched_get_priority_max_nid_postfix(int policy) { - (void)policy; - NotImplemented_nid_no_patch(__func__); - return 0; + validateSchedulingPolicy(policy); + return 256; } int APS5_VABI sched_get_priority_min_nid_postfix(int policy) { - (void)policy; - NotImplemented_nid_no_patch(__func__); - return 0; + validateSchedulingPolicy(policy); + return 767; +} + +int APS5_VABI getrusage_nid_postfix(int who, GuestResourceUsage* usage) { + if (usage == nullptr) + throw std::invalid_argument("getrusage: usage is null"); + if (who != 0 && who != 1) + throw std::invalid_argument("getrusage: unsupported who"); +#ifdef _WIN32 + FILETIME creation{}; + FILETIME exitTime{}; + FILETIME kernel{}; + FILETIME user{}; + if (who == 0) { + if (!GetProcessTimes(GetCurrentProcess(), &creation, &exitTime, &kernel, &user)) + throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetProcessTimes failed"); + } else { + if (!GetThreadTimes(GetCurrentThread(), &creation, &exitTime, &kernel, &user)) + throw std::system_error(GetLastError(), std::system_category(), "getrusage: GetThreadTimes failed"); + } + const auto toMicros = [](const FILETIME& time) { + return ((static_cast(time.dwHighDateTime) << 32) | time.dwLowDateTime) / 10ULL; + }; + const auto userMicros = toMicros(user); + const auto kernelMicros = toMicros(kernel); + usage->ru_utime.tv_sec = static_cast(userMicros / 1000000ULL); + usage->ru_utime.tv_usec = static_cast(userMicros % 1000000ULL); + usage->ru_stime.tv_sec = static_cast(kernelMicros / 1000000ULL); + usage->ru_stime.tv_usec = static_cast(kernelMicros % 1000000ULL); + usage->ru_maxrss = 0; + usage->ru_ixrss = 0; + usage->ru_idrss = 0; + usage->ru_isrss = 0; + usage->ru_minflt = 0; + usage->ru_majflt = 0; + usage->ru_nswap = 0; + usage->ru_inblock = 0; + usage->ru_oublock = 0; + usage->ru_msgsnd = 0; + usage->ru_msgrcv = 0; + usage->ru_nsignals = 0; + usage->ru_nvcsw = 0; + usage->ru_nivcsw = 0; +#else + if (who == 1) + throw std::invalid_argument("getrusage: RUSAGE_THREAD is not supported on this platform"); + struct rusage native{}; + if (::getrusage(RUSAGE_SELF, &native) != 0) + throw std::system_error(errno, std::generic_category(), "getrusage: getrusage failed"); + usage->ru_utime.tv_sec = static_cast(native.ru_utime.tv_sec); + usage->ru_utime.tv_usec = static_cast(native.ru_utime.tv_usec); + usage->ru_stime.tv_sec = static_cast(native.ru_stime.tv_sec); + usage->ru_stime.tv_usec = static_cast(native.ru_stime.tv_usec); + usage->ru_maxrss = static_cast(native.ru_maxrss); + usage->ru_ixrss = static_cast(native.ru_ixrss); + usage->ru_idrss = static_cast(native.ru_idrss); + usage->ru_isrss = static_cast(native.ru_isrss); + usage->ru_minflt = static_cast(native.ru_minflt); + usage->ru_majflt = static_cast(native.ru_majflt); + usage->ru_nswap = static_cast(native.ru_nswap); + usage->ru_inblock = static_cast(native.ru_inblock); + usage->ru_oublock = static_cast(native.ru_oublock); + usage->ru_msgsnd = static_cast(native.ru_msgsnd); + usage->ru_msgrcv = static_cast(native.ru_msgrcv); + usage->ru_nsignals = static_cast(native.ru_nsignals); + usage->ru_nvcsw = static_cast(native.ru_nvcsw); + usage->ru_nivcsw = static_cast(native.ru_nivcsw); +#endif + return 0; } int APS5_VABI sceKernelIsTrinityMode(void) { diff --git a/core/libs/prx/libkernel/System/src/Rtld.cpp b/core/libs/prx/libkernel/System/src/Rtld.cpp index e8b44eae9..7a2f27d81 100644 --- a/core/libs/prx/libkernel/System/src/Rtld.cpp +++ b/core/libs/prx/libkernel/System/src/Rtld.cpp @@ -1,13 +1,37 @@ #include #include +#include +#include +#include +#include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" -#include "prx/libc/include/ApplicationHeap.hpp" +#include "prx/libkernel/Pthread/include/ThreadLifecycle.hpp" + +namespace { + +std::mutex rtldHeapMutex; +std::array rtldHeapApi{}; + +void registerApplicationHeapApi(void* const* api) { + if (api == nullptr) + throw std::invalid_argument("RTLD application heap: null allocator API"); + std::array replacement; + std::memcpy(replacement.data(), api, sizeof(replacement)); + if (replacement[0] == nullptr || replacement[1] == nullptr) + throw std::invalid_argument("RTLD application heap: malloc and free are required"); + std::lock_guard lock(rtldHeapMutex); + if (rtldHeapApi[0] != nullptr && rtldHeapApi != replacement) + throw std::runtime_error("RTLD application heap: cannot replace an active allocator"); + rtldHeapApi = replacement; +} + +} extern "C" { void APS5_VABI sceKernelRtldSetApplicationHeapAPI(void* api[]) { - ApplicationHeapRegister_nid_no_patch(api); + registerApplicationHeapApi(api); } int APS5_VABI sceKernelRtldThreadAtexitDecrement(uint64_t* c) { @@ -23,18 +47,47 @@ int APS5_VABI sceKernelRtldThreadAtexitIncrement(uint64_t* c) { } void APS5_VABI sceKernelSetThreadAtexitCount(get_thread_atexit_count_func_t func) { - (void)func; - NotImplemented_nid_no_patch(__func__); + ThreadLifecycle::SetThreadAtexitCount(func); } void APS5_VABI sceKernelSetThreadAtexitReport(thread_atexit_report_func_t func) { - (void)func; - NotImplemented_nid_no_patch(__func__); + ThreadLifecycle::SetThreadAtexitReport(func); } void APS5_VABI sceKernelSetThreadDtors(thread_dtors_func_t dtors) { - (void)dtors; - NotImplemented_nid_no_patch(__func__); + ThreadLifecycle::SetThreadDtors(dtors); +} + +} + +extern "C" { + +void APS5_VABI _sceKernelRtldSetApplicationHeapAPI_nid_postfix(void* api[]) { + registerApplicationHeapApi(api); +} + +int APS5_VABI _sceKernelRtldThreadAtexitDecrement_nid_postfix(std::uint64_t* counter) { + (void)counter; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI _sceKernelRtldThreadAtexitIncrement_nid_postfix(std::uint64_t* counter) { + (void)counter; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +void APS5_VABI _sceKernelSetThreadAtexitCount_nid_postfix(get_thread_atexit_count_func_t callback) { + ThreadLifecycle::SetThreadAtexitCount(callback); +} + +void APS5_VABI _sceKernelSetThreadAtexitReport_nid_postfix(thread_atexit_report_func_t callback) { + ThreadLifecycle::SetThreadAtexitReport(callback); +} + +void APS5_VABI _sceKernelSetThreadDtors_nid_postfix(thread_dtors_func_t callback) { + ThreadLifecycle::SetThreadDtors(callback); } } diff --git a/core/libs/prx/libkernel/System/src/Sanitizer.cpp b/core/libs/prx/libkernel/System/src/Sanitizer.cpp index 452a93743..15f2d3f5f 100644 --- a/core/libs/prx/libkernel/System/src/Sanitizer.cpp +++ b/core/libs/prx/libkernel/System/src/Sanitizer.cpp @@ -1,23 +1,19 @@ -#include -#include #include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" extern "C" { int APS5_VABI sceKernelIsAddressSanitizerEnabled(void) { - NotImplemented_nid_no_patch(__func__); - return 0; + return 0; } MallocReplace* APS5_VABI sceKernelGetSanitizerMallocReplaceExternal(void) { - NotImplemented_nid_no_patch(__func__); - return nullptr; + static MallocReplace replacement{}; + return &replacement; } NewReplace* APS5_VABI sceKernelGetSanitizerNewReplaceExternal(void) { - NotImplemented_nid_no_patch(__func__); - return nullptr; + static NewReplace replacement{}; + return &replacement; } } diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp new file mode 100644 index 000000000..94a4e3be5 --- /dev/null +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -0,0 +1,97 @@ +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" int* APS5_VABI __error_nid_postfix(); +namespace { +using GuestHandler = void (APS5_VABI *)(int); +std::atomic handlers[32]{}; +static_assert(std::atomic::is_always_lock_free); +std::atomic blockedMask{0}; +static_assert(std::atomic::is_always_lock_free); +std::mutex registration; +int NativeSignal(int guest) { + switch (guest) { + case 2: return SIGINT; + case 4: return SIGILL; + case 6: return SIGABRT; + case 8: return SIGFPE; + case 11: return SIGSEGV; + case 15: return SIGTERM; + default: return 0; + } +} +void Dispatch(int native) { + int guest = 0; + for (int candidate : {2, 4, 6, 8, 11, 15}) + if (NativeSignal(candidate) == native) { guest = candidate; break; } + if (!guest) return; +#ifdef _WIN32 + // Preserve the guest's persistent registration across CRT delivery. + std::signal(native, Dispatch); +#endif + if ((blockedMask.load() & (1u << guest)) != 0) return; + const auto callback = handlers[guest].load(); + if (reinterpret_cast(callback) > 1) callback(guest); +} +} + +struct GuestSignalSet { + std::uint32_t bits[4]; +}; + +extern "C" { +GuestHandler APS5_VABI signal_nid_postfix(int guest, GuestHandler handler) { + const auto invalid = reinterpret_cast(static_cast(-1)); + const int native = NativeSignal(guest); + if (!native || handler == invalid) { *__error_nid_postfix() = 22; return invalid; } + std::lock_guard lock(registration); + const auto previous = handlers[guest].exchange(handler); + const auto address = reinterpret_cast(handler); + auto hostHandler = address == 0 ? SIG_DFL : address == 1 ? SIG_IGN : Dispatch; + if (std::signal(native, hostHandler) == SIG_ERR) { + handlers[guest].store(previous); + *__error_nid_postfix() = 22; + return invalid; + } + return previous; +} +int APS5_VABI raise_nid_postfix(int guest) { + const int native = NativeSignal(guest); + if (!native) { *__error_nid_postfix() = 22; return -1; } + const int result = std::raise(native); + if (result) *__error_nid_postfix() = 22; + return result ? -1 : 0; +} +int APS5_VABI _sigprocmask_nid_postfix(int how, const GuestSignalSet* set, GuestSignalSet* previousSet) { + std::lock_guard lock(registration); + if (previousSet != nullptr) { + previousSet->bits[0] = blockedMask.load(); + previousSet->bits[1] = 0; + previousSet->bits[2] = 0; + previousSet->bits[3] = 0; + } + if (set != nullptr) { + switch (how) { + case 1: blockedMask.fetch_or(set->bits[0]); break; + case 2: blockedMask.fetch_and(~set->bits[0]); break; + case 3: blockedMask.store(set->bits[0]); break; + default: throw std::invalid_argument("_sigprocmask: invalid how"); + } + } + return 0; +} +} + +extern "C" { + +int APS5_VABI _is_signal_return_nid_postfix(std::uint64_t programCounter) { + (void)programCounter; + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp new file mode 100644 index 000000000..b5a5e777b --- /dev/null +++ b/core/libs/prx/libkernel/System/src/SystemConfiguration.cpp @@ -0,0 +1,51 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libkernel/DirectMemory/DirectMemory.hpp" +#include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +extern "C" int* APS5_VABI __error_nid_postfix(); + +extern "C" { +// Guest long is 64 bits, including when the Windows host long is 32 bits. +std::int64_t APS5_VABI sysconf_nid_postfix(int name) { + const int saved = *__error_nid_postfix(); + std::int64_t result = -1; + switch (name) { + case 47: // _SC_PAGESIZE + result = PS5_PAGE_SIZE; + break; + case 57: // _SC_NPROCESSORS_CONF + case 58: // _SC_NPROCESSORS_ONLN +#ifdef _WIN32 + result = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); +#else + result = ::sysconf(name == 57 ? _SC_NPROCESSORS_CONF : _SC_NPROCESSORS_ONLN); +#endif + break; + case 121: { // _SC_PHYS_PAGES: report host physical capacity in guest pages +#ifdef _WIN32 + MEMORYSTATUSEX memory{}; + memory.dwLength = sizeof(memory); + if (GlobalMemoryStatusEx(&memory)) result = memory.ullTotalPhys / PS5_PAGE_SIZE; +#else + const auto pages = ::sysconf(_SC_PHYS_PAGES); + const auto pageSize = ::sysconf(_SC_PAGESIZE); + if (pages > 0 && pageSize > 0) + result = static_cast(pages) * pageSize / PS5_PAGE_SIZE; +#endif + break; + } + default: + *__error_nid_postfix() = 22; + return -1; + } + if (result <= 0) { *__error_nid_postfix() = 5; return -1; } + *__error_nid_postfix() = saved; + return result; +} +} diff --git a/core/libs/prx/libkernel/Time/Time.cpp b/core/libs/prx/libkernel/Time/Time.cpp index 93fd8c725..f379bcf0a 100644 --- a/core/libs/prx/libkernel/Time/Time.cpp +++ b/core/libs/prx/libkernel/Time/Time.cpp @@ -241,12 +241,16 @@ int APS5_VABI nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* return sceKernelNanosleep(rqtp, rmtp); } +int APS5_VABI _nanosleep_nid_postfix(const KernelTimespec* rqtp, KernelTimespec* rmtp) { + return sceKernelNanosleep(rqtp, rmtp); +} + int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { if (tp == nullptr) { APS5_INVALID_ARG_EX; } #ifdef _WIN32 - if (clockId == 0 || clockId == 9 || clockId == 10) { + if (clockId == 0 || clockId == 9) { FILETIME ft{}; GetSystemTimePreciseAsFileTime(&ft); std::uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; @@ -256,6 +260,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { tp->tv_nsec = static_cast(t % 1000000000ULL); return 0; } + if (clockId == 10 || clockId == 13) { + FILETIME ft{}; + GetSystemTimeAsFileTime(&ft); + std::uint64_t t = (static_cast(ft.dwHighDateTime) << 32) | ft.dwLowDateTime; + t -= 116444736000000000ULL; + t *= 100ULL; + tp->tv_sec = static_cast(t / 1000000000ULL); + tp->tv_nsec = static_cast(t % 1000000000ULL); + return 0; + } if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 11 || clockId == 12) { std::uint64_t nanos = GetMonotonicNanos(); tp->tv_sec = static_cast(nanos / 1000000000ULL); @@ -268,9 +282,16 @@ int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp) { switch (clockId) { case 0: case 9: - case 10: nativeId = CLOCK_REALTIME; break; + case 10: + case 13: +#ifdef CLOCK_REALTIME_COARSE + nativeId = CLOCK_REALTIME_COARSE; +#else + nativeId = CLOCK_REALTIME; +#endif + break; case 4: case 7: case 11: @@ -336,7 +357,7 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { res->tv_nsec = 100LL; return 0; } - if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12) { + if (clockId == 4 || clockId == 1 || clockId == 5 || clockId == 7 || clockId == 8 || clockId == 10 || clockId == 11 || clockId == 12 || clockId == 13) { static const std::uint64_t freq = [] { LARGE_INTEGER f{}; QueryPerformanceFrequency(&f); @@ -353,9 +374,16 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res) { switch (clockId) { case 0: case 9: - case 10: nativeId = CLOCK_REALTIME; break; + case 10: + case 13: +#ifdef CLOCK_REALTIME_COARSE + nativeId = CLOCK_REALTIME_COARSE; +#else + nativeId = CLOCK_REALTIME; +#endif + break; case 4: case 7: case 11: diff --git a/core/libs/tests/GuestAddressConversion.cpp b/core/libs/tests/GuestAddressConversion.cpp new file mode 100644 index 000000000..458ec1b13 --- /dev/null +++ b/core/libs/tests/GuestAddressConversion.cpp @@ -0,0 +1,34 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +int APS5_VABI __inet_pton_nid_postfix(int, const char*, void*); +const char* APS5_VABI __inet_ntop_nid_postfix(int, const void*, char*, std::uint32_t); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + for (int family : {2, 28}) { + const char* input = family == 2 ? "192.0.2.17" : "2001:db8::17"; + std::array address{}; + Require(__inet_pton_nid_postfix(family, input, address.data()) == 1); + char output[64]{}; + Require(__inet_ntop_nid_postfix(family, address.data(), output, sizeof(output)) == output); + Require(std::strcmp(input, output) == 0); + output[0] = 'x'; + Require(__inet_ntop_nid_postfix(family, address.data(), output, 1) == nullptr); + Require(*__error_nid_postfix() == 28 && output[0] == 'x'); + const auto original = address; + Require(__inet_pton_nid_postfix(family, "not-an-address", address.data()) == 0); + Require(address == original); + } + std::array address{}; + Require(__inet_pton_nid_postfix(10, "::1", address.data()) == -1); + Require(*__error_nid_postfix() == 47); // Linux AF_INET6 is not PS5 AF_INET6 + Require(__inet_pton_nid_postfix(2, "256.1.2.3", address.data()) == 0); + Require(__inet_pton_nid_postfix(28, "::ffff:192.0.2.17", address.data()) == 1); + Require(address[10] == 255 && address[11] == 255 && address[12] == 192 && address[15] == 17); +} diff --git a/core/libs/tests/GuestBufferReuse.cpp b/core/libs/tests/GuestBufferReuse.cpp new file mode 100644 index 000000000..06b629f32 --- /dev/null +++ b/core/libs/tests/GuestBufferReuse.cpp @@ -0,0 +1,26 @@ +#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp" +#include +static void Require(bool value) { if (!value) std::abort(); } +int main() { + BufferReuseTracker buffer; + Require(buffer.IsComplete(buffer.Capture())); + const auto first = buffer.Reserve(); + const auto second = buffer.Reserve(); + const auto fence = buffer.Capture(); + const auto future = buffer.Reserve(); + Require(!buffer.IsComplete(fence)); + buffer.Complete(second); // Out-of-order retirement must not release first. + Require(!buffer.IsComplete(fence)); + buffer.Complete(first); + Require(buffer.IsComplete(fence)); // Later reservations do not block this wait. + Require(!buffer.IsComplete(buffer.Capture())); + buffer.Complete(future); // Cancellation uses the same retirement operation. + Require(buffer.IsComplete(buffer.Capture())); + const auto next = buffer.Reserve(); + Require(next > future && buffer.IsComplete(fence)); + buffer.Complete(next); + bool rejected = false; + try { buffer.IsComplete(next + 1); } + catch (const std::invalid_argument&) { rejected = true; } + Require(rejected); +} diff --git a/core/libs/tests/GuestCharacter.cpp b/core/libs/tests/GuestCharacter.cpp new file mode 100644 index 000000000..60331fc87 --- /dev/null +++ b/core/libs/tests/GuestCharacter.cpp @@ -0,0 +1,45 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +extern "C" { +int APS5_VABI isupper_nid_postfix(int); +int APS5_VABI islower_nid_postfix(int); +int APS5_VABI isalpha_nid_postfix(int); +int APS5_VABI isdigit_nid_postfix(int); +int APS5_VABI isalnum_nid_postfix(int); +int APS5_VABI isspace_nid_postfix(int); +int APS5_VABI isblank_nid_postfix(int); +int APS5_VABI iscntrl_nid_postfix(int); +int APS5_VABI isprint_nid_postfix(int); +int APS5_VABI isgraph_nid_postfix(int); +int APS5_VABI ispunct_nid_postfix(int); +int APS5_VABI isxdigit_nid_postfix(int); +int APS5_VABI toupper_nid_postfix(int); +int APS5_VABI tolower_nid_postfix(int); +const short* APS5_VABI _Getptolower_nid_postfix(); +const short* APS5_VABI _Getptoupper_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + Require(std::setlocale(LC_CTYPE, "C") != nullptr); + for (int c = EOF; c <= 255; ++c) { + Require(bool(isupper_nid_postfix(c)) == bool(std::isupper(c))); + Require(bool(islower_nid_postfix(c)) == bool(std::islower(c))); + Require(bool(isalpha_nid_postfix(c)) == bool(std::isalpha(c))); + Require(bool(isdigit_nid_postfix(c)) == bool(std::isdigit(c))); + Require(bool(isalnum_nid_postfix(c)) == bool(std::isalnum(c))); + Require(bool(isspace_nid_postfix(c)) == bool(std::isspace(c))); + Require(bool(isblank_nid_postfix(c)) == bool(std::isblank(c))); + Require(bool(iscntrl_nid_postfix(c)) == bool(std::iscntrl(c))); + Require(bool(isprint_nid_postfix(c)) == bool(std::isprint(c))); + Require(bool(isgraph_nid_postfix(c)) == bool(std::isgraph(c))); + Require(bool(ispunct_nid_postfix(c)) == bool(std::ispunct(c))); + Require(bool(isxdigit_nid_postfix(c)) == bool(std::isxdigit(c))); + Require(toupper_nid_postfix(c) == std::toupper(c)); + Require(tolower_nid_postfix(c) == std::tolower(c)); + Require(toupper_nid_postfix(c) == _Getptoupper_nid_postfix()[c]); + Require(tolower_nid_postfix(c) == _Getptolower_nid_postfix()[c]); + } +} diff --git a/core/libs/tests/GuestDirectory.cpp b/core/libs/tests/GuestDirectory.cpp new file mode 100644 index 000000000..6d3288f0d --- /dev/null +++ b/core/libs/tests/GuestDirectory.cpp @@ -0,0 +1,52 @@ +#include "prx/libc/include/GuestDirectory.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +extern "C" { +void* APS5_VABI opendir_nid_postfix(const char*); +GuestDirectoryEntry* APS5_VABI readdir_nid_postfix(void*); +int APS5_VABI closedir_nid_postfix(void*); +void APS5_VABI rewinddir_nid_postfix(void*); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const auto root = std::filesystem::path("anyps5-directory-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + Require(std::filesystem::create_directory(root / "subdirectory")); + { std::ofstream file(root / "sample.txt"); file << "test"; } + void* directory = opendir_nid_postfix(root.string().c_str()); + Require(directory != nullptr); + std::map entries; + *__error_nid_postfix() = 13; + while (auto* entry = readdir_nid_postfix(directory)) { + Require(entry->nameLength == std::strlen(entry->name)); + Require(entry->recordLength == 8 + ((entry->nameLength + 4) & ~3)); + Require(entry->recordLength <= sizeof(*entry)); + entries[entry->name] = entry->type; + } + Require(*__error_nid_postfix() == 13); // EOF preserves errno. + Require(entries.at("sample.txt") == 8); + Require(entries.at("subdirectory") == 4); + Require(entries.at(".") == 4 && entries.at("..") == 4); + rewinddir_nid_postfix(directory); + std::size_t count = 0; + while (readdir_nid_postfix(directory)) ++count; + Require(count == entries.size()); + Require(closedir_nid_postfix(directory) == 0); + Require(opendir_nid_postfix((root / "missing").string().c_str()) == nullptr); + Require(*__error_nid_postfix() == 2); + Require(opendir_nid_postfix((root / "sample.txt").string().c_str()) == nullptr); + Require(*__error_nid_postfix() == 20); + Require(opendir_nid_postfix("") == nullptr && *__error_nid_postfix() == 2); + Require(closedir_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 9); + std::filesystem::remove(root / "sample.txt"); + std::filesystem::remove(root / "subdirectory"); + std::filesystem::remove(root); +} diff --git a/core/libs/tests/GuestDynamicLoader.cpp b/core/libs/tests/GuestDynamicLoader.cpp new file mode 100644 index 000000000..2fa27e917 --- /dev/null +++ b/core/libs/tests/GuestDynamicLoader.cpp @@ -0,0 +1,40 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +extern "C" { +void* APS5_VABI dlopen_nid_postfix(const char*, int); +void* APS5_VABI dlsym_nid_postfix(void*, const char*); +int APS5_VABI dlclose_nid_postfix(void*); +char* APS5_VABI dlerror_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main(int argc, char** argv) { + Require(argc == 2); + Require(dlerror_nid_postfix() == nullptr); + Require(dlopen_nid_postfix(argv[1], 0x2000) == nullptr); + Require(dlerror_nid_postfix() != nullptr); + Require(dlerror_nid_postfix() == nullptr); + Require(dlopen_nid_postfix("anyps5-missing-module-for-test.prx", 2) == nullptr); + Require(dlerror_nid_postfix() != nullptr); + void* executable = dlopen_nid_postfix(nullptr, 2); + Require(executable != nullptr && dlclose_nid_postfix(executable) == 0); + void* module = dlopen_nid_postfix(argv[1], 2 | 0x100); + Require(module != nullptr); + using Add = int (APS5_VABI *)(int, int); + auto add = reinterpret_cast(dlsym_nid_postfix(module, "GuestModuleAdd")); + Require(add && add(17, 25) == 42); + Require(dlsym_nid_postfix(reinterpret_cast(-2), "GuestModuleAdd") == reinterpret_cast(add)); + Require(dlsym_nid_postfix(module, "missing_symbol") == nullptr); + std::thread other([] { Require(dlerror_nid_postfix() == nullptr); }); + other.join(); + Require(dlerror_nid_postfix() != nullptr); + Require(dlerror_nid_postfix() == nullptr); + void* second = dlopen_nid_postfix(argv[1], 1); + Require(second && second != module); + Require(dlclose_nid_postfix(module) == 0); + Require(dlsym_nid_postfix(module, "GuestModuleAdd") == nullptr); + add = reinterpret_cast(dlsym_nid_postfix(second, "GuestModuleAdd")); + Require(add && add(2, 3) == 5); + Require(dlclose_nid_postfix(second) == 0); + Require(dlclose_nid_postfix(second) == -1); +} diff --git a/core/libs/tests/GuestEnvironment.cpp b/core/libs/tests/GuestEnvironment.cpp new file mode 100644 index 000000000..df1910cf4 --- /dev/null +++ b/core/libs/tests/GuestEnvironment.cpp @@ -0,0 +1,46 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +extern "C" { +char* APS5_VABI getenv_nid_postfix(const char*); +int APS5_VABI setenv_nid_postfix(const char*, const char*, int); +int APS5_VABI unsetenv_nid_postfix(const char*); +int APS5_VABI putenv_nid_postfix(char*); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const char* key = "ANYPS5_GUEST_ENV_TEST_4C27"; +#ifdef _WIN32 + Require(_putenv_s(key, "inherited") == 0); +#else + Require(::setenv(key, "inherited", 1) == 0); +#endif + Require(std::strcmp(getenv_nid_postfix(key), "inherited") == 0); + Require(setenv_nid_postfix(key, "kept out", 0) == 0); + Require(std::strcmp(getenv_nid_postfix(key), "inherited") == 0); + char value[] = "copied"; + Require(setenv_nid_postfix(key, value, 1) == 0); + value[0] = 'X'; + Require(std::strcmp(getenv_nid_postfix(key), "copied") == 0); + Require(std::strcmp(std::getenv(key), "inherited") == 0); + Require(setenv_nid_postfix(key, "", 1) == 0); + Require(getenv_nid_postfix(key) && *getenv_nid_postfix(key) == 0); + char borrowed[] = "ANYPS5_GUEST_ENV_TEST_4C27=one"; + Require(putenv_nid_postfix(borrowed) == 0); + char* position = std::strchr(borrowed, '=') + 1; + Require(getenv_nid_postfix(key) == position); + std::memcpy(position, "two", 3); + Require(std::strcmp(getenv_nid_postfix(key), "two") == 0); + Require(setenv_nid_postfix("anyps5_guest_env_test_4c27", "lower", 1) == 0); + Require(std::strcmp(getenv_nid_postfix(key), "two") == 0); + Require(setenv_nid_postfix("bad=name", "x", 1) == -1); + Require(*__error_nid_postfix() == 22); + Require(unsetenv_nid_postfix("") == -1); + char invalid[] = "no-separator"; + Require(putenv_nid_postfix(invalid) == -1); + Require(unsetenv_nid_postfix(key) == 0); + Require(getenv_nid_postfix(key) == nullptr); + Require(unsetenv_nid_postfix(key) == 0); + Require(unsetenv_nid_postfix("anyps5_guest_env_test_4c27") == 0); +} diff --git a/core/libs/tests/GuestErrors.cpp b/core/libs/tests/GuestErrors.cpp new file mode 100644 index 000000000..453dae221 --- /dev/null +++ b/core/libs/tests/GuestErrors.cpp @@ -0,0 +1,37 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +char* APS5_VABI strerror_nid_postfix(int); +int APS5_VABI strerror_r_nid_postfix(int, char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + *__error_nid_postfix() = 13; + Require(std::strcmp(strerror_nid_postfix(35), "Resource temporarily unavailable") == 0); + Require(*__error_nid_postfix() == 13); + Require(std::strcmp(strerror_nid_postfix(78), "Function not implemented") == 0); + char* parent = strerror_nid_postfix(22); + std::thread worker([] { + Require(std::strcmp(strerror_nid_postfix(45), "Operation not supported") == 0); + }); + worker.join(); + Require(std::strcmp(parent, "Invalid argument") == 0); + char buffer[128]; + for (int error = 0; error <= 96; ++error) { + Require(strerror_r_nid_postfix(error, buffer, sizeof(buffer)) == 0); + Require(buffer[0] && std::strstr(buffer, "Unknown") == nullptr); + } + Require(strerror_r_nid_postfix(-1, buffer, sizeof(buffer)) == 22); + Require(std::strstr(buffer, "-1") != nullptr); + Require(*__error_nid_postfix() == 13); + char sentinel[] = "xyz"; + Require(strerror_r_nid_postfix(22, sentinel, 1) == 34); + Require(sentinel[0] == 0 && sentinel[1] == 'y'); + sentinel[0] = 'x'; + Require(strerror_r_nid_postfix(22, sentinel, 0) == 34 && sentinel[0] == 'x'); + Require(strerror_r_nid_postfix(22, nullptr, 0) == 34); +} diff --git a/core/libs/tests/GuestFilesystem.cpp b/core/libs/tests/GuestFilesystem.cpp new file mode 100644 index 000000000..0a5b998cc --- /dev/null +++ b/core/libs/tests/GuestFilesystem.cpp @@ -0,0 +1,46 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +extern "C" { +int APS5_VABI remove_nid_postfix(const char*); +int APS5_VABI rename_nid_postfix(const char*, const char*); +int* APS5_VABI __error_nid_postfix(); +} +static void Check(bool value, int line) { + if (!value) { + std::fprintf(stderr, "Filesystem check failed at line %d (guest errno %d)\n", line, *__error_nid_postfix()); + std::abort(); + } +} +#define Require(value) Check((value), __LINE__) +int main() { + const auto root = std::filesystem::path("anyps5-filesystem-test-" + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + Require(std::filesystem::create_directory(root)); + const auto file = root / "file.txt"; + { std::ofstream stream(file); stream << "retained until removal"; } + Require(remove_nid_postfix(root.string().c_str()) == -1); + Require(*__error_nid_postfix() == 66); + Require(std::filesystem::is_regular_file(file)); + Require(remove_nid_postfix((file / "invalid").string().c_str()) == -1); + Require(remove_nid_postfix("") == -1 && *__error_nid_postfix() == 2); + Require(remove_nid_postfix(nullptr) == -1 && *__error_nid_postfix() == 14); + const auto renamed = root / "renamed.txt"; + { std::ofstream stream(renamed); stream << "old contents"; } + Require(rename_nid_postfix(file.string().c_str(), renamed.string().c_str()) == 0); + Require(!std::filesystem::exists(file)); + { std::ifstream stream(renamed); std::string contents; std::getline(stream, contents); + Require(contents == "retained until removal"); } + Require(rename_nid_postfix(renamed.string().c_str(), renamed.string().c_str()) == 0); + Require(rename_nid_postfix(file.string().c_str(), renamed.string().c_str()) == -1); + Require(*__error_nid_postfix() == 2); + Require(rename_nid_postfix(renamed.string().c_str(), file.string().c_str()) == 0); + Require(remove_nid_postfix(file.string().c_str()) == 0); + Require(!std::filesystem::exists(file)); + Require(remove_nid_postfix(file.string().c_str()) == -1 && *__error_nid_postfix() == 2); + Require(remove_nid_postfix(root.string().c_str()) == 0); + Require(!std::filesystem::exists(root)); +} diff --git a/core/libs/tests/GuestMath.cpp b/core/libs/tests/GuestMath.cpp new file mode 100644 index 000000000..96ccf6af8 --- /dev/null +++ b/core/libs/tests/GuestMath.cpp @@ -0,0 +1,74 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +extern "C" { +double APS5_VABI atof_nid_postfix(const char*); +float APS5_VABI strtof_nid_postfix(const char*, char**); +long double APS5_VABI strtold_nid_postfix(const char*, char**); +int* APS5_VABI __error_nid_postfix(); +float APS5_VABI fmodf_nid_postfix(float, float); +float APS5_VABI asinf_nid_postfix(float); +float APS5_VABI acosf_nid_postfix(float); +float APS5_VABI atan2f_nid_postfix(float, float); +float APS5_VABI tanf_nid_postfix(float); +float APS5_VABI log10f_nid_postfix(float); +double APS5_VABI exp2_nid_postfix(double); +double APS5_VABI ldexp_nid_postfix(double, int); +double APS5_VABI scalbn_nid_postfix(double, int); +float APS5_VABI scalbnf_nid_postfix(float, int); +double APS5_VABI frexp_nid_postfix(double, int*); +float APS5_VABI frexpf_nid_postfix(float, int*); +std::int64_t APS5_VABI lround_nid_postfix(double); +std::int64_t APS5_VABI lroundf_nid_postfix(float); +std::int64_t APS5_VABI llround_nid_postfix(double); +int APS5_VABI __isfinitef_nid_postfix(float); +int APS5_VABI __isnormal_nid_postfix(double); +int APS5_VABI __isnormalf_nid_postfix(float); +int APS5_VABI __isinff_nid_postfix(float); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + Require(atof_nid_postfix(" -12.5tail") == -12.5); + char* end = nullptr; + const char input[] = "0x1.8p+2 remainder"; + Require(strtof_nid_postfix(input, &end) == 6.f && end == input + 8); + const char invalid[] = "invalid"; + Require(strtof_nid_postfix(invalid, &end) == 0.f && end == invalid); + *__error_nid_postfix() = 0; + Require(std::isinf(strtof_nid_postfix("1e1000", nullptr))); + Require(*__error_nid_postfix() == 34); + Require(strtold_nid_postfix("1.0000000000000000001!", &end) > 1.L && *end == '!'); + Require(fmodf_nid_postfix(5.5f, 2.f) == 1.5f); + Require(fmodf_nid_postfix(-5.5f, 2.f) == -1.5f); + Require(std::signbit(fmodf_nid_postfix(-4.f, 2.f))); + Require(std::isnan(fmodf_nid_postfix(1.f, 0.f))); + Require(std::abs(asinf_nid_postfix(0.5f) - 0.5235988f) < 0.000001f); + Require(std::abs(acosf_nid_postfix(0.5f) - 1.0471976f) < 0.000001f); + Require(std::abs(atan2f_nid_postfix(1.f, -1.f) - 2.3561945f) < 0.000001f); + Require(tanf_nid_postfix(0.f) == 0.f); + Require(log10f_nid_postfix(100.f) == 2.f); + Require(exp2_nid_postfix(-3.) == 0.125); + Require(ldexp_nid_postfix(0.75, 4) == 12.); + Require(scalbn_nid_postfix(0.75, -2) == 0.1875); + Require(scalbnf_nid_postfix(0.75f, 4) == 12.f); + int exponent = 0; + Require(frexp_nid_postfix(12., &exponent) == 0.75 && exponent == 4); + Require(frexpf_nid_postfix(-12.f, &exponent) == -0.75f && exponent == 4); + Require(lround_nid_postfix(4294967296.5) == INT64_C(4294967297)); + Require(lround_nid_postfix(-2.5) == -3); + Require(lroundf_nid_postfix(4294967296.f) == INT64_C(4294967296)); + Require(lroundf_nid_postfix(2.5f) == 3); + Require(llround_nid_postfix(-4294967296.5) == -INT64_C(4294967297)); + const auto infinity = std::numeric_limits::infinity(); + const auto nan = std::numeric_limits::quiet_NaN(); + Require(__isinff_nid_postfix(infinity) == 1 && __isinff_nid_postfix(-infinity) == 1); + Require(__isinff_nid_postfix(nan) == 0 && __isinff_nid_postfix(1.f) == 0); + Require(__isfinitef_nid_postfix(0.f) == 1 && __isfinitef_nid_postfix(infinity) == 0); + Require(__isfinitef_nid_postfix(nan) == 0); + Require(__isnormalf_nid_postfix(1.f) == 1 && __isnormalf_nid_postfix(0.f) == 0); + Require(__isnormalf_nid_postfix(std::numeric_limits::denorm_min()) == 0); + Require(__isnormal_nid_postfix(1.) == 1 && __isnormal_nid_postfix(0.) == 0); + Require(__isnormal_nid_postfix(std::numeric_limits::denorm_min()) == 0); +} diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp new file mode 100644 index 000000000..e0e906e3d --- /dev/null +++ b/core/libs/tests/GuestMemory.cpp @@ -0,0 +1,70 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/GuestAllocations.hpp" +#include +#include +#include +#include + +extern "C" { +void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; +int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; +int* APS5_VABI __error_nid_postfix(); +} + +static void Require(bool condition) { + if (!condition) { + std::fputs("Guest memory check failed\n", stderr); + std::abort(); + } +} + +int main() { + constexpr std::size_t page = 0x4000; + const auto failed = reinterpret_cast(static_cast(-1)); + const auto reject = [&](std::size_t length, int protection, int flags, int fd, + std::int64_t offset, int error) { + *__error_nid_postfix() = 0; + Require(mmap_nid_postfix(nullptr, length, protection, flags, fd, offset) == failed); + Require(*__error_nid_postfix() == error); + }; + reject(0, 3, 0x1002, -1, 0, 22); + reject(std::numeric_limits::max(), 3, 0x1002, -1, 0, 22); + reject(page, 8, 0x1002, -1, 0, 22); + reject(page, 3, 0x1002, 0, 0, 22); + reject(page, 3, 0x1002, -1, 1, 22); + reject(page, 3, 0x1001, -1, 0, 45); // shared + reject(page, 3, 0x1012, -1, 0, 45); // fixed + reject(page, 3, 0x2, 0, 0, 45); // file-backed + reject(page, 3, 0x22, -1, 0, 45); // Linux MAP_ANON is not guest MAP_ANON + + auto* memory = static_cast(mmap_nid_postfix(nullptr, page * 3 - 1, 3, 0x1002, -1, 0)); + Require(memory != failed && (reinterpret_cast(memory) & (page - 1)) == 0); + for (std::size_t i = 0; i < page * 3; ++i) Require(memory[i] == 0); + memory[0] = 42; + memory[page * 2] = 73; + { + GuestAllocations::Mutation mutation; + const auto range = mutation.Find(memory); + Require(range.bytes == page * 3 && range.readable && range.writable); + } + Require(munmap_nid_postfix(memory + 1, page) == -1 && *__error_nid_postfix() == 22); + Require(munmap_nid_postfix(memory, 0) == -1 && *__error_nid_postfix() == 22); + Require(memory[0] == 42); + Require(munmap_nid_postfix(memory + page, 1) == 0); // round to one guest page + Require(memory[0] == 42 && memory[page * 2] == 73); + Require(munmap_nid_postfix(memory, page) == 0); + Require(memory[page * 2] == 73); + Require(munmap_nid_postfix(memory + page * 2, page) == 0); + Require(munmap_nid_postfix(memory, page) == -1); + for (int protection : {0, 1, 3, 5}) { + void* mapped = mmap_nid_postfix(memory, 1, protection, 0x1002, -1, 0); + Require(mapped != failed); + { + GuestAllocations::Mutation mutation; + const auto range = mutation.Find(mapped); + Require(range.readable == ((protection & 3) != 0)); + Require(range.writable == ((protection & 2) != 0)); + } + Require(munmap_nid_postfix(mapped, 1) == 0); + } +} diff --git a/core/libs/tests/GuestModuleFixture.cpp b/core/libs/tests/GuestModuleFixture.cpp new file mode 100644 index 000000000..e8f8521d6 --- /dev/null +++ b/core/libs/tests/GuestModuleFixture.cpp @@ -0,0 +1,9 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#ifdef _WIN32 +#define MODULE_EXPORT __declspec(dllexport) +#else +#define MODULE_EXPORT __attribute__((visibility("default"))) +#endif +extern "C" MODULE_EXPORT int APS5_VABI GuestModuleAdd_nid_postfix(int a, int b) { + return a + b; +} diff --git a/core/libs/tests/GuestOnce.cpp b/core/libs/tests/GuestOnce.cpp new file mode 100644 index 000000000..b82ec22ef --- /dev/null +++ b/core/libs/tests/GuestOnce.cpp @@ -0,0 +1,44 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include + +struct GuestOnce { std::int32_t state; void* mutex; }; +static_assert(sizeof(GuestOnce) == 16 && offsetof(GuestOnce, mutex) == 8); +using Initializer = void (APS5_VABI *)(); +extern "C" int APS5_VABI pthread_once_nid_postfix(GuestOnce*, Initializer); +static void Require(bool value) { if (!value) std::abort(); } +static GuestOnce control{}, nested{}, retry{}; +static std::atomic calls{0}, arrived{0}; +static int published = 0, nestedValue = 0, attempts = 0; +static void APS5_VABI Nested() { nestedValue = 17; } +static void APS5_VABI Initialize() { + ++calls; + while (arrived.load() != 16) std::this_thread::yield(); + Require(pthread_once_nid_postfix(&nested, Nested) == 0); + published = 42; +} +static void APS5_VABI Retry() { + if (++attempts == 1) throw 7; +} +int main() { + std::array workers; + for (auto& worker : workers) worker = std::thread([] { + ++arrived; + Require(pthread_once_nid_postfix(&control, Initialize) == 0); + Require(published == 42 && nestedValue == 17); + }); + for (auto& worker : workers) worker.join(); + Require(calls == 1 && control.state == 1 && control.mutex == nullptr); + Require(pthread_once_nid_postfix(&control, Initialize) == 0 && calls == 1); + try { pthread_once_nid_postfix(&retry, Retry); std::abort(); } + catch (int value) { Require(value == 7 && retry.state == 0); } + Require(pthread_once_nid_postfix(&retry, Retry) == 0 && attempts == 2); + Require(pthread_once_nid_postfix(nullptr, Initialize) == 22); + Require(pthread_once_nid_postfix(&control, nullptr) == 22); + GuestOnce invalid{9, nullptr}; + Require(pthread_once_nid_postfix(&invalid, Initialize) == 22); +} diff --git a/core/libs/tests/GuestRandom.cpp b/core/libs/tests/GuestRandom.cpp new file mode 100644 index 000000000..f611927f8 --- /dev/null +++ b/core/libs/tests/GuestRandom.cpp @@ -0,0 +1,26 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include + +extern "C" { +int APS5_VABI sceRandomGetRandomNumber(void*, std::size_t); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + constexpr int invalid = static_cast(0x817C0016); + unsigned char buffer[80]; + Require(sceRandomGetRandomNumber(nullptr, 16) == invalid); + std::memset(buffer, 0xAA, sizeof(buffer)); + Require(sceRandomGetRandomNumber(buffer, 65) == invalid); + Require(buffer[0] == 0xAA); + Require(sceRandomGetRandomNumber(buffer, 0) == 0); + Require(buffer[0] == 0xAA); + Require(sceRandomGetRandomNumber(buffer, 7) == 0); + Require(buffer[7] == 0xAA); + unsigned char first[64] = {}; + unsigned char second[64] = {}; + Require(sceRandomGetRandomNumber(first, sizeof(first)) == 0); + Require(sceRandomGetRandomNumber(second, sizeof(second)) == 0); + Require(std::memcmp(first, second, sizeof(first)) != 0); +} diff --git a/core/libs/tests/GuestRenderingWait.cpp b/core/libs/tests/GuestRenderingWait.cpp new file mode 100644 index 000000000..71d3cbe98 --- /dev/null +++ b/core/libs/tests/GuestRenderingWait.cpp @@ -0,0 +1,80 @@ +#include "prx/libSceAgcDriver/Execution/include/VideoOutput.hpp" +#include "prx/libSceAgcDriver/Execution/include/Driver.hpp" +#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp" +#include "prx/libSceVideoOut/include/BufferReuseTracker.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#include +extern "C" std::uint32_t APS5_VABI sceAgcDriverGetWaitRenderingPacketSizeInDwords(); +extern "C" std::uint32_t APS5_VABI sceAgcDriverWaitUntilSafeForRendering(std::uint32_t**, std::uint32_t, std::uint32_t, std::uint32_t, int); +static void Require(bool value) { if (!value) std::abort(); } +class Output final : public AgcDriver::IVideoOutput, public AgcDriver::IRenderingWait, + public AgcDriver::IFlipRequest, public std::enable_shared_from_this { +public: + std::mutex mutex; + std::condition_variable changed; + BufferReuseTracker reuse; + const std::uint64_t earlier = reuse.Reserve(); + std::uint64_t captured = 0, later = 0; + bool entered = false, flipped = false; + std::shared_ptr CaptureRenderingWait(std::uint32_t index) override { + Require(index == 0); + std::lock_guard lock(mutex); + captured = reuse.Capture(); + return shared_from_this(); + } + std::shared_ptr Reserve(const AgcDriver::FlipInfo& info) override { + Require(info.index == 0); + std::lock_guard lock(mutex); + later = reuse.Reserve(); + return shared_from_this(); + } + void Wait() override { + std::unique_lock lock(mutex); + entered = true; + changed.notify_all(); + Require(changed.wait_for(lock, std::chrono::seconds(5), [&] { return reuse.IsComplete(captured); })); + } + void GpuReady(const std::shared_ptr&) override { + std::lock_guard lock(mutex); + Require(reuse.IsComplete(captured)); + reuse.Complete(later); + flipped = true; + } + void Fail(std::exception_ptr) noexcept override { std::abort(); } +}; +int main() { + auto output = std::make_shared(); + AgcDriverRegisterVideoOutput_nid_postfix(7, output); + std::array words{}; + words.back() = 0x12345678; + auto* cursor = words.data(); + const auto size = sceAgcDriverGetWaitRenderingPacketSizeInDwords(); + Require(size == AgcDriver::RenderingWaitPacketWords); + bool rejected = false; + try { sceAgcDriverWaitUntilSafeForRendering(&cursor, size - 1, 0, 7, 0); } + catch (const std::invalid_argument&) { rejected = true; } + Require(rejected && cursor == words.data() && words[0] == 0); + Require(sceAgcDriverWaitUntilSafeForRendering(&cursor, size, 0, 7, 0) == 0); + Require(cursor == words.data() + size); + Require(words[0] == AgcDriver::RenderingWaitPacketHeader && words[1] == 7 && words[2] == 0 && words[3] == 0); + const std::uint32_t flip[] = {AgcDriver::FlipPacketHeader, 7, 0, 1, 0, 0}; + for (auto word : flip) *cursor++ = word; + Require(words.back() == 0x12345678); + Packet packet{words.data(), 10, 0, {}}; + sceAgcDriverSubmitDcb(&packet); + { + std::unique_lock lock(output->mutex); + Require(output->changed.wait_for(lock, std::chrono::seconds(5), [&] { return output->entered; })); + Require(!output->flipped && output->later > output->captured); + output->reuse.Complete(output->earlier); + output->changed.notify_all(); + } + AgcDriverWaitIdle_nid_postfix(); + Require(output->flipped); + AgcDriverUnregisterVideoOutput_nid_postfix(7, output); + LibcRunShutdown_nid_postfix(); +} diff --git a/core/libs/tests/GuestResolver.cpp b/core/libs/tests/GuestResolver.cpp new file mode 100644 index 000000000..d65089c55 --- /dev/null +++ b/core/libs/tests/GuestResolver.cpp @@ -0,0 +1,61 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libScePosixForWebKit/GuestResolver.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI getaddrinfo_nid_postfix(const char*, const char*, const GuestResolver::AddressInfo*, GuestResolver::AddressInfo**); +void APS5_VABI freeaddrinfo_nid_postfix(GuestResolver::AddressInfo*); +int APS5_VABI getnameinfo_nid_postfix(const void*, std::uint32_t, char*, std::uint32_t, char*, std::uint32_t, int); +const char* APS5_VABI gai_strerror_nid_postfix(int); +} +void Require(bool condition, std::source_location location = std::source_location::current()) { + if (!condition) { + std::fprintf(stderr, "Resolver check failed at line %u\n", location.line()); + std::abort(); + } +} +int main() { + std::array v4{16, 2, 0x6d, 0x06, 127, 0, 0, 1}; + char host[128]{}, service[32]{}; + Require(getnameinfo_nid_postfix(v4.data(), v4.size(), host, sizeof(host), service, sizeof(service), 10) == 0); + Require(std::strcmp(host, "127.0.0.1") == 0 && std::strcmp(service, "27910") == 0); + std::array v6{28, 28}; + v6[23] = 1; + Require(getnameinfo_nid_postfix(v6.data(), v6.size(), host, sizeof(host), nullptr, 0, 10) == 0); + Require(std::strcmp(host, "::1") == 0); + Require(getnameinfo_nid_postfix(v4.data(), 2, host, sizeof(host), nullptr, 0, 10) == 5); + Require(getnameinfo_nid_postfix(v4.data(), v4.size(), host, sizeof(host), nullptr, 0, 32) == 3); + Require(getnameinfo_nid_postfix(v4.data(), v4.size(), host, 1, nullptr, 0, 10) == 14); + Require(std::strlen(gai_strerror_nid_postfix(14)) > 0); + Require(std::strstr(gai_strerror_nid_postfix(-1), "Unknown") != nullptr); + GuestResolver::AddressInfo hints{}; + hints.flags = 12; // numeric host and service: no external DNS dependency + hints.socketType = 1; + for (const auto* numeric : {"127.0.0.1", "::1"}) { + GuestResolver::AddressInfo* result = nullptr; + Require(getaddrinfo_nid_postfix(numeric, "27910", &hints, &result) == 0); + Require(result != nullptr); + for (auto* entry = result; entry; entry = entry->next) { + Require(entry->family == 2 || entry->family == 28); + Require(entry->socketType == 1); + Require(getnameinfo_nid_postfix(entry->address, entry->addressLength, + host, sizeof(host), service, sizeof(service), 10) == 0); + Require(std::strcmp(host, numeric) == 0 && std::strcmp(service, "27910") == 0); + } + freeaddrinfo_nid_postfix(result); + } + GuestResolver::AddressInfo* result = reinterpret_cast(1); + hints.family = 1234; + Require(getaddrinfo_nid_postfix("127.0.0.1", "1", &hints, &result) == 5 && !result); + hints.family = 0; + hints.flags = 0x200; + Require(getaddrinfo_nid_postfix("127.0.0.1", "1", &hints, &result) == 3 && !result); + hints.flags = 12; + Require(getaddrinfo_nid_postfix("invalid-numeric-address", "1", &hints, &result) == 8 && !result); + freeaddrinfo_nid_postfix(nullptr); +} diff --git a/core/libs/tests/GuestSearch.cpp b/core/libs/tests/GuestSearch.cpp new file mode 100644 index 000000000..f1572c364 --- /dev/null +++ b/core/libs/tests/GuestSearch.cpp @@ -0,0 +1,45 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +using Comparator = int (APS5_VABI *)(const void*, const void*); +extern "C" void* APS5_VABI bsearch_nid_postfix(const void*, const void*, std::size_t, std::size_t, Comparator); +static void Require(bool value, std::source_location location = std::source_location::current()) { + if (!value) { std::fprintf(stderr, "Guest search failed at line %u\n", location.line()); std::abort(); } +} +struct Record { int key; char payload[13]; }; +static int calls = 0; +static int APS5_VABI Compare(const void* key, const void* record) { + ++calls; + const int left = *static_cast(key); + const int right = static_cast(record)->key; + return (left > right) - (left < right); +} +int main() { + const std::array records{{{1, "one"}, {3, "three"}, {5, "five"}, + {5, "duplicate"}, {7, "seven"}, {9, "nine"}}}; + std::array before{}; + std::memcpy(before.data(), records.data(), sizeof(records)); + for (int key = 0; key <= 10; ++key) { + calls = 0; + const auto* found = static_cast(bsearch_nid_postfix(&key, records.data(), records.size(), sizeof(Record), Compare)); + Require(calls <= 3); + if (key % 2) Require(found && found->key == key && found >= records.data() && found < records.data() + records.size()); + else Require(!found); + } + Require(std::memcmp(before.data(), records.data(), sizeof(records)) == 0); + calls = 0; + int key = 1; + Require(bsearch_nid_postfix(&key, nullptr, 0, sizeof(Record), Compare) == nullptr && calls == 0); + Require(bsearch_nid_postfix(&key, records.data(), 1, sizeof(Record), Compare) == records.data()); + key = 2; + Require(!bsearch_nid_postfix(&key, records.data(), 1, sizeof(Record), Compare)); + bool rejected = false; + try { bsearch_nid_postfix(&key, records.data(), std::numeric_limits::max(), sizeof(Record), Compare); } + catch (const std::overflow_error&) { rejected = true; } + Require(rejected); +} diff --git a/core/libs/tests/GuestShaderLink.cpp b/core/libs/tests/GuestShaderLink.cpp new file mode 100644 index 000000000..5e8f3b31a --- /dev/null +++ b/core/libs/tests/GuestShaderLink.cpp @@ -0,0 +1,86 @@ +#include "SceShaders.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libSceAgc/Shader/include/ShaderConstants.hpp" +#include +#include +#include +#include +extern "C" int APS5_VABI sceAgcLinkShaders(ShaderRegister*, ShaderRegister*, const void*, const Shader*, const Shader*, std::uint32_t); +extern "C" void* APS5_VABI sceAgcGetRegisterDefaults(); +extern "C" void* APS5_VABI sceAgcGetRegisterDefaults2(std::uint32_t); +static void Require(bool value) { if (!value) std::abort(); } +int main() { + using namespace ShaderRegs; + auto* defaults = static_cast(sceAgcGetRegisterDefaults()); + Require(defaults && defaults == sceAgcGetRegisterDefaults()); + Require(defaults == sceAgcGetRegisterDefaults2(0)); + // These are the byte offsets used by native callers, independent of the + // private C++ structure used to construct the defaults object. + ShaderRegister** contextBlocks = nullptr; + std::uint32_t contextCount = 0; + std::memcpy(&contextBlocks, defaults, sizeof(contextBlocks)); + std::memcpy(&contextCount, defaults + 0x20, sizeof(contextCount)); + Require(contextBlocks && contextBlocks[0] && contextCount == 523); + bool hasRenderTarget = false; + bool hasRasterizer = false; + for (std::uint32_t i = 0; i < contextCount; ++i) { + hasRenderTarget |= contextBlocks[0][i].offset == 0x318; + hasRasterizer |= contextBlocks[0][i].offset == 0x205; + } + Require(hasRenderTarget && hasRasterizer); + ShaderSpecialRegs special{}; + special.vgt_shader_stages_en = {VGT_SHADER_STAGES_EN, VGT_SHADER_STAGES_NGG_BIT}; + special.vgt_gs_out_prim_type = {VGT_GS_OUT_PRIM_TYPE, 0}; + special.ge_cntl = {GE_CNTL, 0x123}; + special.ge_user_vgpr_en = {GE_USER_VGPR_EN, 7}; + Shader vertex{}; + vertex.type = static_cast(ShaderBinaryType::Gs); + vertex.specials = &special; + Shader pixel{}; + pixel.type = static_cast(ShaderBinaryType::Ps); + std::array context{}; + std::array primitive{}; + context.back() = {0xdeadbeef, 0xcafebabe}; + primitive.back() = context.back(); + Require(sceAgcLinkShaders(context.data(), primitive.data(), nullptr, &vertex, &pixel, 4) == 0); + Require(context[0].offset == VGT_SHADER_STAGES_EN && context[0].value == VGT_SHADER_STAGES_NGG_BIT); + Require(context[1].offset == VGT_GS_OUT_PRIM_TYPE && context[1].value == 2); + for (unsigned i = 0; i < 32; ++i) + Require(context[i + 2].offset == SPI_PS_INPUT_CNTL_0 + i && context[i + 2].value == i); + Require(primitive[0].offset == GE_CNTL && primitive[0].value == 0x123); + Require(primitive[1].offset == GE_USER_VGPR_EN && primitive[1].value == 7); + Require(primitive[2].offset == VGT_PRIMITIVE_TYPE && primitive[2].value == 4); + Require(context.back().value == 0xcafebabe && primitive.back().value == 0xcafebabe); + const auto savedContext = context; + const auto savedPrimitive = primitive; + pixel.num_input_semantics = 33; + Require(sceAgcLinkShaders(context.data(), primitive.data(), nullptr, &vertex, &pixel, 4) == GRAPHICS5_ERROR_INVALID_SHADER_PROGRAM); + Require(std::memcmp(context.data(), savedContext.data(), sizeof(context)) == 0); + Require(std::memcmp(primitive.data(), savedPrimitive.data(), sizeof(primitive)) == 0); + pixel.num_input_semantics = 0; + special.ge_cntl.offset = 0; + bool rejected = false; + try { sceAgcLinkShaders(context.data(), primitive.data(), nullptr, &vertex, &pixel, 4); } + catch (const std::runtime_error&) { rejected = true; } + Require(rejected); + Require(std::memcmp(context.data(), savedContext.data(), sizeof(context)) == 0); + special.ge_cntl.offset = GE_CNTL; + Require(sceAgcLinkShaders(context.data(), primitive.data(), nullptr, &vertex, nullptr, 2) == 0); + Require(context[1].value == 1 && primitive[2].value == 2); + ShaderSemantic output{}; + output.semantic = 9; + output.hardware_mapping = 5; + vertex.output_semantics = &output; + vertex.num_output_semantics = 1; + ShaderSemantic input{}; + input.semantic = 9; + input.is_flat_shaded = 1; + pixel.input_semantics = &input; + pixel.num_input_semantics = 1; + Require(sceAgcLinkShaders(context.data(), primitive.data(), nullptr, &vertex, &pixel, 4) == 0); + Require(context[2].offset == SPI_PS_INPUT_CNTL_0 && context[2].value == (5 | 0x400)); + input.semantic = 10; + input.default_value = 2; + Require(sceAgcLinkShaders(context.data(), primitive.data(), nullptr, &vertex, &pixel, 4) == 0); + Require(context[2].value == 0x220); +} diff --git a/core/libs/tests/GuestSignals.cpp b/core/libs/tests/GuestSignals.cpp new file mode 100644 index 000000000..9d3ba7afe --- /dev/null +++ b/core/libs/tests/GuestSignals.cpp @@ -0,0 +1,28 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +using Handler = void (APS5_VABI *)(int); +extern "C" { +Handler APS5_VABI signal_nid_postfix(int, Handler); +int APS5_VABI raise_nid_postfix(int); +int* APS5_VABI __error_nid_postfix(); +} +volatile std::sig_atomic_t received = 0; +void APS5_VABI Callback(int value) { received = value; } +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const auto invalid = reinterpret_cast(static_cast(-1)); + const auto ignore = reinterpret_cast(std::uintptr_t{1}); + Require(signal_nid_postfix(9, Callback) == invalid); + Require(*__error_nid_postfix() == 22); + Require(signal_nid_postfix(15, Callback) != invalid); + Require(raise_nid_postfix(15) == 0 && received == 15); + received = 0; + Require(raise_nid_postfix(15) == 0 && received == 15); + Require(signal_nid_postfix(15, ignore) != invalid); + received = 0; + Require(raise_nid_postfix(15) == 0 && received == 0); + Require(signal_nid_postfix(15, nullptr) == ignore); + Require(raise_nid_postfix(100) == -1 && *__error_nid_postfix() == 22); +} diff --git a/core/libs/tests/GuestStandardStreams.cpp b/core/libs/tests/GuestStandardStreams.cpp new file mode 100644 index 000000000..4ffb0fcaf --- /dev/null +++ b/core/libs/tests/GuestStandardStreams.cpp @@ -0,0 +1,158 @@ +#include "prx/libc/include/FileStream.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +extern "C" { +FileStream* APS5_VABI freopen_nid_postfix(const char*, const char*, FileStream*); +int APS5_VABI fseeko_nid_postfix(FileStream*, std::int64_t, int); +std::int64_t APS5_VABI ftello_nid_postfix(FileStream*); +int APS5_VABI fseek_nid_postfix(FileStream*, std::int64_t, int); +std::int64_t APS5_VABI ftell_nid_postfix(FileStream*); +int* APS5_VABI __error_nid_postfix(); +extern FileStream* __stdinp_nid_postfix; +extern FileStream* __stdoutp_nid_postfix; +extern FileStream* __stderrp_nid_postfix; +extern int __isthreaded_nid_postfix; +int APS5_VABI fprintf_nid_postfix(FileStream*, const char*, ...); +int APS5_VABI vfprintf_nid_postfix(FileStream*, const char*, void*); +int APS5_VABI vsprintf_nid_postfix(char*, const char*, void*); +int APS5_VABI fgetc_nid_postfix(FileStream*); +int APS5_VABI fputc_nid_postfix(int, FileStream*); +int APS5_VABI __srget_nid_postfix(FileStream*); +int APS5_VABI __swbuf_nid_postfix(int, FileStream*); +int APS5_VABI ungetc_nid_postfix(int, FileStream*); +char* APS5_VABI fgets_nid_postfix(char*, int, FileStream*); +int APS5_VABI feof_nid_postfix(FileStream*); +int APS5_VABI fileno_nid_postfix(FileStream*); +void APS5_VABI clearerr_nid_postfix(FileStream*); +int APS5_VABI setvbuf_nid_postfix(FileStream*, char*, int, std::size_t); +} +static void Require(bool value) { if (!value) std::abort(); } +static int APS5_VABI WriteFormatted(FileStream* stream, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vfprintf_nid_postfix(stream, format, args); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} +static int APS5_VABI FormatString(char* buffer, const char* format, ...) { +#ifdef _WIN32 + __builtin_sysv_va_list args; + __builtin_sysv_va_start(args, format); +#else + std::va_list args; + va_start(args, format); +#endif + const int result = vsprintf_nid_postfix(buffer, format, args); +#ifdef _WIN32 + __builtin_sysv_va_end(args); +#else + va_end(args); +#endif + return result; +} +int main() { + char stringOutput[256]; + std::memset(stringOutput, '!', sizeof(stringOutput)); + std::int64_t count = -1; + const char expectedString[] = "guest:4294967297: 3.50:1,2,3,4,5,6,7,8:%"; + const int written = FormatString(stringOutput, "%s:%ld:%*.*f:%d,%d,%d,%d,%d,%d,%d,%d:%%%ln", + "guest", std::int64_t{4294967297}, 6, 2, 3.5, 1, 2, 3, 4, 5, 6, 7, 8, &count); + Require(written == sizeof(expectedString) - 1 && count == written); + Require(std::strcmp(stringOutput, expectedString) == 0 && stringOutput[written + 1] == '!'); + Require(FormatString(stringOutput, "%.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.0f %.2Lf", + 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 1.25L) == 25); + Require(std::strcmp(stringOutput, "1 2 3 4 5 6 7 8 9 10 1.25") == 0); + Require(FormatString(stringOutput, "") == 0 && stringOutput[0] == '\0'); + Require(__isthreaded_nid_postfix == 1); + Require(__stdoutp_nid_postfix == &_Stdout_nid_postfix); + Require(__stderrp_nid_postfix == &_Stderr_nid_postfix); + Require(fileno_nid_postfix(__stdinp_nid_postfix) == 0); + Require(fileno_nid_postfix(__stdoutp_nid_postfix) == 1); + Require(fileno_nid_postfix(__stderrp_nid_postfix) == 2); + FileStream stream(std::tmpfile()); + auto& guest = *reinterpret_cast(&stream); + Require(&guest == &stream.GuestState()); + Require(guest.position == nullptr && guest.readRemaining == 0 && guest.writeRemaining == 0); + Require(guest.descriptor == fileno_nid_postfix(&stream)); + Require(setvbuf_nid_postfix(&stream, nullptr, 2, 0) == 0); + Require(fputc_nid_postfix('A', &stream) == 'A'); + Require(--guest.writeRemaining < 0 && __swbuf_nid_postfix('\n', &stream) == '\n'); + std::rewind(stream.GetHandle()); + Require(--guest.readRemaining < 0 && __srget_nid_postfix(&stream) == 'A'); + Require(ungetc_nid_postfix('B', &stream) == 'B'); + char text[8]{}; + Require(fgets_nid_postfix(text, sizeof(text), &stream) == text); + Require(std::strcmp(text, "B\n") == 0); + Require(fgetc_nid_postfix(&stream) == EOF); + Require(feof_nid_postfix(&stream) && (guest.flags & 0x20)); + clearerr_nid_postfix(&stream); + Require(!feof_nid_postfix(&stream) && !(guest.flags & 0x20)); + stream.Close(); + Require(guest.flags == 0 && guest.descriptor == -1); + + FileStream formatted(std::tmpfile()); + const char expected[] = "guest 4294967297 1.25 1 2 3 4 5 6 7 8\n"; + Require(fprintf_nid_postfix(&formatted, "%s %ld %.2f %d %d %d %d %d %d %d %d\n", + "guest", std::int64_t{4294967297}, 1.25, 1, 2, 3, 4, 5, 6, 7, 8) == sizeof(expected) - 1); + Require(WriteFormatted(&formatted, "%*.*f:%s", 6, 2, 3.5, "end") == 10); + std::rewind(formatted.GetHandle()); + char output[128]{}; + Require(fgets_nid_postfix(output, sizeof(output), &formatted) == output); + Require(std::strcmp(output, expected) == 0); + Require(fgets_nid_postfix(output, sizeof(output), &formatted) == output); + Require(std::strcmp(output, " 3.50:end") == 0); + formatted.Close(); + + FileStream positioned(std::tmpfile()); + constexpr std::int64_t largeOffset = INT64_C(4294967313); + Require(fseeko_nid_postfix(&positioned, largeOffset, SEEK_SET) == 0); + Require(ftello_nid_postfix(&positioned) == largeOffset); + Require(ftell_nid_postfix(&positioned) == largeOffset); + Require(fseek_nid_postfix(&positioned, -9, SEEK_CUR) == 0); + Require(ftello_nid_postfix(&positioned) == largeOffset - 9); + Require(fseek_nid_postfix(&positioned, largeOffset, SEEK_SET) == 0); + Require(ftell_nid_postfix(&positioned) == largeOffset); + Require(fseeko_nid_postfix(&positioned, 0, 12345) == -1 && *__error_nid_postfix() == 22); + Require(ftello_nid_postfix(&positioned) == largeOffset); + Require(fseeko_nid_postfix(&positioned, 0, SEEK_END) == 0); + Require(ftello_nid_postfix(&positioned) == 0); // Seeking alone did not extend the file. + Require(fgetc_nid_postfix(&positioned) == EOF && feof_nid_postfix(&positioned)); + Require(fseeko_nid_postfix(&positioned, 0, SEEK_SET) == 0); + Require(!feof_nid_postfix(&positioned)); + positioned.Close(); + + const auto filename = "anyps5-reopen-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + FileStream redirected(std::tmpfile()); + Require(fgetc_nid_postfix(&redirected) == EOF && feof_nid_postfix(&redirected)); + Require(freopen_nid_postfix(filename.c_str(), "w+b", &redirected) == &redirected); + Require(!feof_nid_postfix(&redirected)); + Require(fputc_nid_postfix('R', &redirected) == 'R'); + Require(fseeko_nid_postfix(&redirected, 0, SEEK_SET) == 0); + Require(fgetc_nid_postfix(&redirected) == 'R'); + Require(freopen_nid_postfix(filename.c_str(), "ab", &redirected) == &redirected); + Require(fputc_nid_postfix('S', &redirected) == 'S'); + Require(freopen_nid_postfix(nullptr, "r", &redirected) == nullptr && *__error_nid_postfix() == 45); + redirected.Close(); + { std::ifstream input(filename, std::ios::binary); std::string contents; std::getline(input, contents); + Require(contents == "RS"); } + Require(std::filesystem::remove(filename)); + FileStream failed(std::tmpfile()); + Require(freopen_nid_postfix(filename.c_str(), "rb", &failed) == nullptr); + Require(*__error_nid_postfix() == 2); + Require(failed.GuestState().flags == 0 && failed.GuestState().descriptor == -1); +} diff --git a/core/libs/tests/GuestStrings.cpp b/core/libs/tests/GuestStrings.cpp new file mode 100644 index 000000000..cc8eefab6 --- /dev/null +++ b/core/libs/tests/GuestStrings.cpp @@ -0,0 +1,81 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include + +extern "C" { +char* APS5_VABI basename_nid_postfix(const char*); +int* APS5_VABI __error_nid_postfix(); +std::size_t APS5_VABI strnlen_nid_postfix(const char*, std::size_t); +char* APS5_VABI strncat_nid_postfix(char*, const char*, std::size_t); +char* APS5_VABI strpbrk_nid_postfix(const char*, const char*); +std::size_t APS5_VABI strcspn_nid_postfix(const char*, const char*); +std::size_t APS5_VABI strlcat_nid_postfix(char*, const char*, std::size_t); +char* APS5_VABI strtok_r_nid_postfix(char*, const char*, char**); +char* APS5_VABI strtok_nid_postfix(char*, const char*); +char* APS5_VABI strcasestr_nid_postfix(const char*, const char*); +} + +static void Require(bool condition) { + if (!condition) { + std::fputs("Guest string check failed\n", stderr); + std::abort(); + } +} + +int main() { + Require(std::strcmp(basename_nid_postfix(nullptr), ".") == 0); + Require(std::strcmp(basename_nid_postfix(""), ".") == 0); + Require(std::strcmp(basename_nid_postfix("////"), "/") == 0); + const char path[] = "/one/two///"; + Require(std::strcmp(basename_nid_postfix(path), "two") == 0); + Require(std::strcmp(path, "/one/two///") == 0); + Require(std::strcmp(basename_nid_postfix("one\\two"), "one\\two") == 0); + const std::string longName(1024, 'x'); + Require(basename_nid_postfix(longName.c_str()) == nullptr && *__error_nid_postfix() == 63); + const char bounded[] = {'a', 'b', 'c'}; + Require(strnlen_nid_postfix(bounded, 0) == 0); + Require(strnlen_nid_postfix(bounded, sizeof(bounded)) == 3); + Require(strnlen_nid_postfix("a", 8) == 1); + char truncated[] = "abXX"; + Require(strlcat_nid_postfix(truncated, "cd", 2) == 4); + Require(std::strcmp(truncated, "abXX") == 0); + char buffer[8] = "ab"; + Require(strlcat_nid_postfix(buffer, "cdefgh", sizeof(buffer)) == 8); + Require(std::strcmp(buffer, "abcdefg") == 0); + Require(strlcat_nid_postfix(buffer, "xyz", 0) == 3); + buffer[0] = '\0'; + Require(strlcat_nid_postfix(buffer, "x", 1) == 1 && buffer[0] == '\0'); + Require(strncat_nid_postfix(buffer, "xyz", 2) == buffer); + Require(std::strcmp(buffer, "xy") == 0); + Require(strpbrk_nid_postfix(buffer, "ay") == buffer + 1); + Require(strpbrk_nid_postfix(buffer, "") == nullptr); + Require(strcspn_nid_postfix(buffer, "y") == 1); + char first[] = ",a,,b,"; + char second[] = "x:y"; + char* firstState = nullptr; + char* secondState = nullptr; + Require(std::strcmp(strtok_r_nid_postfix(first, ",", &firstState), "a") == 0); + Require(std::strcmp(strtok_r_nid_postfix(second, ":", &secondState), "x") == 0); + Require(std::strcmp(strtok_r_nid_postfix(nullptr, ",", &firstState), "b") == 0); + Require(strtok_r_nid_postfix(nullptr, ",", &firstState) == nullptr); + Require(strtok_r_nid_postfix(nullptr, ",", &firstState) == nullptr); + Require(std::strcmp(strtok_r_nid_postfix(nullptr, "", &secondState), "y") == 0); + char hostTokens[] = "host:next"; + Require(std::strcmp(std::strtok(hostTokens, ":"), "host") == 0); + char guestTokens[] = ",one,,two:three"; + Require(std::strcmp(strtok_nid_postfix(guestTokens, ","), "one") == 0); + Require(std::strcmp(strtok_nid_postfix(nullptr, ":,"), "two") == 0); + Require(std::strcmp(strtok_nid_postfix(nullptr, ""), "three") == 0); + Require(strtok_nid_postfix(nullptr, ",") == nullptr); + Require(strtok_nid_postfix(nullptr, ",") == nullptr); + Require(std::strcmp(std::strtok(nullptr, ":"), "next") == 0); + const char text[] = "aABAbC"; + Require(strcasestr_nid_postfix(text, "ababc") == text + 1); + Require(strcasestr_nid_postfix(text, "") == text); + Require(strcasestr_nid_postfix(text, "abcdef") == nullptr); + Require(strcasestr_nid_postfix("", "a") == nullptr); + const char highBytes[] = {static_cast(0xff), 'A', 0}; + Require(strcasestr_nid_postfix(highBytes, "a") == highBytes + 1); +} diff --git a/core/libs/tests/GuestSystemConfiguration.cpp b/core/libs/tests/GuestSystemConfiguration.cpp new file mode 100644 index 000000000..be698e8b5 --- /dev/null +++ b/core/libs/tests/GuestSystemConfiguration.cpp @@ -0,0 +1,21 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +extern "C" { +std::int64_t APS5_VABI sysconf_nid_postfix(int); +int APS5_VABI getpagesize_nid_postfix(); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + *__error_nid_postfix() = 13; + Require(sysconf_nid_postfix(47) == 0x4000); + Require(getpagesize_nid_postfix() == sysconf_nid_postfix(47)); + Require(sysconf_nid_postfix(57) > 0); + Require(sysconf_nid_postfix(58) > 0); + Require(sysconf_nid_postfix(121) > 0); + Require(*__error_nid_postfix() == 13); + Require(sysconf_nid_postfix(-1) == -1); // verifies full-width signed return + Require(*__error_nid_postfix() == 22); + Require(sysconf_nid_postfix(0x7fffffff) == -1); +} diff --git a/core/libs/tests/GuestSystemExit.cpp b/core/libs/tests/GuestSystemExit.cpp new file mode 100644 index 000000000..701aca721 --- /dev/null +++ b/core/libs/tests/GuestSystemExit.cpp @@ -0,0 +1,39 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libSceSystemService/SystemService.hpp" +#include +#include +#include +#include +extern "C" int APS5_VABI sceSystemServiceLoadExec(const char*, const char* const*); +extern "C" void APS5_VABI _Exit_nid_postfix(int); +namespace { +bool cleaned = false; +void Cleanup() { cleaned = true; } +void VerifyExit() { + if (!cleaned) std::_Exit(1); + std::puts("Guest shutdown and atexit completed"); +} +void Require(bool value) { if (!value) std::abort(); } +void UnexpectedCleanup() { std::_Exit(3); } +} +int main(int argc, char** argv) { + if (argc > 1 && std::strcmp(argv[1], "--immediate") == 0) { + LibcRegisterShutdown_nid_postfix(UnexpectedCleanup); + Require(std::atexit(UnexpectedCleanup) == 0); + _Exit_nid_postfix(0); + return 2; + } + if (argc > 1) { + LibcRegisterShutdown_nid_postfix(Cleanup); + Require(std::atexit(VerifyExit) == 0); + sceSystemServiceLoadExec("exit", nullptr); + return 2; + } + Require(sceSystemServiceLoadExec(nullptr, nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + Require(sceSystemServiceLoadExec("", nullptr) == SYSTEM_SERVICE_ERROR_PARAMETER); + bool rejected = false; + try { sceSystemServiceLoadExec("/app0/another.bin", nullptr); } + catch (const std::runtime_error&) { rejected = true; } + Require(rejected); +} diff --git a/core/libs/tests/GuestUdp.cpp b/core/libs/tests/GuestUdp.cpp new file mode 100644 index 000000000..572e8dc3a --- /dev/null +++ b/core/libs/tests/GuestUdp.cpp @@ -0,0 +1,69 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +extern "C" { +int APS5_VABI socket_nid_postfix(int, int, int); +int APS5_VABI fcntl_nid_postfix(int, int, ...); +int APS5_VABI setsockopt_nid_postfix(int, int, int, const void*, std::uint32_t); +int APS5_VABI getsockopt_nid_postfix(int, int, int, void*, std::uint32_t*); +int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t); +int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*); +int APS5_VABI ioctl_nid_postfix(int, std::uint64_t, void*); +std::int64_t APS5_VABI sendto_nid_postfix(int, const void*, std::uint64_t, int, const void*, std::uint32_t); +std::int64_t APS5_VABI recvfrom_nid_postfix(int, void*, std::uint64_t, int, void*, std::uint32_t*); +int APS5_VABI close_nid_postfix(int); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const int receiver = socket_nid_postfix(2, 2, 0); + const int sender = socket_nid_postfix(2, 2, 17); + Require(receiver >= 0 && sender >= 0 && receiver != sender); + std::array destination{16, 2, 0, 0, 127, 0, 0, 1}; + Require(bind_nid_postfix(receiver, destination.data(), destination.size()) == 0); + std::uint32_t size = destination.size(); + Require(getsockname_nid_postfix(receiver, destination.data(), &size) == 0 && size == 16); + Require(destination[2] || destination[3]); + int enabled = 1; + Require(setsockopt_nid_postfix(sender, 0xffff, 0x20, &enabled, sizeof(enabled)) == 0); + int option = 0; + std::uint32_t optionSize = sizeof(option); + Require(getsockopt_nid_postfix(sender, 0xffff, 0x20, &option, &optionSize) == 0 && option != 0); + Require(setsockopt_nid_postfix(sender, 0xffff, 12345, &enabled, sizeof(enabled)) == -1); + Require(*__error_nid_postfix() == 42); + Require(ioctl_nid_postfix(receiver, 0x8004667e, &enabled) == 0); + Require(fcntl_nid_postfix(receiver, 3) == 6); + Require(fcntl_nid_postfix(receiver, 4, 2) == 0); + Require(fcntl_nid_postfix(receiver, 3) == 2); + Require(fcntl_nid_postfix(receiver, 4, 6) == 0); + Require(fcntl_nid_postfix(receiver, 3) == 6); + Require(fcntl_nid_postfix(receiver, 4, 0x8000) == -1); + Require(fcntl_nid_postfix(receiver, 3) == 6); + char buffer[64]{}; + Require(recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, nullptr, nullptr) == -1); + Require(*__error_nid_postfix() == 35); + const char message[] = "guest UDP loopback"; + Require(sendto_nid_postfix(sender, message, sizeof(message), 0, destination.data(), destination.size()) == sizeof(message)); + int queued = 0; + for (int i = 0; i < 100 && queued == 0; ++i) { + Require(ioctl_nid_postfix(receiver, 0x4004667f, &queued) == 0); + if (!queued) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + Require(queued == sizeof(message)); + Require(recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 2, nullptr, nullptr) == sizeof(message)); + std::array source{}; + size = source.size(); + Require(recvfrom_nid_postfix(receiver, buffer, sizeof(buffer), 0, source.data(), &size) == sizeof(message)); + Require(std::strcmp(buffer, message) == 0 && source[1] == 2 && source[4] == 127); + Require(ioctl_nid_postfix(receiver, 0x4004667f, &queued) == 0 && queued == 0); + Require(ioctl_nid_postfix(receiver, 123, &queued) == -1 && *__error_nid_postfix() == 25); + Require(close_nid_postfix(receiver) == 0); + Require(close_nid_postfix(receiver) == -1 && *__error_nid_postfix() == 9); + Require(ioctl_nid_postfix(receiver, 0x4004667f, &queued) == -1); + Require(close_nid_postfix(sender) == 0); + Require(socket_nid_postfix(2, 1, 0) == -1); // unsupported TCP must not appear to work +} diff --git a/core/libs/tests/GuestWorkingDirectory.cpp b/core/libs/tests/GuestWorkingDirectory.cpp new file mode 100644 index 000000000..feeb9a9f4 --- /dev/null +++ b/core/libs/tests/GuestWorkingDirectory.cpp @@ -0,0 +1,40 @@ +#include "prx/libc/include/General.hpp" +#include "prx/libc/include/GuestHeap.hpp" +#include +#include +#include +#include +extern "C" { +int APS5_VABI chdir_nid_postfix(const char*); +char* APS5_VABI getcwd_nid_postfix(char*, std::size_t); +int* APS5_VABI __error_nid_postfix(); +} +static void Require(bool value) { if (!value) std::abort(); } +int main() { + const auto host = std::filesystem::canonical(std::filesystem::current_path()); + char path[1024]; + Require(getcwd_nid_postfix(path, sizeof(path)) == path && std::strcmp(path, "/") == 0); + const auto name = "anyps5-cwd-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()); + const auto directory = host / name; + Require(std::filesystem::create_directory(directory)); + { std::ofstream file(directory / "sample.txt"); file << "sample"; } + Require(chdir_nid_postfix(name.c_str()) == 0); + Require(std::filesystem::current_path() == host); + Require(getcwd_nid_postfix(path, sizeof(path)) == path && path == "/" + name); + Require(ResolvePath_nid_no_patch("sample.txt") == directory / "sample.txt"); + Require(ResolvePath_nid_no_patch(("/" + name + "/sample.txt").c_str()) == directory / "sample.txt"); + char tiny[] = "xyz"; + Require(getcwd_nid_postfix(tiny, 2) == nullptr && *__error_nid_postfix() == 34); + Require(std::strcmp(tiny, "xyz") == 0); + char* allocated = getcwd_nid_postfix(nullptr, 0); + Require(allocated && std::strcmp(allocated, path) == 0); + GuestHeap::GuestHeapFree_nid_postfix(allocated); + Require(chdir_nid_postfix("sample.txt") == -1 && *__error_nid_postfix() == 20); + Require(chdir_nid_postfix("missing") == -1 && *__error_nid_postfix() == 2); + Require(chdir_nid_postfix("..") == 0); + Require(getcwd_nid_postfix(path, sizeof(path)) == path && std::strcmp(path, "/") == 0); + Require(chdir_nid_postfix("../..") == 0); + Require(getcwd_nid_postfix(path, sizeof(path)) == path && std::strcmp(path, "/") == 0); + std::filesystem::remove(directory / "sample.txt"); + std::filesystem::remove(directory); +} diff --git a/core/libs/tests/Mspace.cpp b/core/libs/tests/Mspace.cpp new file mode 100644 index 000000000..f286a7dc5 --- /dev/null +++ b/core/libs/tests/Mspace.cpp @@ -0,0 +1,65 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +void* APS5_VABI sceLibcMspaceCreate_nid_postfix(const char*, void*, std::size_t, unsigned); +int APS5_VABI sceLibcMspaceDestroy_nid_postfix(void*); +void* APS5_VABI sceLibcMspaceMalloc_nid_postfix(void*, std::size_t); +void* APS5_VABI sceLibcMspaceCalloc_nid_postfix(void*, std::size_t, std::size_t); +void* APS5_VABI sceLibcMspaceRealloc_nid_postfix(void*, void*, std::size_t); +void APS5_VABI sceLibcMspaceFree_nid_postfix(void*, void*); +int APS5_VABI sceLibcMspacePosixMemalign_nid_postfix(void*, void**, std::size_t, std::size_t); +std::size_t APS5_VABI sceLibcMspaceMallocUsableSize_nid_postfix(const void*); +} +static void Require(bool value) { + if (!value) { std::fputs("Mspace check failed\n", stderr); std::abort(); } +} +int main() { + alignas(16) std::array storage{}; + void* arena = sceLibcMspaceCreate_nid_postfix("test", storage.data(), storage.size(), 0); + Require(arena != nullptr); + Require(sceLibcMspaceCreate_nid_postfix("overlap", storage.data(), storage.size(), 0) == nullptr); + auto* first = static_cast(sceLibcMspaceCalloc_nid_postfix(arena, 32, 4)); + Require(first > storage.data() && first + 128 <= storage.data() + storage.size()); + for (int i = 0; i < 128; ++i) { Require(first[i] == 0); first[i] = static_cast(i); } + void* blocker = sceLibcMspaceMalloc_nid_postfix(arena, 128); + auto* grown = static_cast(sceLibcMspaceRealloc_nid_postfix(arena, first, 4096)); + Require(grown && grown != first); + for (int i = 0; i < 128; ++i) Require(grown[i] == i); + Require(sceLibcMspaceMallocUsableSize_nid_postfix(grown) >= 4096); + Require(sceLibcMspaceRealloc_nid_postfix(arena, grown, storage.size()) == nullptr); + Require(grown[127] == 127); + void* aligned = nullptr; + Require(sceLibcMspacePosixMemalign_nid_postfix(arena, &aligned, 4096, 1024) == 0); + Require((reinterpret_cast(aligned) & 4095) == 0); + void* unchanged = aligned; + Require(sceLibcMspacePosixMemalign_nid_postfix(arena, &unchanged, 3, 8) == 22 && unchanged == aligned); + Require(sceLibcMspaceCalloc_nid_postfix(arena, std::numeric_limits::max(), 2) == nullptr); + sceLibcMspaceFree_nid_postfix(arena, blocker); + sceLibcMspaceFree_nid_postfix(arena, aligned); + Require(sceLibcMspaceRealloc_nid_postfix(arena, grown, 0) == nullptr); + void* large = sceLibcMspaceMalloc_nid_postfix(arena, storage.size() - 256); + Require(large != nullptr); // freeing coalesced all the fragmented blocks + sceLibcMspaceFree_nid_postfix(arena, large); + std::array workers; + for (auto& worker : workers) worker = std::thread([&] { + for (int i = 0; i < 1000; ++i) { + void* pointer = sceLibcMspaceMalloc_nid_postfix(arena, 97); + Require(pointer != nullptr); + std::memset(pointer, 42, 97); + Require(sceLibcMspaceMallocUsableSize_nid_postfix(pointer) >= 97); + sceLibcMspaceFree_nid_postfix(arena, pointer); + } + }); + for (auto& worker : workers) worker.join(); + Require(sceLibcMspaceDestroy_nid_postfix(arena) == 0); + Require(sceLibcMspaceMalloc_nid_postfix(arena, 8) == nullptr); + Require(sceLibcMspaceCreate_nid_postfix("reuse", storage.data(), storage.size(), 0) == arena); + Require(sceLibcMspaceDestroy_nid_postfix(arena) == 0); +} diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 732539688..42ebb17ba 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -3,11 +3,17 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cli/src/Autorun.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxElfPatcher.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsGuestStartup.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestImageReader.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/guest/GuestModuleBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPePatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsPeFormat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsLoadImage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTrampolineBuilder.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsRelocationBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsImportBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp @@ -34,6 +40,7 @@ add_executable(relinker ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CodeInstructionCollector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp @@ -125,6 +132,17 @@ if(BUILD_TESTING) ) add_test(NAME amd64_only_converter COMMAND amd64_only_converter_tests) + find_package(Python3 QUIET COMPONENTS Interpreter) + if(Python3_Interpreter_FOUND) + add_test(NAME optional_plt COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_optional_plt.py $) + add_test(NAME empty_tls COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_empty_tls.py $) + add_test(NAME tls_function_coverage COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_tls_function_coverage.py $) + else() + message(STATUS "Python3 interpreter not found; skipping Python relinker tests") + endif() add_executable(strict_nid_filter_tests ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/StrictNidFilterTests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp diff --git a/core/relinker/cli/include/Cli.hpp b/core/relinker/cli/include/Cli.hpp index 369cb7f60..63c1bd38b 100644 --- a/core/relinker/cli/include/Cli.hpp +++ b/core/relinker/cli/include/Cli.hpp @@ -8,6 +8,7 @@ namespace Cli { struct Args { bool skipSyscallCheck = false; + bool skipSceModule = false; bool toIntel = false; bool writeRegistry = false; bool toWindows = false; diff --git a/core/relinker/cli/src/CliArgs.cpp b/core/relinker/cli/src/CliArgs.cpp index 12b1631f0..7629f6802 100644 --- a/core/relinker/cli/src/CliArgs.cpp +++ b/core/relinker/cli/src/CliArgs.cpp @@ -13,6 +13,8 @@ Args ParseArgs(int argc, char* argv[]) { const std::string arg = argv[i]; if (arg == "--skip-syscall-check") { args.skipSyscallCheck = true; + } else if (arg == "--skip-sce-module") { + args.skipSceModule = true; } else if (arg == "--to-intel") { args.toIntel = true; } else if (arg.rfind("unused-filter=", 0) == 0) { @@ -51,7 +53,7 @@ Args ParseArgs(int argc, char* argv[]) { if (args.inputPath.empty() || args.outputPath.empty()) throw std::runtime_error( - "Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" + "Usage: relinker [--windows] [--windows-diagnostics] [--skip-syscall-check] [--skip-sce-module] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath ] [--lazy-binding] [--autorun] \n" "Example: relinker input.elf output.elf" ); diff --git a/core/relinker/domain/include/domain/GuestRuntime.hpp b/core/relinker/domain/include/domain/GuestRuntime.hpp new file mode 100644 index 000000000..43aae70b0 --- /dev/null +++ b/core/relinker/domain/include/domain/GuestRuntime.hpp @@ -0,0 +1,27 @@ +#ifndef DOMAIN_GUESTRUNTIME_HPP +#define DOMAIN_GUESTRUNTIME_HPP + +#include +#include +#include + +namespace Domain { + +struct GuestImport { + std::string Name; + std::uint32_t TargetRva; + std::uint64_t Addend; + std::uint32_t RelocationType = 1; +}; + +struct GuestRuntime { + std::string Path; + std::vector Imports; + std::uint32_t InitRva = 0; + std::uint32_t FiniRva = 0; + bool UsePlatformTlsResolver = true; +}; + +} + +#endif diff --git a/core/relinker/domain/include/domain/Types.hpp b/core/relinker/domain/include/domain/Types.hpp index f75570c5c..40bd960ce 100644 --- a/core/relinker/domain/include/domain/Types.hpp +++ b/core/relinker/domain/include/domain/Types.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Domain { @@ -92,6 +93,7 @@ struct SysVDynamicSection { std::vector DynStrData; std::vector RelaData; std::vector RelaPltData; + std::vector GuestModules; }; struct CallRegistryEntry { diff --git a/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp b/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp new file mode 100644 index 000000000..c820c3879 --- /dev/null +++ b/core/relinker/elfpatcher/include/elfpatcher/general/GuestModuleWriter.hpp @@ -0,0 +1,16 @@ +#ifndef ELFPATCHER_GUESTMODULEWRITER_HPP +#define ELFPATCHER_GUESTMODULEWRITER_HPP + +#include + +namespace Elfpatcher { + +class GuestModuleWriter { +public: + std::vector WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const; + std::vector WriteWindows(const Relinker::GuestImage& image, Domain::GuestRuntime& runtime) const; +}; + +} + +#endif diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp index 1ab27beca..a7a1225c1 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsEntryStubBuilder.hpp @@ -19,7 +19,7 @@ struct WindowsEntryStub { class WindowsEntryStubBuilder { public: - WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector& libraries, const std::vector& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics) const; + WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector& libraries, const std::vector& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector& guestModules = {}) const; }; } diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsGuestStartup.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsGuestStartup.hpp new file mode 100644 index 000000000..209c8d00b --- /dev/null +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsGuestStartup.hpp @@ -0,0 +1,21 @@ +#ifndef ELFPATCHER_WINDOWSGUESTSTARTUP_HPP +#define ELFPATCHER_WINDOWSGUESTSTARTUP_HPP + +#include +#include + +namespace Elfpatcher::Windows { + +class WindowsGuestStartup { +public: + void WriteImport(WindowsStubEmitter& code, const PeImport& import, std::uint32_t handles) const; + void Initialize(WindowsStubEmitter& code, const std::vector& modules, std::uint32_t handles) const; + void Finalize(WindowsStubEmitter& code, const std::vector& modules, std::uint32_t handles, std::uint32_t finished) const; + std::uint32_t EmitTlsResolver(WindowsStubEmitter& code) const; +private: + void callLifecycle(WindowsStubEmitter& code, std::uint32_t handle, std::uint32_t rva) const; +}; + +} + +#endif diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp index 9c2d5036d..bc1c0984d 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsPeFormat.hpp @@ -32,6 +32,8 @@ struct PeImport { std::string Name; std::uint32_t TargetRva; std::uint64_t Addend; + std::int32_t TargetModule = -1; + std::uint32_t RelocationType = 1; }; struct PeRelocations { diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp index 297ef5885..e7dc1ed7d 100644 --- a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsBuilder.hpp @@ -7,7 +7,7 @@ namespace Elfpatcher::Windows { class WindowsTlsBuilder { public: - PeDirectory Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva) const; + PeDirectory Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva = nullptr) const; }; } diff --git a/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsTemplateBuilder.hpp b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsTemplateBuilder.hpp new file mode 100644 index 000000000..4441a73c8 --- /dev/null +++ b/core/relinker/elfpatcher/include/elfpatcher/windows/WindowsTlsTemplateBuilder.hpp @@ -0,0 +1,15 @@ +#ifndef ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP +#define ELFPATCHER_WINDOWSTLSTEMPLATEBUILDER_HPP + +#include + +namespace Elfpatcher::Windows { + +class WindowsTlsTemplateBuilder { +public: + std::vector Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector& sections, std::uint32_t templateRva, std::vector& relocations) const; +}; + +} + +#endif diff --git a/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp new file mode 100644 index 000000000..31168ebd6 --- /dev/null +++ b/core/relinker/elfpatcher/src/linux/LinuxGuestModuleWriter.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include + +namespace Elfpatcher { + +std::vector GuestModuleWriter::WriteLinux(const Relinker::GuestImage& image, const std::vector& dependencies, const std::string& runPath) const { + auto bytes = image.Bytes; + std::vector headers; + std::uint64_t end = 0; + for (auto header : image.Headers) { + if (header.Type != 1 && header.Type != 7 && header.Type != 0x6474e550 && header.Type != 0x6474e551) continue; + if (header.Type == 1) { + header.Flags |= 4; + end = std::max(end, header.MappedAddress + header.MemorySize); + } + headers.push_back(header); + } + if (end > std::numeric_limits::max() - 0x4000) throw Domain::RelinkerException("Guest virtual address overflow"); + Io::AlignBuffer(bytes, 0x4000); + const auto extraOffset = bytes.size(); + const auto extraAddress = Io::AlignUp64(end, 0x4000); + const auto address = [&] { return extraAddress + bytes.size() - extraOffset; }; + auto strings = image.Dynamic.DynStrData; + auto symbols = image.Dynamic.DynSymData; + const auto addString = [&](const std::string& value) { + if (strings.size() > std::numeric_limits::max()) throw Domain::RelinkerException("Guest string table too large"); + const auto offset = static_cast(strings.size()); + Io::AppendString(strings, value); + return offset; + }; + bool needsTlsResolver = false; + for (std::size_t index = 1; index < image.Symbols.size(); ++index) { + const auto& symbol = image.Symbols[index]; + if (image.UsePlatformTlsResolver && symbol.Section == 0 && symbol.Name == "vNe1w4diLCs") { + Io::WriteU32(symbols, index * 24, addString("__tls_get_addr")); + needsTlsResolver = true; + } + if (symbol.Section == 0 && (symbol.Info >> 4) == 2) symbols[index * 24 + 4] = static_cast(0x10 | (symbol.Info & 15)); + } + std::vector needed; + for (const auto& dependency : dependencies) needed.push_back(addString(dependency)); + if (needsTlsResolver) needed.push_back(addString("ld-linux-x86-64.so.2")); + const auto soname = addString(image.OutputName); + const auto search = addString(runPath); + const auto strAddress = address(); + bytes.insert(bytes.end(), strings.begin(), strings.end()); + Io::AlignBuffer(bytes, 8); + const auto symAddress = address(); + bytes.insert(bytes.end(), symbols.begin(), symbols.end()); + const auto hashAddress = address(); + const auto count = static_cast(image.Symbols.size()); + Io::AppendU32(bytes, 1); + Io::AppendU32(bytes, count); + Io::AppendU32(bytes, count > 1 ? 1 : 0); + for (std::uint32_t index = 0; index < count; ++index) Io::AppendU32(bytes, index != 0 && index + 1 < count ? index + 1 : 0); + Io::AlignBuffer(bytes, 8); + const auto relaAddress = address(); + bytes.insert(bytes.end(), image.Dynamic.RelaData.begin(), image.Dynamic.RelaData.end()); + const auto pltAddress = address(); + bytes.insert(bytes.end(), image.Dynamic.RelaPltData.begin(), image.Dynamic.RelaPltData.end()); + const auto lifecycle = [&](std::uint64_t target) { + if (target == 0) return std::uint64_t{}; + const auto start = address(); + bytes.insert(bytes.end(), {0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xe9}); + const auto displacement = static_cast(target) - static_cast(address() + 4); + if (displacement < std::numeric_limits::min() || displacement > std::numeric_limits::max()) throw Domain::RelinkerException("Guest initializer exceeds relative branch range"); + Io::AppendU32(bytes, static_cast(displacement)); + return start; + }; + const auto init = lifecycle(image.Init); + const auto fini = lifecycle(image.Fini); + Io::AlignBuffer(bytes, 8); + const auto dynamicOffset = bytes.size(); + const auto dynamicAddress = address(); + const auto tag = [&](std::uint64_t key, std::uint64_t value) { Io::AppendU64(bytes, key); Io::AppendU64(bytes, value); }; + for (const auto offset : needed) tag(1, offset); + tag(14, soname); + tag(29, search); + tag(4, hashAddress); + tag(5, strAddress); + tag(10, strings.size()); + tag(6, symAddress); + tag(11, 24); + if (!image.Dynamic.RelaData.empty()) { + tag(7, relaAddress); + tag(8, image.Dynamic.RelaData.size()); + tag(9, 24); + } + if (!image.Dynamic.RelaPltData.empty()) { + tag(23, pltAddress); + tag(2, image.Dynamic.RelaPltData.size()); + tag(20, 7); + tag(3, image.Got); + } + if (init != 0) tag(12, init); + if (fini != 0) tag(13, fini); + tag(30, 8); + tag(0, 0); + const auto dynamicSize = bytes.size() - dynamicOffset; + const auto phOffset = bytes.size(); + const auto phCount = headers.size() + 2; + if (phCount > std::numeric_limits::max()) throw Domain::RelinkerException("Too many guest program headers"); + const auto extraSize = bytes.size() + phCount * 56 - extraOffset; + headers.push_back({1, 7, extraOffset, extraAddress, extraAddress, extraSize, extraSize, 0x4000}); + headers.push_back({2, 6, dynamicOffset, dynamicAddress, dynamicAddress, dynamicSize, dynamicSize, 8}); + for (const auto& header : headers) { + Io::AppendU32(bytes, header.Type); + Io::AppendU32(bytes, header.Flags); + Io::AppendU64(bytes, header.Offset); + Io::AppendU64(bytes, header.MappedAddress); + Io::AppendU64(bytes, header.PhysicalAddress); + Io::AppendU64(bytes, header.FileSize); + Io::AppendU64(bytes, header.MemorySize); + Io::AppendU64(bytes, header.Alignment); + } + bytes[7] = 0; + bytes[8] = 0; + Io::WriteU16(bytes, 16, 3); + Io::WriteU64(bytes, 24, 0); + Io::WriteU64(bytes, 32, phOffset); + Io::WriteU64(bytes, 40, 0); + Io::WriteU16(bytes, 56, static_cast(phCount)); + Io::WriteU16(bytes, 58, 0); + Io::WriteU16(bytes, 60, 0); + Io::WriteU16(bytes, 62, 0); + return bytes; +} + +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp index 51f41f827..94d25b832 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,7 @@ std::string normalizeRunPath(std::string path) { } -WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector& libraries, const std::vector& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics) const { +WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector& libraries, const std::vector& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector& guestModules) const { if (!imports.empty() && libraries.empty()) throw Domain::RelinkerException("ELF imports have no DT_NEEDED libraries"); const auto path = normalizeRunPath(runPath); @@ -54,14 +55,17 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const auto programPath = reserve(PathCapacity); const auto modulePath = reserve(PathCapacity); const auto handles = reserve(libraries.size() * 8); + const auto guestFinished = reserve(4); + const WindowsGuestStartup guestStartup; const auto functionTable = reserve(12 * 32); const auto unwindRva = CheckedRva(dataRva + data.size()); data.insert(data.end(), {1, 10, 6, 0, 10, 0xb2, 6, 0xc0, 4, 0x70, 3, 0x60, 2, 0x50, 1, 0x30}); std::vector libraryPaths; std::vector libraryNames; - for (const auto& library : libraries) { - auto name = path + library; + for (std::size_t index = 0; index < libraries.size(); ++index) { + auto name = index < guestModules.size() ? libraries[index] : path + libraries[index]; + std::replace(name.begin(), name.end(), '/', '\\'); if (name.size() + 1 >= PathCapacity) throw Domain::RelinkerException("Windows library path exceeds the startup buffer: " + name); libraryPaths.push_back(addString(name)); @@ -78,6 +82,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const auto loading = addString("Loading PRX: "); const auto loaded = addString(" -> OK\n"); const auto loadFailed = addString(" -> FAILED\n"); + const auto failedModule = addString("Failed to load module: "); const auto errorPrefix = addString("GetLastError: "); const auto messageSeparator = addString(" - "); const auto newline = addString("\n"); @@ -218,7 +223,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.Emit({0x49, 0xff, 0xc4}); for (std::size_t index = 0; index < libraries.size(); ++index) { - if (absolutePath) { + if (absolutePath && index >= guestModules.size()) { code.Rip({0x48, 0x8d, 0x0d}, libraryPaths[index]); } else { code.Emit({0x4c, 0x89, 0xe0, 0x48, 0x29, 0xd8, 0x48, 0x05}); @@ -253,6 +258,9 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const auto loadSucceeded = code.Branch({0x0f, 0x85}); captureLastError(); writeString(loadFailed, true); + writeString(failedModule, true); + writeString(resolvedPaths[index], true); + writeString(newline, true); writeLastError(); if (dependencyDiagnostics) { code.Rip({0x48, 0x8d, 0x0d}, resolvedPaths[index]); @@ -270,7 +278,14 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con std::vector unresolvedBranches; std::vector lazyUnresolvedImports; + std::vector tlsResolverAddresses; for (std::size_t index = 0; index < imports.size(); ++index) { + if (!guestModules.empty() && guestModules.front().UsePlatformTlsResolver && imports[index].Name == "vNe1w4diLCs") { + if (imports[index].Addend != 0) throw Domain::RelinkerException("TLS resolver import has an addend"); + tlsResolverAddresses.push_back(code.Branch({0x48, 0x8d, 0x05})); + guestStartup.WriteImport(code, imports[index], handles); + continue; + } code.Rip({0x48, 0x8d, 0x1d}, handles); code.Rip({0x48, 0x8d, 0x35}, symbolNames[index]); code.Emit({0xbd}); @@ -291,7 +306,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con code.U64(imports[index].Addend); code.Emit({0x48, 0x01, 0xd0}); } - code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva); + guestStartup.WriteImport(code, imports[index], handles); code.PatchBranch(skipGotWrite, code.GetRva()); continue; } @@ -299,20 +314,26 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con writeString(errorRvas.at(3 + index), true); unresolvedBranches.push_back(code.Branch({0xe9})); code.PatchBranch(resolved, code.GetRva()); + if (imports[index].RelocationType == 16) code.Emit({0x48, 0x8b, 0x00}); + if (imports[index].RelocationType == 17) code.Emit({0x48, 0x8b, 0x40, 8}); if (imports[index].Addend != 0) { code.Emit({0x48, 0xba}); code.U64(imports[index].Addend); code.Emit({0x48, 0x01, 0xd0}); } - code.Rip({0x48, 0x89, 0x05}, imports[index].TargetRva); + guestStartup.WriteImport(code, imports[index], handles); } + guestStartup.Initialize(code, guestModules, handles); writeString(enteringElf); code.Emit({0x48, 0xc7, 0x44, 0x24, 0x40, 1, 0, 0, 0}); code.Rip({0x48, 0x8d, 0x05}, programPath); code.Emit({0x48, 0x89, 0x44, 0x24, 0x48, 0x31, 0xc0, 0x48, 0x89, 0x44, 0x24, 0x50, 0x48, 0x89, 0x44, 0x24, 0x58, 0x48, 0x8d, 0x7c, 0x24, 0x40}); const auto exitCallback = code.Branch({0x48, 0x8d, 0x35}); code.Rip({0xe8}, entryRva); + code.Emit({0x89, 0x44, 0x24, 0x58}); + guestStartup.Finalize(code, guestModules, handles, guestFinished); + code.Emit({0x8b, 0x44, 0x24, 0x58}); code.Emit({0x89, 0xc1}); call("ExitProcess"); code.Emit({0x0f, 0x0b}); @@ -332,7 +353,12 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con const auto functionEnd = code.GetRva(); code.PatchBranch(exitCallback, functionEnd); + code.Emit({0x48, 0x83, 0xec, 0x28}); + guestStartup.Finalize(code, guestModules, handles, guestFinished); + code.Emit({0x48, 0x83, 0xc4, 0x28}); code.Emit({0xc3}); + const auto tlsResolver = guestStartup.EmitTlsResolver(code); + for (const auto offset : tlsResolverAddresses) code.PatchBranch(offset, tlsResolver); for (const auto index : lazyUnresolvedImports) { const auto stubRva = code.GetRva(); diff --git a/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp new file mode 100644 index 000000000..78b7c1d83 --- /dev/null +++ b/core/relinker/elfpatcher/src/windows/WindowsGuestModuleWriter.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Elfpatcher { + +std::vector GuestModuleWriter::WriteWindows(const Relinker::GuestImage& guest, Domain::GuestRuntime& runtime) const { + using namespace Windows; + WindowsLoadImage image(guest.Bytes, guest.Headers); + std::vector relocations; + std::vector> tlsModules; + std::map targets; + const auto apply = [&](const std::vector& table) { + for (std::size_t offset = 0; offset < table.size(); offset += 24) { + const auto target = Io::ReadU64(table, offset); + const auto info = Io::ReadU64(table, offset + 8); + const auto addend = Io::ReadU64(table, offset + 16); + const auto type = static_cast(info); + const auto symbolIndex = info >> 32; + const auto& symbol = guest.Symbols.at(symbolIndex); + const auto rva = image.GetRva(target, 8); + for (const auto& header : guest.Headers) { + if (header.Type != 7 || header.FileSize == 0) continue; + const auto templateRva = image.GetRva(header.MappedAddress, header.FileSize); + const auto templateEnd = static_cast(templateRva) + header.FileSize; + if (rva >= templateEnd || static_cast(rva) + 8 <= templateRva) continue; + if (rva < templateRva || templateEnd - rva < 8) throw Domain::RelinkerException("Guest relocation crosses the TLS template boundary", target); + if (type == 16 || (symbolIndex != 0 && symbol.Section == 0)) throw Domain::RelinkerException("Runtime-bound guest TLS template relocation is not supported", target); + } + const auto next = targets.lower_bound(target); + if ((next != targets.end() && next->first < target + 8) || (next != targets.begin() && std::prev(next)->second > target)) throw Domain::RelinkerException("Overlapping guest relocations", target); + targets.emplace(target, target + 8); + image.RequireWritable(target, 8); + if (type == 8) { + if (symbolIndex != 0) throw Domain::RelinkerException("RELATIVE guest relocation has a symbol", target); + image.WritePointer(target, image.GetRelocatedAddress(addend)); + relocations.push_back(rva); + } else if (type == 16) { + if (addend != 0 || (symbolIndex != 0 && (symbol.Info & 15) != 6)) throw Domain::RelinkerException("Invalid guest TLS module relocation", target); + if (symbolIndex != 0 && symbol.Section == 0) { + image.WritePointer(target, 0); + runtime.Imports.push_back({symbol.Name, rva, 0, 16}); + } else tlsModules.emplace_back(target, rva); + } else if (type == 17) { + if (symbolIndex != 0 && (symbol.Info & 15) != 6) throw Domain::RelinkerException("Invalid guest TLS offset relocation", target); + if (symbolIndex != 0 && symbol.Section == 0) { + image.WritePointer(target, 0); + runtime.Imports.push_back({symbol.Name, rva, addend, 17}); + } else image.WritePointer(target, symbol.Value + addend); + } else if (type == 18) { + const auto tls = std::find_if(guest.Headers.begin(), guest.Headers.end(), [](const auto& header) { return header.Type == 7; }); + if (tls == guest.Headers.end() || (symbolIndex != 0 && (symbol.Section == 0 || (symbol.Info & 15) != 6))) throw Domain::RelinkerException("Unsupported external static TLS relocation", target); + const auto size = Io::AlignUp64(tls->MemorySize, std::max(tls->Alignment, 16)); + image.WritePointer(target, symbol.Value + addend - size); + } else if (type == 1 || type == 6 || type == 7) { + if (symbolIndex == 0 || (type != 1 && addend != 0) || (symbol.Info & 15) == 6) throw Domain::RelinkerException("Invalid guest symbol relocation", target); + if (symbol.Section != 0) { + if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported special guest symbol section", target); + image.WritePointer(target, image.GetRelocatedAddress(symbol.Value) + addend); + relocations.push_back(rva); + } else { + if (symbol.Name.empty()) throw Domain::RelinkerException("Empty guest import", target); + image.WritePointer(target, 0); + runtime.Imports.push_back({symbol.Name, rva, addend}); + } + } else throw Domain::RelinkerException("Unsupported Windows guest relocation " + std::to_string(type), target); + } + }; + apply(guest.Dynamic.RelaData); + apply(guest.Dynamic.RelaPltData); + auto sections = image.BuildSections(); + auto nextRva = image.GetEndRva(); + std::array directories{}; + std::uint32_t tlsIndex = 0; + directories[9] = WindowsTlsBuilder().Build(guest.Bytes, guest.Headers, image, sections, relocations, nextRva, &tlsIndex); + for (const auto& [target, rva] : tlsModules) { + if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS relocation has no TLS block", target); + bool written = false; + for (auto& section : sections) { + if (rva < section.Rva || rva - section.Rva > section.Data.size() || section.Data.size() - (rva - section.Rva) < 8) continue; + Io::WriteU64(section.Data, rva - section.Rva, ImageBase + tlsIndex); + written = true; + break; + } + if (!written) throw Domain::RelinkerException("Guest TLS target is unmapped", target); + relocations.push_back(rva); + } + for (const auto& header : guest.Headers) { + if (header.Type != 0x6474e550) continue; + std::vector metadata(4); + Io::WriteU32(metadata, 0, image.GetRva(header.MappedAddress, header.FileSize)); + sections.push_back({".ehmeta", nextRva, SectionRead | 0x40u, std::move(metadata)}); + nextRva = AlignRva(nextRva + sections.back().Data.size()); + } + std::map exports; + PeSection tlsExports{".tlsrefs", nextRva, SectionRead | 0x40u, {}}; + for (const auto& symbol : guest.Symbols) { + if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue; + if (symbol.Section >= 0xff00) throw Domain::RelinkerException("Unsupported guest export section: " + symbol.Name); + std::uint32_t rva = 0; + if ((symbol.Info & 15) == 6) { + if (tlsIndex == 0) throw Domain::RelinkerException("Guest TLS export has no TLS block: " + symbol.Name); + rva = CheckedRva(tlsExports.Rva + tlsExports.Data.size()); + Io::AppendU64(tlsExports.Data, ImageBase + tlsIndex); + Io::AppendU64(tlsExports.Data, symbol.Value); + relocations.push_back(rva); + } else rva = image.GetRva(symbol.Value, std::max(symbol.Size, 1)); + if (!exports.emplace(symbol.Name, rva).second) throw Domain::RelinkerException("Duplicate guest export: " + symbol.Name); + } + if (!tlsExports.Data.empty()) { + nextRva = AlignRva(nextRva + tlsExports.Data.size()); + sections.push_back(std::move(tlsExports)); + } + if (exports.size() > 65535) throw Domain::RelinkerException("Too many guest PE exports"); + PeSection exportSection{".edata", nextRva, SectionRead | 0x40u, std::vector(40)}; + auto& data = exportSection.Data; + const auto count = CheckedRva(exports.size()); + const auto functions = data.size(); + data.resize(data.size() + count * 4); + const auto names = data.size(); + data.resize(data.size() + count * 4); + const auto ordinals = data.size(); + data.resize(data.size() + count * 2); + Io::WriteU32(data, 12, CheckedRva(nextRva + data.size())); + Io::AppendString(data, guest.OutputName); + Io::WriteU32(data, 16, 1); + Io::WriteU32(data, 20, count); + Io::WriteU32(data, 24, count); + Io::WriteU32(data, 28, CheckedRva(nextRva + functions)); + Io::WriteU32(data, 32, CheckedRva(nextRva + names)); + Io::WriteU32(data, 36, CheckedRva(nextRva + ordinals)); + std::size_t index = 0; + for (const auto& [name, rva] : exports) { + Io::WriteU32(data, functions + index * 4, rva); + Io::WriteU32(data, names + index * 4, CheckedRva(nextRva + data.size())); + Io::WriteU16(data, ordinals + index * 2, static_cast(index)); + Io::AppendString(data, name); + ++index; + } + directories[0] = {nextRva, CheckedRva(data.size())}; + nextRva = AlignRva(nextRva + data.size()); + sections.push_back(std::move(exportSection)); + auto relocationData = WindowsRelocationBuilder().BuildBaseRelocations(relocations); + if (!relocationData.empty()) { + directories[5] = {nextRva, CheckedRva(relocationData.size())}; + sections.push_back({".reloc", nextRva, SectionRead | 0x02000040u, std::move(relocationData)}); + nextRva = AlignRva(nextRva + sections.back().Data.size()); + } + const auto entry = nextRva; + sections.push_back({".dllmain", entry, SectionRead | SectionExecute | 0x20u, {0xb8, 1, 0, 0, 0, 0xc3}}); + runtime.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init); + runtime.FiniRva = guest.Fini == 0 ? 0 : image.GetRva(guest.Fini); + auto result = WindowsPeWriter().Write(sections, entry, directories); + const auto characteristics = Io::ReadU16(result, 0x80 + 22); + Io::WriteU16(result, 0x80 + 22, static_cast((characteristics | 0x2000) & ~1u)); + return result; +} + +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp b/core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp new file mode 100644 index 000000000..b5e212bc9 --- /dev/null +++ b/core/relinker/elfpatcher/src/windows/WindowsGuestStartup.cpp @@ -0,0 +1,43 @@ +#include + +namespace Elfpatcher::Windows { + +void WindowsGuestStartup::WriteImport(WindowsStubEmitter& code, const PeImport& import, const std::uint32_t handles) const { + if (import.TargetModule < 0) { + code.Rip({0x48, 0x89, 0x05}, import.TargetRva); + return; + } + code.Rip({0x48, 0x8b, 0x15}, CheckedRva(handles + static_cast(import.TargetModule) * 8)); + code.Emit({0x41, 0xb8}); + code.U32(import.TargetRva); + code.Emit({0x4a, 0x89, 0x04, 0x02}); +} + +void WindowsGuestStartup::callLifecycle(WindowsStubEmitter& code, const std::uint32_t handle, const std::uint32_t rva) const { + if (rva == 0) return; + code.Rip({0x48, 0x8b, 0x05}, handle); + code.Emit({0xb9}); + code.U32(rva); + code.Emit({0x48, 0x01, 0xc8, 0x31, 0xff, 0x31, 0xf6, 0x31, 0xd2, 0xff, 0xd0}); +} + +void WindowsGuestStartup::Initialize(WindowsStubEmitter& code, const std::vector& modules, const std::uint32_t handles) const { + for (std::size_t index = 0; index < modules.size(); ++index) callLifecycle(code, CheckedRva(handles + index * 8), modules[index].InitRva); +} + +void WindowsGuestStartup::Finalize(WindowsStubEmitter& code, const std::vector& modules, const std::uint32_t handles, const std::uint32_t finished) const { + code.Emit({0xb8, 1, 0, 0, 0}); + code.Rip({0x87, 0x05}, finished); + code.Emit({0x85, 0xc0}); + const auto skip = code.Branch({0x0f, 0x85}); + for (std::size_t index = modules.size(); index > 0; --index) callLifecycle(code, CheckedRva(handles + (index - 1) * 8), modules[index - 1].FiniRva); + code.PatchBranch(skip, code.GetRva()); +} + +std::uint32_t WindowsGuestStartup::EmitTlsResolver(WindowsStubEmitter& code) const { + const auto entry = code.GetRva(); + code.Emit({0x48, 0x8b, 0x07, 0x8b, 0x08, 0x65, 0x48, 0x8b, 0x04, 0x25, 0x58, 0, 0, 0, 0x48, 0x8b, 0x04, 0xc8, 0x48, 0x03, 0x47, 8, 0xc3}); + return entry; +} + +} diff --git a/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp index d1b54e371..7a449830c 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsImportBuilder.cpp @@ -48,6 +48,11 @@ std::vector WindowsImportBuilder::ReadLibraries(const Domain::SysVD throw Domain::RelinkerException("Invalid or duplicate DT_NEEDED library: " + name); result.push_back(std::move(name)); } + // AnyPS5 implements the C runtime in libc.prx. Windows GetProcAddress + // does not search a module's dependencies as ELF symbol lookup does. + // Keep the requested module first, then make its shared runtime visible. + if (unique.contains("libSceLibcInternal.prx") && !unique.contains("libc.prx")) + result.push_back("libc.prx"); return result; } diff --git a/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp b/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp index 68676bc2c..3097d3a55 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp @@ -80,10 +80,17 @@ std::vector WindowsPePatcher::Patch(const std::vector guestPaths; + for (std::size_t index = 0; index < dynamicSection.GuestModules.size(); ++index) { + const auto& module = dynamicSection.GuestModules[index]; + guestPaths.push_back(module.Path); + for (const auto& import : module.Imports) relocations.Imports.push_back({import.Name, import.TargetRva, import.Addend, static_cast(index), import.RelocationType}); + } + libraries.insert(libraries.begin(), guestPaths.begin(), guestPaths.end()); if (dependencyDiagnostics) writeDiagnosticsImports(relocations.Imports); - auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics); + auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics, dynamicSection.GuestModules); directories[3] = entry.ExceptionDirectory; const auto entryRva = entry.Code.Rva; sections.push_back(std::move(nativeImports.Section)); diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp index f2390a534..f0dd778e7 100644 --- a/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsBuilder.cpp @@ -1,6 +1,8 @@ #include #include +#include #include +#include #include #include #include @@ -14,6 +16,8 @@ namespace { struct TlsAccess { std::uint32_t Rva; std::size_t Length; + bool StoreImmediate; + std::uint32_t Immediate; }; void patchAccess(std::vector& sections, const TlsAccess& access, const std::uint32_t target) { @@ -35,12 +39,13 @@ void patchAccess(std::vector& sections, const TlsAccess& access, cons } -PeDirectory WindowsTlsBuilder::Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva) const { +PeDirectory WindowsTlsBuilder::Build(const std::vector& source, const std::vector& headers, const WindowsLoadImage& image, std::vector& sections, std::vector& relocations, std::uint32_t& nextRva, std::uint32_t* tlsIndexRva) const { const Domain::ProgramHeader* tls = nullptr; const Codegen::X64InstructionDecoder decoder; std::vector accesses; std::set branchTargets; + const auto instructions = Relinker::CodeInstructionCollector().Collect(source, headers); for (const auto& header : headers) { if (header.Type == 7) { @@ -50,7 +55,8 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co } if (header.Type != 1 || (header.Flags & 1) == 0) continue; - for (std::uint64_t offset = 0; offset < header.FileSize;) { + for (auto instruction = instructions.lower_bound(header.MappedAddress); instruction != instructions.end() && *instruction - header.MappedAddress < header.FileSize; ++instruction) { + const auto offset = *instruction - header.MappedAddress; const auto* bytes = source.data() + header.Offset + offset; const auto info = decoder.DecodeInstruction(bytes, header.FileSize - offset); const auto rva = image.GetRva(header.MappedAddress + offset, info.Length); @@ -63,11 +69,19 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co if (info.SegmentPrefix != 0) { const auto position = info.OpcodeOffset; - if (info.SegmentPrefix != 0x64 || info.RexPrefix != 0x48 || info.Length - position != 7 || bytes[position] != 0x8b || bytes[position + 1] != 0x04 || bytes[position + 2] != 0x25 || Io::ReadU32(source, header.Offset + offset + position + 3) != 0) + bool hasOperandSizePrefix = false; + bool supportedPrefixes = info.SegmentPrefix == 0x64; + for (std::size_t prefix = 0; prefix < position; ++prefix) { + const auto value = bytes[prefix]; + if (value == 0x66) hasOperandSizePrefix = true; + else if (value != 0x64 && !(prefix + 1 == position && value >= 0x40 && value <= 0x4f)) supportedPrefixes = false; + } + const bool loadPointer = supportedPrefixes && info.RexPrefix == 0x48 && info.Length - position == 7 && bytes[position] == 0x8b && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0; + const bool storeImmediate = supportedPrefixes && !hasOperandSizePrefix && (info.RexPrefix == 0 || info.RexPrefix == 0x40) && info.Length - position == 11 && bytes[position] == 0xc7 && bytes[position + 1] == 0x04 && bytes[position + 2] == 0x25 && Io::ReadU32(source, header.Offset + offset + position + 3) == 0x28; + if (!loadPointer && !storeImmediate) throw Domain::RelinkerException("Unsupported Windows guest TLS instruction", header.Offset + offset); - accesses.push_back({rva, info.Length}); + accesses.push_back({rva, info.Length, storeImmediate, storeImmediate ? Io::ReadU32(source, header.Offset + offset + position + 7) : 0}); } - offset += info.Length; } } @@ -77,14 +91,23 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co return {}; } - if (tls->MemorySize == 0 || tls->FileSize > tls->MemorySize || tls->Offset > source.size() || tls->FileSize > source.size() - tls->Offset || !std::has_single_bit(tls->Alignment) || tls->Alignment > 8192 || tls->MemorySize > 0x7fff0000u) + if (tls->FileSize > tls->MemorySize || tls->Offset > source.size() || tls->FileSize > source.size() - tls->Offset || (tls->Alignment > 1 && !std::has_single_bit(tls->Alignment)) || tls->Alignment > 8192 || tls->MemorySize > 0x7fff0000u) throw Domain::RelinkerException("Invalid or unsupported ELF TLS layout", tls->Offset); + // Native homebrew linkers can emit an empty PT_TLS placeholder. + // It needs no Windows TLS directory unless guest code accesses TLS. + if (tls->MemorySize == 0) { + if (!accesses.empty()) + throw Domain::RelinkerException("Guest TLS access with empty PT_TLS", tls->Offset); + return {}; + } const auto alignment = std::max(tls->Alignment, 16); const auto blockSize = CheckedRva((tls->MemorySize + alignment - 1) & ~(alignment - 1)); const auto templateOffset = CheckedRva((64 + alignment - 1) & ~(alignment - 1)); - PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector(templateOffset + blockSize + 16)}; + constexpr std::uint32_t threadControlBlockSize = 0x30; + PeSection data{".gtls", nextRva, SectionRead | SectionWrite | 0x40u, std::vector(templateOffset + blockSize + threadControlBlockSize)}; const auto indexRva = nextRva + 40; + if (tlsIndexRva != nullptr) *tlsIndexRva = indexRva; const auto callbackTableRva = nextRva + 48; const auto templateRva = nextRva + templateOffset; const auto codeRva = AlignRva(nextRva + data.Data.size()); @@ -113,19 +136,26 @@ PeDirectory WindowsTlsBuilder::Build(const std::vector& source, co throw Domain::RelinkerException("Branch enters a guest TLS instruction", *target); patchAccess(sections, access, code.GetRva()); code.Emit({0x48, 0x8d, 0x64, 0x24, 0x80, 0x51}); + if (access.StoreImmediate) code.Emit({0x50}); loadPointer(); + if (access.StoreImmediate) { + code.Emit({0xc7, 0x40, 0x28}); + code.U32(access.Immediate); + code.Emit({0x58}); + } code.Emit({0x59, 0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0}); code.Rip({0xe9}, CheckedRva(access.Rva + access.Length)); } - std::copy_n(source.begin() + tls->Offset, tls->FileSize, data.Data.begin() + templateOffset); + const auto templateBytes = WindowsTlsTemplateBuilder().Build(*tls, image, sections, templateRva, relocations); + std::copy(templateBytes.begin(), templateBytes.end(), data.Data.begin() + templateOffset); const auto writeAddress = [&](const std::size_t offset, const std::uint32_t rva) { Io::WriteU64(data.Data, offset, ImageBase + rva); relocations.push_back(CheckedRva(data.Rva + offset)); }; writeAddress(0, templateRva); - writeAddress(8, CheckedRva(templateRva + blockSize + 16)); + writeAddress(8, CheckedRva(templateRva + blockSize + threadControlBlockSize)); writeAddress(16, indexRva); writeAddress(24, callbackTableRva); writeAddress(48, codeRva); diff --git a/core/relinker/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp b/core/relinker/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp new file mode 100644 index 000000000..4ec5556f0 --- /dev/null +++ b/core/relinker/elfpatcher/src/windows/WindowsTlsTemplateBuilder.cpp @@ -0,0 +1,32 @@ +#include +#include + +namespace Elfpatcher::Windows { + +std::vector WindowsTlsTemplateBuilder::Build(const Domain::ProgramHeader& tls, const WindowsLoadImage& image, const std::vector& sections, const std::uint32_t templateRva, std::vector& relocations) const { + if (tls.FileSize == 0) return {}; + const auto sourceRva = image.GetRva(tls.MappedAddress, tls.FileSize); + const auto sourceEnd = static_cast(sourceRva) + tls.FileSize; + std::vector result; + result.reserve(static_cast(tls.FileSize)); + while (result.size() < tls.FileSize) { + const auto currentRva = static_cast(sourceRva) + result.size(); + const auto section = std::find_if(sections.begin(), sections.end(), [&](const auto& candidate) { + return currentRva >= candidate.Rva && currentRva - candidate.Rva < candidate.Data.size(); + }); + if (section == sections.end()) throw Domain::RelinkerException("TLS template is outside PE section data", currentRva); + const auto offset = static_cast(currentRva - section->Rva); + const auto size = std::min(tls.FileSize - result.size(), section->Data.size() - offset); + result.insert(result.end(), section->Data.begin() + offset, section->Data.begin() + offset + size); + } + const auto originalCount = relocations.size(); + for (std::size_t index = 0; index < originalCount; ++index) { + const auto target = static_cast(relocations[index]); + if (target >= sourceEnd || target + 8 <= sourceRva) continue; + if (target < sourceRva || sourceEnd - target < 8) throw Domain::RelinkerException("PE relocation crosses the TLS template boundary", target); + relocations.push_back(CheckedRva(static_cast(templateRva) + target - sourceRva)); + } + return result; +} + +} diff --git a/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp b/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp index 77f0a6f89..9866f1f07 100644 --- a/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp +++ b/core/relinker/elfpatcher/tests/WindowsDependencyDiagnosticsTests.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,29 @@ namespace { namespace Fs = std::filesystem; using namespace Elfpatcher::Windows; +void checkRuntimeDependencies() { + const auto check = [](const std::vector& input, const std::vector& expected) { + Domain::SysVDynamicSection dynamic; + for (const auto& name : input) { + const auto offset = dynamic.DynStrData.size(); + dynamic.DynStrData.insert(dynamic.DynStrData.end(), name.begin(), name.end()); + dynamic.DynStrData.push_back(0); + const auto position = dynamic.DynamicSegmentData.size(); + dynamic.DynamicSegmentData.resize(position + 16); + Io::WriteU64(dynamic.DynamicSegmentData, position, 1); + Io::WriteU64(dynamic.DynamicSegmentData, position + 8, offset); + } + if (WindowsImportBuilder{}.ReadLibraries(dynamic) != expected) + throw std::runtime_error("Incorrect Windows runtime dependency visibility"); + }; + check({}, {}); + check({"libkernel.prx"}, {"libkernel.prx"}); + check({"libSceLibcInternal.prx", "libkernel.prx"}, + {"libSceLibcInternal.prx", "libkernel.prx", "libc.prx"}); + check({"libc.prx", "libSceLibcInternal.prx"}, + {"libc.prx", "libSceLibcInternal.prx"}); +} + void writeFile(const Fs::path& path, const std::vector& bytes) { std::ofstream stream(path, std::ios::binary); if (!stream.write(reinterpret_cast(bytes.data()), static_cast(bytes.size()))) @@ -154,6 +178,7 @@ void expectDiagnostic(const Fs::path& runner, const std::vector& ex int main() { try { + checkRuntimeDependencies(); std::vector filename(32768); const auto size = GetModuleFileNameA(nullptr, filename.data(), static_cast(filename.size())); if (size == 0 || size >= filename.size()) diff --git a/core/relinker/main.cpp b/core/relinker/main.cpp index a7654d9b1..7904d6b6b 100644 --- a/core/relinker/main.cpp +++ b/core/relinker/main.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -53,10 +54,11 @@ int main(const int argc, char* argv[]) { } auto elfReader = std::make_shared(sourceBytes); + const std::shared_ptr syscallScanner = args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(); const auto pipeline = std::make_shared( elfReader, - args.skipSyscallCheck ? Relinker::MakeNullSyscallScanner() : Relinker::MakeSyscallScanner(), + syscallScanner, Relinker::MakeCallSiteResolver(), std::make_shared(), std::make_shared(), @@ -65,6 +67,7 @@ int main(const int argc, char* argv[]) { ); std::cout << "System: " << (args.toWindows ? "Windows" : "Linux") << "; unused-filter=" << args.unusedFilterLevel << "\n"; + std::cout << "sce_module/sce_modules processing: " << (args.skipSceModule ? "disabled (--skip-sce-module)" : "enabled") << '\n'; auto result = pipeline->Relink(sourceBytes); for (const auto& patch : result.Patches) { if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset) @@ -72,6 +75,11 @@ int main(const int argc, char* argv[]) { for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index]; } + std::vector guestArtifacts; + if (!args.skipSceModule) { + guestArtifacts = Relinker::GuestModuleBuilder().Build(args.inputPath, absPath, result.DynamicSection, args.toWindows, args.toIntel, *syscallScanner, args.lazyBinding, args.runPath); + } + if (args.writeRegistry) { const std::filesystem::path outFsPath(absPath); const std::string registryPath = (outFsPath.parent_path() / (outFsPath.stem().string() + ".registry.json")).string(); @@ -95,7 +103,13 @@ int main(const int argc, char* argv[]) { ); } - fileWriter.Write(absPath, patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics, trampolines)); + const auto executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, args.runPath, args.lazyBinding, args.windowsDiagnostics, trampolines); + for (const auto& artifact : guestArtifacts) { + std::filesystem::create_directories(artifact.Path.parent_path()); + fileWriter.Write(artifact.Path.string(), artifact.Bytes); + std::cout << "Guest module: " << artifact.Path.string() << '\n'; + } + fileWriter.Write(absPath, executableBytes); std::cout << "External prx references: " << result.RegistryEntries.size() << "\nOutput file: " << absPath << '\n'; if (args.autorun) return Cli::Autorun(absPath, args.toWindows); diff --git a/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp b/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp new file mode 100644 index 000000000..2146ab9cd --- /dev/null +++ b/core/relinker/relinker/include/relinker/analysis/CodeInstructionCollector.hpp @@ -0,0 +1,16 @@ +#ifndef RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP +#define RELINKER_CODEINSTRUCTIONCOLLECTOR_HPP + +#include +#include + +namespace Relinker { + +class CodeInstructionCollector { +public: + std::set Collect(const std::vector& bytes, const std::vector& headers) const; +}; + +} + +#endif diff --git a/core/relinker/relinker/include/relinker/guest/GuestImage.hpp b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp new file mode 100644 index 000000000..71cca90fa --- /dev/null +++ b/core/relinker/relinker/include/relinker/guest/GuestImage.hpp @@ -0,0 +1,52 @@ +#ifndef RELINKER_GUESTIMAGE_HPP +#define RELINKER_GUESTIMAGE_HPP + +#include +#include +#include + +namespace Relinker { + +inline constexpr char GuestModuleSuffix[] = ".guest.prx"; + +struct GuestSymbol { + std::string Name; + std::uint8_t Info; + std::uint8_t Visibility; + std::uint16_t Section; + std::uint64_t Value; + std::uint64_t Size; +}; + +struct GuestImage { + std::filesystem::path SourcePath; + std::string OutputName; + std::vector Bytes; + std::vector Headers; + std::vector Symbols; + std::vector Dependencies; + Domain::SysVDynamicSection Dynamic; + std::uint64_t Init = 0; + std::uint64_t Fini = 0; + std::uint64_t Got = 0; + bool UsePlatformTlsResolver = true; +}; + +class GuestImageReader { +public: + GuestImage Read(const std::filesystem::path& path, std::vector bytes) const; +}; + +struct GuestArtifact { + std::filesystem::path Path; + std::vector Bytes; +}; + +class GuestModuleBuilder { +public: + std::vector Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, bool windows, bool toIntel, ISyscallScanner& syscallScanner, bool lazyBinding, const std::string& runPath) const; +}; + +} + +#endif diff --git a/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp new file mode 100644 index 000000000..0b009c39f --- /dev/null +++ b/core/relinker/relinker/src/analysis/CodeInstructionCollector.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +namespace { + +class CodePointers : public UnusedNidFilter::IRelativeRelocationIndex { +public: + std::map Values; + std::optional TargetOfSlot(Domain::VirtualAddress address) const override { + const auto found = Values.find(address); + if (found == Values.end()) return std::nullopt; + return found->second; + } +}; + +} + +std::set CodeInstructionCollector::Collect(const std::vector& bytes, const std::vector& headers) const { + const auto range = [&](std::uint64_t offset, std::uint64_t size) { + if (offset > bytes.size() || size > bytes.size() - offset) throw Domain::RelinkerException("Code analysis: file range exceeds image", offset); + }; + const auto fileOffset = [&](std::uint64_t address, std::uint64_t size) { + for (const auto& header : headers) { + if (header.Type != 1 || address < header.MappedAddress || address - header.MappedAddress > header.FileSize || size > header.FileSize - (address - header.MappedAddress)) continue; + const auto offset = header.Offset + address - header.MappedAddress; + range(offset, size); + return offset; + } + throw Domain::RelinkerException("Code analysis: unmapped address", address); + }; + const auto isCode = [&](std::uint64_t address) { + for (const auto& header : headers) if (header.Type == 1 && (header.Flags & 1) != 0 && address >= header.MappedAddress && address - header.MappedAddress < header.FileSize) return true; + return false; + }; + std::map tags; + std::optional sceBase; + bool hasDynamic = false; + for (const auto& header : headers) { + if (header.Type == 0x61000000) sceBase = header.Offset; + if (header.Type != 2) continue; + if (hasDynamic || header.FileSize % 16 != 0) throw Domain::RelinkerException("Code analysis: invalid dynamic segment"); + hasDynamic = true; + range(header.Offset, header.FileSize); + bool terminated = false; + for (std::uint64_t offset = header.Offset; offset < header.Offset + header.FileSize; offset += 16) { + const auto tag = Io::ReadU64(bytes, offset); + if (tag == 0) { terminated = true; break; } + const auto value = Io::ReadU64(bytes, offset + 8); + if (tag == 1 || (tag >= 0x61000000 && tag != 0x6100003f && (tag < 0x61000027 || tag > 0x6100003b))) continue; + if (!tags.emplace(tag, value).second) throw Domain::RelinkerException("Code analysis: duplicate dynamic tag", offset); + } + if (!terminated) throw Domain::RelinkerException("Code analysis: unterminated dynamic segment"); + } + const auto value = [&](std::uint64_t standard, std::uint64_t sce) -> std::optional { + if (tags.contains(standard) && tags.contains(sce)) throw Domain::RelinkerException("Code analysis: ambiguous dynamic tag"); + if (tags.contains(standard)) return tags.at(standard); + if (tags.contains(sce)) return tags.at(sce); + return std::nullopt; + }; + const auto table = [&](std::uint64_t standard, std::uint64_t sce, std::uint64_t size) { + const auto address = value(standard, sce); + if (!address) throw Domain::RelinkerException("Code analysis: missing table"); + if (tags.contains(standard)) return fileOffset(*address, size); + if (!sceBase || *address > std::numeric_limits::max() - *sceBase) throw Domain::RelinkerException("Code analysis: invalid SCE table"); + const auto offset = *sceBase + *address; + range(offset, size); + return offset; + }; + std::set roots; + std::map functions; + const auto addRoot = [&](std::uint64_t address) { if (isCode(address)) roots.insert(address); }; + const auto addFunction = [&](std::uint64_t begin, std::uint64_t size) { + if (size == 0) return; + if (!isCode(begin) || size > std::numeric_limits::max() - begin) + throw Domain::RelinkerException("Code analysis: invalid function range", begin); + bool mapped = false; + for (const auto& header : headers) { + if (header.Type != 1 || (header.Flags & 1) == 0 || begin < header.MappedAddress || begin - header.MappedAddress >= header.FileSize) continue; + if (size > header.FileSize - (begin - header.MappedAddress)) + throw Domain::RelinkerException("Code analysis: function exceeds executable segment", begin); + range(header.Offset + begin - header.MappedAddress, size); + mapped = true; + break; + } + if (!mapped) throw Domain::RelinkerException("Code analysis: function is not file-backed", begin); + const auto [position, inserted] = functions.emplace(begin, begin + size); + if (!inserted && position->second != begin + size) + throw Domain::RelinkerException("Code analysis: conflicting function ranges", begin); + roots.insert(begin); + }; + range(0, 64); + if (const auto entry = Io::ReadU64(bytes, 24); entry != 0) addRoot(entry); + for (const auto tag : {12u, 13u}) if (const auto address = value(tag, 0x60000000 + tag); address && *address != 0) addRoot(*address); + const auto symbolBytes = value(0x6100003f, 0xffffffffffffffffull); + std::uint64_t symbolCount = 0; + if (symbolBytes) { + if (*symbolBytes % 24 != 0) throw Domain::RelinkerException("Code analysis: invalid symbol table size"); + symbolCount = *symbolBytes / 24; + } else if (tags.contains(4)) symbolCount = Io::ReadU32(bytes, fileOffset(tags.at(4), 8) + 4); + std::uint64_t symbols = 0; + if (symbolCount != 0) { + if (value(11, 0x6100003b) != 24) throw Domain::RelinkerException("Code analysis: invalid symbol entry size"); + symbols = table(6, 0x61000039, symbolCount * 24); + for (std::uint64_t index = 0; index < symbolCount; ++index) { + const auto offset = symbols + index * 24; + if ((bytes[offset + 4] & 15) == 2 && Io::ReadU16(bytes, offset + 6) != 0) { + const auto address = Io::ReadU64(bytes, offset + 8); + addRoot(address); + addFunction(address, Io::ReadU64(bytes, offset + 16)); + } + } + } + CodePointers pointers; + std::set importSlots; + const auto readRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag) { + const auto size = value(sizeTag, sceSizeTag); + if (!size) return; + if (*size % 24 != 0) throw Domain::RelinkerException("Code analysis: invalid relocation table size"); + if (*size == 0) return; + const auto start = table(addressTag, sceAddressTag, *size); + for (std::uint64_t offset = start; offset < start + *size; offset += 24) { + const auto slot = Io::ReadU64(bytes, offset); + const auto info = Io::ReadU64(bytes, offset + 8); + const auto type = static_cast(info); + const auto index = info >> 32; + const auto addend = Io::ReadU64(bytes, offset + 16); + if (type == 8) { + if (index != 0 || !pointers.Values.emplace(slot, addend).second) throw Domain::RelinkerException("Code analysis: invalid relative relocation", slot); + addRoot(addend); + } else if (type == 1 || type == 6 || type == 7) { + if (index == 0 || index >= symbolCount) throw Domain::RelinkerException("Code analysis: invalid relocation symbol", slot); + const auto symbol = symbols + index * 24; + if (Io::ReadU16(bytes, symbol + 6) == 0) importSlots.insert(slot); + else { + const auto target = Io::ReadU64(bytes, symbol + 8) + addend; + if (!pointers.Values.emplace(slot, target).second) throw Domain::RelinkerException("Code analysis: duplicate pointer relocation", slot); + addRoot(target); + } + } + } + }; + readRelocations(7, 0x6100002f, 8, 0x61000031); + readRelocations(23, 0x61000029, 2, 0x6100002d); + for (const auto& [addressTag, sizeTag] : {std::pair{25u, 27u}, std::pair{26u, 28u}, std::pair{32u, 33u}}) { + const auto address = value(addressTag, 0x60000000 + addressTag); + const auto size = value(sizeTag, 0x60000000 + sizeTag); + if (address.has_value() != size.has_value()) throw Domain::RelinkerException("Code analysis: incomplete initializer array"); + if (!size || *size == 0) continue; + if (*size % 8 != 0) throw Domain::RelinkerException("Code analysis: invalid initializer array size"); + const auto offset = fileOffset(*address, *size); + for (std::uint64_t index = 0; index < *size; index += 8) { + const auto relocated = pointers.TargetOfSlot(*address + index); + const auto target = relocated ? *relocated : Io::ReadU64(bytes, offset + index); + if (target == 0 || target == std::numeric_limits::max()) continue; + if (!isCode(target)) throw Domain::RelinkerException("Code analysis: initializer lies outside code", target); + roots.insert(target); + } + } + for (const auto& function : UnusedNidFilter::ReadExceptionFunctions(bytes, headers, pointers.Values, importSlots)) { + if (function.End <= function.Begin) throw Domain::RelinkerException("Code analysis: invalid unwind function range", function.Begin); + addFunction(function.Begin, function.End - function.Begin); + for (const auto target : function.ExtraTargets) addRoot(target); + } + if (roots.empty()) throw Domain::RelinkerException("Code analysis: no code entry points"); + std::set instructions; + const Codegen::X64InstructionDecoder decoder; + for (const auto& [begin, end] : functions) { + const auto offset = fileOffset(begin, end - begin); + for (auto address = begin; address < end;) { + Codegen::DecodedInstructionInfo info; + try { + info = decoder.DecodeInstruction(bytes.data() + offset + address - begin, end - address); + } catch (const Codegen::CodegenException& error) { + throw Domain::RelinkerException(std::string("Code analysis: ") + error.what(), address); + } + if (info.Length == 0 || info.Length > end - address) + throw Domain::RelinkerException("Code analysis: instruction crosses function boundary", address); + instructions.insert(address); + if (info.HasBranchTarget && !info.HasRipRelativeDisp) + addRoot(address + info.Length + static_cast(info.BranchDisp)); + address += info.Length; + } + } + std::size_t previousRoots = 0; + do { + previousRoots = roots.size(); + for (const auto& header : headers) { + if (header.Type != 1 || (header.Flags & 1) == 0 || header.FileSize == 0) continue; + range(header.Offset, header.FileSize); + std::vector entries; + for (const auto root : roots) if (root >= header.MappedAddress && root - header.MappedAddress < header.FileSize) entries.push_back(root); + if (entries.empty()) continue; + const std::vector text(bytes.begin() + header.Offset, bytes.begin() + header.Offset + header.FileSize); + const auto graph = UnusedNidFilter::BuildControlFlowGraph(text, header.MappedAddress, entries.front(), entries, pointers); + for (const auto address : graph->ReachableVaddrs()) { + instructions.insert(address); + const auto offset = address - header.MappedAddress; + const auto info = decoder.DecodeInstruction(text.data() + offset, text.size() - offset); + if (info.HasBranchTarget && !info.HasRipRelativeDisp) addRoot(static_cast(static_cast(address + info.Length) + info.BranchDisp)); + } + } + } while (roots.size() != previousRoots); + std::uint64_t previousEnd = 0; + for (const auto address : instructions) { + if (address < previousEnd) throw Domain::RelinkerException("Code analysis: overlapping instruction boundaries", address); + for (const auto& header : headers) { + if (header.Type != 1 || (header.Flags & 1) == 0 || address < header.MappedAddress || address - header.MappedAddress >= header.FileSize) continue; + const auto offset = address - header.MappedAddress; + previousEnd = address + decoder.DecodeInstruction(bytes.data() + header.Offset + offset, header.FileSize - offset).Length; + break; + } + } + return instructions; +} + +} diff --git a/core/relinker/relinker/src/guest/GuestImageReader.cpp b/core/relinker/relinker/src/guest/GuestImageReader.cpp new file mode 100644 index 000000000..8dec3cf8a --- /dev/null +++ b/core/relinker/relinker/src/guest/GuestImageReader.cpp @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector bytes) const { + const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(path.string() + ": " + message); }; + const auto range = [&](std::uint64_t offset, std::uint64_t size) { + if (offset > bytes.size() || size > bytes.size() - offset) { + std::ostringstream message; + message << "ELF range exceeds file: offset=0x" << std::hex << offset << ", size=0x" << size << ", fileSize=0x" << bytes.size(); + fail(message.str()); + } + }; + range(0, 64); + if (Io::ReadU32(bytes, 0) != 0x464c457f || bytes[4] != 2 || bytes[5] != 1 || bytes[6] != 1 || Io::ReadU16(bytes, 18) != 62 || Io::ReadU32(bytes, 20) != 1) fail("Expected little-endian ELF64 x86-64"); + const auto type = Io::ReadU16(bytes, 16); + if (type != 3 && type != 0xfe18) fail("ELF is not a shared module"); + if (Io::ReadU16(bytes, 52) != 64 || Io::ReadU16(bytes, 54) != 56 || Io::ReadU16(bytes, 56) == 0) fail("Invalid ELF program header table"); + range(Io::ReadU64(bytes, 32), static_cast(Io::ReadU16(bytes, 56)) * 56); + GuestImage image; + image.SourcePath = path; + image.OutputName = path.filename().string() + GuestModuleSuffix; + image.Headers = ElfReader(bytes).ReadProgramHeaders(); + const Domain::ProgramHeader* dynamic = nullptr; + const Domain::ProgramHeader* dynlib = nullptr; + const Domain::ProgramHeader* tls = nullptr; + std::map loads; + constexpr std::uint32_t sceComment = 0x6fffff00; + constexpr std::uint32_t sceLibVersion = 0x6fffff01; + for (const auto& header : image.Headers) { + if (header.Type == sceComment || header.Type == sceLibVersion) continue; + range(header.Offset, header.FileSize); + if (header.Type == 1) { + if (header.FileSize > header.MemorySize || header.MemorySize > std::numeric_limits::max() - header.MappedAddress || (header.Flags & ~7u) != 0) fail("Invalid load segment"); + if (header.Alignment > 1 && (!std::has_single_bit(header.Alignment) || header.Offset % header.Alignment != header.MappedAddress % header.Alignment)) fail("Invalid load segment alignment"); + const auto next = loads.lower_bound(header.MappedAddress); + if ((next != loads.end() && next->first < header.MappedAddress + header.MemorySize) || (next != loads.begin() && std::prev(next)->second > header.MappedAddress)) fail("Overlapping load segments"); + loads.emplace(header.MappedAddress, header.MappedAddress + header.MemorySize); + } + if (header.Type == 2) { + if (dynamic != nullptr) fail("Duplicate dynamic segment"); + dynamic = &header; + } + if (header.Type == 0x61000000) { + if (dynlib != nullptr) fail("Duplicate SCE dynamic data"); + dynlib = &header; + } + if (header.Type == 7) { + if (tls != nullptr || header.FileSize > header.MemorySize || (header.Alignment > 1 && !std::has_single_bit(header.Alignment))) fail("Invalid TLS segment"); + tls = &header; + } + } + if (loads.empty() || dynamic == nullptr || dynamic->FileSize % 16 != 0) fail("Missing or invalid ELF load/dynamic segments"); + std::map tags; + std::vector needed; + bool terminated = false; + for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) { + const auto tag = Io::ReadU64(bytes, offset); + const auto value = Io::ReadU64(bytes, offset + 8); + if (tag == 0) { terminated = true; break; } + if (tag == 1) needed.push_back(value); + else if (tag < 0x60000000 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) { + if (!tags.emplace(tag, value).second) fail("Duplicate dynamic tag " + std::to_string(tag)); + } + } + if (!terminated) fail("Unterminated dynamic segment"); + for (const auto unsupported : {15u, 16u, 17u, 18u, 19u, 22u, 35u, 36u, 37u}) { + if (tags.contains(unsupported)) fail("Unsupported dynamic tag " + std::to_string(unsupported)); + } + if (tags.contains(30) && (tags.at(30) & ~8ull) != 0) fail("Unsupported guest dynamic flags"); + const auto mapped = [&](std::uint64_t address, std::uint64_t size, std::uint32_t flags) { + for (const auto& header : image.Headers) { + if (header.Type == 1 && (header.Flags & flags) == flags && address >= header.MappedAddress && address - header.MappedAddress <= header.MemorySize && size <= header.MemorySize - (address - header.MappedAddress)) return; + } + fail("Unmapped or inaccessible guest address " + std::to_string(address)); + }; + const auto translate = [&](std::uint64_t address, std::uint64_t size) { + for (const auto& header : image.Headers) { + if (header.Type == 1 && address >= header.MappedAddress && address - header.MappedAddress <= header.FileSize && size <= header.FileSize - (address - header.MappedAddress)) return header.Offset + address - header.MappedAddress; + } + fail("Unmapped ELF address " + std::to_string(address)); + return std::uint64_t{}; + }; + const auto get = [&](std::uint64_t standard, std::uint64_t sce) { + if (tags.contains(standard) == tags.contains(sce)) fail("Missing or ambiguous dynamic tag " + std::to_string(standard)); + return tags.at(tags.contains(standard) ? standard : sce); + }; + const auto table = [&](std::uint64_t standard, std::uint64_t sce, std::uint64_t size) { + const auto value = get(standard, sce); + if (tags.contains(standard)) return translate(value, size); + if (dynlib == nullptr || value > dynlib->FileSize || size > dynlib->FileSize - value) fail("SCE table exceeds dynamic data segment"); + return dynlib->Offset + value; + }; + const auto strSize = get(10, 0x61000037); + const auto strOffset = table(5, 0x61000035, strSize); + const auto string = [&](std::uint64_t offset) { + if (offset >= strSize) fail("String offset out of bounds"); + const auto start = bytes.begin() + static_cast(strOffset + offset); + const auto end = bytes.begin() + static_cast(strOffset + strSize); + const auto zero = std::find(start, end, 0); + if (zero == end) fail("Unterminated ELF string"); + return std::string(start, zero); + }; + if (strSize == 0 || bytes[strOffset] != 0 || get(11, 0x6100003b) != 24) fail("Invalid string or symbol table"); + std::uint64_t symSize = 0; + if (tags.contains(0x6100003f)) symSize = tags.at(0x6100003f); + else if (tags.contains(4)) symSize = static_cast(Io::ReadU32(bytes, translate(tags.at(4), 8) + 4)) * 24; + else fail("Missing dynamic symbol count"); + if (symSize == 0 || symSize % 24 != 0 || symSize / 24 > std::numeric_limits::max()) fail("Invalid dynamic symbol count"); + const auto symOffset = table(6, 0x61000039, symSize); + image.Dynamic.DynSymData.assign(bytes.begin() + symOffset, bytes.begin() + symOffset + symSize); + image.Dynamic.DynStrData.push_back(0); + std::set exports; + for (std::uint64_t offset = 0; offset < symSize; offset += 24) { + auto name = string(Io::ReadU32(bytes, symOffset + offset)); + name = name.substr(0, name.find('#')); + const auto info = bytes[symOffset + offset + 4]; + const auto visibility = bytes[symOffset + offset + 5]; + const auto section = Io::ReadU16(bytes, symOffset + offset + 6); + const auto value = Io::ReadU64(bytes, symOffset + offset + 8); + const auto size = Io::ReadU64(bytes, symOffset + offset + 16); + if (offset == 0 && (info != 0 || visibility != 0 || section != 0 || value != 0 || size != 0 || !name.empty())) fail("Invalid null symbol"); + if (offset == 0) { + image.Symbols.push_back({{}, 0, 0, 0, 0, 0}); + continue; + } + if (offset != 0 && ((info >> 4) > 2 || (visibility & ~3u) != 0 || ((info & 15) != 0 && (info & 15) != 1 && (info & 15) != 2 && (info & 15) != 6))) fail("Unsupported symbol attributes: " + name); + if (section != 0 && (info >> 4) != 0 && visibility != 1 && visibility != 2) { + if (name.empty() || !exports.insert(name).second) fail("Duplicate or empty export after stripping #: " + name); + } + if ((info & 15) == 6 && section != 0 && (tls == nullptr || value > tls->MemorySize || size > tls->MemorySize - value)) fail("TLS symbol exceeds TLS segment: " + name); + if (section != 0 && (info & 15) != 6) { + if (section >= 0xff00) fail("Unsupported special symbol section: " + name); + mapped(value, std::max(size, 1), (info & 15) == 2 ? 1 : 0); + } + if (image.Dynamic.DynStrData.size() > std::numeric_limits::max()) fail("String table too large"); + Io::WriteU32(image.Dynamic.DynSymData, offset, static_cast(image.Dynamic.DynStrData.size())); + Io::AppendString(image.Dynamic.DynStrData, name); + image.Symbols.push_back({name, info, visibility, section, value, size}); + } + std::set dependencies; + for (const auto offset : needed) { + const auto name = string(offset); + if (name.empty() || name.find_first_of("/\\:$") != std::string::npos || !dependencies.insert(name).second) fail("Invalid or duplicate dependency: " + name); + image.Dependencies.push_back(name); + } + std::map relocationTargets; + const auto copyRelocations = [&](std::uint64_t addressTag, std::uint64_t sceAddressTag, std::uint64_t sizeTag, std::uint64_t sceSizeTag, std::vector& output, bool plt) { + if (!tags.contains(addressTag) && !tags.contains(sceAddressTag) && !tags.contains(sizeTag) && !tags.contains(sceSizeTag)) return; + const auto size = get(sizeTag, sceSizeTag); + if (size % 24 != 0) fail("Invalid relocation table size"); + const auto offset = table(addressTag, sceAddressTag, size); + output.assign(bytes.begin() + offset, bytes.begin() + offset + size); + for (std::uint64_t index = 0; index < size; index += 24) { + const auto info = Io::ReadU64(output, index + 8); + const auto relocation = static_cast(info); + if ((info >> 32) >= image.Symbols.size() || (plt && relocation != 7) || (relocation != 1 && relocation != 6 && relocation != 7 && relocation != 8 && relocation != 16 && relocation != 17 && relocation != 18)) fail("Unsupported relocation or symbol index"); + const auto target = Io::ReadU64(output, index); + const auto addend = Io::ReadU64(output, index + 16); + mapped(target, 8, 2); + const auto next = relocationTargets.lower_bound(target); + if ((next != relocationTargets.end() && next->first < target + 8) || (next != relocationTargets.begin() && std::prev(next)->second > target)) fail("Overlapping guest relocations"); + relocationTargets.emplace(target, target + 8); + if (relocation == 8) { + if ((info >> 32) != 0) fail("RELATIVE relocation has a symbol"); + mapped(addend, 0, 0); + } else if (relocation == 1 || relocation == 6 || relocation == 7) { + const auto& symbol = image.Symbols[info >> 32]; + if ((info >> 32) == 0 || (symbol.Info & 15) == 6 || (relocation != 1 && addend != 0) || (symbol.Section == 0 && symbol.Name.empty())) fail("Invalid guest symbol relocation"); + } else { + const auto& symbol = image.Symbols[info >> 32]; + if (((info >> 32) == 0 && tls == nullptr) || ((info >> 32) != 0 && (symbol.Info & 15) != 6) || (relocation == 16 && addend != 0)) fail("Invalid guest TLS relocation"); + } + } + }; + if (tags.contains(7) || tags.contains(0x6100002f)) { + if (get(9, 0x61000033) != 24) fail("Invalid RELA entry size"); + } + copyRelocations(7, 0x6100002f, 8, 0x61000031, image.Dynamic.RelaData, false); + copyRelocations(23, 0x61000029, 2, 0x6100002d, image.Dynamic.RelaPltData, true); + if (!image.Dynamic.RelaPltData.empty() && get(20, 0x6100002b) != 7) fail("Unsupported PLT relocation format"); + if (tags.contains(3) || tags.contains(0x61000027)) image.Got = get(3, 0x61000027); + if (tags.contains(12)) image.Init = tags.at(12); + if (tags.contains(13)) image.Fini = tags.at(13); + if (image.Init != 0) mapped(image.Init, 1, 1); + if (image.Fini != 0) mapped(image.Fini, 1, 1); + if ((tags.contains(27) && tags.at(27) != 0) || (tags.contains(28) && tags.at(28) != 0)) fail("Separate guest INIT_ARRAY/FINI_ARRAY is not supported"); + if (tags.contains(33) && tags.at(33) != 0 && image.Init == 0) fail("Guest PREINIT_ARRAY has no module initializer"); + image.Bytes = std::move(bytes); + return image; +} + +} diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp new file mode 100644 index 000000000..4c4c92fea --- /dev/null +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Relinker { + +std::vector GuestModuleBuilder::Build(const std::filesystem::path& inputPath, const std::filesystem::path& outputPath, Domain::SysVDynamicSection& dynamic, const bool windows, const bool toIntel, ISyscallScanner& syscallScanner, const bool lazyBinding, const std::string& runPath) const { + const auto root = std::filesystem::absolute(inputPath).parent_path(); + const auto singular = root / "sce_module"; + const auto plural = root / "sce_modules"; + const bool hasSingular = std::filesystem::exists(singular); + const bool hasPlural = std::filesystem::exists(plural); + if (hasSingular && hasPlural) throw Domain::RelinkerException("Both sce_module and sce_modules exist beside the input executable"); + if (!hasSingular && !hasPlural) { + std::cout << "WARNING: sce_module not found beside the input executable; it may be unnecessary or missing.\n"; + return {}; + } + const auto directory = hasSingular ? singular : plural; + if (!std::filesystem::is_directory(directory)) throw Domain::RelinkerException("Guest module path is not a directory: " + directory.string()); + std::vector paths; + for (const auto& entry : std::filesystem::directory_iterator(directory)) { + if (entry.path().filename().string().ends_with(GuestModuleSuffix)) continue; + if (!entry.is_regular_file()) continue; + std::ifstream stream(entry.path(), std::ios::binary); + if (!stream) throw Domain::RelinkerException("Cannot read guest candidate: " + entry.path().string()); + char magic[4]{}; + stream.read(magic, 4); + if (stream.bad()) throw Domain::RelinkerException("Cannot read guest candidate magic: " + entry.path().string()); + if (stream.gcount() == 4 && static_cast(magic[0]) == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F') paths.push_back(entry.path()); + } + std::sort(paths.begin(), paths.end()); + if (paths.empty()) return {}; + if (lazyBinding) throw Domain::RelinkerException("Guest modules require eager binding; --lazy-binding is incompatible"); + std::vector images; + std::map exports; + std::set outputNames; + Io::FileReader reader; + for (const auto& path : paths) { + auto image = GuestImageReader().Read(path, reader.Read(path.string())); + if (image.OutputName.find_first_of("$\r\n") != std::string::npos) throw Domain::RelinkerException("Unsupported guest filename: " + image.OutputName); + std::string folded = image.OutputName; + if (windows) { + for (auto& value : folded) { + if (static_cast(value) >= 128 || value == ':' || value == '$') throw Domain::RelinkerException("Unsupported Windows guest filename: " + image.OutputName); + if (value >= 'A' && value <= 'Z') value = static_cast(value + ('a' - 'A')); + } + } + if (!outputNames.insert(folded).second) throw Domain::RelinkerException("Conflicting guest output filename: " + image.OutputName); + for (const auto& symbol : image.Symbols) { + if (symbol.Section == 0 || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue; + const auto [existing, inserted] = exports.emplace(symbol.Name, images.size()); + if (!inserted) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + images.at(existing->second).SourcePath.string() + " and " + path.string()); + } + std::vector codeHeaders; + for (const auto& header : image.Headers) if (header.Type == 1 && (header.Flags & 1) != 0) codeHeaders.push_back(header); + if (toIntel) { + auto converted = Codegen::MakeAmd64OnlyConverter()->Convert(std::move(image.Bytes), codeHeaders); + if (!converted.Trampolines.empty()) throw Domain::RelinkerException("--to-intel needs " + std::to_string(converted.Trampolines.size()) + " stub(s) in guest module " + path.string() + "; stub sections are not written for guest modules"); + image.Bytes = std::move(converted.Bytes); + } + for (const auto& header : codeHeaders) { + const std::vector code(image.Bytes.begin() + header.Offset, image.Bytes.begin() + header.Offset + header.FileSize); + syscallScanner.ScanCodeSectionForSyscalls(code, header.MappedAddress, header.FileSize); + } + images.push_back(std::move(image)); + } + std::vector> dependencies(images.size()); + for (auto& image : images) image.UsePlatformTlsResolver = !exports.contains("vNe1w4diLCs"); + for (std::size_t index = 0; index < images.size(); ++index) { + for (const auto& symbol : images[index].Symbols) { + if (symbol.Section != 0 || symbol.Name.empty()) continue; + const auto found = exports.find(symbol.Name); + if (found != exports.end()) { + const auto& provider = images[found->second]; + const auto exported = std::find_if(provider.Symbols.begin(), provider.Symbols.end(), [&](const auto& candidate) { return candidate.Section != 0 && candidate.Name == symbol.Name && (candidate.Info >> 4) != 0 && candidate.Visibility != 1 && candidate.Visibility != 2; }); + if (exported == provider.Symbols.end() || ((symbol.Info & 15) != 0 && (symbol.Info & 15) != (exported->Info & 15))) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name); + if (found->second != index) dependencies[index].insert(found->second); + } else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name); + } + } + std::vector order; + std::vector states(images.size()); + const std::function visit = [&](std::size_t index) { + if (states[index] == 1) throw Domain::RelinkerException("Cyclic guest initialization dependency: " + images[index].SourcePath.string()); + if (states[index] == 2) return; + states[index] = 1; + for (const auto dependency : dependencies[index]) visit(dependency); + states[index] = 2; + order.push_back(index); + }; + for (std::size_t index = 0; index < images.size(); ++index) visit(index); + std::vector hostLibraries; + std::set uniqueHosts; + const auto addHost = [&](const std::string& name) { + if (name.empty() || name.find_first_of("/\\:$") != std::string::npos) throw Domain::RelinkerException("Invalid host dependency: " + name); + if (uniqueHosts.insert(name).second) hostLibraries.push_back(name); + }; + if (dynamic.DynamicSegmentData.size() % 16 != 0) throw Domain::RelinkerException("Invalid executable dependency table"); + for (std::size_t offset = 0; offset < dynamic.DynamicSegmentData.size(); offset += 16) { + if (Io::ReadU64(dynamic.DynamicSegmentData, offset) != 1) throw Domain::RelinkerException("Unexpected executable dependency tag"); + const auto nameOffset = Io::ReadU64(dynamic.DynamicSegmentData, offset + 8); + if (nameOffset >= dynamic.DynStrData.size()) throw Domain::RelinkerException("Invalid dependency string offset"); + const auto start = dynamic.DynStrData.begin() + nameOffset; + const auto end = std::find(start, dynamic.DynStrData.end(), 0); + if (end == dynamic.DynStrData.end()) throw Domain::RelinkerException("Unterminated dependency string"); + addHost(std::string(start, end)); + } + for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency); + dynamic.DynamicSegmentData.clear(); + const auto addNeeded = [&](const std::string& name) { + Io::AppendU64(dynamic.DynamicSegmentData, 1); + Io::AppendU64(dynamic.DynamicSegmentData, dynamic.DynStrData.size()); + Io::AppendString(dynamic.DynStrData, name); + }; + const auto relativeDirectory = directory.filename().generic_string(); + for (const auto index : order) if (!windows) addNeeded("$ORIGIN/" + relativeDirectory + "/" + images[index].OutputName); + for (const auto& name : hostLibraries) addNeeded(name); + std::string guestRunPath = runPath; + if (!windows) { + if (guestRunPath == "$ORIGIN") guestRunPath = "$ORIGIN/.."; + else if (guestRunPath.starts_with("$ORIGIN/")) guestRunPath.insert(8, "../"); + else if (!std::filesystem::path(guestRunPath).is_absolute()) throw Domain::RelinkerException("Guest Linux run path must be absolute or begin with $ORIGIN"); + } + std::vector artifacts; + const auto destination = std::filesystem::absolute(outputPath).parent_path() / directory.filename(); + if (std::filesystem::exists(destination) && std::filesystem::equivalent(destination, directory)) throw Domain::RelinkerException("Guest output directory must differ from the source module directory"); + for (const auto index : order) { + const auto& image = images[index]; + const auto target = destination / image.OutputName; + if (target.lexically_normal() == std::filesystem::absolute(outputPath).lexically_normal()) throw Domain::RelinkerException("Guest output collides with the executable output"); + for (const auto& source : paths) if (std::filesystem::exists(target) && std::filesystem::equivalent(source, target)) throw Domain::RelinkerException("Guest output would overwrite an input module: " + target.string()); + if (std::filesystem::exists(target) && std::filesystem::equivalent(inputPath, target)) throw Domain::RelinkerException("Guest output would overwrite the input executable"); + Domain::GuestRuntime runtime; + runtime.UsePlatformTlsResolver = image.UsePlatformTlsResolver; + runtime.Path = relativeDirectory + "/" + image.OutputName; + std::vector output; + if (windows) output = Elfpatcher::GuestModuleWriter().WriteWindows(image, runtime); + else { + std::vector needed; + for (const auto dependency : dependencies[index]) needed.push_back("$ORIGIN/" + images[dependency].OutputName); + needed.insert(needed.end(), hostLibraries.begin(), hostLibraries.end()); + output = Elfpatcher::GuestModuleWriter().WriteLinux(image, needed, guestRunPath); + } + dynamic.GuestModules.push_back(std::move(runtime)); + artifacts.push_back({target, std::move(output)}); + } + return artifacts; +} + +} diff --git a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp index 7bacccdd1..f53becebc 100644 --- a/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp +++ b/core/relinker/relinker/src/pipeline/RelinkerPipeline.cpp @@ -99,10 +99,14 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf return hasTag(osTag) ? getTagValue(osTag) : getTagValue(sysvTag); }; - gotVAddr = requireExactlyOneOf(DT_OS_PLTGOT, DT_PLTGOT, "DT_PLTGOT") - ? getTagValue(DT_OS_PLTGOT) - : getTagValue(DT_PLTGOT); - gotSize = readAsSize(DT_OS_PLTRELSZ, DT_PLTRELSZ, "DT_PLTRELSZ"); + const bool hasPltRelocations = hasTag(DT_OS_PLTRELSZ) || hasTag(DT_PLTRELSZ) + || hasTag(DT_OS_PLTREL) || hasTag(DT_PLTREL) + || hasTag(DT_OS_JMPREL) || hasTag(DT_JMPREL); + if (hasPltRelocations || hasTag(DT_OS_PLTGOT) || hasTag(DT_PLTGOT)) { + gotVAddr = requireExactlyOneOf(DT_OS_PLTGOT, DT_PLTGOT, "DT_PLTGOT") + ? getTagValue(DT_OS_PLTGOT) + : getTagValue(DT_PLTGOT); + } const FileByteOffset dynStrTabOffset = readAsOffset(DT_OS_STRTAB, DT_STRTAB, "DT_STRTAB"); requireExactlyOneOf(DT_OS_STRSZ, DT_STRSZ, "DT_STRSZ"); @@ -112,13 +116,18 @@ RelinkResult RelinkerPipeline::Relink(const std::vector& sourceElf if (readAsSize(DT_OS_SYMENT, DT_SYMENT, "DT_SYMENT") != symEntSize) throw RelinkerException("Unsupported DT_SYMENT value"); - const std::int64_t jmprelType = requireExactlyOneOf(DT_OS_PLTREL, DT_PLTREL, "DT_PLTREL") - ? getTagValue(DT_OS_PLTREL) - : getTagValue(DT_PLTREL); - if (jmprelType != DT_RELA) - throw RelinkerException("Unsupported DT_PLTREL type"); - - const FileByteOffset dynJmpRelOffset = readAsOffset(DT_OS_JMPREL, DT_JMPREL, "DT_JMPREL"); + FileByteOffset dynJmpRelOffset = 0; + if (hasPltRelocations) { + gotSize = readAsSize(DT_OS_PLTRELSZ, DT_PLTRELSZ, "DT_PLTRELSZ"); + const std::int64_t jmprelType = requireExactlyOneOf(DT_OS_PLTREL, DT_PLTREL, "DT_PLTREL") + ? getTagValue(DT_OS_PLTREL) + : getTagValue(DT_PLTREL); + if (jmprelType != DT_RELA) + throw RelinkerException("Unsupported DT_PLTREL type"); + dynJmpRelOffset = readAsOffset(DT_OS_JMPREL, DT_JMPREL, "DT_JMPREL"); + if (gotSize % 24 != 0) + throw RelinkerException("Invalid DT_PLTRELSZ value"); + } const ByteCount dynJmpRelSize = gotSize; const FileByteOffset dynRelaOffset = readAsOffset(DT_OS_RELA, DT_RELA, "DT_RELA"); diff --git a/core/relinker/relinker/tests/test_empty_tls.py b/core/relinker/relinker/tests/test_empty_tls.py new file mode 100644 index 000000000..aaf0c633e --- /dev/null +++ b/core/relinker/relinker/tests/test_empty_tls.py @@ -0,0 +1,50 @@ +"""Validate empty PT_TLS handling through ELF conversion and PE execution.""" + +import os +from pathlib import Path +import struct +import subprocess +import sys +import tempfile + +from test_optional_plt import fixture + + +def main(): + relinker = Path(sys.argv[1]).resolve() + cases = [ + ("empty", 0, 0, False, None), + ("nonempty", 8, 16, False, None), + ("invalid-sizes", 1, 0, False, "Invalid or unsupported ELF TLS layout"), + ("empty-access", 0, 0, True, "Guest TLS access with empty PT_TLS"), + ] + with tempfile.TemporaryDirectory(prefix="anyps5-tls-") as directory: + for name, file_size, memory_size, access, error in cases: + source = Path(directory) / (name + ".elf") + output = source.with_suffix(".exe") + data = fixture() + struct.pack_into("