nid_patcher turns the _nid_postfix names of a library into NIDs for ELF
and PE. A library built on macOS is a Mach-O dylib, whose exports are in
a dyld export trie and whose imports are in the chained fixups, so it
gets its own patcher.
- The export trie is read, every exported C name (without Mach-O's
leading underscore) is resolved to its NID, and the trie is rebuilt the
way ld64 lays it out: the offsets are uleb128, so the layout is repeated
until it is stable. A trie that no longer fits is appended to
__LINKEDIT, which has to end the file, and a signed image is refused.
- Weak definitions that are not marked _nid_postfix or _nid_no_patch keep
their names: dyld coalesces those across images by name.
- In the chained imports, names bound to a .prx dylib that are guest
facing (a _nid_postfix name or an sce prefix) are patched in place; a
name that would grow, or that shares storage with another import, is an
error. Images that use bind opcodes instead of chained fixups are
refused.
- ExportExclusions reads the exports of a reference Mach-O, so the
exclusion list works for libraries built on macOS as well.
The patcher rewrites .dynstr in place and reorders .dynsym for
.gnu.hash, but .gnu.version_d kept pointing at name offsets that the
rewrite had recycled: a library carrying symbol version definitions
came out with renamed or blanked version names while its symbols
still advertised a version.
No library in the build defines symbol versions, so fail on
.gnu.version_d during the section scan and leave the input untouched
instead of carrying version remapping that nothing exercises. The
test covers the rejection and that a library without version
definitions is still patched.
Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com>
The NID patcher rebuilds .dynstr keeping only the DT_NEEDED and
DT_SONAME strings, so DT_RUNPATH kept a stale offset and the patched
libraries shipped with an empty runpath. A library could then load only
those sibling prx that the executable already loads itself:
libSceVideoOut.prx needs libSceKeyboard.prx, which a title that does
not import libSceKeyboard never loads, so it fails at startup.
The patcher now keeps DT_RPATH and DT_RUNPATH strings, and the prx
libraries are linked with a $ORIGIN runpath instead of the build
directory, so they find each other wherever they are copied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker stores a dynamic string that is the suffix of another once
(for example "cos" inside "acos"). The patcher wrote every string
separately, so a library whose renamed exports do not shrink the table
enough failed with "rebuilt .dynstr exceeds original section size".
Strings are now sorted by their reversed text and a string that ends
another one points into it.
Co-Authored-By: Claude <noreply@anthropic.com>
libSceSysmodule/Export.cpp parsed /proc/self/maps device major/minor
numbers into signed int with a %x format, which is undefined behaviour
per the C standard even though it happened to work on this target;
switched to unsigned int to match the format specifier.
NidPatcherUtils.hpp's StripNidPostfix built a truncated copy with
substr(0, n) where the prefix is already the string itself; resize(n)
does the same truncation without the extra allocation and copy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>