mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 03:16:54 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e679486ed3 | |||
| 6ef406542c | |||
| 35c6e8fff6 | |||
| 4852721005 |
+7
-2
@@ -419,7 +419,10 @@ add_executable(trophy_system_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(trophy_system_tests common)
|
||||
target_include_directories(trophy_system_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL "${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp")
|
||||
add_executable(avplayer_file_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/AvPlayerFileTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/timer.cpp"
|
||||
)
|
||||
target_link_libraries(avplayer_file_tests common FFmpeg::ffmpeg ZArchive::zarchive)
|
||||
target_include_directories(avplayer_file_tests PRIVATE ${inc_headers})
|
||||
|
||||
@@ -689,6 +692,7 @@ if(BUILD_TESTING)
|
||||
memory_tracker_tests
|
||||
page_manager_tests
|
||||
archive_file_tests
|
||||
trophy_system_tests
|
||||
avplayer_file_tests
|
||||
bit_array_tests
|
||||
lru_cache_tests
|
||||
@@ -719,8 +723,9 @@ if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR
|
||||
add_executable(macos_sse4a_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/macosSse4aTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/x64InstructionEmulator.cpp"
|
||||
"${KYTY_SOURCE_DIR}/loader/guestInstructionPatcher.cpp"
|
||||
)
|
||||
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis)
|
||||
target_link_libraries(macos_sse4a_tests common fmt::fmt Zydis::Zydis xbyak::xbyak)
|
||||
target_include_directories(macos_sse4a_tests PRIVATE ${inc_headers})
|
||||
if(BUILD_TESTING)
|
||||
add_test(NAME macos_sse4a COMMAND $<TARGET_FILE:macos_sse4a_tests>)
|
||||
|
||||
+26
-2
@@ -89,6 +89,10 @@ int KYTY_SYSV_ABI NetBind(int s, const void* addr, uint32_t addrlen) {
|
||||
return FinishSocketCall(Net::Bind(s, addr, addrlen));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetConnect(int s, const void* addr, uint32_t addrlen) {
|
||||
return FinishSocketCall(Net::Connect(s, addr, addrlen));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetListen(int s, int backlog) {
|
||||
return FinishSocketCall(Net::Listen(s, backlog));
|
||||
}
|
||||
@@ -170,14 +174,24 @@ int KYTY_SYSV_ABI NetSocketClose(int s) {
|
||||
return FinishNetCall(Net::SocketClose(s));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetGetsockopt(int s, int level, int optname, void* optval, uint32_t* optlen) {
|
||||
return FinishSocketCall(Net::Getsockopt(s, level, optname, optval, optlen));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSetsockopt(int s, int level, int optname, const void* optval,
|
||||
uint32_t optlen) {
|
||||
return FinishSocketCall(Net::Setsockopt(s, level, optname, optval, optlen));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSend(int s, const void* buf, size_t len, int flags) {
|
||||
int KYTY_SYSV_ABI NetSendto(int s, const void* buf, size_t len, int flags, const void* addr,
|
||||
uint32_t addrlen) {
|
||||
const auto size = std::min<size_t>(len, std::numeric_limits<int>::max());
|
||||
return FinishSocketCall(static_cast<int>(Net::Send(s, buf, size, flags | 0x20000)));
|
||||
return FinishSocketCall(
|
||||
static_cast<int>(Net::Sendto(s, buf, size, flags | 0x20000, addr, addrlen)));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetSend(int s, const void* buf, size_t len, int flags) {
|
||||
return NetSendto(s, buf, len, flags, nullptr, 0);
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetRecv(int s, void* buf, size_t len, int flags) {
|
||||
@@ -185,6 +199,12 @@ int KYTY_SYSV_ABI NetRecv(int s, void* buf, size_t len, int flags) {
|
||||
return FinishSocketCall(static_cast<int>(Net::Recv(s, buf, size, flags)));
|
||||
}
|
||||
|
||||
int KYTY_SYSV_ABI NetRecvfrom(int s, void* buf, size_t len, int flags, void* addr,
|
||||
uint32_t* addrlen) {
|
||||
const auto size = std::min<size_t>(len, std::numeric_limits<int>::max());
|
||||
return FinishSocketCall(static_cast<int>(Net::Recvfrom(s, buf, size, flags, addr, addrlen)));
|
||||
}
|
||||
|
||||
uint32_t KYTY_SYSV_ABI NetHtonl(uint32_t host32) {
|
||||
return ((host32 & 0x000000ffu) << 24u) | ((host32 & 0x0000ff00u) << 8u) |
|
||||
((host32 & 0x00ff0000u) >> 8u) | ((host32 & 0xff000000u) >> 24u);
|
||||
@@ -209,6 +229,7 @@ LIB_DEFINE(InitNet_1_Net) {
|
||||
LIB_FUNC("HQOwnfMGipQ", LibNet::GetNetErrorAddr);
|
||||
LIB_FUNC("PIWqhn9oSxc", LibNet::NetAccept);
|
||||
LIB_FUNC("bErx49PgxyY", LibNet::NetBind);
|
||||
LIB_FUNC("OXXX4mUk3uk", LibNet::NetConnect);
|
||||
LIB_FUNC("dgJBaeJnGpo", LibNet::NetPoolCreate);
|
||||
LIB_FUNC("K7RlrTkI-mw", LibNet::NetPoolDestroy);
|
||||
LIB_FUNC("C4UgDHHPvdw", LibNet::NetResolverCreate);
|
||||
@@ -229,9 +250,12 @@ LIB_DEFINE(InitNet_1_Net) {
|
||||
LIB_FUNC("TSM6whtekok", LibNet::NetShutdown);
|
||||
LIB_FUNC("Q4qBuN-c0ZM", LibNet::NetSocket);
|
||||
LIB_FUNC("45ggEzakPJQ", LibNet::NetSocketClose);
|
||||
LIB_FUNC("xphrZusl78E", LibNet::NetGetsockopt);
|
||||
LIB_FUNC("2mKX2Spso7I", LibNet::NetSetsockopt);
|
||||
LIB_FUNC("beRjXBn-z+o", LibNet::NetSend);
|
||||
LIB_FUNC("gvD1greCu0A", LibNet::NetSendto);
|
||||
LIB_FUNC("9wO9XrMsNhc", LibNet::NetRecv);
|
||||
LIB_FUNC("304ooNZxWDY", LibNet::NetRecvfrom);
|
||||
LIB_FUNC("9T2pDF2Ryqg", LibNet::NetHtonl);
|
||||
LIB_FUNC("iWQWrwiSt8A", LibNet::NetHtons);
|
||||
LIB_FUNC("pQGpHYopAIY", LibNet::NetNtohl);
|
||||
|
||||
+84
-12
@@ -992,6 +992,7 @@ static int ConvertHostSocketError(int error) {
|
||||
case WSAENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
|
||||
case WSAENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
|
||||
case WSAENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
|
||||
case WSAENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
|
||||
case WSAEOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
|
||||
case WSAEPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
|
||||
case WSAESHUTDOWN: posix_error = Posix::POSIX_ESHUTDOWN; break;
|
||||
@@ -1028,6 +1029,7 @@ static int ConvertHostSocketError(int error) {
|
||||
case ENETUNREACH: posix_error = Posix::POSIX_ENETUNREACH; break;
|
||||
case ENOTCONN: posix_error = Posix::POSIX_ENOTCONN; break;
|
||||
case ENOTSOCK: posix_error = Posix::POSIX_ENOTSOCK; break;
|
||||
case ENOPROTOOPT: posix_error = Posix::POSIX_ENOPROTOOPT; break;
|
||||
case EOPNOTSUPP: posix_error = Posix::POSIX_EOPNOTSUPP; break;
|
||||
case EPIPE: posix_error = Posix::POSIX_EPIPE; break;
|
||||
case EPROTONOSUPPORT: posix_error = Posix::POSIX_EPROTONOSUPPORT; break;
|
||||
@@ -1066,6 +1068,26 @@ static int ConvertSocketOptionLevel(int level) {
|
||||
return (level == 0xffff ? SOL_SOCKET : level);
|
||||
}
|
||||
|
||||
static int ConvertSocketOptionName(int level, int option) {
|
||||
if (level == 0xffff) {
|
||||
switch (option) {
|
||||
case 0x0020: return SO_BROADCAST;
|
||||
case 0x1001: return SO_SNDBUF;
|
||||
case 0x1002: return SO_RCVBUF;
|
||||
case 0x1007: return SO_ERROR;
|
||||
case 0x1105: return SO_SNDTIMEO;
|
||||
default: break;
|
||||
}
|
||||
} else if (level == 6 && option == 1) {
|
||||
return TCP_NODELAY;
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
return option;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int* P2pSocketOption(P2pEndpoint& endpoint, int option) {
|
||||
switch (option) {
|
||||
case 0x0020: return &endpoint.broadcast;
|
||||
@@ -2149,21 +2171,41 @@ int KYTY_SYSV_ABI Getsockopt(int s, int level, int optname, void* optval, uint32
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Guest socket options: SOL_SOCKET=0xffff, SO_ERROR=0x1007.
|
||||
const bool socket_error = (level == 0xffff && optname == 0x1007);
|
||||
#if !defined(_WIN32)
|
||||
if (!socket_error) {
|
||||
const bool send_timeout = (level == 0xffff && optname == 0x1105);
|
||||
optname = ConvertSocketOptionName(level, optname);
|
||||
if (optname < 0) {
|
||||
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
|
||||
}
|
||||
#if defined(_WIN32)
|
||||
DWORD timeout {};
|
||||
#else
|
||||
timeval timeout {};
|
||||
#endif
|
||||
if (socket_error) {
|
||||
optname = SO_ERROR;
|
||||
}
|
||||
SocketLength len = static_cast<SocketLength>(*optlen);
|
||||
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(optval),
|
||||
void* value = optval;
|
||||
if (send_timeout) {
|
||||
if (*optlen < sizeof(int)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
value = &timeout;
|
||||
len = sizeof(timeout);
|
||||
}
|
||||
if (::getsockopt(socket, ConvertSocketOptionLevel(level), optname, static_cast<char*>(value),
|
||||
&len) != 0) {
|
||||
return SetHostSocketError();
|
||||
}
|
||||
if (send_timeout) {
|
||||
#if defined(_WIN32)
|
||||
const int64_t usec = static_cast<int64_t>(timeout) * 1000;
|
||||
#else
|
||||
const int64_t usec = static_cast<int64_t>(timeout.tv_sec) * 1'000'000 + timeout.tv_usec;
|
||||
#endif
|
||||
const int guest_timeout =
|
||||
static_cast<int>(std::min<int64_t>(usec, std::numeric_limits<int>::max()));
|
||||
std::memcpy(optval, &guest_timeout, sizeof(guest_timeout));
|
||||
len = sizeof(guest_timeout);
|
||||
}
|
||||
if (socket_error && len >= static_cast<SocketLength>(sizeof(int))) {
|
||||
auto* error = static_cast<int*>(optval);
|
||||
*error = ConvertHostSocketError(*error);
|
||||
@@ -2215,6 +2257,18 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
if (level == 0xffff && optname == 0x10000) {
|
||||
if (optlen != sizeof(int)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
int enabled = 0;
|
||||
std::memcpy(&enabled, optval, sizeof(enabled));
|
||||
// Native sends preserve 255.255.255.255 without changing broadcast permission.
|
||||
return enabled != 0 ? 0 : SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr int ORBIS_SO_NBIO = 0x1200;
|
||||
if (ConvertSocketOptionLevel(level) == SOL_SOCKET && optname == ORBIS_SO_NBIO &&
|
||||
optlen >= sizeof(int)) {
|
||||
@@ -2230,14 +2284,32 @@ int KYTY_SYSV_ABI Setsockopt(int s, int level, int optname, const void* optval,
|
||||
#endif
|
||||
return failed ? SetHostSocketError() : 0;
|
||||
}
|
||||
#if !defined(_WIN32)
|
||||
// Guest TCP options: IPPROTO_TCP=6, TCP_NODELAY=1.
|
||||
if (level != 6 || optname != 1) {
|
||||
const bool send_timeout = (level == 0xffff && optname == 0x1105);
|
||||
optname = ConvertSocketOptionName(level, optname);
|
||||
if (optname < 0) {
|
||||
return SetGuestSocketError(Posix::POSIX_ENOPROTOOPT);
|
||||
}
|
||||
level = IPPROTO_TCP;
|
||||
optname = TCP_NODELAY;
|
||||
#if defined(_WIN32)
|
||||
DWORD timeout {};
|
||||
#else
|
||||
timeval timeout {};
|
||||
#endif
|
||||
if (send_timeout) {
|
||||
if (optlen != sizeof(int)) {
|
||||
return SetGuestSocketError(Posix::POSIX_EINVAL);
|
||||
}
|
||||
int usec = 0;
|
||||
std::memcpy(&usec, optval, sizeof(usec));
|
||||
usec = std::max(usec, 0);
|
||||
#if defined(_WIN32)
|
||||
timeout = static_cast<DWORD>((static_cast<int64_t>(usec) + 999) / 1000);
|
||||
#else
|
||||
timeout.tv_sec = usec / 1'000'000;
|
||||
timeout.tv_usec = usec % 1'000'000;
|
||||
#endif
|
||||
optval = &timeout;
|
||||
optlen = sizeof(timeout);
|
||||
}
|
||||
|
||||
if (::setsockopt(socket, ConvertSocketOptionLevel(level), optname,
|
||||
static_cast<const char*>(optval), static_cast<SocketLength>(optlen)) != 0) {
|
||||
|
||||
@@ -21,10 +21,8 @@
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#if !defined(__APPLE__)
|
||||
#include <xbyak/xbyak.h>
|
||||
#include <xbyak/xbyak_util.h>
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
@@ -33,9 +31,7 @@
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
using namespace Xbyak::util;
|
||||
#endif
|
||||
|
||||
namespace Loader {
|
||||
|
||||
@@ -52,8 +48,6 @@ using s64 = int64_t;
|
||||
|
||||
constexpr size_t NearJumpSize = 5;
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
|
||||
struct PatchModule {
|
||||
std::mutex mutex {};
|
||||
u8* start = nullptr;
|
||||
@@ -1573,19 +1567,6 @@ GuestInstructionPatchResult PatchGuestInstructions(u64 segment_addr, u64 segment
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
GuestInstructionHostFeatures GetGuestInstructionHostFeatures() {
|
||||
return {};
|
||||
}
|
||||
|
||||
GuestInstructionPatchResult PatchGuestInstructions(u64, u64, std::span<const uintptr_t>, bool, bool,
|
||||
GuestInstructionHostFeatures) {
|
||||
return {};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint8_t DW_EH_PE_FORMAT_MASK = 0x0f;
|
||||
@@ -1763,7 +1744,6 @@ bool DecodeEhFrameFunctionStarts(uint64_t eh_frame_header_addr, uint64_t eh_fram
|
||||
|
||||
void RegisterGuestInstructionPatchModule(void* module_ptr, uint64_t module_size,
|
||||
void* trampoline_area_ptr, uint64_t trampoline_area_size) {
|
||||
#if !defined(__APPLE__)
|
||||
EXIT_IF(module_ptr == nullptr || module_size == 0 || trampoline_area_ptr == nullptr ||
|
||||
trampoline_area_size == 0);
|
||||
const auto module_addr = reinterpret_cast<u64>(module_ptr);
|
||||
@@ -1772,20 +1752,10 @@ void RegisterGuestInstructionPatchModule(void* module_ptr, uint64_t module_size,
|
||||
std::forward_as_tuple(static_cast<u8*>(module_ptr), module_size,
|
||||
static_cast<u8*>(trampoline_area_ptr),
|
||||
trampoline_area_size));
|
||||
#else
|
||||
(void)module_ptr;
|
||||
(void)module_size;
|
||||
(void)trampoline_area_ptr;
|
||||
(void)trampoline_area_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UnregisterGuestInstructionPatchModule(void* module_ptr) {
|
||||
#if !defined(__APPLE__)
|
||||
g_patch_modules.erase(reinterpret_cast<u64>(module_ptr));
|
||||
#else
|
||||
(void)module_ptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
#undef ASSERT
|
||||
|
||||
@@ -1670,11 +1670,7 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
|
||||
uint64_t tls_handler_size = is_shared ? 0 : Jit::SafeCall::GetSize();
|
||||
EXIT_IF(tls_handler_size > UINT64_MAX - program->base_size_aligned);
|
||||
program->mapped_size = program->base_size_aligned + tls_handler_size;
|
||||
#if !defined(__APPLE__)
|
||||
const bool emulate_amd = Config::AmdCpuEnabled();
|
||||
#else
|
||||
const bool emulate_amd = false;
|
||||
#endif
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
const bool protect_memory_faults = Config::RedZoneProtectionEnabled();
|
||||
#else
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#elif defined(__APPLE__)
|
||||
#include <sched.h>
|
||||
#include <sys/ucontext.h>
|
||||
#else
|
||||
#include <sched.h>
|
||||
@@ -468,6 +469,16 @@ struct Context {
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
// The kernel hands AVX threads the longer context that carries YMM[255:128]; a plain SSE
|
||||
// context has no such state to clear.
|
||||
void ClearUpperYmm(uint8_t index) const {
|
||||
if (index >= 16 || native->uc_mcsize < sizeof(_STRUCT_MCONTEXT_AVX64)) {
|
||||
return;
|
||||
}
|
||||
auto* avx = reinterpret_cast<_STRUCT_MCONTEXT_AVX64*>(native->uc_mcontext);
|
||||
auto* ymmh = &avx->__fs.__fpu_ymmh0;
|
||||
ymmh[index] = {};
|
||||
}
|
||||
#else
|
||||
ucontext_t* native;
|
||||
|
||||
@@ -703,8 +714,6 @@ static bool TryEmulateCpuExtensions(Context& context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__)
|
||||
|
||||
static bool TryEmulateMonitorxMwaitx(Context& context) {
|
||||
const auto* rip = reinterpret_cast<const uint8_t*>(context.Rip());
|
||||
if (rip[0] != 0x0f || rip[1] != 0x01 || (rip[2] != 0xfa && rip[2] != 0xfb)) {
|
||||
@@ -790,8 +799,6 @@ static bool TryEmulateReciprocalSquareRoot(Context& context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool TryEmulate(void* native_context) {
|
||||
if (native_context == nullptr) {
|
||||
return false;
|
||||
@@ -807,12 +814,12 @@ bool TryEmulate(void* native_context) {
|
||||
#else
|
||||
Context context {static_cast<ucontext_t*>(native_context)};
|
||||
#endif
|
||||
#if !defined(__APPLE__)
|
||||
return TryEmulateReciprocalSquareRoot(context) || TryEmulateMonitorxMwaitx(context) ||
|
||||
TryEmulateSse4a(context) || TryEmulateShaNi(context) || TryEmulateCpuExtensions(context);
|
||||
#else
|
||||
return TryEmulateSse4a(context) || TryEmulateCpuExtensions(context);
|
||||
TryEmulateSse4a(context) ||
|
||||
#if !defined(__APPLE__)
|
||||
TryEmulateShaNi(context) ||
|
||||
#endif
|
||||
TryEmulateCpuExtensions(context);
|
||||
}
|
||||
|
||||
} // namespace Loader::X64InstructionEmulator
|
||||
|
||||
@@ -34,6 +34,11 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Libs::LibKernelApr {
|
||||
void InitLibKernel_1_Apr(Loader::SymbolDatabase *symbols);
|
||||
}
|
||||
@@ -1061,19 +1066,40 @@ void CheckSocketWakeup() {
|
||||
namespace Net = Libs::Network::Net;
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::LibNet::InitNet_1_Net(&symbols);
|
||||
const auto *connect_symbol = symbols.Find(
|
||||
{"OXXX4mUk3uk", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *getsockopt_symbol = symbols.Find(
|
||||
{"xphrZusl78E", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *setsockopt_symbol = symbols.Find(
|
||||
{"2mKX2Spso7I", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *send_symbol = symbols.Find(
|
||||
{"beRjXBn-z+o", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *sendto_symbol = symbols.Find(
|
||||
{"gvD1greCu0A", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *recv_symbol = symbols.Find(
|
||||
{"9wO9XrMsNhc", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *recvfrom_symbol = symbols.Find(
|
||||
{"304ooNZxWDY", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
const auto *errno_symbol = symbols.Find(
|
||||
{"HQOwnfMGipQ", "Net", 1, "Net", 1, 1, Loader::SymbolType::Func});
|
||||
Check(send_symbol && recv_symbol && errno_symbol,
|
||||
"Net send, receive and errno exports resolve with the guest ABI versions");
|
||||
Check(connect_symbol && getsockopt_symbol && setsockopt_symbol && send_symbol && sendto_symbol &&
|
||||
recv_symbol && recvfrom_symbol && errno_symbol,
|
||||
"Net socket and errno exports resolve with the guest ABI versions");
|
||||
using Connect = int (KYTY_SYSV_ABI *)(int, const void *, uint32_t);
|
||||
using Getsockopt = int (KYTY_SYSV_ABI *)(int, int, int, void *, uint32_t *);
|
||||
using Setsockopt = int (KYTY_SYSV_ABI *)(int, int, int, const void *, uint32_t);
|
||||
using Send = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int);
|
||||
using Sendto = int (KYTY_SYSV_ABI *)(int, const void *, size_t, int, const void *, uint32_t);
|
||||
using Recv = int (KYTY_SYSV_ABI *)(int, void *, size_t, int);
|
||||
using Recvfrom = int (KYTY_SYSV_ABI *)(int, void *, size_t, int, void *, uint32_t *);
|
||||
using Errno = int *(KYTY_SYSV_ABI *)();
|
||||
const auto net_connect = reinterpret_cast<Connect>(connect_symbol->vaddr);
|
||||
const auto net_getsockopt = reinterpret_cast<Getsockopt>(getsockopt_symbol->vaddr);
|
||||
const auto net_setsockopt = reinterpret_cast<Setsockopt>(setsockopt_symbol->vaddr);
|
||||
const auto net_send = reinterpret_cast<Send>(send_symbol->vaddr);
|
||||
const auto net_sendto = reinterpret_cast<Sendto>(sendto_symbol->vaddr);
|
||||
const auto net_recv = reinterpret_cast<Recv>(recv_symbol->vaddr);
|
||||
const auto net_recvfrom = reinterpret_cast<Recvfrom>(recvfrom_symbol->vaddr);
|
||||
auto *net_errno = reinterpret_cast<Errno>(errno_symbol->vaddr)();
|
||||
const auto [reader, writer] = CreateTcpPair();
|
||||
const int enabled = 1;
|
||||
@@ -1082,10 +1108,15 @@ void CheckSocketWakeup() {
|
||||
int socket_error = -1;
|
||||
uint32_t error_size = sizeof(socket_error);
|
||||
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
|
||||
Check(Net::Getsockopt(writer, 0xffff, 0x1007, &socket_error, &error_size) == 0 &&
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
Check(net_getsockopt(writer, 0xffff, 0x1007, &socket_error, &error_size) == 0 &&
|
||||
socket_error == 0 && error_size == sizeof(socket_error) &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
|
||||
"SO_ERROR reports socket status without changing guest errno");
|
||||
"Net SO_ERROR reports socket status without changing either guest errno");
|
||||
Check(net_getsockopt(writer, 0xffff, 0x1007, nullptr, &error_size) ==
|
||||
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT,
|
||||
"Net getsockopt translates an invalid output buffer");
|
||||
|
||||
std::array<uint64_t, 16> readable {};
|
||||
const auto bit = uint64_t {1} << (reader % 64);
|
||||
@@ -1122,6 +1153,131 @@ void CheckSocketWakeup() {
|
||||
*net_errno == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"Net nonblocking receive translates POSIX failure and Net errno");
|
||||
#endif
|
||||
const int datagram = Net::Socket(2, 2, 0);
|
||||
const int datagram_writer = Net::Socket(2, 2, 0);
|
||||
std::array<uint8_t, 16> address {16, 2, 0, 0, 127, 0, 0, 1};
|
||||
std::array<uint8_t, 16> peer {}, expected_peer {};
|
||||
uint32_t address_size = address.size(), peer_size = peer.size();
|
||||
uint32_t expected_peer_size = expected_peer.size();
|
||||
Check(datagram >= 0 && datagram_writer >= 0 &&
|
||||
Net::Bind(datagram, address.data(), address.size()) == 0 &&
|
||||
Net::Bind(datagram_writer, address.data(), address.size()) == 0 &&
|
||||
Net::Getsockname(datagram, address.data(), &address_size) == 0 &&
|
||||
Net::Getsockname(datagram_writer, expected_peer.data(), &expected_peer_size) == 0 &&
|
||||
Net::Setsockopt(datagram, 0xffff, 0x1200, &enabled, sizeof(enabled)) == 0,
|
||||
"create nonblocking loopback datagrams for Net ABI verification");
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
*Libs::Posix::GetErrorAddr() = Libs::Posix::POSIX_EINVAL;
|
||||
for (const int option : {0x1001, 0x1002}) {
|
||||
constexpr int requested = 16384;
|
||||
int actual = 0;
|
||||
uint32_t size = sizeof(actual);
|
||||
Check(net_setsockopt(datagram, 0xffff, option, &requested, sizeof(requested)) == 0 &&
|
||||
net_getsockopt(datagram, 0xffff, option, &actual, &size) == 0 &&
|
||||
actual >= requested && size == sizeof(actual),
|
||||
"Net UDP send and receive buffers accept guest option numbers");
|
||||
}
|
||||
for (const int value : {1, 0}) {
|
||||
int actual = -1;
|
||||
uint32_t size = sizeof(actual);
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x20, &value, sizeof(value)) == 0 &&
|
||||
net_getsockopt(datagram, 0xffff, 0x20, &actual, &size) == 0 &&
|
||||
actual == value && size == sizeof(actual),
|
||||
"Net UDP broadcast option can be enabled and disabled");
|
||||
}
|
||||
int timeout = 0;
|
||||
int *timeout_value = &timeout;
|
||||
#if defined(__linux__)
|
||||
int broadcast = -1;
|
||||
uint32_t broadcast_size = sizeof(broadcast);
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x10000, &enabled, sizeof(enabled)) == 0 &&
|
||||
net_getsockopt(datagram, 0xffff, 0x20, &broadcast, &broadcast_size) == 0 &&
|
||||
broadcast == 0,
|
||||
"preserving the all-ones destination does not enable broadcast permission");
|
||||
const long page_size = sysconf(_SC_PAGESIZE);
|
||||
Check(page_size > 0, "get host page size for socket timeout boundary");
|
||||
void *timeout_pages = mmap(nullptr, page_size * 2, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Check(timeout_pages != MAP_FAILED &&
|
||||
mprotect(static_cast<char *>(timeout_pages) + page_size, page_size,
|
||||
PROT_NONE) == 0,
|
||||
"guard memory after the four-byte socket timeout");
|
||||
timeout_value = reinterpret_cast<int *>(static_cast<char *>(timeout_pages) +
|
||||
page_size - sizeof(int));
|
||||
#endif
|
||||
for (const int value : {1500000, 0, -1}) {
|
||||
*timeout_value = value;
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x1105, timeout_value, sizeof(int)) == 0,
|
||||
"Net send timeout reads a four-byte microsecond value");
|
||||
*timeout_value = -2;
|
||||
uint32_t size = sizeof(int);
|
||||
Check(net_getsockopt(datagram, 0xffff, 0x1105, timeout_value, &size) == 0 &&
|
||||
*timeout_value == std::max(value, 0) && size == sizeof(int),
|
||||
"Net send timeout returns four-byte microseconds and disables nonpositive values");
|
||||
}
|
||||
#if defined(__linux__)
|
||||
Check(munmap(timeout_pages, page_size * 2) == 0, "free socket timeout guard pages");
|
||||
#endif
|
||||
Check(*net_errno == Libs::Posix::POSIX_EINVAL &&
|
||||
*Libs::Posix::GetErrorAddr() == Libs::Posix::POSIX_EINVAL,
|
||||
"successful Net socket option calls preserve both guest errno values");
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x1105, nullptr, sizeof(timeout)) ==
|
||||
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT &&
|
||||
net_getsockopt(datagram, 0xffff, 0x1105, &timeout, nullptr) ==
|
||||
Libs::Network::NET_ERROR_EFAULT && *net_errno == Libs::Posix::POSIX_EFAULT,
|
||||
"Net socket options reject null value and length pointers");
|
||||
uint32_t short_size = sizeof(timeout) - 1;
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x1105, &timeout, short_size) ==
|
||||
Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL &&
|
||||
net_getsockopt(datagram, 0xffff, 0x1105, &timeout, &short_size) ==
|
||||
Libs::Network::NET_ERROR_EINVAL && *net_errno == Libs::Posix::POSIX_EINVAL,
|
||||
"Net send timeout rejects undersized values");
|
||||
uint32_t option_size = sizeof(timeout);
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x7fffffff, &timeout, option_size) ==
|
||||
Libs::Network::NET_ERROR_ENOPROTOOPT &&
|
||||
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT &&
|
||||
net_getsockopt(datagram, 0xffff, 0x7fffffff, &timeout, &option_size) ==
|
||||
Libs::Network::NET_ERROR_ENOPROTOOPT &&
|
||||
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT,
|
||||
"Net unknown socket options return protocol-option errors");
|
||||
#if defined(__linux__)
|
||||
Check(net_setsockopt(datagram, 0xffff, 0x1007, &timeout, option_size) ==
|
||||
Libs::Network::NET_ERROR_ENOPROTOOPT &&
|
||||
*net_errno == Libs::Posix::POSIX_ENOPROTOOPT,
|
||||
"Net native protocol-option errors retain their guest error code");
|
||||
#endif
|
||||
received.fill(0);
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
Check(net_sendto(datagram_writer, payload, sizeof(payload), 0,
|
||||
address.data(), address_size) == sizeof(payload) &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL,
|
||||
"Net sendto delivers to a guest sockaddr and preserves errno on success");
|
||||
Check(net_recvfrom(datagram, received.data(), received.size(), 0,
|
||||
peer.data(), &peer_size) == sizeof(payload) &&
|
||||
std::memcmp(received.data(), payload, sizeof(payload)) == 0 &&
|
||||
peer_size == expected_peer_size && peer == expected_peer &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL,
|
||||
"Net recvfrom returns datagram bytes and sender address while preserving errno");
|
||||
Check(net_recvfrom(datagram, received.data(), received.size(), 0, nullptr, nullptr) ==
|
||||
Libs::Network::NET_ERROR_EWOULDBLOCK &&
|
||||
*net_errno == Libs::Posix::POSIX_EWOULDBLOCK,
|
||||
"Net recvfrom translates nonblocking failure with an omitted sender address");
|
||||
*net_errno = Libs::Posix::POSIX_EINVAL;
|
||||
Check(net_connect(datagram_writer, address.data(), address_size) == 0 &&
|
||||
*net_errno == Libs::Posix::POSIX_EINVAL &&
|
||||
net_sendto(datagram_writer, payload, sizeof(payload), 0, nullptr, 0) ==
|
||||
sizeof(payload) && *net_errno == Libs::Posix::POSIX_EINVAL &&
|
||||
net_recvfrom(datagram, received.data(), received.size(), 0, nullptr, nullptr) ==
|
||||
sizeof(payload) && std::memcmp(received.data(), payload, sizeof(payload)) == 0,
|
||||
"Net connect selects the peer used by sendto with an omitted destination");
|
||||
Check(net_connect(-1, address.data(), address_size) == Libs::Network::NET_ERROR_EBADF &&
|
||||
*net_errno == Libs::Posix::POSIX_EBADF,
|
||||
"Net connect translates an invalid socket");
|
||||
Check(net_sendto(-1, payload, sizeof(payload), 0, address.data(), address_size) ==
|
||||
Libs::Network::NET_ERROR_EBADF && *net_errno == Libs::Posix::POSIX_EBADF,
|
||||
"Net sendto translates an invalid socket");
|
||||
Check(Net::SocketClose(datagram) == 0 && Net::SocketClose(datagram_writer) == 0,
|
||||
"close Net loopback datagrams");
|
||||
Check(net_send(-1, payload, sizeof(payload), 0) == Libs::Network::NET_ERROR_EBADF &&
|
||||
*net_errno == Libs::Posix::POSIX_EBADF,
|
||||
"Net send translates an invalid socket instead of returning POSIX minus one");
|
||||
@@ -1138,10 +1294,12 @@ void CheckSocketWakeup() {
|
||||
const auto previous_sigpipe = std::signal(SIGPIPE, SIG_DFL);
|
||||
Check(previous_sigpipe != SIG_ERR, "set default SIGPIPE disposition for Net send");
|
||||
const auto broken_send = net_send(disconnected, payload, sizeof(payload), 0);
|
||||
const auto broken_sendto = net_sendto(disconnected, payload, sizeof(payload), 0, nullptr, 0);
|
||||
std::signal(SIGPIPE, previous_sigpipe);
|
||||
Check(broken_send == Libs::Network::NET_ERROR_EPIPE &&
|
||||
broken_sendto == Libs::Network::NET_ERROR_EPIPE &&
|
||||
*net_errno == Libs::Posix::POSIX_EPIPE,
|
||||
"Net send reports a broken pipe without raising host SIGPIPE");
|
||||
"Net send and sendto report a broken pipe without raising host SIGPIPE");
|
||||
Check(Net::SocketClose(disconnected) == 0, "close unconnected socket");
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
|
||||
@@ -33541,7 +33541,8 @@ void CheckIndirectBufferStore(VulkanHarness &vulkan) {
|
||||
candidate.indirect_search_iterations = 0;
|
||||
candidate.indirect_resources.clear();
|
||||
program.descriptor_sources.resize(1);
|
||||
program.descriptor_sources[0].indirect_descriptor.emplace().table_stride = 16;
|
||||
program.descriptor_sources[0].indirect_descriptor =
|
||||
DescriptorSource::IndirectDescriptor{.table_stride = 16};
|
||||
ShaderComputeInputInfo compute;
|
||||
compute.wave_size = 32;
|
||||
compute.host_subgroup_size = vulkan.SubgroupSize();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "common/hostException.h"
|
||||
#include "loader/guestInstructionPatcher.h"
|
||||
#include "loader/x64InstructionEmulator.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
@@ -82,6 +84,53 @@ constexpr uint8_t kUd2[] = {
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
// VRSQRTPS xmm1, xmm0 as the AMD CPU patcher leaves it when it cannot relocate the instruction: a
|
||||
// reserved VEX.vvvv bit is cleared (c5 f8 -> c5 f0), which raises #UD on every host. Rosetta aborted
|
||||
// the guest on exactly this encoding until the emulator handled it there too.
|
||||
constexpr uint8_t kTrappedVrsqrtpsXmm1Xmm0[] = {
|
||||
0x0f, 0x10, 0x06, // movups (%rsi), %xmm0
|
||||
0xc5, 0xfc, 0x10, 0x0e, // vmovups ymm1, [rsi]: dirty the upper half of ymm1
|
||||
0xc5, 0xf0, 0x52, 0xc8, // vrsqrtps xmm1, xmm0 with the vvvv trap mark
|
||||
0x0f, 0x11, 0x0f, // movups %xmm1, (%rdi)
|
||||
0xc4, 0xe3, 0x7d, 0x19, 0x4f, 0x10, 0x01, // vextractf128 [rdi + 16], ymm1, 1
|
||||
0xc5, 0xf8, 0x77, // vzeroupper
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
// The same sequence with the ordinary VEX.128 VRSQRTPS encoding (vvvv = 1111), as it appears in guest
|
||||
// code before the patcher marks it as a trap.
|
||||
constexpr uint8_t kPlainVrsqrtpsXmm1Xmm0[] = {
|
||||
0x0f, 0x10, 0x06, // movups (%rsi), %xmm0
|
||||
0xc5, 0xfc, 0x10, 0x0e, // vmovups ymm1, [rsi]: dirty the upper half of ymm1
|
||||
0xc5, 0xf8, 0x52, 0xc8, // vrsqrtps xmm1, xmm0
|
||||
0x0f, 0x11, 0x0f, // movups %xmm1, (%rdi)
|
||||
0xc4, 0xe3, 0x7d, 0x19, 0x4f, 0x10, 0x01, // vextractf128 [rdi + 16], ymm1, 1
|
||||
0xc5, 0xf8, 0x77, // vzeroupper
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
// The same again behind a never taken indirect branch. A function with an indirect branch does not
|
||||
// let the patcher relocate neighbours, and the 4-byte VRSQRTPS cannot hold a jump on its own, so it
|
||||
// is left in place with the trap mark.
|
||||
constexpr uint8_t kPlainVrsqrtpsIndirectBranch[] = {
|
||||
0x31, 0xc0, // xor %eax, %eax
|
||||
0x85, 0xc0, // test %eax, %eax
|
||||
0x74, 0x02, // jz over
|
||||
0xff, 0xe0, // jmp *%rax (never taken)
|
||||
0x0f, 0x10, 0x06, // movups (%rsi), %xmm0
|
||||
0xc5, 0xfc, 0x10, 0x0e, // vmovups ymm1, [rsi]: dirty the upper half of ymm1
|
||||
0xc5, 0xf8, 0x52, 0xc8, // vrsqrtps xmm1, xmm0
|
||||
0x0f, 0x11, 0x0f, // movups %xmm1, (%rdi)
|
||||
0xc4, 0xe3, 0x7d, 0x19, 0x4f, 0x10, 0x01, // vextractf128 [rdi + 16], ymm1, 1
|
||||
0xc5, 0xf8, 0x77, // vzeroupper
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
constexpr uint8_t kMwaitx[] = {
|
||||
0x0f, 0x01, 0xfb, // mwaitx
|
||||
0xc3, // ret
|
||||
};
|
||||
|
||||
constexpr size_t kPageSize = 4096;
|
||||
|
||||
std::atomic<int> g_illegal_hits {0};
|
||||
@@ -233,6 +282,13 @@ void TestInsertqRegister() {
|
||||
}
|
||||
}
|
||||
|
||||
void TestMwaitxEmulated() {
|
||||
auto fn = MapCode<VoidBlobFn>(kMwaitx);
|
||||
const int before = IllegalHits();
|
||||
fn();
|
||||
Check(IllegalHits() > before, "host raises SIGILL for MWAITX");
|
||||
}
|
||||
|
||||
void TestUnknownInstructionRefused() {
|
||||
g_refused.store(false, std::memory_order_relaxed);
|
||||
g_refuse_skip.store(2, std::memory_order_relaxed);
|
||||
@@ -244,6 +300,170 @@ void TestUnknownInstructionRefused() {
|
||||
g_refuse_skip.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// The loader rewrites SSE4a instructions into native trampolines when the patcher is enabled. The
|
||||
// trampoline area lives right behind the code page, like it does behind a mapped guest module.
|
||||
struct PatchedCode {
|
||||
void *code = nullptr;
|
||||
Loader::GuestInstructionPatchResult result {};
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
PatchedCode MapPatchedCode(const uint8_t (&blob)[N], bool emulate_amd) {
|
||||
void *area = ::mmap(nullptr, 2 * kPageSize, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
Check(area != MAP_FAILED, "map RWX code and trampoline area");
|
||||
std::memcpy(area, blob, N);
|
||||
auto *trampoline = static_cast<uint8_t *>(area) + kPageSize;
|
||||
Loader::RegisterGuestInstructionPatchModule(area, kPageSize, trampoline, kPageSize);
|
||||
const std::array<uintptr_t, 1> function_starts {reinterpret_cast<uintptr_t>(area)};
|
||||
// A host without SSE4a, RDPID and CLWB, which is what Rosetta presents.
|
||||
const Loader::GuestInstructionHostFeatures host {false, false, false};
|
||||
PatchedCode patched;
|
||||
patched.code = area;
|
||||
patched.result = Loader::PatchGuestInstructions(reinterpret_cast<uintptr_t>(area), N, function_starts,
|
||||
false, emulate_amd, host);
|
||||
Loader::UnregisterGuestInstructionPatchModule(area);
|
||||
return patched;
|
||||
}
|
||||
|
||||
using PatchCounts = Loader::InstructionPatchCounts Loader::GuestInstructionPatchResult::*;
|
||||
|
||||
// Runs the blob through the SIGILL emulator (reference) and through the patched copy, which must
|
||||
// produce the same bytes. EXTRQ becomes a native trampoline without a single SIGILL. INSERTQ is
|
||||
// marked as a trap by the patcher and keeps going through the signal emulator.
|
||||
template <typename Fn, size_t N, typename Invoke>
|
||||
void CheckPatchedMatchesEmulator(const uint8_t (&blob)[N], PatchCounts counts, bool native,
|
||||
Invoke invoke, const char *text) {
|
||||
uint64_t reference[2] {};
|
||||
const int before_reference = IllegalHits();
|
||||
invoke(MapCode<Fn>(blob), reference);
|
||||
Check(IllegalHits() > before_reference, text);
|
||||
|
||||
const auto unpatched = MapPatchedCode(blob, false);
|
||||
Check((unpatched.result.*counts).found == 0, "patcher stays off without emulate_amd");
|
||||
uint64_t unpatched_out[2] {};
|
||||
const int before_off = IllegalHits();
|
||||
invoke(reinterpret_cast<Fn>(unpatched.code), unpatched_out);
|
||||
Check(IllegalHits() > before_off, "unpatched copy still raises SIGILL");
|
||||
|
||||
const auto patched = MapPatchedCode(blob, true);
|
||||
Check((patched.result.*counts).found == 1, text);
|
||||
Check((patched.result.*counts).native == (native ? 1u : 0u), "native trampoline count");
|
||||
Check((patched.result.*counts).trapped == (native ? 0u : 1u), "trapped (signal path) count");
|
||||
uint64_t out[2] {};
|
||||
const int before = IllegalHits();
|
||||
invoke(reinterpret_cast<Fn>(patched.code), out);
|
||||
if (native) {
|
||||
Check(IllegalHits() == before, "patched code raises no SIGILL");
|
||||
} else {
|
||||
Check(IllegalHits() > before, "trapped instruction still goes through the signal emulator");
|
||||
}
|
||||
Check(out[0] == reference[0] && out[1] == reference[1], "patched result equals emulated result");
|
||||
}
|
||||
|
||||
void TestPatchedExtrqImmediate() {
|
||||
CheckPatchedMatchesEmulator<BlobFn>(
|
||||
kExtrqXmm2Len40Index0, &Loader::GuestInstructionPatchResult::extrq, true,
|
||||
[](BlobFn fn, uint64_t *out) { fn(out, 0.0, 0.0, std::bit_cast<double>(0xaabbccddeeff1122ull)); },
|
||||
"EXTRQ immediate found by the patcher");
|
||||
}
|
||||
|
||||
void TestPatchedExtrqRexHigh() {
|
||||
CheckPatchedMatchesEmulator<LoadBlobFn>(
|
||||
kExtrqXmm10Len40Index0, &Loader::GuestInstructionPatchResult::extrq, true,
|
||||
[](LoadBlobFn fn, uint64_t *out) {
|
||||
const uint64_t in[2] {0xaabbccddeeff1122ull, 0x5555555555555555ull};
|
||||
fn(out, in);
|
||||
},
|
||||
"REX EXTRQ found by the patcher");
|
||||
}
|
||||
|
||||
void TestPatchedInsertqImmediate() {
|
||||
CheckPatchedMatchesEmulator<BlobFn>(
|
||||
kInsertqXmm2Xmm0Len32Index0, &Loader::GuestInstructionPatchResult::insertq, false,
|
||||
[](BlobFn fn, uint64_t *out) {
|
||||
fn(out, std::bit_cast<double>(0x1122334455667788ull), 0.0,
|
||||
std::bit_cast<double>(0x0123456789abcdefull));
|
||||
},
|
||||
"INSERTQ immediate found by the patcher");
|
||||
}
|
||||
|
||||
void TestPatchedInsertqIndexedRexHigh() {
|
||||
CheckPatchedMatchesEmulator<PairBlobFn>(
|
||||
kInsertqXmm10Xmm9Len16Index8, &Loader::GuestInstructionPatchResult::insertq, false,
|
||||
[](PairBlobFn fn, uint64_t *out) {
|
||||
const uint64_t src[2] {0x8877665544332211ull, 0xdeadbeefcafebabeull};
|
||||
const uint64_t dest[2] {0xaaaaaaaaaaaaaaaaull, 0xbbbbbbbbbbbbbbbbull};
|
||||
fn(out, src, dest);
|
||||
},
|
||||
"indexed REX INSERTQ found by the patcher");
|
||||
}
|
||||
|
||||
void TestPatchedInsertqRegister() {
|
||||
CheckPatchedMatchesEmulator<PairBlobFn>(
|
||||
kInsertqXmm10Xmm9, &Loader::GuestInstructionPatchResult::insertq, false,
|
||||
[](PairBlobFn fn, uint64_t *out) {
|
||||
const uint64_t src[2] {0x0123456789abcdefull, 0xdeadbeef0000c4c8ull};
|
||||
const uint64_t dest[2] {0x8877665544332211ull, 0xfedcba9876543210ull};
|
||||
fn(out, src, dest);
|
||||
},
|
||||
"register INSERTQ found by the patcher");
|
||||
}
|
||||
|
||||
// The ordinary VEX.128 VRSQRTPS must be found by the patcher and give the reference result, either
|
||||
// through a native trampoline (no SIGILL) or, when it cannot be relocated, marked as a trap (the
|
||||
// vvvv bit is cleared in place) and emulated from the signal handler.
|
||||
template <size_t N>
|
||||
void CheckPatchedReciprocalSquareRoot(const uint8_t (&blob)[N], bool native) {
|
||||
const uint32_t in_bits[8] {std::bit_cast<uint32_t>(4.0f), 0x00000000u, 0xbf800000u, 0x7f800000u,
|
||||
0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
|
||||
const auto Run = [&](LoadBlobFn fn, uint32_t (&out_bits)[8]) {
|
||||
fn(reinterpret_cast<uint64_t *>(out_bits), reinterpret_cast<const uint64_t *>(in_bits));
|
||||
};
|
||||
uint32_t reference[8] {};
|
||||
const int before_reference = IllegalHits();
|
||||
Run(MapCode<LoadBlobFn>(kTrappedVrsqrtpsXmm1Xmm0), reference);
|
||||
Check(IllegalHits() > before_reference, "host raises SIGILL for the pre-marked VRSQRTPS");
|
||||
|
||||
const auto unpatched = MapPatchedCode(blob, false);
|
||||
Check(unpatched.result.reciprocal_sqrt.found == 0, "patcher leaves VRSQRTPS alone without emulate_amd");
|
||||
|
||||
const auto patched = MapPatchedCode(blob, true);
|
||||
const auto &counts = patched.result.reciprocal_sqrt;
|
||||
Check(counts.found == 1, "patcher finds the ordinary VEX.128 VRSQRTPS");
|
||||
Check(counts.native == (native ? 1u : 0u) && counts.trapped == (native ? 0u : 1u),
|
||||
"VRSQRTPS native trampoline / trap count");
|
||||
const auto *code = static_cast<const uint8_t *>(patched.code);
|
||||
size_t site = 0;
|
||||
while (site + 2 < N && !(blob[site] == 0xc5 && blob[site + 1] == 0xf8 && blob[site + 2] == 0x52)) {
|
||||
++site;
|
||||
}
|
||||
Check(site + 2 < N, "locate VRSQRTPS in the test blob");
|
||||
if (!native) {
|
||||
Check(code[site] == 0xc5 && code[site + 1] == 0xf0 && code[site + 2] == 0x52,
|
||||
"the trap mark clears a reserved VEX.vvvv bit (c5 f8 -> c5 f0)");
|
||||
} else {
|
||||
Check(code[site] != 0xc5 || code[site + 1] != 0xf8, "native trampoline replaced the VRSQRTPS");
|
||||
}
|
||||
uint32_t out[8] {};
|
||||
const int before = IllegalHits();
|
||||
Run(reinterpret_cast<LoadBlobFn>(patched.code), out);
|
||||
if (native) {
|
||||
Check(IllegalHits() == before, "natively patched VRSQRTPS raises no SIGILL");
|
||||
} else {
|
||||
Check(IllegalHits() > before, "marked VRSQRTPS goes through the signal emulator");
|
||||
}
|
||||
Check(std::memcmp(out, reference, sizeof(out)) == 0, "patched VRSQRTPS result equals the emulated reference");
|
||||
Check(std::bit_cast<float>(out[0]) == 0.5f && out[1] == 0x7f800000u && out[2] == 0xffc00000u && out[3] == 0,
|
||||
"patched VRSQRTPS produces the exact reciprocal square roots");
|
||||
Check(out[4] == 0 && out[5] == 0 && out[6] == 0 && out[7] == 0, "patched VEX.128 form zeroes the upper half");
|
||||
}
|
||||
|
||||
void TestPatchedReciprocalSquareRoot() {
|
||||
CheckPatchedReciprocalSquareRoot(kPlainVrsqrtpsXmm1Xmm0, true);
|
||||
CheckPatchedReciprocalSquareRoot(kPlainVrsqrtpsIndirectBranch, false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
@@ -255,7 +475,14 @@ int main() {
|
||||
TestRexHighRegisterExtrq();
|
||||
TestRexHighRegisterInsertqWithIndex();
|
||||
TestInsertqRegister();
|
||||
TestMwaitxEmulated();
|
||||
TestUnknownInstructionRefused();
|
||||
TestPatchedExtrqImmediate();
|
||||
TestPatchedExtrqRexHigh();
|
||||
TestPatchedInsertqImmediate();
|
||||
TestPatchedInsertqIndexedRexHigh();
|
||||
TestPatchedInsertqRegister();
|
||||
TestPatchedReciprocalSquareRoot();
|
||||
std::printf("macosSse4aTests: all passed\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user