Skip to content

Fix ARM PLD/PLI handling - #393

Open
Yonatan Ziv (yonatan007ziv) wants to merge 2 commits into
microsoft:mainfrom
yonatan007ziv:fix-arm-prfop-handling
Open

Yonatan Ziv (yonatan007ziv) wants to merge 2 commits into
microsoft:mainfrom
yonatan007ziv:fix-arm-prfop-handling

Conversation

@yonatan007ziv

@yonatan007ziv Yonatan Ziv (yonatan007ziv) commented Sep 19, 2026 •

Copy link
Copy Markdown

Summary

CDetourDis::CopyLoadAndStoreSingle mishandles Thumb-2 memory hints. Two
defects compound: the hint check can never fire, and once repaired it is still
shadowed by the PC-relative literal-load check above it. The result is that a
PLD/PLI (literal) in a hooked function's prologue is copied into the
trampoline as a branch.

1. The hint check is unsatisfiable (#94)

if ((instruction & 0xFE70F000) == 0xF81FF000) {

The mask clears bits 19:16, but the comparand keeps 0xF there, so the
condition never holds and the entire PLD/PLI block - including the noop
conversion inside it - is dead code. This is the bug reported in #94; the
0xF810F000 value is the one Frerich Raabe (@frerich) derived there.

2. Correcting the constant is not sufficient

PLD/PLI (literal) are encoded as PC-relative loads:

PLD (literal)    1111 1000 U001 1111  1111 imm12
LDRB (literal)   1111 1000 U001 1111  Rt   imm12

The only thing marking the hint is Rt == 0b1111, and the preceding check does
not examine bits 15:12:

if ((instruction & 0xF81F0000) == 0xF81F0000) {   // Rn == PC
    return CopyLiteralLoad32(pSource, pDest);
}

so it matches all four literal hint encodings first and returns.
CopyLiteralLoad32 then reads bits 15:12 as the destination register:

LiteralLoad12& load = (LiteralLoad12&)(instruction);
EmitLongLiteralLoad((PUSHORT&)pDest, load.Register, pTarget);

load.Register is 0b1111 == c_PC, so the trampoline receives
ldr pc, [pc, #imm] - an unconditional branch to whatever word happens to lie
at the preload address.

Testing hints before the literal load resolves this. Affected encodings:
0xF81FFxxx, 0xF89FFxxx, 0xF91FFxxx, 0xF99FFxxx.

Scope

ARM32 only; the change lies entirely within #ifdef DETOURS_ARM. Real literal
loads (LDR/LDRB/LDRSB/LDRH (literal)) are unaffected - they carry
Rt != 0b1111 and still reach CopyLiteralLoad32. PLD/PLI with Rn != PC
still blit unchanged.

Fixes #94.

The mask 0xFE70F000 clears bits 19:16, but the comparand 0xF81FF000
keeps 0xF there, so the test can never hold and the PLD/PLI block is
dead code. Zero the Rn nibble to match the mask.
PLD/PLI (literal) are encoded as PC-relative loads, so the literal load
check matches them first and CopyLiteralLoad32 reads their 0b1111 hint
field as Rt == PC, emitting "ldr pc, [pc, #imm]". The preload hint
becomes a branch to arbitrary data. Move the hint check above it so
those instructions reach the noop conversion.
@yonatan007ziv Yonatan Ziv (yonatan007ziv) changed the title Fix arm prfop handling Fix ARM PLD/PLI handling Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Condition will always be false.

1 participant