Skip to content

Conversation

@matthewabbott
Copy link
Contributor

@matthewabbott matthewabbott commented Jan 5, 2026

Issue

When emulated code calls dlinfo(handle, RTLD_DI_LINKMAP, &lmap), the returned link_map->l_ld would point at box64’s internal parsed copy of .dynamic, which contains raw virtual addresses from the ELF file. Those addresses weren’t relocated to the guest’s load base, so guest code that dereferenced tags like DT_SYMTAB would read nonsensical memory locations and crash:

SIGSEGV ... libdoorstop.so/plthook_enum
for accessing 0x172930 (code=1/prot=0)

In the case of the above error, the symbol table actually lived at 0x7fff00172930, but l_ld exposed the file value 0x172930.

Fix

Add methods:

  • GetLoadedDynamicSection() to return the .dynamic address loaded in ELF memory (not the internal copy).
  • and PatchLoadedDynamicSection() which adds the load delta to pointer-type d_ptr entries so they point at the runtime addresses in the loaded ELF mapping. Pages might already be read only, so temporarily mprotect as needed.

Update l_ld assignments now use the loaded, patched section.


Reproduction (pre-fix)

Using BepInEx 5.x (Linux) with a Unity game on ARM64 via box64. BepInEx’s Doorstop/plthook calls dlinfo(..., RTLD_DI_LINKMAP, ...), walks l_ld to find DT_SYMTAB, DT_STRTAB, etc., and segfaults when it dereferences the unpatched file address (e.g. 0x172930). Possibly a known issue with bepinex/box64 compatibility: #222, BepInEx/BepInEx#336.

Steps:

  1. Install BepInEx 5.x (Linux).
  2. Run a Unity game under box64 with BepInEx configured.
  3. Doorstop’s plthook_open_real() calls dlinfo(handle, RTLD_DI_LINKMAP, &lmap).
  4. plthook iterates lmap->l_ld to find dynamic entries.
  5. SIGSEGV on unpatched address (e.g. 0x172930).

Design choices

Dynamic entries use a union, but only pointer-typed tags should be delta-adjusted. The idea here is that an explicit allowlist avoids patching size/flag tags like DT_STRSZ or DT_FLAGS. And then I also added numeric fallbacks (e.g. 0x6ffffef5 for DT_GNU_HASH) to try and keep compatibility on older headers. But this feels a bit ugly to me. Open to suggestions if you have other ideas about how to do this.

The loaded .dynamic address/size come from PT_DYNAMIC (delta + p_vaddr, p_memsz). The internal parsed copy (h->Dynamic._64/_32) isn’t usable for l_ld. Similar pattern to how dl_iterate_phdr_findsymbol locates the dynamic section.

PatchLoadedDynamicSection will patch .dynamic's d_ptr values after load to keep the change localized and avoid restructuring the loader, but that means that it can run into pages with different protections (eg RELRO). So the idea is to check each page via getProtection(), temporarily add PROT_WRITE only where needed, roll back on failure, then restore protections. Untracked pages (protection=0) are left untouched. Perhaps it'd be better to just restructure the loader though if you prefer or if you have a suggestion.

  • Idempotency:

dynamic_patched prevents double-adjusting the same ELF.

@ptitSeb
Copy link
Owner

ptitSeb commented Jan 5, 2026

That looks good to me. I have no problem with your design choices.

The only way the patching can fail is if mprotect fail right? That should not happen I guess, so it should be safe. We'll see in practice if the "unprotect/restore" process is too time consuming or not.

@ptitSeb ptitSeb merged commit 9d0f8d1 into ptitSeb:main Jan 5, 2026
27 checks passed
@matthewabbott
Copy link
Contributor Author

Yeah RE: patch failure, the only hard failure is mprotect returning non‑zero, in which case we roll back any pages already made writable and return without setting dynamic_patched, the idea being to have behavior fall back to 'no patch'.

Other early exits are 'no PT_DYNAMIC' or delta == 0. As for overhead, it should be 1-2 pages per ELF (which I believe should be pretty small), and we leave untracked pages alone to avoid changing protection state.

@ptitSeb
Copy link
Owner

ptitSeb commented Jan 5, 2026

Yeah, I just try to launch a software that loads tens of library (intel FPGA Quartus Prime Programmer) and there was no noticable longer delay... So, that looks good to me.

Thank your Pull Request.

@matthewabbott
Copy link
Contributor Author

Happy to help!

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.

2 participants