diff --git a/dm/tools/patch_nso.py b/dm/tools/patch_nso.py index 5c807d9..28d3836 100644 --- a/dm/tools/patch_nso.py +++ b/dm/tools/patch_nso.py @@ -506,6 +506,9 @@ def find_windows(insl): j = i while j < len(insl) and insl[j].mnemonic not in EXCL_ST: j += 1 if j >= len(insl) or j - i >= 16: continue + # Another exclusive load before the store: this one has no store of its own (a retry block, see retry_block); spanning both would merge two sequences + # (Tears of the Kingdom main+0x5a4674: ldxr w18; cmp; b.eq ; ...; 0x5a469c: ldxr w13; and; stxr; cbnz, a loop other code branches to) + if any(x.mnemonic in EXCL_LD for x in insl[i + 1:j]): continue if j + 1 < len(insl) and insl[j + 1].mnemonic in ('cbz', 'cbnz'): j += 1 seg = insl[i:j + 1] if any(has18(x) for x in seg): out.append(seg) @@ -544,6 +547,57 @@ def site_window(seg): emit(STRX(S, T)); emit(LDP(S, T, 31, -16)); emit(B(here(), t)); emit(NOP) return start, lo, hi +def retry_block(insl, k, wseg): + """The branch insl[k] lands in the middle of window wseg. Accepted only when it ends an out-of-line retry block that re-enters the window at its store-exclusive: + an exclusive load, then instructions that do not branch, then this branch. Tears of the Kingdom main+0x18598: ldxr w17,[x10]; cmp w15,w17; b.eq 0x181fc, + where 0x181fc is the stxr w17,w18,[x10] of the window 0x181ec..0x18200, whose failed stxr branches back to 0x18598 (same shape at 0x185e8 → 0x18244). + Returns (the retry block, the window from that store on), or None""" + t = insl[k].operands[-1].imm + tail = [y for y in wseg if y.address >= t] + if not tail or tail[0].mnemonic not in EXCL_ST: return None + for p in range(k - 1, max(-1, k - 8), -1): + q = insl[p] + if q.mnemonic in EXCL_LD: return insl[p:k + 1], tail + if q.mnemonic in ('b', 'bl', 'br', 'blr', 'ret', 'cbz', 'cbnz', 'tbz', 'tbnz') or q.mnemonic.startswith('b.') or q.mnemonic in EXCL_ST: return None + return None + +def site_retry_window(parts): + """A retry block (see retry_block) gets a patch copy of its own: it cannot jump into the window's copy, because the x18 stand-in would have to be loaded + between its exclusive load and the store. parts = [(retry block, its fall-through), (window from the store on, the window's fall-through)] are copied one + after the other under one prologue, each followed by its fall-through stub; branches to any copied instruction (between the parts, and the failed store's + branch back to the retry block's head) stay inside the copy, every other target leaves through an exit stub as in site_window""" + used = set() + for seg, _ in parts: + for x in seg: used |= regs_in(x) + S, T = pick(used) + start = here() + emit(STP(S, T, 31, -16)); emit(MRS_TPIDRRO(T)); emit_slot_addr(T, S, 'K18'); emit(LDRX(S, T)) + pos, at = {}, here() + for seg, _ in parts: + for x in seg: pos[x.address] = at; at += 4 + at += 16 + exits, ex_targets = at, [] + for seg, ft in parts: + for x in seg: + w = struct.unpack('> 2 + if m in ('tbz', 'tbnz'): w = (w & 0xFFF8001F) | ((off & 0x3FFF) << 5) + else: w = (w & 0xFF00001F) | ((off & 0x7FFFF) << 5) + dd = dis(w, pc); assert dd.operands[-1].imm == newt and dd.mnemonic == m + else: + assert m not in ('adr', 'adrp', 'b', 'bl', 'br', 'blr', 'ret') and not (m == 'ldr' and '[' not in x.op_str), hex(x.address) + emit(w) + emit(STRX(S, T)); emit(LDP(S, T, 31, -16)); emit(B(here(), ft)); emit(NOP) + for t in ex_targets: + emit(STRX(S, T)); emit(LDP(S, T, 31, -16)); emit(B(here(), t)); emit(NOP) + return start + # ---- Function boundaries (eh_frame_hdr) ---- m0 = struct.unpack_from('= 0 else (a, a + 4) -done = set(); windows = []; jumps = {}; tabjumps = {} +done = set(); windows = []; jumps = {}; tabjumps = {}; retries = [] def jump_offsets(insl, j): """The br x18 is preceded by the offset-table form (Engage main+0x12eec94): cmp w18,#N; b.hi; ldrsw x18,[xB,x18,lsl #2]; add x18,x18,xB. The index may be another register, and the bounds check may be on a register copied into it: Link's Awakening's GLSLC (subsdk0+0x1be6e8) has @@ -636,6 +690,52 @@ def jump_offsets(insl, j): if page is None: return None tb = page + lo return [tb + struct.unpack_from('