Skip to content

fix(relinker): prefer 0x400000 for Windows executables - #9332

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
rooklz:fix/windows-console-base
Oct 24, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
rooklz:fix/windows-console-base

Conversation

@rooklz

@rooklz rooklz commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

On Windows, Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) only start when they're converted with --windows-fixed-base 0x400000. They read their GOT slots at 0x400000 plus the offsets in app0/imports.txt (#4013), but the default executable puts its lowest ELF load page at 0x140010000, so the first read faults. babyhuey on #5732, about VLC on main 64a9caa62: with the option it "runs the full 60 s at about 119 fps with no fault. Without --windows-fixed-base it still faults at start-up as before (#4013)". dantdmdarealguy measured the same for Ruffle on #4013. The web converter has no way to pass the option.

This makes 0x400000 the preferred address of the executable's lowest ELF load page, which is a PE image base of 0x3f0000, since the headers take the 64 KiB before it. Otherwise nothing changes:

  • The executable keeps its base relocations and still has no DYNAMIC_BASE, as since fix(relinker): load Windows executables at a fixed low base #4237. Windows maps the executable before anything else, so the range is normally free and it loads there. If the range were taken, the kept relocations let Windows move it; no run has shown that happen at either base. --windows-fixed-base is unchanged: an exact address, no relocations, and an occupied address fails to load.
  • It's still inside the 40-bit window that fix(relinker): load Windows executables at a fixed low base #4237 was about and below the guest arena at 0x200000000. No image can reach the shared user data page at 0x7ffe0000 from here: Windows refuses a SizeOfImage above 0x77000000 (@roboterspinne measured it), and the debt entry now says so.
  • Bundled modules keep DYNAMIC_BASE and their preferred base 0x140000000. macOS output is unchanged.
  • WindowsPeWriter::Write now takes the image base and whether it's fixed, instead of an optional fixed base, so the patcher can pass a preferred base. Its alignment and lower-bound check now applies to every image, secondary executables converted with --executable get the new preferred base too, and WindowsImportIdentityTests (added on main since) passes true, false to keep its fixed-base probe.

#8973 (merged) does the same on Linux.

Tested

  • Windows 11, Ryzen 7 5800X, WinLibs GCC 15.2, Release, by @babyhuey: PPSA99099 wiliwili (homebrew), which reads its own import table at its offset plus 0x400000, faults with 0xC0000005 at 2.5 s on main (0x140000000 base) and runs for the 45 s window with this head and with main plus --windows-fixed-base 0x400000.
    No title run on my side. These runs were on the pre-rebase head 14ffbf945 (same diff) unless noted. ycbuzi ran VLC (PPSA85300) on this head merged onto main a849f2516e (comment), Windows 11, RTX 4070 SUPER: on main the default conversion crashed at start-up with 0xc0000005 and 0 presents (2 runs); with this PR the default conversion rendered at 72.6 to 118.9 fps (4 of 5 runs; the fifth stopped presenting after about 20 s, which also happened once with --windows-fixed-base 0x400000). ccleste (PPSA59322) and DriftCheck (PPSA98766) render on the new default too. Ruffle and XPSemu weren't run.

  • windows_fixed_base now converts its imports.txt-style fixture without any option and runs it. The fixture reads the slot at 0x400300 and returns 42 when the slot is filled. With only that check added to main 950388bfd, the converted executable exits with 0xC0000005 on a GitHub-hosted Windows runner (verify run on my fork), which is the fault in Ruffle, VLC and XPSemu read their GOT at a fixed 0x400000 base from app0/imports.txt and fault once all imports load #4013. With this change it passes. The header check there and in windows_tls_protected_entries now expects 0x3f0000.

  • Windows Server 2025 (GitHub-hosted), WinLibs GCC 15.2.0, relinker-only build, run on my fork: 233/233 with the change and 233/233 on its base 950388bfd. That includes windows_address_space, which runs the default executable 8 times and checks that its main thread stack stays below the arena.

  • macOS 26.2 (Apple M5), AppleClang 21, relinker-only build: 229/229.

  • Windows 11 Pro 10.0.26300, WinLibs GCC 15.2.0 r7, relinker-only build, by @BKlaasWerkman on 92fbd0adc1 merged into main 990f45e17d: 235 of 235 pass, windows_fixed_base and windows_tls_protected_entries with their changed expectations among them, and no result differs from main on the same machine.

  • check_conventions.py passes. pr_overlap.py against current main lists only shared docs files it could not compare.

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 under Silent stubs in their library's technical debt file
  • 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
  • Fixes: none
  • Unblocks: PPSA85300 VLC (PPSA85300) #5732, PPSA97358 XPSemu (PPSA97358) #5600, PPSA99099 (homebrew)
  • GPUs tested: n/a
  • AI-assisted: yes

