Skip to content

feat(relinker): load Linux executables at a fixed base - #4593

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
Recapi:feat/relinker-linux-fixed-base
Oct 14, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
Recapi:feat/relinker-linux-fixed-base

Conversation

@Recapi

@Recapi Recapi commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

What

Linux side of #4013, the counterpart of #4140's --windows-fixed-base. Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) read GOT slots at offset + 0x400000, where each offset comes from app0/imports.txt, so they assume the console's load address. The relinked Linux executable is a PIE loaded at 0x555555554000, and the first read faults.

The relinker option. relinker --linux-fixed-base <address> writes the executable as ET_EXEC and adds the address to every virtual address it holds, so virtual address 0 lands at the address, as on the console:

  • program headers, except PT_NULL and PT_GNU_STACK;
  • the entry;
  • the address tags of the dynamic table (DT_STRTAB, DT_SYMTAB, DT_HASH, DT_RELA, DT_JMPREL, DT_PLTGOT);
  • every relocation offset;
  • the addends of R_X86_64_RELATIVE and R_X86_64_IRELATIVE, which ld.so no longer adds a load bias to;
  • defined symbols. Undefined, SHN_ABS and STT_TLS symbols stay unchanged.

It throws instead of writing an image that would not load:

  • a relocation type outside the ones the Linux output uses;
  • a RELATIVE target below the image;
  • a dynamic entry other than DT_NEEDED in the executable's dependency table;
  • a base that is not a multiple of a PT_LOAD alignment;
  • an image that would end past the 47-bit user address space.

The CLI takes a 64 KiB-aligned address from 0x10000 through 0x7fffffff0000, in decimal or hexadecimal, and rejects the option with --windows, --macos or --lazy-binding. Lazy binding would need the PLT slots' initial contents rebased, and it is deprecated. Bundled guest modules keep their normal placement. Without the option the output is unchanged: the DOOM catalog executable relinks byte for byte the same as with main's relinker.

The libkernel fix. dlsym in the default scope recognized the main program by comparing dladdr's dli_fbase with dl_iterate_phdr's dlpi_addr. For an ET_EXEC image these differ: dlpi_addr is 0 and dli_fbase is the image's first page, so symbols of a non-PIE executable were not found. It now compares link maps, taken from dladdr1(RTLD_DL_LINKMAP) and dlinfo(dlopen(NULL), RTLD_DI_LINKMAP).

Tested

Linux (Ubuntu, GCC, Release, NVIDIA RTX 2080 SUPER) on main d592d54:

  • Ruffle (PPSA68091), catalog build with sce_module/libc.prx unwrapped:
  • New linux_fixed_base test:
    • relinks a fixture with and without the option and compares every program header, the entry, every dynamic tag and the relocation table;
    • runs the fixed output, whose code checks that it runs at 0x400000 + entry and that its RELATIVE slot holds that address (SIGTRAP on success);
    • checks the errors for an unaligned base, an image past the user address space, an unsupported relocation type and a negative RELATIVE target.
  • linux_executable_exports now also runs at a fixed base, where dlsym(RTLD_DEFAULT) from a needed library resolves an executable export. Without the symbol rebasing it fails with SIGSEGV.
  • New guest_dynamic_loader_fixed_base: GuestDynamicLoader.cpp built with -no-pie. Before the libkernel fix it aborts (sceKernelDlsym(0, GuestDefaultScopeOnly) misses the executable symbol); after the fix it passes.
  • cli_args covers parsing, the range, alignment, malformed values, repetition and the conflicts.
  • Full suite: ctest 959/959. check_conventions.py and check_test_registration.py pass.
  • Fork CI: green on Linux, Windows and macOS (run 38273228109). linux_fixed_base, guest_dynamic_loader_fixed_base, linux_executable_exports and cli_args pass on Linux. The branch merges cleanly onto current main 2a5eca1.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@Recapi

Recapi commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

The overlap check on #4140 lists this PR. The two only touch the same lines: both add an option to Cli.hpp, CliArgs.cpp (the usage string and the parser), CliArgsTests.cpp and the USAGE.md table. The code paths are separate: #4140 changes the Windows PE writer, and this PR changes the Linux ELF patcher and libkernel's dlsym. Neither depends on the other, and whichever lands second needs only a mechanical rebase. I'll do it here if #4140 goes first. @veosett, I'm happy to rebase on yours or follow your parsing style if you prefer one address parser for both options.

