From e11b4b55540a29ee2287f086a7cbee5b777b9769 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Mon, 5 Oct 2026 02:02:08 +0200 Subject: [PATCH] feat(relinker): lower SHA-1 instructions with a memory operand SHA1RNDS4/NEXTE/MSG1/MSG2 with a memory source made `--to-intel` fail the whole relink. The stub now spills a scratch xmm, loads the operand into it with the unaligned load already used for SHA-256 and runs the register-form lowering. The SHA1RNDS4 immediate is read after the SIB byte and displacement. RIP-relative operands still fail: the stub runs at a different address. Co-Authored-By: Claude Opus 5.5 --- .../include/codegen/x86/Sha1Operands.hpp | 3 + .../include/codegen/x86/StubBodyBuilder.hpp | 1 + .../relinker/codegen/src/x86/Sha1Lowering.cpp | 11 ++ .../relinker/codegen/src/x86/Sha1Operands.cpp | 22 ++- .../codegen/src/x86/StubBodyBuilder.cpp | 3 +- .../codegen/tests/Amd64OnlyConverterTests.cpp | 49 +++++- .../tests/Amd64OnlyWindowsTests.cpp | 145 ++++++++++++++++++ docs/dev/TechnicalDebt.md | 2 +- 8 files changed, 220 insertions(+), 16 deletions(-) diff --git a/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp b/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp index 6f00e1d92..01faa731e 100644 --- a/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp +++ b/core/relinker/codegen/include/codegen/x86/Sha1Operands.hpp @@ -1,8 +1,10 @@ #ifndef CODEGEN_X86_SHA1OPERANDS_HPP #define CODEGEN_X86_SHA1OPERANDS_HPP +#include #include #include +#include namespace Codegen { @@ -18,6 +20,7 @@ struct Sha1Operands { std::uint8_t Destination; std::uint8_t Source; std::uint8_t Function; + std::optional Memory; }; [[nodiscard]] Sha1Operands DecodeSha1(const std::uint8_t* data, std::size_t length); diff --git a/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp b/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp index 942f7db56..692cbc6a4 100644 --- a/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp +++ b/core/relinker/codegen/include/codegen/x86/StubBodyBuilder.hpp @@ -25,6 +25,7 @@ struct MemoryOperand { std::uint8_t Sib; std::int32_t Displacement; bool StackBase; + std::size_t EncodedSize; }; [[nodiscard]] MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, std::size_t length, std::size_t modRmOffset, std::uint8_t rex, std::vector prefixes); diff --git a/core/relinker/codegen/src/x86/Sha1Lowering.cpp b/core/relinker/codegen/src/x86/Sha1Lowering.cpp index 0c5253747..c8a4d2b5c 100644 --- a/core/relinker/codegen/src/x86/Sha1Lowering.cpp +++ b/core/relinker/codegen/src/x86/Sha1Lowering.cpp @@ -156,6 +156,17 @@ void _emitMessage2(StubBodyBuilder& body, const Sha1Operands& operands) { } void Sha1Lowering::EmitOutOfLine(StubBodyBuilder& body, const Sha1Operands& operands) const { + if (operands.Memory) { + auto loaded = operands; + loaded.Memory.reset(); + loaded.Source = loaded.Destination; + loaded.Source = _scratch<1>(loaded)[0]; + body.Spill(loaded.Source); + body.Load(loaded.Source, *operands.Memory); + EmitOutOfLine(body, loaded); + body.Restore(loaded.Source); + return; + } switch (operands.Operation) { case Sha1Operation::Rnds4: _emitRounds(body, operands); diff --git a/core/relinker/codegen/src/x86/Sha1Operands.cpp b/core/relinker/codegen/src/x86/Sha1Operands.cpp index ffcb25b82..950267e12 100644 --- a/core/relinker/codegen/src/x86/Sha1Operands.cpp +++ b/core/relinker/codegen/src/x86/Sha1Operands.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include namespace Codegen { @@ -9,6 +11,7 @@ using namespace X64OpcodeConstants; Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { std::size_t pos = 0; std::uint8_t rex = 0; + std::vector prefixes; while (pos < length) { const std::uint8_t b = data[pos]; @@ -25,6 +28,8 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { b != PrefixSegEs && b != PrefixSegFs && b != PrefixSegGs) { break; } + if (b != PrefixLock) + prefixes.push_back(b); rex = 0; pos += 1; } @@ -46,19 +51,22 @@ Sha1Operands DecodeSha1(const std::uint8_t* data, const std::size_t length) { throw CodegenException("Not a SHA-1 instruction"); } - const std::uint8_t modrm = data[pos + 3]; + const std::size_t modRmOffset = pos + 3; + const std::uint8_t modrm = data[modRmOffset]; + std::size_t immediateOffset = modRmOffset + 1; + operands.Destination = static_cast(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0)); if (((modrm >> ModRmModShift) & ModRmModMask) != ModRmModRegister) { - throw CodegenException("SHA-1 instruction with a memory operand"); + operands.Memory = DecodeMemoryOperand(data, length, modRmOffset, rex, std::move(prefixes)); + immediateOffset = modRmOffset + operands.Memory->EncodedSize; + } else { + operands.Source = static_cast((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0)); } if (operands.Operation == Sha1Operation::Rnds4) { - if (pos + 5 > length) { + if (immediateOffset >= length) { throw CodegenException("SHA1RNDS4 truncated before its immediate"); } - operands.Function = static_cast(data[pos + 4] & 3); + operands.Function = static_cast(data[immediateOffset] & 3); } - - operands.Destination = static_cast(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0)); - operands.Source = static_cast((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0)); return operands; } diff --git a/core/relinker/codegen/src/x86/StubBodyBuilder.cpp b/core/relinker/codegen/src/x86/StubBodyBuilder.cpp index 0fef6e627..7828b9691 100644 --- a/core/relinker/codegen/src/x86/StubBodyBuilder.cpp +++ b/core/relinker/codegen/src/x86/StubBodyBuilder.cpp @@ -58,7 +58,7 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le if (modRmOffset >= length) throw CodegenException("Memory operand truncated before its ModRM byte"); const auto modrm = data[modRmOffset]; - MemoryOperand operand{std::move(prefixes), static_cast(rex & (kRexX | kRexB)), static_cast((modrm >> ModRmModShift) & ModRmModMask), static_cast(modrm & ModRmRmMask), 0, 0, false}; + MemoryOperand operand{std::move(prefixes), static_cast(rex & (kRexX | kRexB)), static_cast((modrm >> ModRmModShift) & ModRmModMask), static_cast(modrm & ModRmRmMask), 0, 0, false, 0}; if (operand.Mod == ModRmModRegister) throw CodegenException("Register operand where a memory operand was expected"); if (operand.Mod == ModRmModIndirect && operand.Rm == ModRmRmRipRelative) @@ -73,6 +73,7 @@ MemoryOperand DecodeMemoryOperand(const std::uint8_t* data, const std::size_t le const auto size = _displacementSize(operand.Mod, operand.Rm, operand.Sib); if (pos + size > length) throw CodegenException("Memory operand truncated in its displacement"); + operand.EncodedSize = pos + size - modRmOffset; if (size == Disp8Size) operand.Displacement = static_cast(data[pos]); for (std::size_t index = 0; size == Disp32Size && index < size; ++index) diff --git a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp index 4bd58800d..c4fab89f4 100644 --- a/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp +++ b/core/relinker/codegen/tests/Amd64OnlyConverterTests.cpp @@ -221,9 +221,26 @@ void sha1Operands() { check({0x44, 0x0F, 0x38, 0xC8, 0xC0}, Codegen::Sha1Operation::Nexte, 8, 0, 0); check({0x0F, 0x38, 0xC9, 0xD5}, Codegen::Sha1Operation::Msg1, 2, 5, 0); check({0x2E, 0x41, 0x0F, 0x38, 0xCA, 0xCA}, Codegen::Sha1Operation::Msg2, 1, 10, 0); - requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x08}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 memory operand was accepted"); - requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x08, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory operand was accepted"); + const Bytes memory = {0x65, 0x67, 0x44, 0x0F, 0x3A, 0xCC, 0x54, 0x8C, 0xF0, 0xFE}; + const auto decoded = Codegen::DecodeSha1(memory.data(), memory.size()); + require(decoded.Operation == Codegen::Sha1Operation::Rnds4 && decoded.Destination == 10 && decoded.Function == 2 && decoded.Memory && decoded.Memory->Prefixes == Bytes{0x65, 0x67} && decoded.Memory->Mod == 1 && decoded.Memory->Rm == 4 && decoded.Memory->Sib == 0x8C && decoded.Memory->Displacement == -16 && decoded.Memory->StackBase, "SHA-1 memory operand was decoded incorrectly"); + const auto memoryFunction = [](const Bytes& site, const int dst, const std::int32_t displacement, const int function) { + const auto operands = Codegen::DecodeSha1(site.data(), site.size()); + require(operands.Operation == Codegen::Sha1Operation::Rnds4 && operands.Destination == dst && operands.Memory && !operands.Memory->StackBase && operands.Memory->Displacement == displacement && operands.Function == function, "SHA1RNDS4 immediate was not read after its memory operand"); + }; + memoryFunction({0x0F, 0x3A, 0xCC, 0x08, 0x03}, 1, 0, 3); + memoryFunction({0x0F, 0x3A, 0xCC, 0x50, 0x02, 0x01}, 2, 2, 1); + memoryFunction({0x0F, 0x3A, 0xCC, 0x91, 0x78, 0x56, 0x34, 0x12, 0x01}, 2, 0x12345678, 1); + memoryFunction({0x41, 0x0F, 0x3A, 0xCC, 0x14, 0x24, 0x02}, 2, 0, 2); + memoryFunction({0x0F, 0x3A, 0xCC, 0x14, 0x25, 0x78, 0x56, 0x34, 0x12, 0x03}, 2, 0x12345678, 3); + const Bytes message = {0x0F, 0x38, 0xC9, 0x08}; + const auto decodedMessage = Codegen::DecodeSha1(message.data(), message.size()); + require(decodedMessage.Operation == Codegen::Sha1Operation::Msg1 && decodedMessage.Destination == 1 && decodedMessage.Memory && decodedMessage.Memory->Mod == 0 && decodedMessage.Memory->Rm == 0, "SHA1MSG1 memory operand was decoded incorrectly"); + requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x15, 0, 0, 0, 0}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-1 RIP-relative operand was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x15, 0, 0, 0, 0, 0x00}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 RIP-relative operand was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xC9, 0x54, 0x24}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Truncated SHA-1 memory operand was accepted"); requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 without its immediate was accepted"); + requireFailure([] { const Bytes bytes = {0x0F, 0x3A, 0xCC, 0x50, 0x02}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA1RNDS4 memory form without its immediate was accepted"); requireFailure([] { const Bytes bytes = {0x66, 0x0F, 0x38, 0xC8, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "Prefixed 0F 38 C8 was decoded as SHA-1"); requireFailure([] { const Bytes bytes = {0x0F, 0x38, 0xCC, 0xCA}; (void)Codegen::DecodeSha1(bytes.data(), bytes.size()); }, "SHA-256 was decoded as SHA-1"); const Bytes prefixedRounds = {0x66, 0x0F, 0x3A, 0xCC, 0xCA, 0x00}; @@ -293,6 +310,12 @@ void matcherSubstitutions() { const auto sha1 = match(bytes); require(sha1 && sha1->Lowering == Codegen::Amd64OnlyLowering::Trampoline && sha1->InstructionName == name, "SHA-1 instruction was not lowered through a stub"); } + for (const auto& [bytes, name] : {std::pair{Bytes{0x0F, 0x3A, 0xCC, 0x08, 0x01}, "SHA1RNDS4"}, {Bytes{0x0F, 0x38, 0xC8, 0x48, 0x10}, "SHA1NEXTE"}, {Bytes{0x0F, 0x38, 0xC9, 0x4C, 0x24, 0x18}, "SHA1MSG1"}, {Bytes{0x45, 0x0F, 0x38, 0xCA, 0x21}, "SHA1MSG2"}}) { + const auto sha1 = match(bytes); + require(sha1 && sha1->Lowering == Codegen::Amd64OnlyLowering::Trampoline && sha1->InstructionName == name, "SHA-1 memory form was not lowered through a stub"); + } + const auto stackMessage = match({0x0F, 0x38, 0xC9, 0x4C, 0x24, 0x18}); + require(stackMessage && stackMessage->StubBody.size() > 22 && Bytes(stackMessage->StubBody.begin() + 13, stackMessage->StubBody.begin() + 22) == Bytes{0xF3, 0x0F, 0x6F, 0x84, 0x24, 0xA8, 0x00, 0x00, 0x00}, "SHA-1 stack operand was not loaded past the spilled scratch register"); const auto stub = match(kInsertqHighSite); require(stub && stub->Lowering == Codegen::Amd64OnlyLowering::Trampoline && stub->StubBody == kInsertqHighBody && stub->ReturnBranchOffset == 15 && stub->InstructionName == "INSERTQ", "INSERTQ was not lowered through a stub"); const auto topAligned = match({0x66, 0x0F, 0x78, 0xC3, 0x18, 0x28}); @@ -466,7 +489,12 @@ void converterSha1() { requireFailure([&] { (void)converter->Convert(beforeReturn, {segmentHeader(text.size())}); }, "Short SHA-1 instruction followed by a return was relocated"); auto memoryForm = file; memoryForm[0x214] = 0x28; - require(failureOffset([&] { (void)converter->Convert(memoryForm, {segmentHeader(text.size())}); }, "SHA-1 memory form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset"); + const auto memoryResult = converter->Convert(memoryForm, {segmentHeader(text.size())}); + require(memoryResult.Trampolines.size() == 3 && memoryResult.Reports[2].InstructionName == "SHA1MSG2", "SHA-1 memory form was not lowered through a stub"); + auto ripRelative = file; + const Bytes ripMessage = {0x0F, 0x38, 0xCA, 0x2D, 0x00, 0x00, 0x00, 0x00, 0xC3}; + std::copy(ripMessage.begin(), ripMessage.end(), ripRelative.begin() + 0x211); + require(failureOffset([&] { (void)converter->Convert(ripRelative, {segmentHeader(text.size() + 3)}); }, "SHA-1 RIP-relative form was accepted") == 0x211, "SHA-1 operand failure does not carry the file offset"); } void converterMonitorWait() { @@ -1030,7 +1058,9 @@ void sha1Execution() { {0x0F, 0x3A, 0xCC, 0xD2, 0x02}, {0x45, 0x0F, 0x3A, 0xCC, 0xCE, 0x00}, {0x41, 0x0F, 0x3A, 0xCC, 0xC0, 0x01}, {0x0F, 0x38, 0xC8, 0xD5}, {0x0F, 0x38, 0xC8, 0xD2}, {0x44, 0x0F, 0x38, 0xC8, 0xC7}, {0x0F, 0x38, 0xC9, 0xD5}, {0x0F, 0x38, 0xC9, 0xD2}, {0x41, 0x0F, 0x38, 0xC9, 0xC7}, - {0x0F, 0x38, 0xCA, 0xD5}, {0x0F, 0x38, 0xCA, 0xD2}, {0x45, 0x0F, 0x38, 0xCA, 0xFF}}; + {0x0F, 0x38, 0xCA, 0xD5}, {0x0F, 0x38, 0xCA, 0xD2}, {0x45, 0x0F, 0x38, 0xCA, 0xFF}, + {0x0F, 0x3A, 0xCC, 0x10, 0x02}, {0x44, 0x0F, 0x3A, 0xCC, 0x40, 0x10, 0x01}, {0x0F, 0x3A, 0xCC, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00}, {0x0F, 0x3A, 0xCC, 0x44, 0x20, 0x10, 0xFF}, + {0x0F, 0x38, 0xC8, 0x00}, {0x0F, 0x38, 0xC9, 0x48, 0x10}, {0x44, 0x0F, 0x38, 0xCA, 0xB8, 0x10, 0x00, 0x00, 0x00}}; for (const auto& site : sites) { const auto operands = Codegen::DecodeSha1(site.data(), site.size()); const auto match = matcher->Match(site.data(), site.size()); @@ -1041,19 +1071,24 @@ void sha1Execution() { reg[0] = random(); reg[1] = random(); } + alignas(16) const std::uint64_t memory[4] = {random(), random(), random(), random()}; + const auto rax = operands.Memory ? reinterpret_cast(memory) : 0x5A5A5A5A5A5A5A5Aull; std::uint32_t x[4]; std::uint32_t y[4]; std::memcpy(x, xmmIn[operands.Destination], sizeof(x)); - std::memcpy(y, xmmIn[operands.Source], sizeof(y)); + if (operands.Memory) + std::memcpy(y, reinterpret_cast(memory) + operands.Memory->Displacement, sizeof(y)); + else + std::memcpy(y, xmmIn[operands.Source], sizeof(y)); const auto expected = sha1Reference(operands, x, y); - const auto run = runStubBody(*match, 0x5A5A5A5A5A5A5A5Aull, xmmIn); + const auto run = runStubBody(*match, rax, xmmIn); for (unsigned reg = 0; reg < 16; ++reg) { if (reg == operands.Destination) require(std::memcmp(run.Xmm[reg], expected.data(), 16) == 0, "SHA-1 stub computed the wrong result"); else require(run.Xmm[reg][0] == xmmIn[reg][0] && run.Xmm[reg][1] == xmmIn[reg][1], "SHA-1 stub clobbered an xmm register"); } - require(run.Rax == 0x5A5A5A5A5A5A5A5Aull && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); + require(run.Rax == rax && run.Rcx == 0x1122334455667788ull && (run.Flags & 0x8D5) == (0x8D7 & 0x8D5), "SHA-1 stub changed a general register or RFLAGS"); } } } diff --git a/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp b/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp index 2a48ce66d..749aa1923 100644 --- a/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp +++ b/core/relinker/elfpatcher/tests/Amd64OnlyWindowsTests.cpp @@ -344,6 +344,150 @@ void executeSha256(const Harness& harness, const Codegen::IAmd64OnlyInstructionM } } +struct Sha1Memory { + std::uint8_t ModRm; + Bytes Tail; + std::optional Slot; + const char* Name; +}; + +const std::array kSha1Memory = {{ + {0x03, {}, std::uint8_t{0}, "[rbx]"}, + {0x43, {0x70}, std::uint8_t{7}, "[rbx+0x70]"}, + {0x83, {0xF0, 0x00, 0x00, 0x00}, std::uint8_t{15}, "[rbx+0xf0]"}, + {0x44, {0x24, 0xF0}, std::nullopt, "[rsp-0x10]"}}}; + +struct Sha1Step { + std::uint8_t Opcode; + std::uint8_t Destination; + std::uint8_t Source; + std::uint8_t Immediate = 0; + bool Memory = false; +}; + +Bytes encodeSha1(const Sha1Step& step) { + Bytes bytes; + const auto rex = static_cast(0x40 | (step.Destination >= 8 ? 4 : 0) | (!step.Memory && step.Source >= 8 ? 1 : 0)); + if (rex != 0x40) bytes.push_back(rex); + bytes.insert(bytes.end(), {0x0F, static_cast(step.Opcode == 0xCC ? 0x3A : 0x38), step.Opcode}); + if (step.Memory) { + const auto& memory = kSha1Memory[step.Source]; + bytes.push_back(static_cast(memory.ModRm | ((step.Destination & 7) << 3))); + bytes.insert(bytes.end(), memory.Tail.begin(), memory.Tail.end()); + } else { + bytes.push_back(static_cast(0xC0 | ((step.Destination & 7) << 3) | (step.Source & 7))); + } + if (step.Opcode == 0xCC) bytes.push_back(step.Immediate); + return bytes; +} + +std::uint32_t rotl(const std::uint32_t value, const unsigned count) { + return (value << count) | (value >> (32 - count)); +} + +void sha1Reference(const Sha1Step& step, const State& input, std::uint8_t (&xmm)[16][16]) { + std::uint32_t x[4]; + std::uint32_t y[4]; + std::uint32_t r[4]; + std::memcpy(x, xmm[step.Destination], sizeof(x)); + if (!step.Memory) { + std::memcpy(y, xmm[step.Source], sizeof(y)); + } else if (const auto slot = kSha1Memory[step.Source].Slot) { + std::memcpy(y, input.Xmm[*slot], sizeof(y)); + } else { + const std::uint64_t canary[2] = {kCanary, kCanary}; + std::memcpy(y, canary, sizeof(y)); + } + if (step.Opcode == 0xC8) { + r[0] = y[0]; + r[1] = y[1]; + r[2] = y[2]; + r[3] = y[3] + rotl(x[3], 30); + } else if (step.Opcode == 0xC9) { + r[0] = x[0] ^ y[2]; + r[1] = x[1] ^ y[3]; + r[2] = x[2] ^ x[0]; + r[3] = x[3] ^ x[1]; + } else if (step.Opcode == 0xCA) { + r[3] = rotl(x[3] ^ y[2], 1); + r[2] = rotl(x[2] ^ y[1], 1); + r[1] = rotl(x[1] ^ y[0], 1); + r[0] = rotl(x[0] ^ r[3], 1); + } else { + constexpr std::uint32_t keys[4] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6}; + const auto function = step.Immediate & 3; + std::uint32_t a = x[3], b = x[2], c = x[1], d = x[0], e = 0; + for (int round = 0; round < 4; ++round) { + std::uint32_t f = b ^ c ^ d; + if (function == 0) f = (b & c) ^ (~b & d); + if (function == 2) f = (b & c) ^ (b & d) ^ (c & d); + const auto next = f + rotl(a, 5) + y[3 - round] + e + keys[function]; + e = d; d = c; c = rotl(b, 30); b = a; a = next; + } + r[0] = d; + r[1] = c; + r[2] = b; + r[3] = a; + } + std::memcpy(xmm[step.Destination], r, sizeof(r)); +} + +std::string describeSha1(const std::vector& steps) { + std::string text; + for (const auto& step : steps) { + text += std::string(step.Opcode == 0xCC ? "sha1rnds4" : step.Opcode == 0xC8 ? "sha1nexte" : step.Opcode == 0xC9 ? "sha1msg1" : "sha1msg2") + " xmm" + std::to_string(step.Destination) + ", "; + text += step.Memory ? std::string(kSha1Memory[step.Source].Name) : "xmm" + std::to_string(step.Source); + if (step.Opcode == 0xCC) text += ", " + std::to_string(step.Immediate); + text += "; "; + } + return text; +} + +void executeSha1(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, const std::vector& steps, std::mt19937_64& random, const int images) { + std::vector sites; + for (const auto& step : steps) sites.push_back(encodeSha1(step)); + const std::vector> spans(sites.begin(), sites.end()); + const auto match = steps.size() == 1 ? matcher.Match(sites[0].data(), sites[0].size()) : matcher.MatchSequence(spans, {}); + require(match.has_value() && match->Lowering == Codegen::Amd64OnlyLowering::Trampoline, "SHA-1 was not lowered through a stub: " + describeSha1(steps)); + std::uint16_t written = 0; + for (const auto& step : steps) written = static_cast(written | (1u << step.Destination)); + const std::array flags = {0x202, 0x203, 0x246, 0xAC7}; + for (int image = 0; image < images; ++image) { + State input{}; + for (auto& lane : input.Xmm) for (auto& byte : lane) byte = static_cast(random()); + input.FlagsIn = flags[static_cast(image) % flags.size()]; + State state = input; + harness.Run(match->StubBody, match->ReturnBranchOffset, state); + ++g_executions; + State expected = input; + for (const auto& step : steps) sha1Reference(step, input, expected.Xmm); + for (unsigned reg = 0; reg < 16; ++reg) + if (((written >> reg) & 1) != 0) require(std::memcmp(state.Xmm[reg], expected.Xmm[reg], 16) == 0, "Lowered SHA-1 computed the wrong xmm" + std::to_string(reg) + ": " + describeSha1(steps)); + requireEnvironment(input, state, written, describeSha1(steps)); + } +} + +void sha1Execution(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, std::mt19937_64& random) { + const std::array opcodes = {0xCC, 0xC8, 0xC9, 0xCA}; + for (const auto opcode : opcodes) { + for (std::uint8_t dst = 0; dst < 16; ++dst) { + for (std::uint8_t src = 0; src < 16; ++src) + executeSha1(harness, matcher, {{opcode, dst, src, static_cast(random())}}, random, 2); + for (std::uint8_t memory = 0; memory < kSha1Memory.size(); ++memory) + for (std::uint8_t function = 0; function < 4; ++function) + executeSha1(harness, matcher, {{opcode, dst, memory, static_cast((random() & 0xFC) | function), true}}, random, 2); + } + } + for (int sequence = 0; sequence < 256; ++sequence) { + std::vector steps(2 + random() % 2); + for (auto& step : steps) { + const bool memory = random() % 2 == 0; + step = {opcodes[random() % opcodes.size()], static_cast(random() % 16), static_cast(random() % (memory ? kSha1Memory.size() : 16)), static_cast(random()), memory}; + } + executeSha1(harness, matcher, steps, random, 2); + } +} + void clzeroExecution(const Harness& harness, const Codegen::IAmd64OnlyInstructionMatcher& matcher, std::mt19937_64& random) { const Bytes plain = {0x0F, 0x01, 0xFC}; const Bytes addressSize32 = {0x67, 0x0F, 0x01, 0xFC}; @@ -418,6 +562,7 @@ void cpuExecution() { executeSha256(harness, *matcher, steps, random, 2); } clzeroExecution(harness, *matcher, random); + sha1Execution(harness, *matcher, random); } Bytes elfFixture(const Bytes& text) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5c87a3d98..0acc50fb9 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -65,7 +65,7 @@ Throughout the project, every function at every stage either **does exactly what - [v_fma_f64](../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterFloat64.cpp) is fused exactly only for normal inputs: with a subnormal input it is a separate multiply and add, which rounds twice. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability. - [libSceJpegEnc](../../core/libs/prx/libSceJpegEnc/Export.cpp) encodes 4:2:2 sampling requests with 4:2:0 chroma subsampling, and grayscale requests as a 3-component JPEG with neutral chroma instead of a single-component one: the [JPEG encoder](../../core/Decoder/Jpeg/src/Jpeg.cpp) (stb) only produces 3-component 4:2:0 and 4:4:4 images. -- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 instructions with a memory operand and SHA-256 instructions with a RIP-relative operand fail the relink. +- `--to-intel` does not lower RDPRU/MCOMMIT; the [matcher](../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp) fails the relink instead. SHA-1 and SHA-256 instructions with a RIP-relative operand fail the relink. - The length-changing path of the [instruction rewriter](../../core/relinker/codegen/src/x86/X64InstructionRewriter.cpp) is not used by the [converter](../../core/relinker/codegen/src/Amd64OnlyConverter.cpp): it does not adjust VEX/0F38/0F3A RIP-relative operands, data-to-code references (relocations, FDEs, jump tables) or segment sizes, so every substitution keeps the instruction length. - The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test. - The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.