revert: undo #1156's bundled libc lifecycle suppression
Since #1156 a title's bundled sce_module/libc.prx is loaded for its exports but its init is suppressed, while titles still call its malloc, which then returns NULL (#1500). On Linux, titles that bundle libc crash at launch: one segfaults within 0.2 s, another stops with bad_alloc. This reverts commit2d6aff7a1e, reversing changes made toe82e03f11e.
This commit is contained in:
parent
4b2413fb57
commit
2096c66a91
|
|
@ -76,7 +76,6 @@ file(GLOB LIBS_DIRS LIST_DIRECTORIES true ${CMAKE_CURRENT_SOURCE_DIR}/prx/*)
|
|||
list(REMOVE_ITEM LIBS_DIRS ${LIBC_DIR})
|
||||
|
||||
set(PATCHED_OUTPUTS ${LIBC_PATCHED})
|
||||
set(ANYPS5_REPLACEMENT_MODULES libc.prx)
|
||||
|
||||
foreach(LIB_DIR ${LIBS_DIRS})
|
||||
if(IS_DIRECTORY ${LIB_DIR})
|
||||
|
|
@ -126,10 +125,8 @@ foreach(LIB_DIR ${LIBS_DIRS})
|
|||
)
|
||||
|
||||
list(APPEND PATCHED_OUTPUTS ${PATCHED_FILE})
|
||||
list(APPEND ANYPS5_REPLACEMENT_MODULES ${LIB_NAME}.prx)
|
||||
endif()
|
||||
endforeach()
|
||||
set(ANYPS5_REPLACEMENT_MODULES ${ANYPS5_REPLACEMENT_MODULES} PARENT_SCOPE)
|
||||
|
||||
add_custom_target(libs DEPENDS ${PATCHED_OUTPUTS})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,3 @@
|
|||
if(NOT ANYPS5_REPLACEMENT_MODULES)
|
||||
message(FATAL_ERROR "Replacement module catalog is empty")
|
||||
endif()
|
||||
set(replacementModuleEntries "")
|
||||
foreach(module IN LISTS ANYPS5_REPLACEMENT_MODULES)
|
||||
string(APPEND replacementModuleEntries " \"${module}\",\n")
|
||||
endforeach()
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated/relinker/guest/ReplacementModules.hpp"
|
||||
"#ifndef RELINKER_REPLACEMENTMODULES_HPP\n#define RELINKER_REPLACEMENTMODULES_HPP\n#include <string_view>\nnamespace Relinker {\ninline constexpr std::string_view ReplacementModules[] = {\n${replacementModuleEntries}};\n}\n#endif\n")
|
||||
|
||||
add_executable(relinker
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/src/CliArgs.cpp
|
||||
|
|
@ -76,7 +66,6 @@ add_executable(relinker
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/pipeline/RelinkerPipeline.cpp
|
||||
)
|
||||
target_include_directories(relinker PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/generated
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cli/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/domain/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include
|
||||
|
|
@ -241,18 +230,6 @@ if(BUILD_TESTING)
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_symbol_names.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME guest_host_libc COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $<TARGET_FILE:relinker>)
|
||||
add_library(guest_lifecycle_replacement_fixture SHARED elfpatcher/tests/GuestLifecycleReplacement.cpp)
|
||||
target_include_directories(guest_lifecycle_replacement_fixture PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libs)
|
||||
if(MINGW)
|
||||
target_compile_options(guest_lifecycle_replacement_fixture PRIVATE
|
||||
-fno-asynchronous-unwind-tables -fno-exceptions)
|
||||
endif()
|
||||
configure_windows_unwind(guest_lifecycle_replacement_fixture)
|
||||
set_target_properties(guest_lifecycle_replacement_fixture PROPERTIES
|
||||
PREFIX "" SUFFIX ".prx" WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
add_test(NAME guest_lifecycle_ownership COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_lifecycle_ownership.py
|
||||
$<TARGET_FILE:relinker> $<TARGET_FILE:guest_lifecycle_replacement_fixture>)
|
||||
add_test(NAME guest_absolute_symbols COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_import_modules COMMAND ${Python3_EXECUTABLE}
|
||||
|
|
|
|||
|
|
@ -73,9 +73,8 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
|
|||
Io::AppendU32(bytes, static_cast<std::uint32_t>(displacement));
|
||||
return start;
|
||||
};
|
||||
const bool ownsLifecycle = image.ReplacementModule.empty();
|
||||
const auto init = ownsLifecycle ? lifecycle(image.Init) : 0;
|
||||
const auto fini = ownsLifecycle ? lifecycle(image.Fini) : 0;
|
||||
const auto init = lifecycle(image.Init);
|
||||
const auto fini = lifecycle(image.Fini);
|
||||
Io::AlignBuffer(bytes, 8);
|
||||
const auto dynamicOffset = bytes.size();
|
||||
const auto dynamicAddress = address();
|
||||
|
|
@ -99,8 +98,8 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteLinux(const Relinker::GuestIma
|
|||
tag(20, 7);
|
||||
tag(3, image.Got);
|
||||
}
|
||||
if (ownsLifecycle && !image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); }
|
||||
if (ownsLifecycle && !image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); }
|
||||
if (!image.InitArray.empty()) { tag(25, image.InitArray.front()); tag(27, image.InitArray.size() * 8); }
|
||||
if (!image.FiniArray.empty()) { tag(26, image.FiniArray.front()); tag(28, image.FiniArray.size() * 8); }
|
||||
if (init != 0) tag(12, init);
|
||||
if (fini != 0) tag(13, fini);
|
||||
tag(30, 8);
|
||||
|
|
|
|||
|
|
@ -170,12 +170,10 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
|
|||
}
|
||||
const auto entry = nextRva;
|
||||
sections.push_back({".dllmain", entry, SectionRead | SectionExecute | 0x20u, {0xb8, 1, 0, 0, 0, 0xc3}});
|
||||
if (guest.ReplacementModule.empty()) {
|
||||
for (const auto slot : guest.InitArray) runtime.InitArrayRvas.push_back(image.GetRva(slot, 8));
|
||||
for (const auto slot : guest.FiniArray) runtime.FiniArrayRvas.push_back(image.GetRva(slot, 8));
|
||||
runtime.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init);
|
||||
runtime.FiniRva = guest.Fini == 0 ? 0 : image.GetRva(guest.Fini);
|
||||
}
|
||||
for (const auto slot : guest.InitArray) runtime.InitArrayRvas.push_back(image.GetRva(slot, 8));
|
||||
for (const auto slot : guest.FiniArray) runtime.FiniArrayRvas.push_back(image.GetRva(slot, 8));
|
||||
runtime.InitRva = guest.Init == 0 ? 0 : image.GetRva(guest.Init);
|
||||
runtime.FiniRva = guest.Fini == 0 ? 0 : image.GetRva(guest.Fini);
|
||||
auto result = WindowsPeWriter().Write(sections, entry, directories);
|
||||
const auto characteristics = Io::ReadU16(result, 0x80 + 22);
|
||||
Io::WriteU16(result, 0x80 + 22, static_cast<std::uint16_t>((characteristics | 0x2000) & ~1u));
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void Fail(const char* message) noexcept {
|
||||
std::fprintf(stderr, "Guest lifecycle replacement fixture: %s\n", message);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C" void APS5_VABI Record(int event) {
|
||||
const auto path = std::getenv("ANYPS5_LIFECYCLE_EVENTS");
|
||||
if (!path) Fail("Missing ANYPS5_LIFECYCLE_EVENTS");
|
||||
auto* file = std::fopen(path, "ab");
|
||||
if (!file) Fail("Cannot open lifecycle event file");
|
||||
const auto result = std::fputc(event, file);
|
||||
const auto closed = std::fclose(file);
|
||||
if (result == EOF || closed != 0) Fail("Cannot write lifecycle event");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Runtime {
|
||||
Runtime() { Record('H'); }
|
||||
~Runtime() { Record('h'); }
|
||||
} runtime;
|
||||
|
||||
}
|
||||
|
|
@ -28,8 +28,6 @@ struct GuestImage {
|
|||
std::filesystem::path SourcePath;
|
||||
std::string OutputName;
|
||||
std::string Soname;
|
||||
std::vector<std::string> ModuleNames;
|
||||
std::string ReplacementModule;
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
std::vector<Codegen::TrampolineSite> Trampolines;
|
||||
std::vector<Domain::ProgramHeader> Headers;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
std::map<std::uint64_t, std::uint64_t> tags;
|
||||
std::vector<std::uint64_t> needed;
|
||||
std::vector<std::uint64_t> moduleImports;
|
||||
std::vector<std::uint64_t> moduleExports;
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = dynamic->Offset; offset < dynamic->Offset + dynamic->FileSize; offset += 16) {
|
||||
const auto tag = Io::ReadU64(bytes, offset);
|
||||
|
|
@ -71,7 +70,6 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
if (tag == 0) { terminated = true; break; }
|
||||
if (tag == 1) needed.push_back(value);
|
||||
else if (tag == 0x61000045) moduleImports.push_back(value);
|
||||
else if (tag == 0x6100000d || tag == 0x61000043) moduleExports.push_back(value);
|
||||
else if (tag < 0x60000000 || tag == 0x6100003f || (tag >= 0x61000027 && tag <= 0x6100003b)) {
|
||||
if (!tags.emplace(tag, value).second) fail("Duplicate dynamic tag " + std::to_string(tag));
|
||||
}
|
||||
|
|
@ -161,13 +159,6 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
image.Soname = string(tags.at(14));
|
||||
if (image.Soname.empty() || image.Soname.find_first_of("\\:$\r\n") != std::string::npos) fail("Invalid guest SONAME: " + image.Soname);
|
||||
}
|
||||
std::set<std::uint64_t> moduleIds;
|
||||
for (const auto value : moduleExports) {
|
||||
const auto name = string(value & 0xffffffffu);
|
||||
if (name.empty() || name.find_first_of("/\\:$\r\n") != std::string::npos) fail("Invalid guest module name: " + name);
|
||||
if (!moduleIds.insert(value >> 48).second) fail("Duplicate export module ID");
|
||||
image.ModuleNames.push_back(name);
|
||||
}
|
||||
std::set<std::string> dependencies;
|
||||
for (const auto offset : needed) {
|
||||
const auto name = string(offset);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <relinker/guest/ReplacementModules.hpp>
|
||||
#include <domain/ImportModule.hpp>
|
||||
#include <elfpatcher/general/GuestModuleWriter.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <io/FileReader.hpp>
|
||||
|
|
@ -86,25 +84,6 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
Io::FileReader reader;
|
||||
for (const auto& path : paths) {
|
||||
auto image = GuestImageReader().Read(path, reader.Read(path.string()));
|
||||
auto identities = image.ModuleNames;
|
||||
if (identities.empty()) identities.push_back(image.Soname.empty() ? path.filename().string() : image.Soname);
|
||||
for (const auto& identity : identities) {
|
||||
const auto moduleName = identity.ends_with(".prx") ? identity : identity + ".prx";
|
||||
std::string owner;
|
||||
for (const auto replacement : ReplacementModules) {
|
||||
if (replacement == moduleName) { owner = replacement; break; }
|
||||
}
|
||||
if (owner.empty()) {
|
||||
for (const auto replacement : ReplacementModules) {
|
||||
if (Domain::ImportModuleName(std::string(replacement)) != Domain::ImportModuleName(moduleName)) continue;
|
||||
if (!owner.empty()) throw Domain::RelinkerException("Ambiguous replacement module identity: " + identity);
|
||||
owner = replacement;
|
||||
}
|
||||
}
|
||||
if (owner.empty()) continue;
|
||||
if (!image.ReplacementModule.empty() && image.ReplacementModule != owner) throw Domain::RelinkerException("Conflicting replacement lifecycle owners: " + path.string());
|
||||
image.ReplacementModule = std::move(owner);
|
||||
}
|
||||
if (image.OutputName.find_first_of("$\r\n") != std::string::npos) throw Domain::RelinkerException("Unsupported guest filename: " + image.OutputName);
|
||||
std::string folded = image.OutputName;
|
||||
if (windows) {
|
||||
|
|
@ -249,9 +228,6 @@ std::vector<GuestArtifact> GuestModuleBuilder::Build(const std::filesystem::path
|
|||
}
|
||||
for (const auto& image : images) for (const auto& dependency : image.Dependencies) addHost(dependency);
|
||||
if (uniqueHosts.contains("libSceLibcInternal.prx") && uniqueHosts.insert("libc.prx").second) hostLibraries.push_back("libc.prx");
|
||||
for (const auto& image : images) {
|
||||
if (!image.ReplacementModule.empty() && uniqueHosts.insert(image.ReplacementModule).second) hostLibraries.push_back(image.ReplacementModule);
|
||||
}
|
||||
dynamic.DynamicSegmentData.clear();
|
||||
const auto addNeeded = [&](const std::string& name) {
|
||||
Io::AppendU64(dynamic.DynamicSegmentData, 1);
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ def main():
|
|||
"libkernel.prx", "libSceLibcInternal.prx", "libc.prx"], needed
|
||||
|
||||
needed = convert(relinker, work / "without-internal", ("libkernel.prx", "libc.prx"), ["libc.prx"])
|
||||
assert needed == ["$ORIGIN/app0/sce_module/libc.prx.guest.prx", "libkernel.prx", "libc.prx"], needed
|
||||
assert needed == ["$ORIGIN/app0/sce_module/libc.prx.guest.prx", "libkernel.prx"], needed
|
||||
|
||||
needed = convert(relinker, work / "not-bundled", imports, ["other.prx"])
|
||||
assert needed == ["$ORIGIN/app0/sce_module/other.prx.guest.prx", *imports], needed
|
||||
|
|
|
|||
|
|
@ -1,198 +0,0 @@
|
|||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_guest_intel_trampolines import elf_loads
|
||||
from test_guest_module_directories import needed_libraries
|
||||
from test_guest_symbol_names import module_symbols
|
||||
from test_windows_import_modules import executable
|
||||
|
||||
|
||||
def guest(identity=None, module_name=None, missing_import=False, module_info_tag=0x6100000d):
|
||||
image = bytearray(0x3000)
|
||||
image[:16] = b'\x7fELF\x02\x01\x01' + bytes(9)
|
||||
struct.pack_into('<HHIQQQIHHHHHH', image, 16,
|
||||
3, 62, 1, 0, 64, 0, 0, 64, 56, 3, 0, 0, 0)
|
||||
struct.pack_into('<IIQQQQQQ', image, 64, 1, 7, 0, 0, 0, len(image), len(image), 0x1000)
|
||||
struct.pack_into('<IIQQQQQQ', image, 176, 0x6474e551, 6, 0, 0, 0, 0, 0, 16)
|
||||
strings = b'\0shared#A#B\0' + (b'absent' if missing_import else b'Record') + b'\0'
|
||||
dependency = len(strings)
|
||||
strings += b'libc.prx\0'
|
||||
tags = [(5, 0x2600), (10, 0), (6, 0x2400), (11, 24), (4, 0x2500),
|
||||
(7, 0x2700), (8, 72), (9, 24), (1, dependency),
|
||||
(12, 0x1010), (25, 0x2800), (27, 8),
|
||||
(26, 0x2808), (28, 8), (13, 0x1070)]
|
||||
module_names = module_name if isinstance(module_name, tuple) else (module_name,)
|
||||
for index, (name, tag) in enumerate([(identity, 14)] + [(name, module_info_tag) for name in module_names]):
|
||||
if name:
|
||||
tags.append((tag, len(strings) | (((0x101 << 32) | (index << 48)) if tag in (0x6100000d, 0x61000043) else 0)))
|
||||
strings += name.encode() + b'\0'
|
||||
tags[1] = (10, len(strings))
|
||||
tags.append((0, 0))
|
||||
struct.pack_into('<IIQQQQQQ', image, 120, 2, 6, 0x2000, 0x2000, 0x2000,
|
||||
len(tags) * 16, len(tags) * 16, 8)
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into('<qQ', image, 0x2000 + index * 16, *tag)
|
||||
image[0x2600:0x2600 + len(strings)] = strings
|
||||
struct.pack_into('<IBBHQQ', image, 0x2418, 1, 0x12, 0, 1, 0x1000, 6)
|
||||
struct.pack_into('<IBBHQQ', image, 0x2430, 12, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into('<II', image, 0x2500, 1, 3)
|
||||
struct.pack_into('<QQq', image, 0x2700, 0x2820, (2 << 32) | 6, 0)
|
||||
struct.pack_into('<QQq', image, 0x2718, 0x2800, 8, 0x1030)
|
||||
struct.pack_into('<QQq', image, 0x2730, 0x2808, 8, 0x1050)
|
||||
image[0x1000:0x1006] = b'\xb8\x2a\0\0\0\xc3'
|
||||
for address, event in ((0x1010, 'I'), (0x1030, 'A'), (0x1050, 'a'), (0x1070, 'i')):
|
||||
code = b'\xbf' + struct.pack('<I', ord(event)) + b'\x48\x83\xec\x08\xff\x15'
|
||||
code += struct.pack('<i', 0x2820 - (address + 15)) + b'\x48\x83\xc4\x08\xc3'
|
||||
image[address:address + len(code)] = code
|
||||
return image
|
||||
|
||||
|
||||
def lifecycle_tags(image):
|
||||
phoff, = struct.unpack_from('<Q', image, 32)
|
||||
phsize, count = struct.unpack_from('<HH', image, 54)
|
||||
headers = [struct.unpack_from('<IIQQQQQQ', image, phoff + index * phsize) for index in range(count)]
|
||||
dynamic = next(header for header in headers if header[0] == 2)
|
||||
return {tag for position in range(dynamic[2], dynamic[2] + dynamic[5], 16)
|
||||
for tag, value in [struct.unpack_from('<qQ', image, position)] if tag in (12, 13, 25, 26, 27, 28)}
|
||||
|
||||
|
||||
def consumer(owner):
|
||||
image = guest()
|
||||
struct.pack_into('<IBBHQQ', image, 0x2418, 1, 0x12, 0, 0, 0, 0)
|
||||
struct.pack_into('<QQq', image, 0x2700, 0x2820, (1 << 32) | 6, 0)
|
||||
code = b'\x48\x83\xec\x08\xff\x15' + struct.pack('<i', 0x2820 - 0x101a)
|
||||
code += b'\x48\x83\xc4\x08\x83\xf8\x2a\x74\x02\x0f\x0b\xc3'
|
||||
image[0x1010:0x1010 + len(code)] = code
|
||||
tags = []
|
||||
for position in range(0x2000, 0x2200, 16):
|
||||
tag, value = struct.unpack_from('<qQ', image, position)
|
||||
if tag == 0:
|
||||
break
|
||||
if tag in (25, 26, 27, 28, 13):
|
||||
continue
|
||||
if tag == 10 and owner != 'libc.prx':
|
||||
value += len(owner) + 1
|
||||
tags.append((tag, value))
|
||||
size, = struct.unpack_from('<Q', image, 0x2018)
|
||||
if owner != 'libc.prx':
|
||||
image[0x2600 + size:0x2600 + size + len(owner) + 1] = owner.encode() + b'\0'
|
||||
tags.append((1, size))
|
||||
tags.append((0, 0))
|
||||
for index, tag in enumerate(tags):
|
||||
struct.pack_into('<qQ', image, 0x2000 + index * 16, *tag)
|
||||
struct.pack_into('<QQ', image, 120 + 32, len(tags) * 16, len(tags) * 16)
|
||||
return image
|
||||
|
||||
|
||||
def main():
|
||||
if sys.argv[1] == '--load':
|
||||
import ctypes
|
||||
import _ctypes
|
||||
library = ctypes.CDLL(sys.argv[2])
|
||||
second = ctypes.CDLL(sys.argv[2])
|
||||
assert getattr(library, 'shared#guest')() == 42
|
||||
assert getattr(second, 'shared#guest')() == 42
|
||||
before = Path(os.environ['ANYPS5_LIFECYCLE_EVENTS']).read_text()
|
||||
_ctypes.dlclose(library._handle)
|
||||
assert Path(os.environ['ANYPS5_LIFECYCLE_EVENTS']).read_text() == before
|
||||
_ctypes.dlclose(second._handle)
|
||||
return
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
fixture = Path(sys.argv[2]).resolve() if len(sys.argv) > 2 else None
|
||||
with tempfile.TemporaryDirectory(prefix='anyps5-guest-lifecycle-') as directory:
|
||||
work = Path(directory)
|
||||
for windows in (False, True):
|
||||
for name, identity, module_name, replacement, module_info_tag in (
|
||||
('libc.prx', None, None, 'libc.prx', 0x6100000d),
|
||||
('renamed.prx', 'libc.prx', None, 'libc.prx', 0x6100000d),
|
||||
('renamed.prx', 'libc', None, 'libc.prx', 0x6100000d),
|
||||
('renamed.prx', None, 'libc', 'libc.prx', 0x6100000d),
|
||||
('renamed.prx', 'ordinary.prx', 'libc', 'libc.prx', 0x61000043),
|
||||
('renamed.prx', None, ('custom', 'libc'), 'libc.prx', 0x6100000d),
|
||||
('ordinary.prx', None, ('custom', 'other'), None, 0x6100000d),
|
||||
('renamed.prx', None, 'libSceFont_native', 'libSceFont.native.prx', 0x6100000d),
|
||||
('libkernel.prx', None, None, 'libkernel.prx', 0x6100000d),
|
||||
('ordinary.prx', None, None, None, 0x6100000d),
|
||||
('unimplemented.prx', 'libNotReplaced.prx', None, None, 0x6100000d)):
|
||||
suppressed = replacement is not None
|
||||
case = work / f'{windows}-{name}-{identity}-{module_name}'
|
||||
modules = case / 'sce_module'
|
||||
modules.mkdir(parents=True)
|
||||
original = guest(identity, module_name, module_info_tag=module_info_tag)
|
||||
(modules / name).write_bytes(original)
|
||||
(modules / 'consumer.prx').write_bytes(consumer(name))
|
||||
source = case / 'input.elf'
|
||||
source.write_bytes(executable(name))
|
||||
output = case / ('output.exe' if windows else 'output.elf')
|
||||
result = subprocess.run([str(relinker), *(['--windows'] if windows else []),
|
||||
'--skip-syscall-check', str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
converted = case / 'app0' / 'sce_module' / (name + '.guest.prx')
|
||||
image = converted.read_bytes()
|
||||
if not windows:
|
||||
assert 'shared#guest' in module_symbols(image)
|
||||
assert 'shared#guest' in module_symbols((converted.parent / 'consumer.prx.guest.prx').read_bytes())
|
||||
assert lifecycle_tags(image) == (set() if suppressed else {12, 13, 25, 26, 27, 28}), name
|
||||
for header in elf_loads(image):
|
||||
if header[3] <= 0x1000 < header[3] + header[5]:
|
||||
start = header[2] + 0x1000 - header[3]
|
||||
assert image[start:start + 0x100] == original[0x1000:0x1100]
|
||||
needed = needed_libraries(image)
|
||||
assert 'libc.prx' in needed
|
||||
if replacement:
|
||||
assert replacement in needed
|
||||
native = (os.name == 'nt') if windows else (sys.platform == 'linux' and os.uname().machine == 'x86_64')
|
||||
if native and not fixture:
|
||||
raise AssertionError('Native lifecycle test requires the replacement fixture')
|
||||
if native and fixture:
|
||||
libraries = case / 'libs'
|
||||
libraries.mkdir()
|
||||
shutil.copyfile(fixture, libraries / 'libc.prx')
|
||||
if replacement and replacement != 'libc.prx':
|
||||
shutil.copyfile(fixture, libraries / replacement)
|
||||
events = case / 'events.txt'
|
||||
output.chmod(0o755)
|
||||
command = [str(output)] if windows else [sys.executable, __file__, '--load',
|
||||
str(converted.parent / 'consumer.prx.guest.prx')]
|
||||
run = subprocess.run(command, env={**os.environ, 'ANYPS5_LIFECYCLE_EVENTS': str(events)},
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert run.returncode == (42 if windows else 0), (name, run.returncode, run.stdout, run.stderr)
|
||||
actual = events.read_text()
|
||||
assert actual == ('HHhh' if replacement and replacement != 'libc.prx' else 'Hh' if suppressed else 'HIAaih'), (name, actual)
|
||||
case = work / f'{windows}-missing-import'
|
||||
modules = case / 'sce_module'
|
||||
modules.mkdir(parents=True)
|
||||
(modules / 'libc.prx').write_bytes(guest(missing_import=True))
|
||||
source = case / 'input.elf'
|
||||
source.write_bytes(executable('libc.prx'))
|
||||
output = case / ('output.exe' if windows else 'output.elf')
|
||||
result = subprocess.run([str(relinker), *(['--windows'] if windows else []),
|
||||
'--skip-syscall-check', str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
converted = case / 'app0' / 'sce_module' / 'libc.prx.guest.prx'
|
||||
if not windows:
|
||||
assert 'absent' in module_symbols(converted.read_bytes())
|
||||
assert lifecycle_tags(converted.read_bytes()) == set()
|
||||
if native:
|
||||
libraries = case / 'libs'
|
||||
libraries.mkdir()
|
||||
shutil.copyfile(fixture, libraries / 'libc.prx')
|
||||
output.chmod(0o755)
|
||||
command = [str(output)] if windows else [sys.executable, __file__, '--load', str(converted)]
|
||||
run = subprocess.run(command, env={**os.environ, 'ANYPS5_LIFECYCLE_EVENTS': str(case / 'events.txt')},
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert run.returncode != 0 and 'absent' in run.stderr, (run.returncode, run.stderr)
|
||||
print('Guest lifecycle ownership conversion tests passed')
|
||||
if os.name != 'nt' and (sys.platform != 'linux' or os.uname().machine != 'x86_64'):
|
||||
print('Native lifecycle execution skipped: Linux x86-64 or Windows required')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Loading…
Reference in New Issue