@Recapi

Recapi commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main 1acfa7f. The only conflict was an #include in DynamicLoader.cpp, where main had dropped <set>. After the rebase on Linux: ctest 973/973, and check_conventions.py and check_test_registration.py pass.

More from Ruffle (PPSA68091) with --linux-fixed-base 0x400000, with this PR on main 1acfa7f:

  • Logging. The title now redirects its output into /data/ruffle/ruffle.log (dup2, feat(libkernel): implement dup and dup2 #2478), so the run shows up there.
  • Without the option, the same build still stops with SIGSEGV right after can't write /data/ruffle/ruffle.log.
  • With it, the game library UI runs: the display, pad, keyboard, mouse and audio initialize, it reports [Library] 0 games and presents 2,400 frames in 60 s. It ran until the timeout.
  • Opening Favorites starts the Flash player (wgpu renderer). Its first pass throws only normal color rendering with copy ROP is supported (CB_COLOR_CONTROL 0x00cc0050, mode FMASK decompress). That is AGC and outside this PR; I'm looking at it separately.

@ee6aba54

Copy link
Copy Markdown
Contributor

Tested on Linux with PPSA15304 (engine: Unreal Engine 5), a second case besides #4013 that needs the console's load address. Its bundled libc.prx creates a "Low Address Malloc" mspace over a static buffer inside the executable and rejects a base outside the application area 0x200000000..0xFC00000000. With the PIE at 0x555555554000 the creation returns NULL, and the title faults in its global constructors before main. #3043 describes the same check on Windows.

@Recapi

Recapi commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @ee6aba54, that's a good second case. It also exercises a base above 4 GiB (0x400000000), where the image has to fit the console's application area rather than 0x400000. It's good to know the 1.3M RELATIVE relocations match a hand-rebased image.

@Recapi

Recapi commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main after #4547 (--trace-imports); new head c25a5783e. The conflicts were additive in Cli.hpp, CliArgs.cpp (usage string and option checks) and the USAGE.md table; all options are kept. Linux (GCC 15, Release): full build, full CTest 1,071/1,071, conventions and test-registration checks pass. --windows --trace-imports --windows-fixed-base 0x400000 still converts. Ruffle (PPSA68091), relinked with --registry --linux-fixed-base 0x400000, runs its library UI for 30 s until timeout, 2,400 presented frames.

@Recapi
Recapi force-pushed the feat/relinker-linux-fixed-base branch from 66b3c81 to c25a578 Compare October 13, 2026 16:13
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
@babyhuey babyhuey mentioned this pull request Oct 13, 2026
10 tasks done
@babyhuey

Copy link
Copy Markdown
Contributor

XPSemu (PPSA97358), the third title in #4013, on Linux (headless): on main 18c438b with #5061 merged, every import resolves and it segfaults on its GOT read. Relinked with --linux-fixed-base 0x400000 from this PR (c25a578, clean merge), it runs past that to getrlimit(RLIMIT_NOFILE) (#5187), and with that too, into xemu's start-up and its GPU probe (details in #5061).

@Recapi

Recapi commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @babyhuey. That makes XPSemu the second #4013 title past the GOT read on Linux, after Ruffle, and it shows the option works together with #5187.

@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (one conflict in docs/user/USAGE.md: both options kept, the Windows paragraph now says it does not change macOS output) and added one commit: dropped R_X86_64_DTPMOD64, R_X86_64_DTPOFF64, R_X86_64_TPOFF64 and STT_TLS from ElfConstants.hpp, which main already defines and broke the build.

Checked: relinker + libkernel build and 263/263 ctest (linux_fixed_base, guest_dynamic_loader_fixed_base, linux_executable_exports, cli_args, conversion); the new tests don't build or pass without the change. Confirmed on XPSemu (PPSA97358) by @babyhuey and PPSA15304 by @ee6aba54; merging once the full build is green.

@adriadam10
adriadam10 force-pushed the feat/relinker-linux-fixed-base branch from c25a578 to 3d101e8 Compare October 14, 2026 10:50
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
#4972 core/libs/prx/libkernel/CMakeLists.txt L208
#5640 core/relinker/cli/src/CliArgs.cpp L13

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants