target/mips: fix spurious EXCP_RI under UC_TLB_VIRTUAL after a delay-slot TLB fill#2347
target/mips: fix spurious EXCP_RI under UC_TLB_VIRTUAL after a delay-slot TLB fill#2347retrocpugeek wants to merge 2 commits into
Conversation
…slot TLB fill
unicorn_fill_tlb() unconditionally called cpu_restore_state() at the top. On a
successful fill the faulting access is resumed in place within the same TB, so
the env rollback performed by the target's restore_state_to_opc() leaks into the
continued execution. On MIPS this re-applies the branch-delay hflags
(MIPS_HFLAG_B / MIPS_HFLAG_BDS32) that were saved for a delay-slot instruction.
Those bits are normally never present in the runtime hflags, so nothing ever
clears them again, and the next TB (the branch target) is translated as if it
were sitting in a delay slot, raising a spurious EXCP_RI ("branch in
delay/forbidden slot").
Restore CPU state only on the exception path (via cpu_loop_exit_restore), which
mirrors the CPUs' own *_cpu_tlb_fill handlers. The UC_HOOK_TLB_FILL callbacks
only need the faulting address, which is passed explicitly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…corn-engine#2272) Exercises a branch whose target is itself a branch, with a TLB-filled memory access in the first branch's delay slot, under UC_TLB_VIRTUAL with a UC_HOOK_TLB_FILL hook registered. Before the fix this raised a spurious EXCP_RI; it now runs to completion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the detailed explanation of the bug.
This is not correct, see #2342 and #2049. The FAQ also says following:
So general it's assumed that the PC is synced (and all other registers) when a hook is called. I see that the vtlb hook is a candidate for an exception and would also like to remove the By the way: Is it possible to trigger similar bug with an |
|
You're right — thanks, I've corrected the PR description. "Hooks only need the faulting address" over-generalized the contract; the accurate framing is that the restore here is unconditional on a resume-in-place path, so it corrupts live On the The real root cause is that MIPS |
Fixes #2272.
Symptom
Under
UC_TLB_VIRTUAL, a memory access in a branch delay slot raises a spuriousEXCP_RI("Reserved Instruction / branch in delay-forbidden slot") on the branchtarget, aborting emulation. The MIPS repro is glibc's
__libc_setup_tlsPT_TLSprogram-header scan, whose loop body ends in a branch whose delay-slot load forces a
TLB fill.
Root cause
unicorn_fill_tlb()calledcpu_restore_state(cs, retaddr, false)unconditionally.cpu_restore_state()→restore_state_to_opc()rollsenvback to the instructionboundary of the faulting access, and on MIPS that re-applies the per-insn start
hflags. For a delay-slot instruction those include the branch hflags
(
MIPS_HFLAG_B/MIPS_HFLAG_BDS32/ …), which are only ever meant to exist duringtranslation, never in the live runtime hflags.
That rollback is fine on the paths
cpu_restore_state()was designed for — the CPU isabout to leave the run loop (raise an exception, or read the exact PC then exit), so
the transient hflags are re-derived on the next entry and never observed. But on a
successful TLB fill the faulting access is resumed in place inside the same TB.
Nothing clears the leaked bits, so the next TB — the branch target — is translated as
if it sat in a delay slot, and MIPS raises
EXCP_RI.Fix
Remove the unconditional
cpu_restore_state()from the fill path. The exception pathstill restores via
cpu_loop_exit_restore()(inraise_mmu_exception()), mirroring theCPUs' own
*_cpu_tlb_fillhandlers, which only restore on the exception path. TheUC_HOOK_TLB_FILLcallback is invoked with the faulting page/type and the entry tofill; it does not consume rolled-back register state.
This also removes a per-fill
cpu_restore_state()from the hot path, which is exactlythe change requested for performance reasons in #2342.
Note on the earlier wording: I originally justified this as "the TLB_FILL callback only
needs the faulting address." That over-generalized — per the FAQ and #2342/#2049, hooks
are normally expected to see a synced PC. The accurate framing is the one above: the
restore is unconditional on a resume-in-place path, where it corrupts live
envstateregardless of whether any hook is registered.
Note on #2273: it also targets #2272 but changes only the no-hook fallback path's
permissions; it does not fix the case where a
UC_HOOK_TLB_FILLhook is registered(verified — the RI still fires). This fix covers both paths and is orthogonal to #2273.
Answer to "can a
UC_HOOK_MEM_*_UNMAPPEDhook trigger a similar bug?"Yes — and it is not specific to the virtual TLB. The MEM hook paths in
cputlb.c(
UC_HOOK_MEM_READ/WRITEand the*_UNMAPPEDvariants) also callcpu_restore_state(uc->cpu, retaddr, false)before invoking the hook, and after a*_UNMAPPEDhook maps the page and returnstruethe faulting access likewise resumesin place. So the same delay-slot hflag leak occurs.
I reproduced it in the ordinary soft-MMU (no
UC_TLB_VIRTUAL): a delay-slot loadthat faults, serviced by a
UC_HOOK_MEM_READ_UNMAPPEDcallback that maps the page andreturns
true, resumes with the leaked hflags and raisesEXCP_RI(intno 20 viaUC_HOOK_INTR;UC_ERR_EXCEPTIONotherwise). Pre-mapping the page so no hook firesmakes the same program complete correctly — isolating the cause to the hook-triggered
cpu_restore_state(). Standalone failing repro (kept out of this PR): #2352.The important difference: those MEM paths call
cpu_restore_state()deliberately, tohonour the "synced PC in hooks" contract, and are already gated (
!synced && !skip_sync_pc_on_exit). They can't simply drop the call the way the vtlb fill can.The proper fix for that class is lower-level — clearing the transient branch hflags
after a resume-in-place restore (in
restore_state_to_opc()/ its MIPS callers) so thecontract and correctness both hold. That's a broader change than this regression fix, so
I've kept it out of this PR; happy to open a follow-up if you agree with the direction.
Test
Adds
test_mips_virtual_tlb_branch_in_delay_slot_2272intests/unit/test_mips.c,exercising the
UC_HOOK_TLB_FILLpath (how a soft-MMU front-end such as Qiling drivesthe virtual TLB).
Downstream tracking: qilingframework/qiling#1645 (Qiling MIPS64 real-ELF/glibc support gates its CI tests on this fix).