@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 21, 2026
@github-actions

github-actions Bot commented Oct 21, 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
PR 9408 docs/user/USAGE.md L67-68
PR 10647 docs/dev/debt/relinker.md L12-13

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 docs/dev/debt/relinker.md
docs/user/USAGE.md
PR 5925 docs/user/USAGE.md
PR 6171 docs/user/USAGE.md
PR 6924 docs/user/USAGE.md
PR 7642 docs/user/USAGE.md
PR 8076 docs/dev/debt/relinker.md
PR 9345 docs/user/USAGE.md
PR 9401 docs/user/USAGE.md
PR 10193 docs/user/USAGE.md

@ycbuzi

ycbuzi commented Oct 21, 2026

Copy link
Copy Markdown
Contributor

Windows run of 14ffbf9 merged onto main a849f25 (189a4bb233, clean), with #7264's tools/title_run.py merged on top to run the titles (it changes only tools/ and docs/dev/TRACING.md). Windows 11 Home 10.0.26200, MinGW-w64 GCC 15.2.0 (ucrt-posix-seh r7), Release, BUILD_TESTING=ON, RTX 4070 SUPER (NVIDIA 616.92), Ryzen 9 7900.

VLC (PPSA85300, 01.000.007), converted by title_run.py with --registry, 60 s per run:

Build Conversion Runs Outcome ImageBase Base relocations
main a849f25 default 2 crashed at start-up, FATAL: unhandled exception 0xc0000005, 0 presents 0x140000000 kept
this PR default 5 4 rendering, 1 stalled (below); 72.6 to 118.9 fps 0x3f0000 kept
this PR --windows-fixed-base 0x400000 4 3 rendering, 1 stalled (below); 99.4 to 119.8 fps 0x3f0000 stripped

