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 commit 2d6aff7a1e, reversing
changes made to e82e03f11e.
This commit is contained in:
Dean Galvin 2026-10-08 23:49:33 +13:00
parent 4b2413fb57
commit 2096c66a91
10 changed files with 9 additions and 303 deletions

View File

@ -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})

View File

@ -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}

View File

@ -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);

View File

@ -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));

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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()