Repository navigation
feat(relinker): frame red-zone leaves without a frame pointer by moving their rsp operands - #5640
EagleBearerYS wants to merge 6 commits into
Conversation
…e on Windows Windows writes an exception frame directly below RSP, so a fault resumed on a guest thread overwrites values a System V leaf function keeps in its red zone. With --frame-red-zone, a leaf function with a push rbp; mov rbp, rsp; push ... prologue that reads or writes below its pushes gets two kinds of trampoline: the prologue lowers RSP by 128 bytes after its pushes, and each pop ...; pop rbp; ret exit raises it back first. Sites a branch enters, sites shorter than a jump and sites over a --to-intel stub are skipped; the conversion reports the framed and skipped counts.
…ng their rsp operands
… start A leaf's body ran from its start to the next known start or int3 padding, so a function reached only through a pointer, placed right after the leaf without int3 padding, was framed as part of the leaf: its rsp operands were moved and its exits raised rsp although nothing had lowered it. The body now also ends at 16-byte-aligned code after a ret or ud2, and at the target of a jmp when only padding lies between them (a tail call to the next function). Code past such a point is framed only from a real start. A leaf whose code continues past it is skipped as branching out of the function. Aligned code after any other jmp stays in the body: loop headers are aligned there, and cutting after every jmp skipped 2,129 of 2,131 framed leaves in PPSA01556. With this cut 2,132 are framed, and none of them has rewritten bytes in another function of the unwind table.
…her open PR changes
…ge row off lines other open PRs change
adriadam10
left a comment
There was a problem hiding this comment.
Thanks for the detailed write-up. Two blockers before this can be reviewed:
- Base PR is closed.
Depends on: #5052was closed unmerged on 2026-10-14, so this branch still carries its wholeRedZoneFramer(the diff againstmainis +659) plus Josip Parlov's commit. Either coordinate with them to reopen/land it, or resubmit as a self-contained PR with the authorship preserved. - Motivation has changed. #361 is now fixed at the source by #5596 (merged). On #5052 parl0v measured 0 crashes in 20 boots of PPSA25344 without the flag on
mainwith #5596. Please re-measure the Village boot crash (2 of 5 without framing) on currentmainwith #5596 included, with and without--frame-red-zone. If it no longer reproduces, there is nothing for this opt-in pass to fix.
If it still reproduces, also note that #5052's review asked for function bounds from the .eh_frame FDEs (EhFrameReader) rather than heuristics; the "end of body" rule added in a8ed810 (aligned code after ret/ud2, jmp to padding) is a heuristic that leaves a documented gap, so it should use the FDEs here too.
|
Re-measured on current
So there's nothing left for this pass to fix in Village, and I'm closing it. Thanks for the review. parl0v's |
What
#5052's
--frame-red-zoneframes leaf functions with apush rbp; mov rbp, rspprologue. Titles whose red-zone leaves have no frame pointer get no framing at all:In Village this is a boot crash, the #361 mechanism. A frameless merge leaf (
app.exe+0x1469cbb..) keeps its loop state at-0x8to-0x28(%rsp)and copies 48-byte entries withvmovups. One of its stores faulted on a write-tracked view and was resumed. Afterwards the slots below RSP held zeros and YMM upper halves. The loop stopped short and left zeroed entries, and GameThread later read0x18from a null pointer atapp.exe+0x69cb959. That happened in 2 of 5 boots.This builds on #5052. Only the last two commits are this pull request:
pop ...; retexits of at least five bytes, gets the same prologue and exit stubs as feat(relinker): --frame-red-zone gives red-zone leaf functions a frame on Windows #5052's leaves.leaof a slot below RSP moves the same way, so the address it computes is unchanged.cmovandshlx), and in every case feat(relinker): --frame-red-zone gives red-zone leaf functions a frame on Windows #5052 skips.0x1693a0follows theretof the leaf at0x1691f0with no padding, and is never called directly.retorud2, and at the target of ajmpwhen only padding lies between them (a tail call to the next function). Code past such a point is framed only from a real function start.jmpstays in the body, because clang aligns rotated loop headers there. Cutting after everyjmpskipped 2,129 of Village's 2,131 framed leaves.jmpto elsewhere, such as a loop's back edge, without int3 padding. Village has none (see Tested). If feat(relinker): --frame-red-zone gives red-zone leaf functions a frame on Windows #5052 takes its bounds from the.eh_frameFDEs, as its review asks, this pull request uses them on rebase.The debt entry and
USAGE.mddescribe the new shape. Inside a moved frame, the CFA rules of the guest's unwind tables are 128 bytes off; the debt entry records it.#5596, tibzejoker's port of #637, avoids the write fault itself for direct memory mapped at one address. Framing protects a leaf from any exception frame Windows writes below RSP while the leaf runs, whatever memory faulted, so the two cover different cases.
Tested
red_zone_framer.ret, and both its RSP operands are moved;jmpis still framed.red_zone_framerpasses 20 of 20 runs. With the first commit's framer, the second commit's case fails (https://github.com/EagleBearerYS/AnyPS5/actions/runs/38498470455).mainfeddf7f:--frame-red-zone:PT_GNU_EH_FRAME, comparing the framed and unframed executables):0x1693a0was the one exception.python3 tools/check_conventions.py --base fab7d203f(feat(relinker): --frame-red-zone gives red-zone leaf functions a frame on Windows #5052's head): passed.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)