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 <noreply@anthropic.com>
This commit is contained in:
DotDebian 2026-10-05 02:02:08 +02:00
parent 9651ec9a92
commit e11b4b5554
8 changed files with 220 additions and 16 deletions

View File

@ -1,8 +1,10 @@
#ifndef CODEGEN_X86_SHA1OPERANDS_HPP
#define CODEGEN_X86_SHA1OPERANDS_HPP
#include <codegen/x86/StubBodyBuilder.hpp>
#include <cstddef>
#include <cstdint>
#include <optional>
namespace Codegen {
@ -18,6 +20,7 @@ struct Sha1Operands {
std::uint8_t Destination;
std::uint8_t Source;
std::uint8_t Function;
std::optional<MemoryOperand> Memory;
};
[[nodiscard]] Sha1Operands DecodeSha1(const std::uint8_t* data, std::size_t length);

View File

@ -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<std::uint8_t> prefixes);

View File

@ -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);

View File

@ -1,6 +1,8 @@
#include <codegen/x86/Sha1Operands.hpp>
#include <codegen/x86/X64OpcodeConstants.hpp>
#include <codegen/CodegenException.hpp>
#include <utility>
#include <vector>
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<std::uint8_t> 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<std::uint8_t>(((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<std::uint8_t>((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<std::uint8_t>(data[pos + 4] & 3);
operands.Function = static_cast<std::uint8_t>(data[immediateOffset] & 3);
}
operands.Destination = static_cast<std::uint8_t>(((modrm >> ModRmRegShift) & ModRmRegMask) | (((rex & 0x4) != 0) ? 8 : 0));
operands.Source = static_cast<std::uint8_t>((modrm & ModRmRmMask) | (((rex & 0x1) != 0) ? 8 : 0));
return operands;
}

View File

@ -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<std::uint8_t>(rex & (kRexX | kRexB)), static_cast<std::uint8_t>((modrm >> ModRmModShift) & ModRmModMask), static_cast<std::uint8_t>(modrm & ModRmRmMask), 0, 0, false};
MemoryOperand operand{std::move(prefixes), static_cast<std::uint8_t>(rex & (kRexX | kRexB)), static_cast<std::uint8_t>((modrm >> ModRmModShift) & ModRmModMask), static_cast<std::uint8_t>(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<std::int8_t>(data[pos]);
for (std::size_t index = 0; size == Disp32Size && index < size; ++index)

View File

@ -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<std::uint64_t>(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<const std::uint8_t*>(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");
}
}
}

View File

@ -344,6 +344,150 @@ void executeSha256(const Harness& harness, const Codegen::IAmd64OnlyInstructionM
}
}
struct Sha1Memory {
std::uint8_t ModRm;
Bytes Tail;
std::optional<std::uint8_t> Slot;
const char* Name;
};
const std::array<Sha1Memory, 4> 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<std::uint8_t>(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<std::uint8_t>(step.Opcode == 0xCC ? 0x3A : 0x38), step.Opcode});
if (step.Memory) {
const auto& memory = kSha1Memory[step.Source];
bytes.push_back(static_cast<std::uint8_t>(memory.ModRm | ((step.Destination & 7) << 3)));
bytes.insert(bytes.end(), memory.Tail.begin(), memory.Tail.end());
} else {
bytes.push_back(static_cast<std::uint8_t>(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<Sha1Step>& 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<Sha1Step>& steps, std::mt19937_64& random, const int images) {
std::vector<Bytes> sites;
for (const auto& step : steps) sites.push_back(encodeSha1(step));
const std::vector<std::span<const std::uint8_t>> 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<std::uint16_t>(written | (1u << step.Destination));
const std::array<std::uint64_t, 4> 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<std::uint8_t>(random());
input.FlagsIn = flags[static_cast<std::size_t>(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<std::uint8_t, 4> 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<std::uint8_t>(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<std::uint8_t>((random() & 0xFC) | function), true}}, random, 2);
}
}
for (int sequence = 0; sequence < 256; ++sequence) {
std::vector<Sha1Step> steps(2 + random() % 2);
for (auto& step : steps) {
const bool memory = random() % 2 == 0;
step = {opcodes[random() % opcodes.size()], static_cast<std::uint8_t>(random() % 16), static_cast<std::uint8_t>(random() % (memory ? kSha1Memory.size() : 16)), static_cast<std::uint8_t>(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) {

View File

@ -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.