#include #include #include #include #include #include namespace { void require(const bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } constexpr std::uint64_t Base = 0x1000; std::vector padTo(std::vector bytes, const std::size_t size) { bytes.resize(size, 0xcc); return bytes; } void append(std::vector& bytes, const std::vector& more) { bytes.insert(bytes.end(), more.begin(), more.end()); } std::vector callTo(const std::size_t from, const std::size_t target) { const auto displacement = static_cast(target) - static_cast(from + 5); return {0xe8, static_cast(displacement), static_cast(displacement >> 8), static_cast(displacement >> 16), static_cast(displacement >> 24)}; } const std::vector Prologue{0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x53}; const std::vector Exit{0x5b, 0x41, 0x5f, 0x5d, 0xc3}; const std::vector StoreBelowPushes{0x48, 0x89, 0x7d, 0xe8}; const std::vector LoadBelowPushes{0x48, 0x8b, 0x45, 0xe8}; const std::vector LoadAbovePushes{0x48, 0x8b, 0x45, 0x08}; std::vector leaf(const std::vector& body) { std::vector bytes = Prologue; append(bytes, body); append(bytes, Exit); return bytes; } struct Image { std::vector bytes; std::vector segments; }; Image image(const std::vector>& functions) { Image result; std::vector starts; std::size_t at = 0x40; for (const auto& function : functions) { starts.push_back(at); at += (function.size() + 15) / 16 * 16 + 16; } std::vector caller; for (const auto start : starts) append(caller, callTo(caller.size(), start)); caller.push_back(0xc3); result.bytes = padTo(caller, 0x40); for (std::size_t index = 0; index < functions.size(); ++index) { result.bytes = padTo(result.bytes, starts[index]); append(result.bytes, functions[index]); } result.bytes = padTo(result.bytes, (result.bytes.size() + 15) / 16 * 16 + 16); result.segments.push_back({1, 5, 0, Base, Base, result.bytes.size(), result.bytes.size(), 16}); return result; } void framesRedZoneLeaf() { std::vector body = StoreBelowPushes; append(body, LoadBelowPushes); const auto input = image({leaf(body)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.Exits == 1, "a red-zone leaf was not framed"); require(sites.size() == 2, "a framed leaf needs one prologue and one exit site"); require(displacements.empty() && report.FramedWithoutFramePointer == 0, "a leaf with a frame pointer had rsp operands moved"); const auto& entry = sites[0]; require(entry.Address == Base + 0x40 && entry.Length == Prologue.size(), "the prologue site does not cover the pushes"); require(entry.OriginalBytes == Prologue, "the prologue site lost its bytes"); std::vector entryBody = Prologue; append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0x80, 0xe9, 0, 0, 0, 0}); require(entry.Body == entryBody && entry.ReturnBranchOffset == Prologue.size() + 5, "the prologue stub does not lower rsp after the pushes"); const auto& exit = sites[1]; require(exit.Address == Base + 0x40 + Prologue.size() + body.size() && exit.Length == Exit.size(), "the exit site does not cover the pops and ret"); std::vector exitBody{0x48, 0x8d, 0xa4, 0x24, 0x80, 0, 0, 0}; append(exitBody, Exit); append(exitBody, {0xe9, 0, 0, 0, 0}); require(exit.Body == exitBody && exit.ReturnBranchOffset == 8 + Exit.size(), "the exit stub does not raise rsp before the pops"); } const std::vector FramelessPrologue{0x53, 0x41, 0x56, 0x41, 0x57}; const std::vector FramelessExit{0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xc3}; const std::vector StoreBelowRsp{0x48, 0x89, 0x7c, 0x24, 0xf0}; const std::vector LoadBelowRsp{0x48, 0x8b, 0x44, 0x24, 0xf0}; const std::vector LoadAboveRsp{0x48, 0x8b, 0x44, 0x24, 0x08}; const std::vector LoadFarAboveRsp{0x48, 0x8b, 0x44, 0x24, 0x78}; const std::vector LoadAtRsp{0x48, 0x8b, 0x04, 0x24}; const std::vector StoreDeepBelowRsp{0x48, 0x89, 0x74, 0x24, 0xd8}; const std::vector LoadDeepBelowRsp{0x48, 0x8b, 0x44, 0x24, 0xd8}; const std::vector AddressBelowRsp{0x48, 0x8d, 0x44, 0x24, 0xe0}; const std::vector AddressAboveRsp{0x48, 0x8d, 0x44, 0x24, 0x08}; const std::vector StoreIndexedBelowRsp{0x48, 0x89, 0x7c, 0xcc, 0xf0}; std::vector framelessLeaf(const std::vector& prologue, const std::vector& body, const std::vector& exit) { std::vector bytes = prologue; append(bytes, body); append(bytes, exit); return bytes; } void framesLeafWithoutFramePointer() { std::vector body = StoreBelowRsp; append(body, LoadBelowRsp); const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.FramedWithoutFramePointer == 1 && report.MovedOperands == 2, "a leaf that only pushes was not framed"); require(sites.size() == 2, "a framed leaf needs one prologue and one exit site"); const auto& entry = sites[0]; require(entry.Address == Base + 0x40 && entry.Length == FramelessPrologue.size(), "the prologue site does not cover the pushes"); std::vector entryBody = FramelessPrologue; append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xf0, 0xe9, 0, 0, 0, 0}); require(entry.Body == entryBody, "the prologue stub does not lower rsp by the leaf's depth below rsp after the pushes"); const auto& exit = sites[1]; require(exit.Address == Base + 0x40 + FramelessPrologue.size() + body.size() && exit.OriginalBytes == FramelessExit, "the exit site does not cover the pops and ret"); std::vector exitBody{0x48, 0x8d, 0xa4, 0x24, 0x10, 0, 0, 0}; append(exitBody, FramelessExit); append(exitBody, {0xe9, 0, 0, 0, 0}); require(exit.Body == exitBody, "the exit stub does not raise rsp by the leaf's depth below rsp before the pops"); const auto storeDisplacement = 0x40 + FramelessPrologue.size() + 4; const auto loadDisplacement = storeDisplacement + StoreBelowRsp.size(); require(displacements.size() == 2, "both rsp operands must move"); require(displacements[0].Offset == storeDisplacement && displacements[0].Bytes == std::vector{0x00}, "the store below rsp was not moved to the lowered rsp"); require(displacements[1].Offset == loadDisplacement && displacements[1].Bytes == std::vector{0x00}, "the load below rsp was not moved to the lowered rsp"); } void shiftsByTheDeepestOperandBelowRsp() { std::vector body = StoreBelowRsp; append(body, StoreDeepBelowRsp); append(body, LoadDeepBelowRsp); const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 1 && sites.size() == 2 && displacements.size() == 3, "a leaf reaching 0x28 bytes below rsp was not framed"); std::vector entryBody = FramelessPrologue; append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xd0, 0xe9, 0, 0, 0, 0}); require(sites[0].Body == entryBody, "rsp is not lowered by the deepest displacement rounded up to 16"); require(std::vector(sites[1].Body.begin(), sites[1].Body.begin() + 8) == std::vector{0x48, 0x8d, 0xa4, 0x24, 0x30, 0, 0, 0}, "rsp is not raised by the shift it was lowered by"); require(displacements[0].Bytes == std::vector{0x20} && displacements[1].Bytes == std::vector{0x08} && displacements[2].Bytes == std::vector{0x08}, "the operands below rsp were not moved by the shift"); } void movesPositiveOperandsThatFitAfterTheShift() { std::vector body = StoreBelowRsp; append(body, LoadAboveRsp); const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 1 && report.MovedOperands == 2 && sites.size() == 2, "a leaf with an 8-bit displacement above rsp that fits after the shift was not framed"); const auto loadDisplacement = 0x40 + FramelessPrologue.size() + StoreBelowRsp.size() + 4; require(displacements.size() == 2 && displacements[1].Offset == loadDisplacement && displacements[1].Bytes == std::vector{0x18}, "the load above rsp was not moved in place by the shift"); } void shiftsByTheWholeRedZoneForAddressesBelowRsp() { std::vector below = StoreBelowRsp; append(below, AddressBelowRsp); std::vector above = StoreBelowRsp; append(above, AddressAboveRsp); const auto input = image({framelessLeaf(FramelessPrologue, below, FramelessExit), framelessLeaf(FramelessPrologue, above, FramelessExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 2 && sites.size() == 4 && displacements.size() == 4, "leaves that take the address of a stack slot were not framed"); require(sites[0].Body[FramelessPrologue.size() + 4] == 0x80 && displacements[0].Bytes == std::vector{0x70} && displacements[1].Bytes == std::vector{0x60}, "a leaf that takes the address of a slot below rsp was not shifted by the whole red zone"); require(sites[2].Body[FramelessPrologue.size() + 4] == 0xf0 && displacements[2].Bytes == std::vector{0x00} && displacements[3].Bytes == std::vector{0x18}, "a leaf that only takes the address of a slot above rsp was shifted by more than its depth"); } void shiftsByTheWholeRedZoneForIndexedOperands() { std::vector body = StoreIndexedBelowRsp; append(body, LoadBelowRsp); const auto input = image({framelessLeaf(FramelessPrologue, body, FramelessExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 1 && sites.size() == 2 && displacements.size() == 2, "a leaf with an indexed operand below rsp was not framed"); require(sites[0].Body[FramelessPrologue.size() + 4] == 0x80 && displacements[0].Bytes == std::vector{0x70} && displacements[1].Bytes == std::vector{0x70}, "a leaf whose indexed operand can reach deeper than its displacement was not shifted by the whole red zone"); } void skipsLeavesWithOperandsThatCannotMove() { std::vector farAbove = StoreBelowRsp; append(farAbove, LoadFarAboveRsp); std::vector atRsp = StoreBelowRsp; append(atRsp, LoadAtRsp); const auto input = image({framelessLeaf(FramelessPrologue, farAbove, FramelessExit), framelessLeaf(FramelessPrologue, atRsp, FramelessExit), framelessLeaf({0x41, 0x56, 0x41, 0x57}, StoreBelowRsp, {0x41, 0x5f, 0x41, 0x5e, 0xc3})}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(sites.empty() && displacements.empty() && report.RedZoneFunctions == 3, "a leaf whose rsp operands cannot all move, or whose prologue is too short, was framed"); require(report.Skipped.at("rsp operand that cannot move with rsp") == 2 && report.Skipped.at("prologue shorter than a jump") == 1, "the skipped leaves were not reported with their reasons"); } void skipsLeavesThatCopyRsp() { std::vector withCmov = StoreBelowRsp; append(withCmov, {0x48, 0x0f, 0x45, 0xc4}); std::vector withShlx = StoreBelowRsp; append(withShlx, {0xc4, 0xe2, 0xf1, 0xf7, 0xc4}); const auto input = image({framelessLeaf(FramelessPrologue, withCmov, FramelessExit), framelessLeaf(FramelessPrologue, withShlx, FramelessExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(sites.empty() && displacements.empty() && report.Skipped.at("other use of rsp") == 2, "a leaf that copies rsp into another register was framed"); } const std::vector InflatePrologue{0x55, 0x41, 0x57, 0x41, 0x56, 0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x83, 0xec, 0x40}; const std::vector InflatePops{0x5b, 0x41, 0x5c, 0x41, 0x5d, 0x41, 0x5e, 0x41, 0x5f, 0x5d, 0xc3}; const std::vector InflateFree{0x48, 0x83, 0xc4, 0x40}; std::vector inflateExit() { std::vector bytes = InflateFree; append(bytes, InflatePops); return bytes; } std::vector inflateBody() { return { 0x48, 0x89, 0x7c, 0x24, 0xe0, 0x48, 0x89, 0x7c, 0x24, 0x00, 0x48, 0x89, 0x74, 0x24, 0x08, 0x48, 0x89, 0x54, 0x24, 0x38, 0xb9, 0x04, 0x00, 0x00, 0x00, 0x31, 0xc0, 0x48, 0x03, 0x44, 0x24, 0x08, 0x48, 0x03, 0x44, 0x24, 0xe0, 0x48, 0x03, 0x44, 0x24, 0x00, 0xff, 0xc9, 0x75, 0xed, 0x48, 0x8b, 0x54, 0x24, 0x38, }; } void framesLeavesThatAllocateBelowTheirPushes() { const auto body = inflateBody(); const auto input = image({framelessLeaf(InflatePrologue, body, inflateExit())}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.RedZoneFunctions == 1 && report.Framed == 1 && report.FramedWithoutFramePointer == 1 && report.Exits == 1 && sites.size() == 2, "a leaf that allocates with sub after its pushes and spills below its allocation was not framed"); const auto& entry = sites[0]; require(entry.Address == Base + 0x40 && entry.OriginalBytes == InflatePrologue, "the prologue site does not cover the pushes and the sub"); std::vector entryBody = InflatePrologue; append(entryBody, {0x48, 0x8d, 0x64, 0x24, 0xe0, 0xe9, 0, 0, 0, 0}); require(entry.Body == entryBody, "the prologue stub does not lower rsp by 0x20 after the sub"); const auto& exit = sites[1]; require(exit.Address == Base + 0x40 + InflatePrologue.size() + body.size() && exit.OriginalBytes == inflateExit(), "the exit site does not cover the add, the pops and the ret"); std::vector exitBody{0x48, 0x8d, 0xa4, 0x24, 0x20, 0, 0, 0}; append(exitBody, inflateExit()); append(exitBody, {0xe9, 0, 0, 0, 0}); require(exit.Body == exitBody, "the exit stub does not raise rsp by 0x20 before the add"); const std::vector moved{0x00, 0x20, 0x28, 0x58, 0x28, 0x00, 0x20, 0x58}; require(displacements.size() == moved.size() && report.MovedOperands == moved.size(), "not every rsp operand of the allocating leaf was moved"); for (std::size_t index = 0; index < moved.size(); ++index) require(displacements[index].Bytes == std::vector{moved[index]}, "an rsp operand of the allocating leaf was not moved by 0x20 from the post-sub rsp"); require(displacements[0].Offset == 0x40 + InflatePrologue.size() + 4, "the spill below the allocation was not the first operand moved"); } void framesLeavesThatAllocateWithLeaOrALargeSub() { const std::vector leaPrologue{0x53, 0x41, 0x56, 0x48, 0x8d, 0x64, 0x24, 0xc0}; const std::vector leaExit{0x48, 0x8d, 0x64, 0x24, 0x40, 0x41, 0x5e, 0x5b, 0xc3}; const std::vector largePrologue{0x53, 0x48, 0x81, 0xec, 0x00, 0x01, 0x00, 0x00}; const std::vector largeExit{0x48, 0x81, 0xc4, 0x00, 0x01, 0x00, 0x00, 0x5b, 0xc3}; std::vector largeBody = StoreBelowRsp; append(largeBody, {0x48, 0x8b, 0x84, 0x24, 0x80, 0x00, 0x00, 0x00}); const auto input = image({framelessLeaf(leaPrologue, StoreBelowRsp, leaExit), framelessLeaf(largePrologue, largeBody, largeExit)}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 2 && sites.size() == 4 && displacements.size() == 3, "leaves that allocate with lea or a 32-bit sub were not framed"); require(sites[0].OriginalBytes == leaPrologue && sites[1].OriginalBytes == leaExit, "the lea allocation and free are not part of the prologue and exit sites"); require(sites[2].OriginalBytes == largePrologue && sites[3].OriginalBytes == largeExit, "the 32-bit sub and add are not part of the prologue and exit sites"); require(displacements[2].Bytes == std::vector{0x90, 0x00, 0x00, 0x00}, "the 32-bit displacement into the allocation was not moved by the shift"); } void skipsAllocatingLeavesWithoutAMatchingFree() { const std::vector otherFree{0x48, 0x83, 0xc4, 0x30}; std::vector wrongFree = otherFree; append(wrongFree, InflatePops); std::vector moreRsp = StoreBelowRsp; append(moreRsp, {0x48, 0x83, 0xec, 0x08}); const auto input = image({framelessLeaf(InflatePrologue, StoreBelowRsp, InflatePops), framelessLeaf(InflatePrologue, StoreBelowRsp, wrongFree), framelessLeaf(InflatePrologue, moreRsp, inflateExit())}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(sites.empty() && displacements.empty() && report.RedZoneFunctions == 3, "an allocating leaf without a matching free, or with more rsp arithmetic, was framed"); require(report.Skipped.at("exit does not free the prologue's allocation") == 1, "an exit that does not free the allocation was not reported with its own reason"); require(report.Skipped.at("other use of rsp") == 2, "an add of another size before the pops, or more rsp arithmetic in the body, was not reported as another use of rsp"); } Image calledThenUncalled(const std::vector& called, const std::vector& uncalled) { Image result; result.bytes = callTo(0, 0x40); result.bytes.push_back(0xc3); result.bytes = padTo(result.bytes, 0x40); append(result.bytes, called); result.bytes.resize(0x60, 0x90); append(result.bytes, uncalled); result.bytes = padTo(result.bytes, 0x80); result.segments.push_back({1, 5, 0, Base, Base, result.bytes.size(), result.bytes.size(), 16}); return result; } void endsBodiesAtAlignedCodeAfterARet() { std::vector body = StoreBelowRsp; append(body, LoadBelowRsp); const auto input = calledThenUncalled(framelessLeaf(FramelessPrologue, body, FramelessExit), framelessLeaf(FramelessPrologue, StoreBelowRsp, FramelessExit)); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 1 && report.Exits == 1 && sites.size() == 2 && displacements.size() == 2, "the called leaf was not framed on its own"); for (const auto& site : sites) require(site.Address < Base + 0x60, "a leaf reached without a call was framed as part of the leaf before it"); for (const auto& displacement : displacements) require(displacement.Offset < 0x60, "an rsp operand of a leaf reached without a call was moved"); } void endsBodiesAtTailCallsToTheNextFunction() { std::vector called = FramelessPrologue; append(called, StoreBelowRsp); append(called, {0x85, 0xff, 0x75, 0x06}); append(called, FramelessExit); append(called, {0x41, 0x5f, 0x41, 0x5e, 0x5b, 0xe9, 0x02, 0x00, 0x00, 0x00}); const auto input = calledThenUncalled(called, framelessLeaf(FramelessPrologue, StoreBelowRsp, FramelessExit)); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(sites.empty() && displacements.empty(), "a leaf that tail-calls the function after it was framed together with that function"); require(report.Skipped.at("branch leaves the function") == 1, "the tail call was not reported as leaving the function"); } void keepsLoopsAlignedAfterAJump() { std::vector loop = FramelessPrologue; append(loop, StoreBelowRsp); append(loop, {0x31, 0xc0, 0xeb, 0x04, 0x66, 0x90, 0xff, 0xc0, 0x3b, 0x44, 0x24, 0xf0, 0x72, 0xf8}); append(loop, FramelessExit); const auto input = image({loop}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(report.Framed == 1 && sites.size() == 2 && displacements.size() == 2, "a leaf whose loop header is aligned after a jump was not framed"); } void leavesOtherFunctions() { std::vector nonLeaf = Prologue; append(nonLeaf, StoreBelowPushes); append(nonLeaf, {0xff, 0xd0}); append(nonLeaf, Exit); const auto input = image({leaf(LoadAbovePushes), nonLeaf}); Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {}, report, displacements); require(sites.empty() && report.RedZoneFunctions == 0, "a leaf without red-zone use or a function that calls was framed"); } void skipsSitesTakenByOtherStubs() { const auto input = image({leaf(StoreBelowPushes)}); Codegen::TrampolineSite other{}; other.Address = Base + 0x40; other.Length = 5; Codegen::RedZoneFrameReport report; std::vector displacements; const auto sites = Codegen::FrameRedZoneFunctions(input.bytes, input.segments, {other}, report, displacements); require(sites.empty() && report.Skipped.at("overlaps another stub") == 1, "a site over another stub was framed"); } } int main() { try { framesRedZoneLeaf(); leavesOtherFunctions(); skipsSitesTakenByOtherStubs(); framesLeafWithoutFramePointer(); shiftsByTheDeepestOperandBelowRsp(); movesPositiveOperandsThatFitAfterTheShift(); shiftsByTheWholeRedZoneForAddressesBelowRsp(); shiftsByTheWholeRedZoneForIndexedOperands(); framesLeavesThatAllocateBelowTheirPushes(); framesLeavesThatAllocateWithLeaOrALargeSub(); skipsAllocatingLeavesWithoutAMatchingFree(); skipsLeavesWithOperandsThatCannotMove(); skipsLeavesThatCopyRsp(); endsBodiesAtAlignedCodeAfterARet(); endsBodiesAtTailCallsToTheNextFunction(); keepsLoopsAlignedAfterAJump(); std::cout << "red-zone framer tests passed\n"; return 0; } catch (const std::exception& error) { std::cerr << "red-zone framer tests failed: " << error.what() << '\n'; return 1; } }