fix(relinker): pass module start arguments to macOS guest initializers
The macOS image initializer called a guest module's DT_INIT with no
arguments, so a module started with sceKernelLoadStartModule never saw
its args and argp, and its result was dropped. The Linux writer already
calls DT_INIT through libkernel's pending argument handoff. On the
macOS port, the PSN plugin of PPSA12544 refuses to start without its
argument block ("PSN is an old version that cannot be used by the
current player runtime") and the title then crashes.
A guest module with DT_INIT now gets two slots bound to
__aps5_get_pending_module_args_nid_no_patch and
__aps5_set_module_init_result_nid_no_patch, and its initializer calls
DT_INIT with the args and argp that sceKernelLoadStartModule records,
as on Linux, then hands the result back. The TechnicalDebt sentence
about the missing arguments on macOS is removed.
guest_module_args also relinks its fixture with --macos and checks the
two binds; on x86-64 macOS it loads the module and checks that DT_INIT
receives the arguments and that its result is returned.
AI-assisted: yes
This commit is contained in:
parent
0f863c1022
commit
1b865fc3ea
|
|
@ -424,6 +424,15 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
|
|||
metaEnd += 8;
|
||||
input.Binds.push_back({symbol, metaRva + static_cast<std::uint32_t>(slot)});
|
||||
}
|
||||
std::uint64_t moduleArgsSlot = 0;
|
||||
std::uint64_t moduleResultSlot = 0;
|
||||
if (input.InitRva != 0) {
|
||||
moduleArgsSlot = metaEnd;
|
||||
moduleResultSlot = metaEnd + 8;
|
||||
metaEnd += 16;
|
||||
input.Binds.push_back({"___aps5_get_pending_module_args_nid_no_patch", metaRva + static_cast<std::uint32_t>(moduleArgsSlot)});
|
||||
input.Binds.push_back({"___aps5_set_module_init_result_nid_no_patch", metaRva + static_cast<std::uint32_t>(moduleResultSlot)});
|
||||
}
|
||||
meta.resize(metaEnd, 0);
|
||||
for (std::size_t index = 0; index < input.TlsExports.size(); ++index) {
|
||||
const auto& variable = input.TlsExports[index];
|
||||
|
|
@ -545,8 +554,11 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
|
|||
code.Rip({0x48, 0x89, 0x05}, import.Rva);
|
||||
}
|
||||
if (input.InitRva != 0) {
|
||||
callZeroArgs();
|
||||
code.Rip({0xff, 0x15}, metaRva + static_cast<std::uint32_t>(moduleArgsSlot));
|
||||
code.Emit({0x48, 0x8b, 0x38, 0x48, 0x8b, 0x70, 0x08, 0x31, 0xd2});
|
||||
code.Rip({0xe8}, input.InitRva);
|
||||
code.Emit({0x89, 0xc7});
|
||||
code.Rip({0xff, 0x15}, metaRva + static_cast<std::uint32_t>(moduleResultSlot));
|
||||
}
|
||||
for (const auto slot : input.InitArrayRvas) callSlot(slot);
|
||||
if (hasFini) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
|
|
@ -7,6 +8,7 @@ import sys
|
|||
import tempfile
|
||||
|
||||
from test_guest_symbol_names import dynamic_tags, imported_symbols, module_symbols
|
||||
from test_macos_output import MachO
|
||||
from test_windows_import_modules import executable
|
||||
|
||||
ARGS = 0x1122334455667788
|
||||
|
|
@ -53,17 +55,34 @@ def no_relocation_module():
|
|||
return image
|
||||
|
||||
|
||||
def macho_binds(image):
|
||||
linkedit = MachO(image).segment('__LINKEDIT')
|
||||
return image[linkedit['fileoff']:linkedit['fileoff'] + linkedit['filesize']]
|
||||
|
||||
|
||||
def load_and_record(work, provider, module):
|
||||
events = work / 'events.txt'
|
||||
run = subprocess.run([sys.executable, __file__, '--load', str(provider), str(module)],
|
||||
env={**os.environ, 'ANYPS5_GUEST_MODULE_ARGS': str(events)},
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert run.returncode == 0, (run.returncode, run.stdout, run.stderr)
|
||||
recorded = events.read_text()
|
||||
assert f'args={ARGS:016x} argp={ARGP:016x}' in recorded, recorded
|
||||
assert f'result={RETURNED}' in recorded, recorded
|
||||
|
||||
|
||||
def main():
|
||||
if sys.argv[1] == '--load':
|
||||
import ctypes
|
||||
host = ctypes.CDLL(sys.argv[2])
|
||||
host = ctypes.CDLL(sys.argv[2], mode=ctypes.RTLD_GLOBAL if sys.platform == 'darwin' else ctypes.DEFAULT_MODE)
|
||||
host.SetPendingArgs.argtypes = [ctypes.c_size_t, ctypes.c_void_p]
|
||||
host.SetPendingArgs(ARGS, ARGP)
|
||||
ctypes.CDLL(sys.argv[3])
|
||||
return
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
host = Path(sys.argv[2]).resolve()
|
||||
native = sys.platform == 'linux' and os.uname().machine == 'x86_64'
|
||||
linux = sys.platform == 'linux' and os.uname().machine == 'x86_64'
|
||||
macos = sys.platform == 'darwin' and platform.machine() == 'x86_64'
|
||||
with tempfile.TemporaryDirectory(prefix='anyps5-guest-args-') as directory:
|
||||
work = Path(directory)
|
||||
modules = work / 'sce_module'
|
||||
|
|
@ -95,20 +114,33 @@ def main():
|
|||
assert GETTER in bare_imports, bare_imports
|
||||
assert SETTER in bare_imports, bare_imports
|
||||
assert dynamic_tags(bare_image).get(8) == 48, dynamic_tags(bare_image)
|
||||
if not native:
|
||||
mach = work / 'macos'
|
||||
mach_modules = mach / 'sce_module'
|
||||
mach_modules.mkdir(parents=True)
|
||||
(mach_modules / name).write_bytes(args_module())
|
||||
mach_source = mach / 'input.elf'
|
||||
mach_source.write_bytes(executable(name))
|
||||
mach_result = subprocess.run([str(relinker), '--macos', '--skip-syscall-check', str(mach_source), str(mach / 'output')],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert mach_result.returncode == 0, (mach_result.stdout, mach_result.stderr)
|
||||
mach_converted = mach / 'app0' / 'sce_module' / (name + '.guest.prx')
|
||||
binds = macho_binds(mach_converted.read_bytes())
|
||||
assert ('_' + GETTER).encode() in binds, f'the macOS module does not bind {GETTER}'
|
||||
assert ('_' + SETTER).encode() in binds, f'the macOS module does not bind {SETTER}'
|
||||
if linux:
|
||||
libraries = work / 'libs'
|
||||
libraries.mkdir()
|
||||
shutil.copyfile(host, libraries / 'libc.prx')
|
||||
load_and_record(work, libraries / 'libc.prx', converted)
|
||||
elif macos:
|
||||
libraries = mach / 'libs'
|
||||
libraries.mkdir()
|
||||
shutil.copyfile(host, libraries / 'libkernel.prx')
|
||||
(libraries / 'libc.prx').symlink_to('libkernel.prx')
|
||||
load_and_record(mach, libraries / 'libkernel.prx', mach_converted)
|
||||
else:
|
||||
print('Guest module argument tests passed: native execution skipped')
|
||||
return
|
||||
libraries = work / 'libs'
|
||||
libraries.mkdir()
|
||||
shutil.copyfile(host, libraries / 'libc.prx')
|
||||
events = work / 'events.txt'
|
||||
run = subprocess.run([sys.executable, __file__, '--load', str(libraries / 'libc.prx'), str(converted)],
|
||||
env={**os.environ, 'ANYPS5_GUEST_MODULE_ARGS': str(events)},
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert run.returncode == 0, (run.returncode, run.stdout, run.stderr)
|
||||
recorded = events.read_text()
|
||||
assert f'args={ARGS:016x} argp={ARGP:016x}' in recorded, recorded
|
||||
assert f'result={RETURNED}' in recorded, recorded
|
||||
print('Guest module argument tests passed')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -379,7 +379,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
|
||||
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
|
||||
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
|
||||
- [Runtime-loaded modules](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded. On macOS, `sceKernelLoadStartModule` does not pass start arguments to a module's initializer.
|
||||
- [Runtime-loaded modules](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded.
|
||||
- [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done
|
||||
- [sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands and their Multiple variants](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) on Windows transfer through the same positioned path as `sceKernelPread`/`sceKernelPwrite` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console
|
||||
- [sceKernelGetFsSandboxRandomWord](../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist
|
||||
|
|
|
|||
Loading…
Reference in New Issue