Skip to content

target/mips: fix spurious EXCP_AdEL on ll/lld under UC_TLB_VIRTUAL#2346

Open
retrocpugeek wants to merge 5 commits into
unicorn-engine:devfrom
retrocpugeek:fix/mips-vtlb-llsc-dev
Open

target/mips: fix spurious EXCP_AdEL on ll/lld under UC_TLB_VIRTUAL#2346
retrocpugeek wants to merge 5 commits into
unicorn-engine:devfrom
retrocpugeek:fix/mips-vtlb-llsc-dev

Conversation

@retrocpugeek

@retrocpugeek retrocpugeek commented Jun 23, 2026

Copy link
Copy Markdown

Fixes #2345.

Under UC_TLB_VIRTUAL, MIPS ll/lld raised a spurious EXCP_AdEL for addresses above useg (>= 0x80000000) because HELPER_LD_ATOMIC (qemu/target/mips/op_helper.c) called do_translate_address() to populate CP0_LLAddr, which segment-checks the address and rejects anything outside useg. The load itself already goes through the soft-TLB, so in virtual-TLB mode this records the virtual address into CP0_LLAddr directly instead of segment-translating it.

The alignment check is preserved (a misaligned ll still raises AdEL); sc/scd are unaffected (they use tcg_gen_atomic_cmpxchg_tl, which never calls do_translate_address).

Adds a regression test in tests/unit/test_mips.c (high-address ll reads correctly; misaligned ll still faults).


Downstream tracking: qilingframework/qiling#1645 (Qiling MIPS64 real-ELF/glibc support gates its CI tests on this fix).

retrocpugeek and others added 2 commits June 23, 2026 22:22
The LL atomic helper (HELPER_LD_ATOMIC) performs the load through the soft-TLB
but additionally calls do_translate_address() purely to populate CP0_LLAddr.
That walks the MIPS segments and rejects any address outside useg (e.g. an
address >= 0x80000000 with UX unset), raising EXCP_AdEL even though the
soft-TLB / virtual-TLB hook maps the address and the load itself succeeds.

In virtual-TLB mode, record the virtual address into CP0_LLAddr directly instead
of segment-translating it. The alignment check is left intact, so a genuinely
misaligned ll/lld still raises AdEL. sc/scd are unaffected (they go through
tcg_gen_atomic_cmpxchg_tl and never call do_translate_address).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Under UC_TLB_VIRTUAL, a Load-Linked from an address above useg (>= 2GB) used
to raise a spurious EXCP_AdEL because the ll helper segment-checked the address
when populating CP0_LLAddr. Adds a test that ll from a high address now reads
correctly, plus a guard that a misaligned ll still raises an Address Error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@PhilippTakacs

Copy link
Copy Markdown
Contributor

I had only a quick look, but you missed that the virtual tlb option also allows hooks to change the mapping.

retrocpugeek and others added 2 commits June 24, 2026 22:43
Extends the MIPS virtual-TLB ll regression with the case PhilippTakacs
raised on the PR: UC_TLB_VIRTUAL lets the fill hook map a page to an
arbitrary physical address, and CP0_LLAddr must hold that mapped physical
address (readable by the guest via mfc0 LLAddr), not the virtual address.

The added hook maps the lock page to a different physical page; the test
performs an ll and checks that mfc0 LLAddr reads back the physical address.
It fails against the previous fix (which recorded the virtual address) and
passes with the follow-up commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The earlier fix recorded the virtual address into CP0_LLAddr in virtual-TLB
mode. But UC_TLB_VIRTUAL lets the fill hook remap a page to any physical
address, and CP0_LLAddr is architecturally the physical address of the
linked load (a guest can read it via mfc0/dmfc0 LLAddr). Recording the raw
virtual address is wrong whenever the hook does not map identity.

Recover the hook-mapped physical address from the soft-TLB via
tlb_vaddr_to_paddr() (which probes the fill hook non-faulting) instead. This
still avoids the spurious EXCP_AdEL from the MIPS segment walk and now also
reports the correct physical address. The non-virtual-TLB path is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@retrocpugeek

Copy link
Copy Markdown
Author

Thanks @PhilippTakacs
Hope the updated test and fix covers the case you identified.

@wtdcode

wtdcode commented Jul 4, 2026

Copy link
Copy Markdown
Member

Looks good to me. @PhilippTakacs do you also approve this?

@PhilippTakacs

Copy link
Copy Markdown
Contributor

there is still an issue, because with the mips mmu the code raises an exeption (see cpu_mips_translate_address and exits the translation loop). this change just put the 1:1 mapping back in, but the vtlb mmu allows to have a translation failing. I would guess using CPU_GET_CLASS(env_cpu(env))->tlb_fill() direct and setting probe to false would be the correct implementation, but i'm not familiar with mips and have the next two weeks not the time to look at this.

Addresses review feedback on unicorn-engine#2346: the previous vtlb path probed the
soft-TLB non-faulting (tlb_vaddr_to_paddr) and, when a UC_TLB_VIRTUAL fill
hook declined the page, fell back to recording the raw virtual address in
CP0_LLAddr - effectively putting a 1:1 mapping back in and masking the
fact that the hook is allowed to fail a translation.

Instead, under virtual-TLB perform the soft-TLB load first: it goes
through the normal softmmu slow path and raises the proper MMU fault if
the hook declines the page, then recover the hook-mapped physical address
from the now-populated soft-TLB. The non-virtual-TLB path is unchanged
(cpu_mips_translate_address before the load).

(An earlier draft called CPUClass::tlb_fill() directly with probe=false,
as suggested in review, but unicorn_fill_tlb() calls cpu_restore_state()
unconditionally, which is only safe when exiting the CPU loop on a fault;
driving it inline and then continuing corrupts CPU state. Doing the load
first gets the same faulting semantics through the supported path.)

Add a regression test asserting that ll on a page the vtlb hook declines
raises UC_ERR_MMU_READ.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@retrocpugeek

Copy link
Copy Markdown
Author

Thanks @PhilippTakacs, good catch — you're right that the previous version put a 1:1 mapping back in and swallowed a translation the vtlb hook is allowed to decline.

I tried your suggestion of calling CPUClass::tlb_fill() directly with probe=false first, but it doesn't compose with unicorn's soft-TLB fill: unicorn_fill_tlb() calls cpu_restore_state() unconditionally at its top, which is only safe when you're about to leave the CPU loop on a fault. Driving it inline from the ll helper and then continuing execution corrupts CPU state — it broke real MIPS64 workloads (the guest ended up taking an unexpected exception mid-instruction).

So instead I get the same faulting semantics through the supported path: under UC_TLB_VIRTUAL, do the soft-TLB load first (it goes through the normal softmmu slow path and raises the proper MMU fault if the hook declines the page), then recover the hook-mapped physical address from the now-populated soft-TLB for CP0_LLAddr. A genuinely-declined page now faults instead of recording a bogus 1:1 address, and the non-virtual-TLB path is unchanged (cpu_mips_translate_address before the load, as before).

Added a regression test asserting that ll on a page the hook declines raises UC_ERR_MMU_READ.

Pushed as 5573568.

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.

3 participants