[Loader] Rewrite SHA instructions in spans that touch no guest memory
The red-zone patcher computes a shifted core for every span: the run of faultable guest memory accesses that executes with RSP moved below the red zone. A span over register-only instructions has no such access, so the core could not be computed and the span was refused as too short. SHA rewrite sites are exactly that shape (sha256msg1/msg2/rnds2 on registers), so no SHA instruction was rewritten any more: on hosts without the SHA extensions every one trapped and was emulated one at a time. Poppy Playtime logged sha_rewrites=0 with 144 too-short refusals, hit hundreds of thousands of illegal-instruction traps and stopped making progress within a minute. A span with no guest memory access has nothing that must run outside the shift, and the span builder has already refused any instruction that touches RSP, so the whole span is the core.
This commit is contained in:
parent
50198a694b
commit
eb7521543c
|
|
@ -349,6 +349,41 @@ internal static class GuestRedZonePatcher
|
|||
/// behaviour can be pinned on the byte sequences that occur in real guest
|
||||
/// code rather than on a mocked decode.
|
||||
/// </summary>
|
||||
// Test hook: the forward span a site at siteAddress gets, as the patcher builds it.
|
||||
internal static bool TryBuildForwardSpan(
|
||||
byte[] code,
|
||||
ulong baseAddress,
|
||||
ulong siteAddress,
|
||||
out int spanLength,
|
||||
out int coreStart,
|
||||
out int coreCount)
|
||||
{
|
||||
spanLength = 0;
|
||||
coreStart = 0;
|
||||
coreCount = 0;
|
||||
var decoded = DecodeFunction(code, baseAddress, baseAddress, baseAddress + (ulong)code.Length);
|
||||
var siteIndex = -1;
|
||||
for (var index = 0; index < decoded.Count; index++)
|
||||
{
|
||||
if (decoded[index].Instruction.IP == siteAddress)
|
||||
{
|
||||
siteIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (siteIndex < 0 ||
|
||||
!TryBuildPatchSpan(decoded, siteIndex, CollectBranchTargets(decoded), out var site, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
spanLength = site.ByteLength;
|
||||
coreStart = site.CoreStart;
|
||||
coreCount = site.CoreCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool TryBuildEnclosingSpan(
|
||||
byte[] code,
|
||||
ulong baseAddress,
|
||||
|
|
@ -417,8 +452,13 @@ internal static class GuestRedZonePatcher
|
|||
|
||||
if (coreStart < 0)
|
||||
{
|
||||
coreCount = 0;
|
||||
return false;
|
||||
// Nothing in the span touches guest memory: it is a SHA or vector-store rewrite over
|
||||
// register-only instructions. The span builder already refused anything that touches
|
||||
// RSP, so the whole span can run inside the shift. Refusing it here left every SHA
|
||||
// instruction unrewritten, trapping on each execution on hosts without SHA.
|
||||
coreStart = 0;
|
||||
coreCount = instructions.Count;
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var index = coreStart; index <= coreEnd; index++)
|
||||
|
|
|
|||
|
|
@ -127,4 +127,27 @@ public sealed class GuestRedZoneEnclosingSpanTests
|
|||
Assert.False(GuestRedZonePatcher.TryBuildEnclosingSpan(
|
||||
code, Base, Base + 2, out _, out _, out _, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildsASpanOverRegisterOnlyShaInstructions()
|
||||
{
|
||||
// sha256msg1 xmm1, xmm2 -> 4 bytes, no memory operand: the rewrite site
|
||||
// sha256msg2 xmm1, xmm2 -> 4 bytes, taken to reach the 5-byte jump
|
||||
// ret
|
||||
byte[] code =
|
||||
[
|
||||
0x0F, 0x38, 0xCC, 0xCA,
|
||||
0x0F, 0x38, 0xCD, 0xCA,
|
||||
0xC3,
|
||||
];
|
||||
|
||||
// A span with no guest memory access has nothing to keep outside the RSP shift, so
|
||||
// the whole span is the core. Refusing it left SHA instructions unrewritten.
|
||||
Assert.True(GuestRedZonePatcher.TryBuildForwardSpan(
|
||||
code, Base, Base, out var length, out var coreStart, out var coreCount));
|
||||
|
||||
Assert.Equal(8, length);
|
||||
Assert.Equal(0, coreStart);
|
||||
Assert.Equal(2, coreCount);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue