mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
AnyPS5 exports some libkernel functions, such as __error, from libc.prx, which libkernel.prx links. On Linux they resolve through that dependency. On Windows, the import stubs search only the listed modules, and libc.prx was listed only when libSceLibcInternal.prx was needed. A PS4 homebrew that needs libkernel.prx but no C library (the OpenOrbis graphics sample) failed with "unresolved ELF import 9BcDykPmo1I". libc.prx is now also listed when libkernel.prx is needed. PS5 executables need libSceLibcInternal.prx and convert unchanged.
283 lines
14 KiB
C++
283 lines
14 KiB
C++
#define WIN32_LEAN_AND_MEAN
|
|
#define NOMINMAX
|
|
#include <windows.h>
|
|
#include <elfpatcher/windows/WindowsDependencyStubBuilder.hpp>
|
|
#include <elfpatcher/windows/WindowsPeWriter.hpp>
|
|
#include <elfpatcher/windows/WindowsImportBuilder.hpp>
|
|
#include <io/BufferUtils.hpp>
|
|
#include <cerrno>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <random>
|
|
#include <stdexcept>
|
|
|
|
namespace {
|
|
|
|
namespace Fs = std::filesystem;
|
|
using namespace Elfpatcher::Windows;
|
|
|
|
Fs::path createFixtureDirectory(const Fs::path& parent) {
|
|
std::random_device random;
|
|
for (int attempt = 0; attempt < 100; ++attempt) {
|
|
auto name = Fs::path(u8"windows-diagnostic-fixtures-ü日-");
|
|
name += std::to_string(GetCurrentProcessId()) + "-" + std::to_string(random());
|
|
const auto directory = parent / name;
|
|
if (Fs::create_directory(directory))
|
|
return directory;
|
|
}
|
|
throw std::runtime_error("Cannot create a unique diagnostic fixture directory");
|
|
}
|
|
|
|
void checkRuntimeDependencies() {
|
|
const auto check = [](const std::vector<std::string>& input, const std::vector<std::string>& expected) {
|
|
Domain::SysVDynamicSection dynamic;
|
|
for (const auto& name : input) {
|
|
const auto offset = dynamic.DynStrData.size();
|
|
dynamic.DynStrData.insert(dynamic.DynStrData.end(), name.begin(), name.end());
|
|
dynamic.DynStrData.push_back(0);
|
|
const auto position = dynamic.DynamicSegmentData.size();
|
|
dynamic.DynamicSegmentData.resize(position + 16);
|
|
Io::WriteU64(dynamic.DynamicSegmentData, position, 1);
|
|
Io::WriteU64(dynamic.DynamicSegmentData, position + 8, offset);
|
|
}
|
|
if (WindowsImportBuilder{}.ReadLibraries(dynamic) != expected)
|
|
throw std::runtime_error("Incorrect Windows runtime dependency visibility");
|
|
};
|
|
check({}, {});
|
|
check({"libkernel.prx"}, {"libkernel.prx", "libc.prx"});
|
|
check({"libkernel.prx", "libc.prx"}, {"libkernel.prx", "libc.prx"});
|
|
check({"libSceLibcInternal.prx", "libkernel.prx"},
|
|
{"libSceLibcInternal.prx", "libkernel.prx", "libc.prx"});
|
|
check({"libc.prx", "libSceLibcInternal.prx"},
|
|
{"libc.prx", "libSceLibcInternal.prx"});
|
|
check({"libSceVideoOut.prx", "libkernel.prx", "libSceVideoOut.prx"},
|
|
{"libSceVideoOut.prx", "libkernel.prx", "libc.prx"});
|
|
check({"libSceVideoOut.prx", "libSceVideoOut.prx"}, {"libSceVideoOut.prx"});
|
|
}
|
|
|
|
void writeFile(const Fs::path& path, const std::vector<std::uint8_t>& bytes) {
|
|
const auto fail = [&](const char* phase) {
|
|
const auto error = errno;
|
|
const auto nativeError = _doserrno;
|
|
throw std::runtime_error("Cannot write diagnostic fixture: phase=" + std::string(phase) +
|
|
" path=" + path.string() + " errno=" + std::to_string(error) +
|
|
" native_error=" + std::to_string(nativeError) +
|
|
" bytes=" + std::to_string(bytes.size()));
|
|
};
|
|
errno = 0;
|
|
_set_doserrno(0);
|
|
std::ofstream stream(path, std::ios::binary);
|
|
if (!stream.is_open())
|
|
fail("open");
|
|
if (!stream.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size())))
|
|
fail("write");
|
|
stream.close();
|
|
if (!stream)
|
|
fail("close");
|
|
}
|
|
|
|
void createImage(const Fs::path& path, const std::vector<std::pair<std::string, std::string>>& imports, const std::string& exported, const std::string& forwarded = {}) {
|
|
PeSection section{".fixture", LoadRva, SectionRead | SectionExecute | 0x20u, std::vector<std::uint8_t>(4096)};
|
|
auto& bytes = section.Data;
|
|
const auto put = [&](const std::size_t offset, const std::uint64_t value, const std::size_t size = 4) {
|
|
for (std::size_t index = 0; index < size; ++index)
|
|
bytes.at(offset + index) = static_cast<std::uint8_t>(value >> (index * 8));
|
|
};
|
|
std::size_t cursor = 0x600;
|
|
const auto string = [&](const std::string& text) {
|
|
const auto start = cursor;
|
|
for (const auto character : text)
|
|
bytes.at(cursor++) = static_cast<std::uint8_t>(character);
|
|
bytes.at(cursor++) = 0;
|
|
return LoadRva + start;
|
|
};
|
|
std::array<PeDirectory, 16> directories{};
|
|
if (!exported.empty()) {
|
|
directories[0] = {LoadRva + 0x200, 0x100};
|
|
put(0x210, 7);
|
|
put(0x214, 1);
|
|
put(0x218, 1);
|
|
put(0x21c, LoadRva + 0x240);
|
|
put(0x220, LoadRva + 0x248);
|
|
put(0x224, LoadRva + 0x250);
|
|
put(0x240, LoadRva + (forwarded.empty() ? 0x800 : 0x260));
|
|
put(0x248, string(exported));
|
|
for (std::size_t index = 0; index < forwarded.size(); ++index)
|
|
bytes.at(0x260 + index) = static_cast<std::uint8_t>(forwarded[index]);
|
|
}
|
|
if (!imports.empty()) {
|
|
directories[1] = {LoadRva + 0x300, CheckedRva((imports.size() + 1) * 20)};
|
|
for (std::size_t index = 0; index < imports.size(); ++index) {
|
|
const auto& [library, symbol] = imports[index];
|
|
const auto thunk = 0x400 + index * 16;
|
|
put(0x300 + index * 20, LoadRva + thunk);
|
|
put(0x300 + index * 20 + 12, string(library));
|
|
put(0x300 + index * 20 + 16, LoadRva + thunk);
|
|
if (symbol.starts_with('#')) {
|
|
put(thunk, (std::uint64_t{1} << 63) | std::stoull(symbol.substr(1)), 8);
|
|
} else {
|
|
cursor += 2;
|
|
put(thunk, string(symbol) - 2, 8);
|
|
}
|
|
}
|
|
}
|
|
bytes[0x800] = 0xc3;
|
|
auto file = WindowsPeWriter().Write({section}, LoadRva + 0x800, directories);
|
|
Io::WriteU16(file, 0x96, 0x2022);
|
|
Io::WriteU32(file, 0xa8, 0);
|
|
writeFile(path, file);
|
|
}
|
|
|
|
void createRunner(const Fs::path& path, const Fs::path& root) {
|
|
auto imports = WindowsImportBuilder().Build(LoadRva);
|
|
PeSection data{".startup", AlignRva(LoadRva + imports.Section.Data.size()), SectionRead | SectionWrite | 0x40u, {}};
|
|
const auto libraryPath = data.Rva;
|
|
Io::AppendString(data.Data, root.string());
|
|
const auto executablePath = CheckedRva(data.Rva + data.Data.size());
|
|
Io::AppendString(data.Data, path.string());
|
|
Io::AlignBuffer(data.Data, 4);
|
|
const auto table = CheckedRva(data.Rva + data.Data.size());
|
|
data.Data.resize(data.Data.size() + 32 * 12);
|
|
const auto unwind = CheckedRva(data.Rva + data.Data.size());
|
|
data.Data.insert(data.Data.end(), {1, 4, 1, 0, 4, 0x42, 0, 0});
|
|
WindowsDependencyStubBuilder builder(data);
|
|
const auto codeRva = AlignRva(data.Rva + data.Data.size());
|
|
WindowsStubEmitter code(codeRva);
|
|
code.Emit({0x48, 0x83, 0xec, 0x28});
|
|
code.Rip({0x48, 0x8d, 0x0d}, libraryPath);
|
|
code.Rip({0x48, 0x8d, 0x15}, executablePath);
|
|
const auto call = code.Branch({0xe8});
|
|
code.Emit({0x48, 0x83, 0xc4, 0x28, 0xc3});
|
|
const auto wrapperEnd = code.GetRva();
|
|
const auto diagnostic = builder.Build(code, imports);
|
|
code.PatchBranch(call, diagnostic.EntryRva);
|
|
Io::WriteU32(data.Data, table - data.Rva, codeRva);
|
|
Io::WriteU32(data.Data, table - data.Rva + 4, wrapperEnd);
|
|
Io::WriteU32(data.Data, table - data.Rva + 8, unwind);
|
|
for (std::size_t index = 0; index < diagnostic.Functions.size(); ++index) {
|
|
for (std::size_t field = 0; field < 3; ++field)
|
|
Io::WriteU32(data.Data, table - data.Rva + (index + 1) * 12 + field * 4, diagnostic.Functions[index][field]);
|
|
}
|
|
std::array<PeDirectory, 16> directories{};
|
|
directories[1] = imports.Directory;
|
|
directories[3] = {table, CheckedRva((diagnostic.Functions.size() + 1) * 12)};
|
|
directories[12] = imports.AddressTable;
|
|
PeSection executable{".entry", codeRva, SectionRead | SectionExecute | 0x20u, code.TakeBytes()};
|
|
writeFile(path, WindowsPeWriter().Write({imports.Section, data, executable}, codeRva, directories));
|
|
}
|
|
|
|
void expectDiagnostic(const Fs::path& runner, const std::vector<std::string>& expected) {
|
|
SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
|
|
HANDLE input = nullptr;
|
|
HANDLE output = nullptr;
|
|
if (!CreatePipe(&input, &output, &security, 0) || !SetHandleInformation(input, HANDLE_FLAG_INHERIT, 0))
|
|
throw std::runtime_error("Cannot create diagnostic test pipe");
|
|
STARTUPINFOW startup{};
|
|
startup.cb = sizeof(startup);
|
|
startup.dwFlags = STARTF_USESTDHANDLES;
|
|
startup.hStdOutput = output;
|
|
startup.hStdError = output;
|
|
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
|
PROCESS_INFORMATION process{};
|
|
const auto filename = runner.wstring();
|
|
if (!CreateProcessW(filename.c_str(), nullptr, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, runner.parent_path().wstring().c_str(), &startup, &process))
|
|
throw std::runtime_error("Cannot start diagnostic test: " + std::to_string(GetLastError()));
|
|
CloseHandle(output);
|
|
if (WaitForSingleObject(process.hProcess, 20000) != WAIT_OBJECT_0) {
|
|
TerminateProcess(process.hProcess, 1);
|
|
throw std::runtime_error("Diagnostic test timed out");
|
|
}
|
|
DWORD status = 0;
|
|
if (!GetExitCodeProcess(process.hProcess, &status))
|
|
throw std::runtime_error("Cannot read diagnostic test exit code");
|
|
std::string message;
|
|
char buffer[4096];
|
|
DWORD size = 0;
|
|
while (ReadFile(input, buffer, sizeof(buffer), &size, nullptr) && size != 0)
|
|
message.append(buffer, size);
|
|
CloseHandle(input);
|
|
CloseHandle(process.hThread);
|
|
CloseHandle(process.hProcess);
|
|
if (status != 0xc0000135u)
|
|
throw std::runtime_error("Wrong diagnostic exit code: " + std::to_string(status) + "\n" + message);
|
|
for (const auto& text : expected) {
|
|
if (message.find(text) == std::string::npos)
|
|
throw std::runtime_error("Missing diagnostic field: " + text + "\nActual: " + message);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
try {
|
|
checkRuntimeDependencies();
|
|
std::vector<char> filename(32768);
|
|
const auto size = GetModuleFileNameA(nullptr, filename.data(), static_cast<DWORD>(filename.size()));
|
|
if (size == 0 || size >= filename.size())
|
|
throw std::runtime_error("Cannot locate diagnostic tests");
|
|
const auto directory = createFixtureDirectory(Fs::path(filename.data()).parent_path());
|
|
const auto root = directory / "diagnostic-root.prx";
|
|
const auto middle = directory / "diagnostic-middle.dll";
|
|
const auto leaf = directory / "diagnostic-leaf.dll";
|
|
const auto runner = directory / "diagnostic-runner.exe";
|
|
createRunner(runner, root);
|
|
createImage(root, {{middle.filename().string(), "Middle"}}, {});
|
|
createImage(middle, {{leaf.filename().string(), "Missing"}}, "Middle");
|
|
createImage(leaf, {}, "Present");
|
|
expectDiagnostic(runner, {"Importer: " + middle.string(), "Provider: " + leaf.string(), "Symbol: Missing", runner.string() + " -> " + root.string() + " -> " + middle.string() + " -> " + leaf.string()});
|
|
createImage(middle, {{leaf.filename().string(), "#8"}}, "Middle");
|
|
expectDiagnostic(runner, {"Symbol: #8"});
|
|
createImage(middle, {{leaf.filename().string(), "#7"}}, "Middle");
|
|
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
|
|
createImage(middle, {}, "Middle", "diagnostic-leaf.Missing");
|
|
expectDiagnostic(runner, {"Symbol: Missing", "Importer: " + middle.string()});
|
|
createImage(middle, {}, "Middle", "diagnostic-leaf.#7");
|
|
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
|
|
createImage(leaf, {}, "Present", "diagnostic-middle.Middle");
|
|
expectDiagnostic(runner, {"capacity exceeded"});
|
|
createImage(middle, {{leaf.filename().string(), "Present"}}, "Middle");
|
|
createImage(leaf, {{middle.filename().string(), "Middle"}}, "Present");
|
|
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
|
|
createImage(leaf, {{middle.filename().string(), "MissingInCycle"}}, "Present");
|
|
expectDiagnostic(runner, {"Symbol: MissingInCycle", "Importer: " + leaf.string()});
|
|
createImage(root, {{"diagnostic-absent.dll", "Missing"}}, {});
|
|
expectDiagnostic(runner, {"dependency module not found", "Importer: " + root.string()});
|
|
createImage(root, {{"api-ms-win-core-relinkerdiagnostics-l1-1-0.dll", "Missing"}}, {});
|
|
expectDiagnostic(runner, {"dependency module not found: api-ms-win-core-relinkerdiagnostics-l1-1-0.dll", "Importer: " + root.string(), runner.string() + " -> " + root.string() + " -> api-ms-win-core-relinkerdiagnostics-l1-1-0.dll"});
|
|
createImage(root, {{"KERNEL32.dll", "CreateRemoteThreadEx"}}, {});
|
|
expectDiagnostic(runner, {"Dependency tables contain no missing imports"});
|
|
createImage(root, {{"KERNEL32.dll", "RelinkerDiagnosticsMissingExport"}}, {});
|
|
expectDiagnostic(runner, {"Symbol: RelinkerDiagnosticsMissingExport"});
|
|
{
|
|
std::fstream corrupt(root, std::ios::binary | std::ios::in | std::ios::out);
|
|
const std::array<char, 4> invalidRva{static_cast<char>(0xf0), static_cast<char>(0xff), static_cast<char>(0xff), static_cast<char>(0xff)};
|
|
corrupt.seekp(LoadRva + 0x300);
|
|
if (!corrupt.write(invalidRva.data(), invalidRva.size()))
|
|
throw std::runtime_error("Cannot corrupt diagnostic fixture");
|
|
}
|
|
expectDiagnostic(runner, {"invalid or unsupported PE dependency metadata"});
|
|
writeFile(root, {0, 1, 2});
|
|
expectDiagnostic(runner, {"dependency module is not a Windows PE image: " + root.string()});
|
|
std::vector<std::uint8_t> elf{0x7f, 'E', 'L', 'F', 2, 1, 1};
|
|
elf.resize(64);
|
|
writeFile(root, elf);
|
|
expectDiagnostic(runner, {"dependency module is not a Windows PE image: " + root.string(), "Importer: " + runner.string(), "Chain: " + runner.string() + " -> " + root.string() + "\n"});
|
|
Fs::remove(root);
|
|
expectDiagnostic(runner, {"dependency module not found: " + root.string(), "Importer: " + runner.string(), "Chain: " + runner.string() + " -> " + root.string() + "\n"});
|
|
Fs::create_directory(root);
|
|
expectDiagnostic(runner, {"Windows API failed"});
|
|
Fs::remove(root);
|
|
createImage(root, {{middle.filename().string(), "Middle"}}, {});
|
|
writeFile(middle, elf);
|
|
expectDiagnostic(runner, {"dependency module is not a Windows PE image: " + middle.string(), "Importer: " + root.string(), "Chain: " + runner.string() + " -> " + root.string() + " -> " + middle.string() + "\n"});
|
|
Fs::remove_all(directory);
|
|
std::cout << "Windows dependency machine-code tests passed\n";
|
|
} catch (const std::exception& error) {
|
|
std::cerr << error.what() << '\n';
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|