mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-21 08:10:25 -05:00
Link Vulkan instead of dlopening it, because a PS5 title cannot dlopen a driver
../PS5_Vulkan measured this on the console with their e2-module runner, and it
overturns the delivery route this port was built around: sceKernelLoadStartModule
refuses a linker-produced .so with ENOEXEC (0x80020008), a bare name gives ENOENT
(0x80020002), dlopen answers NULL for all twelve candidates - including modules the
process already holds - with dlerror() NULL every time, and sceKernelDlsym gives
ESRCH for every name on the modules that do load. A PS5 module image carries SCE
module parameters and an export table; a linker .so does not. So no name, path or
container fixes it, and RetroArch's dylib_load("libvulkan.so.1") can never succeed.
patches/series 0012 replaces the dylib_load/dylib_proc pair in
gfx/common/vulkan_common.c with a direct reference to the linked
vkGetInstanceProcAddr, declared locally because RetroArch's Vulkan headers carry the
PFN_ type but not the function. The frontend compiles 276 of 276 with Vulkan on and
no longer performs a dynamic load at all.
tools/build-title.sh names the sibling's four released archives (libps5vk.ps5.a,
Mesa's libvk_runtime.ps5.a, libpsbc_driver.ps5.a, libpsbc_support.ps5.a), with
PS5_VULKAN_DIR to point at a release kept elsewhere, and fails with a clear message
when one is missing rather than linking an unresolved entry point.
tools/build.sh links them --whole-archive with the sibling's
--no-dynamic-linker -z nodynamic-undefined-weak, and adds libc++.a, libc++abi.a,
libunwind.a and the compiler builtins in a --start-group, because the shader
compiler and Mesa's runtime are C++.
The link is not finished: it still reports __eh_frame_start/__eh_frame_end/
__eh_frame_hdr_start/__eh_frame_hdr_end, which come from ../PS5_Vulkan's own
tooling/psbc/ps5-pie-unwind.ld linker script, and u_thread_create and
util_barrier_init, which its tools/psbc-link.sh satisfies from its psbc runtime
group. That script names the whole recipe.
Two corrections recorded in docs/FINDINGS.md. The "console re-signs on write"
observation was wrong: the difference is this project's FTP tooling decrypting a
SELF on read (driver 16,695,760 -> 16,706,040, libc.prx 1,284,674 -> 1,335,962), and
nothing re-signs on write. And /app0 is the only path a title can see, so the /app0
dlopen fallbacks are moot - the load is refused for every path, not just the bare
name.
gates: bash tools/verify.sh unit -> PASS; the build gate is red until the link
recipe above is adopted.
This commit is contained in:
1 parent
ff6c31205c
commit
9d29a1f6a5
4 files changed
+181
-36
No files matched your search
@@ -1989,3 +1989,56 @@ inside `video_driver_init_internal` before our driver is called - and the new
|
||||
The candidates are therefore the graphics-context step and the `[Video] Found video
|
||||
driver` lookup, not the ICD, not the dlopen path, and not the delivered object. That
|
||||
is a much smaller space than "exits 1 with no message".
|
||||
|
||||
## A PS5 title cannot dlopen a driver, so Vulkan has to be linked - and the link is now the only gap
|
||||
|
||||
**Measured by ../PS5_Vulkan, on the console, with their e2-module runner.** This
|
||||
overturns the delivery route this port has been built around for three sessions:
|
||||
|
||||
sceKernelLoadStartModule("/app0/libvulkan.so.1") -> 0x80020008 (ENOEXEC)
|
||||
the same for the FSELF-wrapped, soname-as-path and control variants
|
||||
"libvulkan.so.1" bare -> 0x80020002 (ENOENT)
|
||||
dlopen answered NULL for all twelve candidates, including modules the process
|
||||
already holds, with dlerror() NULL every time
|
||||
sceKernelDlsym -> ESRCH for every name, on modules that do load
|
||||
|
||||
The discriminator is the module shape: a PS5 module image carries SCE module
|
||||
parameters and an export table, which a linker-produced `.so` does not. So no name,
|
||||
path or container fixes this, and RetroArch's `dylib_load("libvulkan.so.1")` can
|
||||
never succeed here. The route that is proven on this console is their runner title's:
|
||||
**link `libps5vk.ps5.a` into `eboot.bin` and call `vkGetInstanceProcAddr` as an
|
||||
ordinary symbol.**
|
||||
|
||||
**Two corrections this forces, both recorded rather than quietly dropped.**
|
||||
|
||||
1. The "the console re-signs on write" observation in the previous entry is wrong.
|
||||
The byte difference is this project's own FTP tooling: `RETR` decrypts a SELF on
|
||||
read (the driver 16,695,760 -> 16,706,040, and `libc.prx` 1,284,674 ->
|
||||
1,335,962). Nothing re-signs on write, and the title sees the staged containers
|
||||
at their staged sizes. The `.so` marker verification added on the strength of
|
||||
that claim is harmless but was justified by the wrong reason.
|
||||
2. `/app0` and `/app0/sce_module` are the only paths a title can see; `/data/homebrew/`
|
||||
and `/temp0` do not exist inside a title. The `/app0` dlopen fallbacks are
|
||||
therefore moot - the load is refused for every path, not just the bare name.
|
||||
|
||||
**What is in place now, and it is most of the work.** `patches/series` 0012 replaces
|
||||
RetroArch's `dylib_load`/`dylib_proc` pair with a direct reference to the linked
|
||||
`vkGetInstanceProcAddr` (with a local prototype, because RetroArch's Vulkan headers
|
||||
declare the `PFN_` type but not the function). The frontend compiles **276 of 276**
|
||||
with Vulkan on and no longer contains a dynamic load. `tools/build-title.sh` names
|
||||
the sibling's four released archives - `libps5vk.ps5.a`, Mesa's
|
||||
`libvk_runtime.ps5.a`, `libpsbc_driver.ps5.a`, `libpsbc_support.ps5.a` - and
|
||||
`PS5_VULKAN_DIR` overrides their root; `tools/build.sh` links them whole
|
||||
(`--whole-archive`) with the sibling's `--no-dynamic-linker
|
||||
-z nodynamic-undefined-weak`, and adds `libc++.a`, `libc++abi.a`, `libunwind.a` and
|
||||
the compiler builtins in a `--start-group`, because the shader compiler and Mesa's
|
||||
runtime are C++.
|
||||
|
||||
**What the link still wants, and it is a short list.** With all of the above the
|
||||
linker reports `__eh_frame_start`, `__eh_frame_end`, `__eh_frame_hdr_start`,
|
||||
`__eh_frame_hdr_end`, `u_thread_create` and `util_barrier_init`. The first four come
|
||||
from ../PS5_Vulkan's own linker script, `tooling/psbc/ps5-pie-unwind.ld`, which its
|
||||
`tools/psbc-link.sh` passes with `-T`; the last two are Mesa's threading util, which
|
||||
that script satisfies from its psbc runtime group. So the remaining work is to adopt
|
||||
that link recipe rather than to discover anything: archives, the unwind script, and
|
||||
the group ordering, all named in their `tools/psbc-link.sh`.
|
||||
+55
-36
@@ -134,51 +134,70 @@ EDITS = [
|
||||
" &input_ps5,",
|
||||
),
|
||||
(
|
||||
# Vulkan is loaded from the title's own folder, because the console's
|
||||
# loader does not search it.
|
||||
# Vulkan is linked, not loaded: a PS5 title cannot dlopen a driver.
|
||||
#
|
||||
# RetroArch does not link Vulkan; it dlopens by bare name
|
||||
# (gfx/common/vulkan_common.c):
|
||||
# RetroArch obtains every Vulkan entry point through one symbol,
|
||||
# vkGetInstanceProcAddr, which it fetches by dlopen of "libvulkan.so.1".
|
||||
# That cannot work in a title on this console, and ../PS5_Vulkan measured
|
||||
# it rather than assumed it - their e2-module runner asked the console's
|
||||
# loader directly:
|
||||
#
|
||||
# vulkan_library = dylib_load("libvulkan.so.1");
|
||||
# if (!vulkan_library)
|
||||
# vulkan_library = dylib_load("libvulkan.so");
|
||||
# sceKernelLoadStartModule("/app0/libvulkan.so.1") -> 0x80020008 (ENOEXEC)
|
||||
# the same for FSELF-wrapped, soname-as-path and control variants
|
||||
# "libvulkan.so.1" bare -> 0x80020002 (ENOENT)
|
||||
# dlopen answered NULL for all twelve candidates, including modules the
|
||||
# process already holds, with dlerror() NULL every time
|
||||
# sceKernelDlsym -> ESRCH for every name on the modules that do load
|
||||
#
|
||||
# A bare dlopen resolves through the loader's search path, which on this
|
||||
# console is the system module directories - not the folder the title was
|
||||
# started from. So the driver ../PS5_Vulkan delivers is invisible to it
|
||||
# even when it sits beside eboot.bin, and the failure is silent: the title
|
||||
# returns 1 with no line in its log, because a failed dylib_load logs
|
||||
# nothing. That is the shape the Vulkan build has failed in all along.
|
||||
# A linker-produced .so is not a PS5 module image (it has no SCE module
|
||||
# parameters and no export table), so no name or path fixes this. The
|
||||
# route that is proven on this console is the one their runner title uses:
|
||||
# link libps5vk.ps5.a and call the entry point as an ordinary symbol.
|
||||
#
|
||||
# The title folder is mounted at /app0, so naming it absolutely is what
|
||||
# makes the delivered object reachable. The bare names are tried first, so
|
||||
# a future table that does search the app directory still wins and this
|
||||
# changes nothing.
|
||||
# So the loader path is replaced by a direct reference. Everything below
|
||||
# this point in RetroArch is unchanged: it still goes through
|
||||
# vkGetInstanceProcAddr for every other entry point, which is exactly what
|
||||
# the driver expects (it exports the loader-facing spelling).
|
||||
#
|
||||
# The archives this needs are the sibling's released set, named in
|
||||
# tools/build-title.sh: libps5vk.ps5.a, Mesa's libvk_runtime.ps5.a, the
|
||||
# shader compiler libpsbc_driver.ps5.a, and libpsbc_support.ps5.a.
|
||||
"gfx/common/vulkan_common.c",
|
||||
" vulkan_library = dylib_load(\"libvulkan.so.1\");\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" vulkan_library = dylib_load(\"libvulkan.so\");\n",
|
||||
" /* Fallbacks added by this port (patches/series, 0011): the title's\n"
|
||||
" * own folder, which the console mounts at /app0. */\n"
|
||||
"#else\n"
|
||||
" vulkan_library = dylib_load(\"libvulkan.so.1\");\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" vulkan_library = dylib_load(\"libvulkan.so\");\n"
|
||||
"#endif\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" {\n"
|
||||
" RARCH_ERR(\"[Vulkan] Failed to open Vulkan loader.\\n\");\n"
|
||||
" return false;\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" RARCH_LOG(\"[Vulkan] Vulkan dynamic library loaded.\\n\");\n"
|
||||
"\n"
|
||||
" GetInstanceProcAddr =\n"
|
||||
" (PFN_vkGetInstanceProcAddr)dylib_proc(vulkan_library, \"vkGetInstanceProcAddr\");\n",
|
||||
"#else\n"
|
||||
" vulkan_library = dylib_load(\"libvulkan.so.1\");\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" vulkan_library = dylib_load(\"/app0/libvulkan.so.1\");\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" vulkan_library = dylib_load(\"/app0/libvulkan.so\");\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" vulkan_library = dylib_load(\"/app0/sce_module/libvulkan.so.1\");\n"
|
||||
" /* A failed load is silent in upstream - dylib_load only returns NULL -\n"
|
||||
" * and a silent Vulkan failure is what made this path undiagnosable for\n"
|
||||
" * three sessions. The four names and the loader's own message go to the\n"
|
||||
" * log so a console run says which path was tried and why it failed. */\n"
|
||||
" if (!vulkan_library)\n"
|
||||
" RARCH_ERR(\"[Vulkan] libvulkan not loadable; tried libvulkan.so.1, "
|
||||
"libvulkan.so, /app0/libvulkan.so.1, /app0/libvulkan.so, "
|
||||
"/app0/sce_module/libvulkan.so.1: %s\\n\", dylib_error());\n",
|
||||
"/app0/libvulkan.so.1",
|
||||
" vulkan_library = dylib_load(\"libvulkan.so\");\n"
|
||||
"#endif\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" /* Changed by this port (patches/series, 0012): this console refuses a\n"
|
||||
" * title's dlopen, so the entry point is linked, not loaded. It is the\n"
|
||||
" * same symbol the loader path looked up, taken from ../PS5_Vulkan's\n"
|
||||
" * libps5vk.ps5.a, which tools/build-title.sh links into the title. The\n"
|
||||
" * declaration is local because the Vulkan headers RetroArch carries\n"
|
||||
" * declare the PFN_ type but not the function. */\n"
|
||||
" extern VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL\n"
|
||||
" vkGetInstanceProcAddr(VkInstance instance, const char *pName);\n"
|
||||
" GetInstanceProcAddr =\n"
|
||||
" (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr;\n",
|
||||
"(PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr",
|
||||
),
|
||||
(
|
||||
# A core that has not loaded registers no controller-port callback, and
|
||||
|
||||
@@ -93,6 +93,42 @@ echo "==> [title] compiling src/ with ${#title_definition_names[@]} frontend def
|
||||
# cannot disagree about what this build is.
|
||||
cp -f -- "$root/build/ra-conf/config.h" "$root/build/config.h"
|
||||
|
||||
# The Vulkan driver is linked, not loaded. ../PS5_Vulkan measured that a PS5 title
|
||||
# cannot dlopen a driver (sceKernelLoadStartModule refuses a linker-produced .so
|
||||
# with ENOEXEC, a bare name gives ENOENT, dlopen answers NULL for every candidate
|
||||
# and sceKernelDlsym gives ESRCH even for modules the process holds), so RetroArch's
|
||||
# dlopen of "libvulkan.so.1" can never succeed here. The route proven on this
|
||||
# console is their runner title's: link the driver and call its entry point as an
|
||||
# ordinary symbol.
|
||||
#
|
||||
# Their released set, exactly as tools/build.sh links it for a driver-enabled title:
|
||||
# libps5vk.ps5.a the driver (build/driver/ps5/)
|
||||
# libvk_runtime.ps5.a Mesa's Vulkan runtime (.deps/native/vulkan-runtime/lib/)
|
||||
# libpsbc_driver.ps5.a the shader compiler (build/driver/ps5/)
|
||||
# libpsbc_support.ps5.a the package writer (.deps/native/psbc/lib/)
|
||||
# PS5_VULKAN_DIR overrides the sibling's root, so a release kept elsewhere works.
|
||||
vulkan_dir="${PS5_VULKAN_DIR:-$root/../PS5_Vulkan}"
|
||||
vulkan_archives=(
|
||||
"$vulkan_dir/build/driver/ps5/libps5vk.ps5.a"
|
||||
"$vulkan_dir/.deps/native/vulkan-runtime/lib/libvk_runtime.ps5.a"
|
||||
"$vulkan_dir/build/driver/ps5/libpsbc_driver.ps5.a"
|
||||
"$vulkan_dir/.deps/native/psbc/lib/libpsbc_support.ps5.a"
|
||||
)
|
||||
vulkan_missing=()
|
||||
for archive in "${vulkan_archives[@]}"; do
|
||||
[[ -f $archive ]] || vulkan_missing+=("$archive")
|
||||
done
|
||||
if (( ${#vulkan_missing[@]} )); then
|
||||
printf 'error: the Vulkan driver archives are missing; the title would link with\n' >&2
|
||||
printf ' vkGetInstanceProcAddr unresolved. Build them in ../PS5_Vulkan\n' >&2
|
||||
printf ' (tools/build-driver.sh) or set PS5_VULKAN_DIR.\n' >&2
|
||||
printf ' missing: %s\n' "${vulkan_missing[@]}" >&2
|
||||
exit 2
|
||||
fi
|
||||
# Mesa's weak entry points resolve at link time, and the driver's own symbols must
|
||||
# survive the archive boundary (--whole-archive), which is how the sibling links it.
|
||||
vulkan_flags="--no-dynamic-linker -z nodynamic-undefined-weak"
|
||||
|
||||
echo "==> [title] step 2/3: the title"
|
||||
PS5_PAYLOAD_SDK="$sdk" \
|
||||
PS5_CLANG=/usr/bin/clang \
|
||||
@@ -100,6 +136,8 @@ PYTHONPATH="$root/tooling/pystub${PYTHONPATH:+:$PYTHONPATH}" \
|
||||
APP_DEFINITIONS="${title_definition_names[*]}" \
|
||||
APP_INCLUDE_PATHS="build/ra-conf build vendor/retroarch vendor/retroarch/libretro-common/include vendor/retroarch/deps vendor/retroarch/deps/stb" \
|
||||
APP_STATIC_ARCHIVES="build/ra/libretroarch.a" \
|
||||
APP_VULKAN_ARCHIVES="${vulkan_archives[*]}" \
|
||||
APP_LINK_FLAGS="$vulkan_flags" \
|
||||
make app
|
||||
|
||||
title_id=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["titleId"])' \
|
||||
|
||||
@@ -209,6 +209,40 @@ agc_driver_stub=$(build_system_link_stub libSceAgcDriver vendor/ps5/sdk/stubs/ag
|
||||
|
||||
link_inputs=("$build/obj/app_crt.o" "$build/obj/app_cpp_runtime.o" "${objects[@]}")
|
||||
link_inputs+=("$agc_stub" "$agc_driver_stub")
|
||||
# The Vulkan driver, linked whole. These archives are the reason this title does
|
||||
# not dlopen: a PS5 title cannot (../PS5_Vulkan measured ENOEXEC/ENOENT/NULL from
|
||||
# sceKernelLoadStartModule and dlopen, and ESRCH from sceKernelDlsym), so the
|
||||
# driver's entry points are ordinary symbols here. --whole-archive keeps its
|
||||
# command tables, and the two flags are the sibling's, for Mesa's weak entry
|
||||
# points (tools/build.sh there links its runner title the same way).
|
||||
if [[ -n ${APP_VULKAN_ARCHIVES:-} ]]; then
|
||||
read -r -a vulkan_archives <<< "$APP_VULKAN_ARCHIVES"
|
||||
for archive in "${vulkan_archives[@]}"; do
|
||||
[[ $archive =~ ^[A-Za-z0-9_./+-]+(/[A-Za-z0-9_.+-]+)*\.a$ && -f $archive ]] || {
|
||||
echo "invalid Vulkan archive: $archive" >&2; exit 2;
|
||||
}
|
||||
done
|
||||
link_inputs+=(--whole-archive "${vulkan_archives[@]}" --no-whole-archive)
|
||||
# The shader compiler and Mesa's runtime are C++, so they need libc++,
|
||||
# libc++abi and libunwind - the same three the sibling project links, plus the
|
||||
# compiler's own builtins archive. They are grouped because the dependency runs
|
||||
# both ways between them.
|
||||
vulkan_cxx=(
|
||||
"$sdk_root/target/lib/libc++.a"
|
||||
"$sdk_root/target/lib/libc++abi.a"
|
||||
"$sdk_root/target/lib/libunwind.a"
|
||||
)
|
||||
builtins="$("$PS5_CLANG" --print-resource-dir 2>/dev/null)/lib/linux/libclang_rt.builtins-x86_64.a"
|
||||
[[ -f $builtins ]] || builtins=""
|
||||
for archive in "${vulkan_cxx[@]}"; do
|
||||
[[ -f $archive ]] || {
|
||||
echo "missing Vulkan C++ runtime archive: $archive" >&2; exit 2;
|
||||
}
|
||||
done
|
||||
link_inputs+=(-L "$sdk_root/target/lib" --start-group "${vulkan_cxx[@]}")
|
||||
[[ -n $builtins ]] && link_inputs+=("$builtins")
|
||||
link_inputs+=(--end-group)
|
||||
fi
|
||||
for archive in "${archives[@]}"; do
|
||||
[[ $archive =~ ^[A-Za-z0-9_.-]+(/[A-Za-z0-9_.-]+)*\.a$ && -f $root/$archive ]] || {
|
||||
echo "invalid static archive: $archive" >&2; exit 2;
|
||||
@@ -219,6 +253,7 @@ if (( ${#pacbrew_libs[@]} > 0 )); then
|
||||
link_inputs+=(--start-group "${pacbrew_libs[@]}" --end-group)
|
||||
fi
|
||||
"$sdk_root/bin/prospero-lld" -T "$native/ps5-pie.ld" --eh-frame-hdr \
|
||||
${APP_LINK_FLAGS:-} \
|
||||
--version-script "$native/app-symbols.map" \
|
||||
--exclude-libs=ALL -L "$build/obj" \
|
||||
-e _start -o "$build/llvm-pie.elf" "${link_inputs[@]}" \
|
||||
|
||||
Reference in new issue
Block a user