Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions core/relinker/relinker/src/analysis/CallSiteResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ std::optional<std::size_t> ripRelativeDispOffset(const std::vector<std::uint8_t>
++cur;
continue;
}
if (b == PrefixRepne || b == PrefixSegDs) {

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: 64-bit mode ignores all four segment overrides 2E, 26, 36 and 3E for memory operands (Intel SDM Vol. 2A 2.2.1), but only 3E is skipped here, so 2e ff 15 ... or 26 48 8b 05 ... stay unresolved while the 3E form resolves. Conservative and compiler-irrelevant, so fine; but the forms that remain unclassified after this change (F3, 66, 67, F0, 2E/26/36, FS/GS, 8b without REX.W) are wider than the body's "address-size and FS/GS overrides" and live only in the body. One sentence in docs/dev/debt/relinker.md beside the registry entry would keep that findable; your call.

hasRexW = false;
++cur;
continue;
}
break;
}
if (cur + 2 > end) return std::nullopt;
Expand Down
22 changes: 22 additions & 0 deletions core/relinker/relinker/tests/CallSiteResolverTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ int main() {
check("multiple REX prefixes on pointer load", {0x40, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true);
check("earlier REX.W does not widen a pointer load", {0x48, 0x40, 0x8b, 0x05, 0xf8, 0, 0, 0}, false);
check("final REX.W widens a pointer load", {0x48, 0x40, 0x48, 0x8b, 0x05, 0xf7, 0, 0, 0}, true);
check("BND indirect call", {0xf2, 0xff, 0x15, 0xf9, 0, 0, 0}, true);
check("BND indirect tail jump", {0xf2, 0xff, 0x25, 0xf9, 0, 0, 0}, true);
check("NOTRACK indirect call", {0x3e, 0xff, 0x15, 0xf9, 0, 0, 0}, true);
check("NOTRACK indirect tail jump", {0x3e, 0xff, 0x25, 0xf9, 0, 0, 0}, true);
check("NOTRACK BND indirect call", {0x3e, 0xf2, 0xff, 0x15, 0xf8, 0, 0, 0}, true);
check("BND NOTRACK indirect tail jump", {0xf2, 0x3e, 0xff, 0x25, 0xf8, 0, 0, 0}, true);
check("BND REX indirect call", {0xf2, 0x48, 0xff, 0x15, 0xf8, 0, 0, 0}, true);
check("NOTRACK REX indirect tail jump", {0x3e, 0x48, 0xff, 0x25, 0xf8, 0, 0, 0}, true);
check("NOTRACK-prefixed pointer load", {0x3e, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true);
check("REPNE-prefixed pointer load", {0xf2, 0x48, 0x8b, 0x05, 0xf8, 0, 0, 0}, true);
check("DS after REX.W leaves a narrow load", {0x48, 0x3e, 0x8b, 0x05, 0xf8, 0, 0, 0}, false);
check("REPNE after REX.W leaves a narrow load", {0x48, 0xf2, 0x8b, 0x05, 0xf8, 0, 0, 0}, false);
check("final REX.W after DS widens a load", {0x48, 0x3e, 0x48, 0x8b, 0x05, 0xf7, 0, 0, 0}, true);
check("final REX.W after REPNE widens a load", {0x48, 0xf2, 0x48, 0x8b, 0x05, 0xf7, 0, 0, 0}, true);
check("NOTRACK preserves final narrow REX", {0x3e, 0x48, 0x40, 0x8b, 0x05, 0xf7, 0, 0, 0}, false);
check("FS call is not a flat GOT reference", {0x64, 0xf2, 0xff, 0x15, 0xf8, 0, 0, 0}, false);
check("GS jump is not a flat GOT reference", {0x3e, 0x65, 0xff, 0x25, 0xf8, 0, 0, 0}, false);
check("address-size override is not a RIP64 reference", {0x3e, 0x67, 0xff, 0x15, 0xf8, 0, 0, 0}, false);
check("BND register call has no RIP operand", {0xf2, 0xff, 0xd0}, false);
check("NOTRACK register jump has no RIP operand", {0x3e, 0xff, 0xe0}, false);
check("BND-prefixed increment is not an import access", {0xf2, 0xff, 0x05, 0xf9, 0, 0, 0}, false);
check("DS-prefixed decrement is not an import access", {0x3e, 0xff, 0x0d, 0xf9, 0, 0, 0}, false);
check("pointer load without REX.W", {0x40, 0x8b, 0x05, 0xf9, 0, 0, 0}, false);
check("register pointer load", {0x48, 0x8b, 0x00}, false);
check("increment memory", {0xff, 0x05, 0xfa, 0, 0, 0}, false);
Expand Down
1 change: 1 addition & 0 deletions docs/dev/debt/relinker.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Runtime-loaded modules the executable needs](../../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) are mapped at startup on Windows and started when the title loads them with `sceKernelLoadStartModule`, with their start arguments, as a Unity player loads `mono-ps4.sprx`. A title that calls such a module before loading it finds its initializers not run; whether the console starts it earlier is not measured. A module the executable only imports from and the title never loads itself gets no `module_start` and no initializers at all under this deferral. The [entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp) prints `Deferred module <path>` at startup for each such module that an import of the executable names, whether or not the title loads it later; an import without a module name is not reported. macOS still starts it at startup.
- 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.
- Registry [call-site analysis](../../../core/relinker/relinker/src/analysis/CallSiteResolver.cpp) does not classify instructions with `F3`, `66`, `67`, `F0`, `2E`, `26`, `36`, FS/GS prefixes, or `8B` loads without REX.W, so missing call sites do not establish that an import is unused.
- 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 can relocate the image, possibly above that window; no run has observed this, and from this base an image cannot reach the shared user data page at `0x7ffe0000`, since Windows refuses a `SizeOfImage` above `0x77000000` (#9332). Verified on Windows 11 only.
- [SDK memory-management syscall traps](../../../core/relinker/relinker/src/analysis/SyscallScanner.cpp) under `--trap-sdk-syscall-stubs` recognize three exact instruction sequences from [ps5-payload-dev SDK libc/mman.c](https://github.com/ps5-payload-dev/sdk/blob/12cf3dda40/libc/mman.c), observed in Nuvio Maxd 0.5.15 (PPSA99288): syscall 74 and the two syscall 477 paths, with their argument preparation and carry-flag error handling. Matching requires a decoded instruction boundary and the complete sequence inside the scanned code range. Other SDK/compiler output remains rejected. The matched syscall becomes `UD2`; this does not implement mmap or mprotect and a call still faults.
- The [instruction decoder](../../../core/relinker/codegen/src/x86/X64InstructionDecoder.cpp) reads a 16-bit displacement for `66 E8`, `66 E9` and `66 0F 80`-`8F` without REX.W, as AMD processors, the console included, do (binutils `objdump -M amd64`; REX.W selects the 32-bit form). AMD also truncates the target of every near relative branch with that prefix, rel8, LOOP and JRCXZ forms included, to 16 bits. Instruction metadata marks these branches as truncated and reports no branch target for them, and stubs refuse to move them. Compilers do not emit these forms.
Expand Down
Loading