mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
fix(relinker): prefer 0x400000 for Windows executables
This commit is contained in:
1 parent
522e32e3b6
commit
39df8e081c
9 files changed
+17
-16
No files matched your search
@@ -13,6 +13,7 @@ inline constexpr std::uint32_t SectionAlignment = 0x1000;
|
||||
inline constexpr std::uint32_t FileAlignment = 0x200;
|
||||
inline constexpr std::uint32_t LoadRva = 0x10000;
|
||||
inline constexpr std::uint64_t ImageBase = 0x140000000;
|
||||
inline constexpr std::uint64_t ExecutableBase = 0x400000;
|
||||
inline constexpr std::uint32_t SectionRead = 0x40000000;
|
||||
inline constexpr std::uint32_t SectionWrite = 0x80000000;
|
||||
inline constexpr std::uint32_t SectionExecute = 0x20000000;
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
#define ELFPATCHER_WINDOWS_PEWRITER_HPP
|
||||
|
||||
#include <elfpatcher/windows/WindowsPeFormat.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
class WindowsPeWriter {
|
||||
public:
|
||||
std::vector<std::uint8_t> Write(const std::vector<PeSection>& sections, std::uint32_t entryRva, const std::array<PeDirectory, 16>& directories, bool windowsGui = false, std::optional<std::uint64_t> fixedImageBase = std::nullopt, bool dynamicBase = true) const;
|
||||
std::vector<std::uint8_t> Write(const std::vector<PeSection>& sections, std::uint32_t entryRva, const std::array<PeDirectory, 16>& directories, bool windowsGui = false, std::uint64_t imageBase = ImageBase, bool fixedBase = false, bool dynamicBase = true) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(std::vector<std::uint8_t> sour
|
||||
const auto report = [&](std::uint64_t completed) { if (progress) progress(completed, 10000); };
|
||||
report(0);
|
||||
diagnostics.clear();
|
||||
WindowsLoadImage image(sourceElf, originalHeaders, true, fixedBase ? *fixedBase - LoadRva : ImageBase);
|
||||
WindowsLoadImage image(sourceElf, originalHeaders, true, fixedBase.value_or(ExecutableBase) - LoadRva);
|
||||
if (originalPltGotVaddr != 0)
|
||||
image.GetRva(originalPltGotVaddr, 8);
|
||||
report(500);
|
||||
@@ -111,7 +111,7 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(std::vector<std::uint8_t> sour
|
||||
}
|
||||
directories[2] = icon ? WindowsIconResourceBuilder().Build(*icon, iconName, sections, nextRva) : PeDirectory{};
|
||||
report(9500);
|
||||
auto result = WindowsPeWriter().Write(sections, entryRva, directories, windowsGui, fixedBase ? std::optional<std::uint64_t>(image.GetImageBase()) : std::nullopt, false);
|
||||
auto result = WindowsPeWriter().Write(sections, entryRva, directories, windowsGui, image.GetImageBase(), fixedBase.has_value(), false);
|
||||
report(10000);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace Elfpatcher::Windows {
|
||||
|
||||
std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& sections, const std::uint32_t entryRva, const std::array<PeDirectory, 16>& directories, const bool windowsGui, const std::optional<std::uint64_t> fixedImageBase, const bool dynamicBase) const {
|
||||
std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& sections, const std::uint32_t entryRva, const std::array<PeDirectory, 16>& directories, const bool windowsGui, const std::uint64_t imageBase, const bool fixedBase, const bool dynamicBase) const {
|
||||
constexpr std::size_t peOffset = 0x80;
|
||||
constexpr std::size_t optionalOffset = peOffset + 24;
|
||||
constexpr std::size_t sectionTable = optionalOffset + 240;
|
||||
@@ -18,12 +18,12 @@ std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& s
|
||||
Io::WriteU16(result, peOffset + 4, 0x8664);
|
||||
Io::WriteU16(result, peOffset + 6, static_cast<std::uint16_t>(sections.size()));
|
||||
Io::WriteU16(result, peOffset + 20, 240);
|
||||
if (fixedImageBase && (directories[5].Size != 0 || *fixedImageBase < 0x10000 || (*fixedImageBase & 0xffff) != 0))
|
||||
throw Domain::RelinkerException("Invalid fixed Windows image base or base relocations");
|
||||
Io::WriteU16(result, peOffset + 22, fixedImageBase ? 0x23 : 0x22);
|
||||
if (imageBase < 0x10000 || (imageBase & 0xffff) != 0 || (fixedBase && directories[5].Size != 0))
|
||||
throw Domain::RelinkerException("Invalid Windows image base or base relocations");
|
||||
Io::WriteU16(result, peOffset + 22, fixedBase ? 0x23 : 0x22);
|
||||
Io::WriteU16(result, optionalOffset, 0x20b);
|
||||
Io::WriteU32(result, optionalOffset + 16, entryRva);
|
||||
Io::WriteU64(result, optionalOffset + 24, fixedImageBase.value_or(ImageBase));
|
||||
Io::WriteU64(result, optionalOffset + 24, imageBase);
|
||||
Io::WriteU32(result, optionalOffset + 32, SectionAlignment);
|
||||
Io::WriteU32(result, optionalOffset + 36, FileAlignment);
|
||||
Io::WriteU16(result, optionalOffset + 40, 6);
|
||||
@@ -72,7 +72,7 @@ std::vector<std::uint8_t> WindowsPeWriter::Write(const std::vector<PeSection>& s
|
||||
}
|
||||
if (!foundEntry)
|
||||
throw Domain::RelinkerException("PE entry point is not inside executable section data", entryRva);
|
||||
if (fixedImageBase && *fixedImageBase > 0x7fffffffffff - static_cast<std::uint64_t>(endRva))
|
||||
if (fixedBase && imageBase > 0x7fffffffffff - static_cast<std::uint64_t>(endRva))
|
||||
throw Domain::RelinkerException("Fixed Windows image exceeds the user address space");
|
||||
Io::WriteU32(result, optionalOffset + 4, codeSize);
|
||||
Io::WriteU32(result, optionalOffset + 8, dataSize);
|
||||
|
||||
@@ -74,7 +74,7 @@ void generateProbe(const Fs::path& file, const std::string& libraries, const std
|
||||
directories[1] = imports.Directory;
|
||||
directories[3] = startup.ExceptionDirectory;
|
||||
directories[12] = imports.AddressTable;
|
||||
const auto image = WindowsPeWriter().Write({entry, got, imports.Section, startup.Data, startup.Code}, startup.Code.Rva, directories, false, ImageBase, false);
|
||||
const auto image = WindowsPeWriter().Write({entry, got, imports.Section, startup.Data, startup.Code}, startup.Code.Rva, directories, false, ImageBase, true, false);
|
||||
std::ofstream stream(file, std::ios::binary);
|
||||
stream.write(reinterpret_cast<const char*>(image.data()), static_cast<std::streamsize>(image.size()));
|
||||
require(static_cast<bool>(stream), "Cannot write " + file.string());
|
||||
|
||||
@@ -68,7 +68,7 @@ def main():
|
||||
assert result.returncode == 0, (name, result.returncode, result.stdout, result.stderr)
|
||||
data = output.read_bytes()
|
||||
pe, optional = pe_headers(data)
|
||||
assert struct.unpack_from("<Q", data, optional + 24)[0] == (0x140000000 if base is None else base - 0x10000)
|
||||
assert struct.unpack_from("<Q", data, optional + 24)[0] == (0x400000 if base is None else base) - 0x10000
|
||||
assert bool(struct.unpack_from("<H", data, pe + 22)[0] & 1) == (base is not None)
|
||||
characteristics = struct.unpack_from("<H", data, optional + 70)[0]
|
||||
assert characteristics & 0x100
|
||||
@@ -85,6 +85,7 @@ def main():
|
||||
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))
|
||||
run(convert("sce-dynexec-imports-offset", image, 0x400000))
|
||||
for base in (0x400000, 0x800000, 0x400000000):
|
||||
image = fixture()
|
||||
|
||||
@@ -85,7 +85,7 @@ def main():
|
||||
pe = output.read_bytes()
|
||||
assert pe_bytes_at(pe, 0x10210, 1) == b"\xe9", name
|
||||
if name == "access-start":
|
||||
assert struct.unpack("<Q", pe_bytes_at(pe, 0x10308, 8))[0] == 0x140010210, name
|
||||
assert struct.unpack("<Q", pe_bytes_at(pe, 0x10308, 8))[0] == 0x400210, name
|
||||
if name == "after-patch":
|
||||
assert pe_bytes_at(pe, 0x1021a, 6) == SEPARATE_FUNCTION, name
|
||||
|
||||
|
||||
@@ -10,7 +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, and nothing reports it. 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.
|
||||
- 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 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.
|
||||
- Converted Linux main executables are [placed at `0x400000`](../../../core/relinker/relinker/src/pipeline/Conversion.cpp) by default, as `ET_EXEC` without ASLR, not only with `--linux-fixed-base`: the console 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). The relinker refuses an image that spans 2 GiB or more, so the executable ends below the guest arena at `0x200000000`. With `--lazy-binding` the executable stays position independent.
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath`
|
||||
| `--macos` | Produce an x86-64 Mach-O executable, with bundled modules as Mach-O libraries in `app0/sce_module`. Conflicts with `--windows`. |
|
||||
| `--windows-diagnostics` | Include startup dependency diagnostics. Requires `--windows`. |
|
||||
| `--windows-gui` | Select the Windows GUI subsystem instead of the console subsystem. Requires `--windows`. |
|
||||
| `--windows-fixed-base <address>` | Map the lowest ELF load page at this fixed Windows address, given in decimal or hexadecimal (`0x`). Requires `--windows`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and a complete image fitting in the user address space. The main executable has no ASLR or base relocations; an occupied address fails to load. Bundled modules keep their normal placement. |
|
||||
| `--windows-fixed-base <address>` | Map the lowest ELF load page at this fixed Windows address instead of the preferred `0x400000`, given in decimal or hexadecimal (`0x`). Requires `--windows`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and a complete image fitting in the user address space. The main executable has no ASLR or base relocations; an occupied address fails to load. Bundled modules keep their normal placement. |
|
||||
| `--trace-imports` | Pass each function import through the libc.prx call tracer, see [tracing system library calls](#tracing-system-library-calls). Windows and Linux output only; conflicts with `--lazy-binding`. |
|
||||
| `--linux-fixed-base <address>` | Load the Linux executable at this fixed address instead of the default `0x400000`, given in decimal or hexadecimal (`0x`): every virtual address of the image is offset by it, so virtual address 0 is placed at the address, as the console places an executable. Requires a 64 KiB-aligned address from `0x10000` through `0x7fffffff0000` that is a multiple of every `PT_LOAD` alignment, and an image that ends in the user address space. The executable is written as `ET_EXEC`, without ASLR, as it is at the default address. Bundled modules keep their normal placement. Conflicts with `--windows`, `--macos` and `--lazy-binding`. |
|
||||
| `--macos-fixed-base <address>` | Map the lowest ELF load page of the macOS executable at this fixed address, given in decimal or hexadecimal (`0x`). Requires `--macos`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and an image that ends in the macOS user address space. `__PAGEZERO` ends 64 KiB below the address and the Mach-O header occupies those 64 KiB. The executable has no ASLR; an address the process cannot map fails to load. Bundled modules keep their normal placement. |
|
||||
@@ -149,7 +149,7 @@ Verify mode rejects the conversion options (`--windows`, `--macos`, `--to-intel`
|
||||
|
||||
## 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 can relocate 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.
|
||||
|
||||
For an executable that assumes its image starts at `0x400000`, as Ruffle (PPSA68091), VLC (PPSA85300) and XPSemu (PPSA97358) do when they read their `app0/imports.txt`, use `relinker --macos --macos-fixed-base 0x400000 source/input.elf app` on macOS. Use this option only when the executable requires a fixed address; without it, macOS output places the lowest ELF load page at `0x140010000`. Linux output places virtual address 0 at `0x400000` without an option, inside the 40-bit window that a title's own `sceLibcMspaceCreate` checks; `--linux-fixed-base` selects another address, and with `--lazy-binding` the executable stays position independent.
|
||||
|
||||
|
||||
Reference in new issue
Block a user