diff --git a/src/loader/jit.h b/src/loader/jit.h index ebc72e431..50f137f36 100644 --- a/src/loader/jit.h +++ b/src/loader/jit.h @@ -58,16 +58,17 @@ struct TlsRegStub { } static uint64_t GetSize() { return 32; } - // sub rsp,0x80 + // lea rsp,[rsp-0x80] + // nop // push rax // call safe_call // mov ,rax // pop rax - // add rsp,0x80 + // lea rsp,[rsp+0x80] // ret - uint8_t code[32] = {0x48, 0x81, 0xEC, 0x80, 0x00, 0x00, 0x00, 0x50, 0xE8, 0x00, 0x00, - 0x00, 0x00, 0x48, 0x89, 0xC0, 0x58, 0x48, 0x81, 0xC4, 0x80, 0x00, - 0x00, 0x00, 0xC3, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90}; + uint8_t code[32] = {0x48, 0x8D, 0x64, 0x24, 0x80, 0x66, 0x90, 0x50, 0xE8, 0x00, 0x00, + 0x00, 0x00, 0x48, 0x89, 0xC0, 0x58, 0x48, 0x8D, 0xA4, 0x24, 0x80, + 0x00, 0x00, 0x00, 0xC3, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90}; }; struct SafeCall { @@ -78,7 +79,8 @@ struct SafeCall { static uint64_t GetSize() { return 0x1000; } uint8_t code[0x90] = { - /*00*/ 0x48, 0x81, 0xec, 0x80, 0x00, 0x00, 0x00, // sub rsp,0x80 + /*00*/ 0x48, 0x8d, 0x64, 0x24, 0x80, // lea rsp,[rsp-0x80] + /*05*/ 0x66, 0x90, // nop /*07*/ 0x9c, // pushfq /*08*/ 0x51, // push rcx /*09*/ 0x52, // push rdx @@ -108,9 +110,9 @@ struct SafeCall { /*40*/ 0x5a, // pop rdx /*41*/ 0x59, // pop rcx /*42*/ 0x9d, // popfq - /*43*/ 0x48, 0x81, 0xc4, 0x80, 0x00, 0x00, - 0x00, // add rsp,0x80 - /*4a*/ 0xc3, // ret + /*43*/ 0x48, 0x8d, 0xa4, 0x24, 0x80, 0x00, 0x00, + 0x00, // lea rsp,[rsp+0x80] + /*4b*/ 0xc3, // ret }; }; diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 111240933..4631e1214 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -8,6 +8,7 @@ #include "kernel/pthread.h" #include "libs/errno.h" #include "loader/guestInstructionPatcher.h" +#include "loader/jit.h" #include "loader/runtimeLinker.h" #include "loader/systemContent.h" #include "loader/x64InstructionEmulator.h" @@ -4775,6 +4776,59 @@ void TestSmallFiberStacksAndMigration() { } // namespace +#if defined(__linux__) || KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS +KYTY_MS_ABI uint8_t* TlsStubTestAddress() { + return reinterpret_cast(static_cast(0x123456789abcull)); +} + +void TestTlsStubsPreserveFlags() { + const char* test = "TlsStubsPreserveFlags"; + const uint64_t stub_size = Loader::Jit::SafeCall::GetSize(); + constexpr uint64_t CODE_SIZE = 0x1000; + constexpr uint8_t REG = 1; + const auto mapping = Libs::LibKernel::Memory::AllocateProgramMemory( + 0x0000000906000000ull, stub_size + CODE_SIZE, Common::VirtualMemory::Mode::ExecuteReadWrite, + "tls_stub_test"); + Check(test, mapping != 0, "failed to allocate TLS stub test code"); + auto* safe_call = new (reinterpret_cast(mapping)) Loader::Jit::SafeCall; + safe_call->SetFunc(TlsStubTestAddress); + const auto stub_address = mapping + Loader::Jit::TlsRegStub::GetOffset(REG); + auto* stub = new (reinterpret_cast(stub_address)) Loader::Jit::TlsRegStub; + stub->SetFunc(mapping); + stub->SetOutputReg(REG); + + using Fixture = uint64_t(KYTY_SYSV_ABI*)(uint64_t*); + bool preserved = true; + for (const auto [input, status]: {std::pair {uint8_t {0x7f}, uint64_t {0x890}}, + std::pair {uint8_t {0xff}, uint64_t {0x055}}}) { + Xbyak::CodeGenerator code(CODE_SIZE, reinterpret_cast(mapping + stub_size)); + code.push(code.rbx); + code.mov(code.rbx, code.rdi); + code.mov(code.rax, 0x5555555555555555ull); + code.mov(code.al, input); + code.add(code.al, 1); + code.mov(code.r11, stub_address); + code.call(code.r11); + code.mov(code.qword[code.rbx], code.rcx); + code.mov(code.qword[code.rbx + 8], code.rax); + code.pushf(); + code.pop(code.rax); + code.pop(code.rbx); + code.ret(); + Check(test, Xbyak::GetError() == 0, "failed to generate TLS stub fixture"); + Check(test, Common::VirtualMemory::FlushInstructionCache(mapping, stub_size + CODE_SIZE), + "failed to flush TLS stub test code"); + std::array registers {}; + const auto flags = reinterpret_cast(mapping + stub_size)(registers.data()); + const auto rax = (0x5555555555555555ull & ~0xffull) | static_cast(input + 1u); + preserved &= (flags & 0x8d5u) == status && registers[0] == 0x123456789abcull && + registers[1] == rax; + } + Libs::LibKernel::Memory::FreeGuestMemory(mapping, stub_size + CODE_SIZE); + Check(test, preserved, "TLS stubs changed the caller's flags or registers"); +} +#endif + int main(int argc, char** argv) { InitSubsystems(); if (argc == 2 && std::strcmp(argv[1], "--virtual-ranges-only") == 0) { @@ -4919,6 +4973,9 @@ int main(int argc, char** argv) { RunTest(TestMemoryPoolCommitDecommitQueryFlags); RunTest(TestProgramMemoryAllocationAndProtection); RunTest(TestModuleRelocationUsesWritableHostMapping); +#if defined(__linux__) || KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS + RunTest(TestTlsStubsPreserveFlags); +#endif if (g_failed_tests != 0) { std::printf("VirtualMemoryAllocationTests: %d case(s) failed\n", g_failed_tests);