DllCharacteristics is 0x100 (NX_COMPAT only, no DYNAMIC_BASE) in all three. Three of the default runs and three of the fixed-base ones alternated (A B B A A B): 118.9, 116.2 and 106.8 fps against 119.8, 99.4 and 107.2, so no difference beyond the run-to-run spread on this machine. The last two of those, one of each, stopped presenting after about 20 and 30 s with no error line (the runner's "stalled"), so it happens with the existing option too.

ccleste (PPSA59322) and DriftCheck (PPSA98766) on the new default: 2 of 2 rendering each (116.5 and 117.1 fps; 54.3 and 58.5 fps).

ctest --test-dir core\relinker (162 tests here), --repeat until-fail:2, -j 4: 159 pass on both repeats, including windows_fixed_base, windows_tls_protected_entries and windows_address_space. guest_none_relocations, guest_relative_section_symbols and overlapping_relocations reached their 20 and 30 s timeouts under that load; run one at a time they pass on both repeats in 1 to 14 s, on this PR and on main alike.

Not run: Ruffle (PPSA68091) and XPSemu (PPSA97358), which I don't have.

@rooklz

rooklz commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, that's the title run this PR was missing. Good to see the default and --windows-fixed-base 0x400000 land in the same fps range, and that the stall shows up with the existing option too, so it isn't from this change. I've added your run to the description.

@wachi35676

Copy link
Copy Markdown
Contributor

Windows and Linux run of 14ffbf9 merged onto main a6dcea9 (merges cleanly). Windows 11 Home 10.0.26300, Ryzen 5 9600X, WinLibs MinGW-w64 GCC 15.2.0, Release relinker-only BUILD_TESTING=ON; Linux: WSL Ubuntu 24.04, GCC 13.3.0, same configuration.

  • ctest (relinker-only): Windows 233/233 (performance_compare hit its 20 s timeout in the parallel pass and passed on rerun; it passed on main in the same setup). Linux 237/238, the one failure being shader_cache_runtime_sources (its internal 15 s CMake configure timed out), which fails the same way on main here.
  • Negative control: the PR's test_windows_fixed_base.py against main's relinker fails; main's version of the test plus only the new default-conversion run(...) line fails with the converted fixture exiting 3221225477 (0xC0000005) after Transferring control to ELF entry point. With this PR both pass.
  • Four homebrew (PPSA99005, PPSA47402, PPSA99581, PPSA99995), each converted for --windows --to-intel, --macos --to-rosetta and Linux --to-intel: macOS and Linux outputs are byte-identical to main. On Windows only app.exe changes; every bundled module is byte-identical. In app.exe only ImageBase changes (0x140000000 to 0x3f0000); SizeOfImage, the size of .reloc, DllCharacteristics 0x100 (no DYNAMIC_BASE) and Characteristics 0x22 (relocations not stripped) match main.
  • Where Windows actually maps the image: each app.exe started with CREATE_SUSPENDED and PEB.ImageBaseAddress read back, 3 times each. main: 0x140000000 for all four. This PR: 0x3f0000 for all four, including PPSA99581 (SizeOfImage 0x6bf1000, so it ends at 0x6fe1000).

Not run: the converted titles themselves (my build is relinker-only); ycbuzi's VLC runs above cover that.

AI-assisted: yes (Claude Code); results above were run on this machine.

@rooklz

rooklz commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (8b3e39832a): head 92fbd0adc1, same diff.

@oneandonlydean oneandonlydean self-assigned this Oct 23, 2026
@BKlaasWerkman

Copy link
Copy Markdown
Contributor

The rebased head on Windows: 92fbd0a merged into main 990f45e (clean merge). Windows 11 Pro 10.0.26300, WinLibs GCC 15.2.0 r7, relinker-only BUILD_TESTING=ON, Release: 235 of 235 pass, and none changes result against a run of that main on the same machine. windows_fixed_base and windows_tls_protected_entries with the changed expectations are among them.

AI-assisted: yes (Claude Code)

@rooklz

rooklz commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

@BKlaasWerkman thanks for the Windows 11 run on the rebased head; the description's Tested section now lists it beside the hosted Server 2025 one, with the two tests whose expectations changed named.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@rooklz thanks, the change looks right and the homebrew results from @wachi35676 are useful. Since this moves every Windows-converted title to the new base, could you (or anyone on Windows) run one converted retail title on this head and confirm it reaches its first rendered frame at 0x3f0000? Any title is fine; a log line or screenshot showing the image base is enough. Once that's in, this can go ahead.

@rooklz

rooklz commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean will do. I have a Windows 11 / RTX machine coming online shortly and will convert one retail title on this head there and post the log line with the mapped image base and the first presented frame. If anyone on Windows with a retail title gets there first, please go ahead; a line showing PEB.ImageBaseAddress or the relinker's base log at 0x3f0000 and the first [present] is all it needs.

@roboterspinne roboterspinne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed head 92fbd0a merged onto upstream/main aeeaefa (2026-10-24); the merge is clean (only the two docs files auto-merge).

Environment: Windows 11 Home 10.0.26200, WinLibs GCC 15.2.0 UCRT, CMake 4.3.1, Ninja 1.13.2, Python 3.13.9, -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Release.

Results

  • Baseline main aeeaefa: 238 tests, 0 failed, long_paths skipped. PR merged: 238 tests, 0 failed, long_paths skipped; windows_fixed_base 2.7 s, windows_tls_protected_entries 0.8 s, windows_address_space 0.5 s.
  • Red/green: with the four C++ changes reverted and the PR's tests kept, windows_fixed_base fails at the header assertion (line 71, 0x140000000 != 0x3f0000) and windows_tls_protected_entries fails at access-start (0x400210); restored, both pass.
  • Native header dump of the converted test_optional_plt fixture: main default ImageBase 0x140000000; PR default ImageBase 0x3f0000, Characteristics 0x22, DllCharacteristics 0x100, .reloc directory kept (size 0xc), SizeOfImage unchanged. The imports-offset fixture from the test exits 0xC0000005 with main's default and 42 with the PR's; started suspended, the PR output reports PEB.ImageBaseAddress = 0x3f0000. --windows-fixed-base 0x400000 output is identical on main and the PR (ImageBase 0x3f0000, Characteristics 0x23, no .reloc).
  • Byte comparison main vs PR: app.exe differs only in the ImageBase field (plus the one absolute GOT slot the fixture bakes in); a bundled module converted alongside it is byte-identical with ImageBase 0x140000000, and the executable with that module exits 42 on both.
  • check_conventions.py passes; pr_overlap.py against current main lists only the "could not compare" docs entries.

Should fix

  1. Rebase note (file is on main, not in this diff): core/relinker/elfpatcher/tests/WindowsImportIdentityTests.cpp:77, added by 2657a81 after this branch's merge-base, calls WindowsPeWriter().Write(..., false, ImageBase, false). With the new signature those tokens mean imageBase=0x140000000, fixedBase=false, dynamicBase=true, so the probe loses IMAGE_FILE_RELOCS_STRIPPED (Characteristics 0x22 instead of 0x23). It compiles cleanly (-Wall -Wextra -Wconversion, no diagnostic), and the windows_import_identity target is only built without ANYPS5_RELINKER_ONLY (CMakeLists.txt:163), so the relinker matrix cannot notice. The test still passes (it only runs the probe, which has no relocation directory), but please change the call to Write(..., false, ImageBase, true, false) on rebase to keep its fixed-base intent.
  2. docs/dev/debt/relinker.md:11 and the body: the relocation example cannot occur. From 0x3f0000 an image needs SizeOfImage ≥ 0x7fbf0000 to reach 0x7ffe0000, but the loader refuses any converted image with SizeOfImage above 0x77000000: measured here with test_optional_plt.fixture and p_memsz patched, SizeOfImage 0x77000000 loads at 0x3f0000 and exits 42, 0x77001000 fails with CreateProcess error 193 (NtCreateSection → STATUS_INVALID_IMAGE_FORMAT), the same at main's 0x140000000 and with --windows-fixed-base. So the default executable ends below 0x773f0000 and never reaches the shared user data page. Also, I found no run in this PR, #4237 or #3043 that observed a relocation of the executable at either base (every reported PEB.ImageBaseAddress here was the preferred one, and the only occupancy test uses a fixed base and expects STATUS_CONFLICTING_ADDRESSES), while the entry drops its "Verified only on Windows 10" sentence. Suggested wording is in the inline comment; "as it did when 0x140000000 was taken" in the body would need a pointer to the run that showed it.

Nits

  • Body: #8973 was merged on 2026-10-22 (44661db) and is an ancestor of this branch, so "the two don't depend on each other" reads as if it were still open. #8729 was closed unmerged on 2026-10-24, so the pr_overlap.py note no longer applies.
  • Body, Tested: ycbuzi's and wachi35676's runs were on the pre-rebase head 14ffbf9 (same diff), only BKlaasWerkman's on 92fbd0a.
  • test_windows_fixed_base.py:84/88: the imports-offset fixture (cmp qword ptr [rax+0x400000], 0) only checks that the read at 0x400300 succeeds and is non-zero; it would also pass if any committed non-zero memory sat there. On the PR build the slot holds the resolved 0x400240; comparing against it, or jumping through it as absolute_jump() does, would make a wrong value fail too (same for the fixed-base variant on line 89).
  • The bundled module's ImageBase 0x140000000 is asserted only for the --windows-fixed-base 0x400000 conversion (line 128); test_windows_address_space.py checks the default module's DYNAMIC_BASE but not its base. A default convert(..., modules=True) with the same assertion would cover the first case where executable and module bases differ. Pre-existing gap.
  • Under Exploit Protection's "Force randomization for images (Mandatory ASLR)", off by default, Windows "will forcibly rebase DLLs and EXEs that haven't opted in to ASLR" (learn.microsoft.com, exploit-protection-reference). The default output (Characteristics 0x22, .reloc kept, no DYNAMIC_BASE) is such an image, so on such a machine the imports.txt titles fault again; --windows-fixed-base output is not rebased, but the sub-option "Do not allow stripped images" refuses it. Worth one sentence in USAGE.md's Runtime layout. main's default was rebased the same way, so this is not a regression.
  • "Otherwise nothing changes" also omits: secondary executables converted through --executable get the new preferred base too (main.cpp:67 passes no fixed base for them), the alignment/>= 0x10000 check in WindowsPeWriter::Write now applies to every image instead of only a fixed one, and the exception text changed. All harmless as far as I can see.
  • Note: on Windows "0x400000" is where the lowest ELF load page lands (WindowsLoadImage subtracts firstAddress), on Linux Conversion.cpp places virtual address 0 there; the two agree only for ELFs whose first PT_LOAD starts at vaddr 0, which PS5 executables do.

Not checked: no retail title (no dumps here, so oneandonlydean's request for a first-frame run at 0x3f0000 stands); no macOS or Linux execution; relocation of the default executable when 0x3f0000 is actually occupied could not be provoked; Mandatory ASLR was not enabled; the full (non relinker-only) build with windows_import_identity was not run; Windows 10 not measured.

I found no blocking issue; the code change is correct and complete as far as I can tell, the two should-fix items are a rebase hazard outside this diff and a documentation correction. For disclosure: I have open relinker PRs (#10390, #10391, #10401, #10402, #10403); none shares a file with this one.

Written with AI assistance (Claude Code). I ran the builds, tests and native checks above on my Windows 11 machine.

Comment thread docs/dev/debt/relinker.md Outdated
- The executable file that [relinker](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens a console when launched, unless it is relinked with `--windows-gui`, which selects the GUI subsystem. A GUI build started from Explorer has no standard output or error handle; the [entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) gives each missing one `anyps5.log` beside the executable, replacing the previous run's file (#3282). Errors about the executable path or about waiting for the previous LoadExec process are written before the log is opened and are lost when the standard error handle is missing. If the log cannot be created, output to the missing handles is lost.
- [Guest import registries](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) - declared host imports of bundled modules are recorded with their module name and relocation target, even when another guest module exports the same stripped NID. No-op relocations are omitted. Registries describe import bindings, not implementation correctness or runtime reachability.
- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Relocations are kept, so if `0x140000000` is taken Windows relocates the image, possibly above that window. Verified only on Windows 10.
- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) and [prefer `0x400000`](../../../core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp) for their lowest ELF load page by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Where the console loads an executable that does not require an address is not measured; `0x400000` is the base that executables reading their GOT through `app0/imports.txt` assume (#4013). Relocations are kept, so if that range is taken, as by an image that reaches the shared user data page at `0x7ffe0000`, Windows relocates the image, possibly above that window.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Measured on Windows 11 build 26200: the loader refuses any converted image with SizeOfImage above 0x77000000 (CreateProcess error 193, STATUS_INVALID_IMAGE_FORMAT), at 0x3f0000 as at 0x140000000, while 0x77000000 itself loads. From 0x3f0000 an image would need SizeOfImage ≥ 0x7fbf0000 to reach 0x7ffe0000, so this example cannot occur: such an image is refused, not relocated. I also found no run that observed a relocation of the executable at either base, and the old "Verified only on Windows 10" is gone without a replacement.

Suggested change
- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) and [prefer `0x400000`](../../../core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp) for their lowest ELF load page by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Where the console loads an executable that does not require an address is not measured; `0x400000` is the base that executables reading their GOT through `app0/imports.txt` assume (#4013). Relocations are kept, so if that range is taken, as by an image that reaches the shared user data page at `0x7ffe0000`, Windows relocates the image, possibly above that window.
- Converted Windows main executables [omit `DYNAMIC_BASE`](../../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) and [prefer `0x400000`](../../../core/relinker/elfpatcher/src/windows/WindowsPePatcher.cpp) for their lowest ELF load page by default, not only with `--windows-fixed-base`: on the console the executable lies inside the 40-bit window that the title's own `sceLibcMspaceCreate` checks (#3043). Where the console loads an executable that does not require an address is not measured; `0x400000` is the base that executables reading their GOT through `app0/imports.txt` assume (#4013). Relocations are kept, so if that range is taken Windows relocates the image, possibly above that window; no relocation has been observed at either base. Images with `SizeOfImage` above `0x77000000` are refused by the loader (`STATUS_INVALID_IMAGE_FORMAT`; measured on Windows 11 build 26200), so the default executable ends below `0x773f0000` and never reaches the shared user data page at `0x7ffe0000`.

Comment thread docs/user/USAGE.md Outdated
## Runtime layout

For an executable that assumes its image starts at `0x400000`, use `relinker --windows --windows-fixed-base 0x400000 source/input.elf app.exe`. This maps its lowest ELF load page there; the PE headers occupy the preceding 64 KiB. Use this option only when the executable requires an exact address. Without it, the Windows executable has no `DYNAMIC_BASE` and loads at its preferred base `0x140000000`; relocations are kept, so Windows relocates it if that range is taken. Modules keep `DYNAMIC_BASE`. This option does not change macOS output.
The Windows executable has no `DYNAMIC_BASE` and prefers to load with its lowest ELF load page at `0x400000`, where Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) expect their image when they read their `app0/imports.txt`; the PE headers occupy the preceding 64 KiB (preferred base `0x3f0000`). Relocations are kept, so Windows relocates it if that range is taken. When the executable requires an exact address, use `relinker --windows --windows-fixed-base <address> source/input.elf app.exe`, which maps the lowest ELF load page at that address without relocations, so an occupied address fails to load. Modules keep `DYNAMIC_BASE`. This option does not change macOS output.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the realistic case for "if that range is taken" is Exploit Protection's "Force randomization for images (Mandatory ASLR)", which Microsoft documents as forcibly rebasing EXEs that have not opted in to ASLR; with it on, titles that read their GOT at 0x400000 fault again, and --windows-fixed-base 0x400000 is the way out (unless "Do not allow stripped images" is also on). One sentence here would save a user that search. Also "This option" at the end now has a distant antecedent since the paragraph opens with the default.

image[0x200:0x200 + len(code)] = code
image[0x240:0x246] = bytes.fromhex("b8 2a 00 00 00 c3")
struct.pack_into("<q", image, 0x710, 0x240)
run(convert("sce-dynexec-imports-offset-default", image))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the fixture code on line 84 is mov eax,0x300; cmp qword ptr [rax+0x400000],0; je ...; mov eax,42; ret, so exit 42 only proves that the read at 0x400300 succeeded and was non-zero (on main it faults because 0x400300 falls into the reserved main-thread stack region here). Comparing the slot with the resolved 0x400240, or jumping through it as absolute_jump() does below, would also catch a wrong non-zero value. Same for the fixed-base variant on line 89.

@rooklz
rooklz force-pushed the fix/windows-console-base branch from 92fbd0a to f65de44 Compare October 24, 2026 13:37
@rooklz

rooklz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@roboterspinne thanks for the careful review. Head f65de448ab, rebased onto current main:

  • Should fix 1: WindowsImportIdentityTests.cpp:77 now passes false, ImageBase, true, false, keeping its fixed-base probe. A full Windows build (Server 2025, WinLibs GCC 15.2.0, Release), which builds that target, passes windows_import_identity, windows_fixed_base, windows_tls_protected_entries and windows_address_space: https://github.com/rooklz/AnyPS5/actions/runs/40098947272.
  • Should fix 2: the debt entry no longer gives the shared-user-data example. It says no run has observed a relocation, that from this base an image can't reach 0x7ffe0000 because Windows refuses a SizeOfImage above 0x77000000 (your measurement), and that it is verified on Windows 11 only. The body's "as it did when 0x140000000 was taken" is gone for the same reason.
  • Nits: the body now marks fix(relinker): load Linux executables at 0x400000 by default #8973 as merged, drops the stale fix(relinker): trap inline SDK mount syscall #8729 overlap note, says which runs were on the pre-rebase head, and lists the --executable base, the wider alignment check and the identity test change.

The relinker-only matrix on this head (https://github.com/rooklz/AnyPS5/actions/runs/40098933979) passes 240 of 241. Its one failure is pr_overlap_encoding, a tools/ test this PR doesn't touch, failing in test_git_diff_preserves_unicode on the runner. The fixture tightening and the Mandatory ASLR note are good ideas, and I've left them for a follow-up so this stays one topic. oneandonlydean's retail-title request still stands.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@rooklz Thanks. I checked f65de44 against the previous head: apart from the rebase, the only code change is WindowsImportIdentityTests.cpp:77, and false, ImageBase, true, false restores the fixed-base, no-DYNAMIC_BASE image that test meant to build. The other WindowsPeWriter::Write callers use the defaults, which match the old behaviour. The debt entry now reads as roboterspinne asked. We'll run CI on this head. Our retail-title request still stands, as you said.

@roboterspinne

Copy link
Copy Markdown
Contributor

@rooklz @oneandonlydean Thanks, this matches what I see. Re-checked f65de44 merged onto upstream/main c9b2660 (2026-10-24) on Windows 11 Home 10.0.26200, WinLibs GCC 15.2.0 UCRT, CMake 4.3.1, Ninja 1.13.2, Python 3.13.9, -DANYPS5_RELINKER_ONLY=ON -DBUILD_TESTING=ON, Release; the merge is clean, and git range-diff against 92fbd0a shows only the line-77 hunk and the rewritten debt bullet.

  • On the callers: four of the six WindowsPeWriter::Write sites use the three-argument form (WindowsGuestModuleWriter.cpp:209, Amd64OnlyWindowsTests.cpp:694, WindowsDependencyDiagnosticsTests.cpp:156 and :197), whose defaults give the old output (0x140000000, 0x22, DYNAMIC_BASE); WindowsPePatcher.cpp:114 passes all seven arguments and is changed by this PR; line 77 was the only one to adjust.
  • Full relinker-only suite on the merged tree: 241 tests, 0 failed, long_paths skipped; windows_fixed_base, windows_tls_protected_entries, windows_address_space and windows_import_modules among them. The converted test_optional_plt fixture, started suspended, reports PEB.ImageBaseAddress = 0x3f0000 (PE ImageBase 0x3f0000, .reloc size 0xc) and exits 42.
  • windows_import_identity is not built in that configuration (if(NOT ANYPS5_RELINKER_ONLY), core/relinker/CMakeLists.txt:174; my review gave the line without the directory, and it has moved since). For it I read your fork's log: the windows-full (change) job checked out f65de448ab (the branch alone, not merged), built windows_import_identity_tests.exe and ran only the four tests via -R, 4 of 4 passed. Upstream's Build run 40098931445 on this head (merged onto 522e32e) is green too, relinker (windows-latest, g++) 241 of 241.
  • The pr_overlap_encoding failure in your relinker-only run is not this PR: tools/ is untouched, and the test passes locally and in that upstream job on the same image (windows-2025-vs2026). Both jobs run ctest from Git Bash; what differs in yours is the layout (cmake -S src -B build with src/ and verify/ as separate checkouts, so build/ lies outside any repository, and TEMP under RUNNER_TEMP). The detail went to the ctest.log artifact, which I did not pull.
  • docs/user/USAGE.md:152 still has the definite "Relocations are kept, so Windows relocates it if that range is taken"; "can relocate" would match the entry, a nit that fits the follow-up you plan.
  • No retail title here, so the first-frame request stands.

Written with AI assistance (Claude Code). I ran the build, tests and native checks above on my Windows 11 machine; the full Windows build result is from the fork's Actions log.

@rooklz
rooklz force-pushed the fix/windows-console-base branch from f65de44 to 39df8e0 Compare October 24, 2026 14:58
@rooklz

rooklz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@roboterspinne thanks for the re-check and for tracing the pr_overlap_encoding layout difference. I took the USAGE.md:152 nit here instead of in a follow-up, since it's one word in a file this PR already edits. Head 39df8e081c changes "Windows relocates it" to "Windows can relocate it" to match the debt entry, and changes nothing else (git range-diff against f65de448ab shows only that line).

@babyhuey

Copy link
Copy Markdown
Contributor

A catalog title that needs this PR: wiliwili (PPSA99099, 1.6.0.1, homebrew, so not the retail check asked for above). It crashes on Windows at the current default base and runs with this head.

The title resolves its own imports: it reads imports.txt from its package (%lx %127s lines) and checks each entry with cmp qword [rcx+0x400000], 0, where rcx is the parsed offset (app.exe+0x1148c in the Windows output). So it assumes its image starts at 0x400000. On Linux, where the executable is placed there, it runs.

Windows 11, Ryzen 7 5800X, WinLibs GCC 15.2, Release, --trap-sdk-syscall-stubs (233 SDK stubs trapped) in all three runs, 45 s each:

Build Image base Result
main 29f539a5f 0x140000000, lowest section 0x140010000 0xC0000005 at 2.5 s, rip app.exe+0x1148c, read of the parsed offset + 0x400000
main 29f539a5f + --windows-fixed-base 0x400000 0x3f0000, lowest section 0x400000, relocations stripped still running at 45 s, Explore feed with thumbnails loaded over the network
this head 39df8e081c, no base flag 0x3f0000, lowest section 0x400000, relocations kept, DYNAMIC_BASE off still running at 45 s, same Explore feed

Linux (main 440cbddca13, lavapipe under Xvfb, --trap-sdk-syscall-stubs): still running at 45 s.

The frame rate is low (about 7 FPS) with and without this PR, so that's unrelated to the base.

AI-assisted: yes (Claude Code)

@rooklz

rooklz commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

@babyhuey thanks, that's a clear case: the title adds 0x400000 to offsets from its own imports.txt, so it only works with the image where the console puts it. I added your run to the description and the title to Unblocks. The retail check the maintainer asked for is still the open item.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@rooklz thanks for adding the new run. The retail-title check I asked for on 23 Oct isn't needed any more: the homebrew runs and the full Windows CI on this head cover it. This PR is ready, and it will merge as soon as the current merge pause on main lifts.

@oneandonlydean
oneandonlydean merged commit c4446bb into boykopovar:main Oct 24, 2026
14 checks passed
LeandroLP23 added a commit to LeandroLP23/AnyPS5 that referenced this pull request Oct 26, 2026
boykopovar#9332 made 0x400000 the default base of Windows executables, as boykopovar#8973
did on Linux, so the three titles that read their GOT at that base no
longer need fixed_base in titles.json: with a plain relink their guest
image starts at 0x400000 and all three render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LeandroLP23 added a commit to LeandroLP23/AnyPS5 that referenced this pull request Oct 31, 2026
boykopovar#9332 made 0x400000 the default base of Windows executables, as boykopovar#8973
did on Linux, so the three titles that read their GOT at that base no
longer need fixed_base in titles.json: with a plain relink their guest
image starts at 0x400000 and all three render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LeandroLP23 added a commit to LeandroLP23/AnyPS5 that referenced this pull request Nov 3, 2026
boykopovar#9332 made 0x400000 the default base of Windows executables, as boykopovar#8973
did on Linux, so the three titles that read their GOT at that base no
longer need fixed_base in titles.json: with a plain relink their guest
image starts at 0x400000 and all three render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LeandroLP23 added a commit to LeandroLP23/AnyPS5 that referenced this pull request Nov 3, 2026
boykopovar#9332 made 0x400000 the default base of Windows executables, as boykopovar#8973
did on Linux, so the three titles that read their GOT at that base no
longer need fixed_base in titles.json: with a plain relink their guest
image starts at 0x400000 and all three render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs relinker unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants