Merge pull request #4547 from codingreapermaps/feat/relinker-trace-imports
feat(relinker): add --trace-imports for Windows executables
This commit is contained in:
commit
9dd3c2d1a6
|
|
@ -441,6 +441,8 @@ if(BUILD_TESTING)
|
|||
add_test(NAME windows_runpath COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_runpath.py $<TARGET_FILE:relinker>)
|
||||
set_tests_properties(windows_runpath PROPERTIES TIMEOUT 30)
|
||||
add_test(NAME windows_trace_imports COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_trace_imports.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME windows_tls_thread_pointer COMMAND ${Python3_EXECUTABLE}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ struct Args {
|
|||
bool windowsDiagnostics = false;
|
||||
bool windowsGui = false;
|
||||
std::optional<std::uint64_t> windowsFixedBase;
|
||||
bool traceImports = false;
|
||||
std::uint32_t unusedFilterLevel = 0;
|
||||
std::string inputPath;
|
||||
std::string outputPath;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
namespace Cli {
|
||||
|
||||
const char* Usage() {
|
||||
return "Usage: relinker [--help] [--windows | --macos] [--windows-diagnostics] [--windows-gui] [--windows-fixed-base <address>] [--skip-syscall-check | --trap-sdk-syscall-stubs] [--skip-sce-module | --sce-module-path <path>] [--exclude-sce-module <file>]... [--module-dir <dir>]... [--executable <path>]... [--to-intel] [--to-rosetta] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
|
||||
return "Usage: relinker [--help] [--windows | --macos] [--windows-diagnostics] [--windows-gui] [--trace-imports] [--windows-fixed-base <address>] [--skip-syscall-check | --trap-sdk-syscall-stubs] [--skip-sce-module | --sce-module-path <path>] [--exclude-sce-module <file>]... [--module-dir <dir>]... [--executable <path>]... [--to-intel] [--to-rosetta] [unused-filter=0|1|2] [--registry] [--rpath <path>] [--lazy-binding] [--autorun] <input.elf> <output.elf>\n"
|
||||
"Example: relinker input.elf output.elf";
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +86,8 @@ Args ParseArgs(int argc, char* argv[]) {
|
|||
args.windowsDiagnostics = true;
|
||||
} else if (arg == "--windows-gui") {
|
||||
args.windowsGui = true;
|
||||
} else if (arg == "--trace-imports") {
|
||||
args.traceImports = true;
|
||||
} else if (arg.rfind("--", 0) == 0 || arg == "unused-filter") {
|
||||
throw std::runtime_error("unknown option: " + arg);
|
||||
} else if (arg.empty()) {
|
||||
|
|
@ -124,6 +126,10 @@ Args ParseArgs(int argc, char* argv[]) {
|
|||
|
||||
if (args.windowsFixedBase && !args.toWindows)
|
||||
throw std::runtime_error("--windows-fixed-base requires --windows");
|
||||
if (args.traceImports && !args.toWindows)
|
||||
throw std::runtime_error("--trace-imports requires --windows");
|
||||
if (args.traceImports && args.lazyBinding)
|
||||
throw std::runtime_error("--trace-imports conflicts with --lazy-binding");
|
||||
|
||||
if (!args.showHelp && (args.inputPath.empty() || args.outputPath.empty()))
|
||||
throw std::runtime_error(Usage());
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ struct GuestImport {
|
|||
std::uint64_t Addend;
|
||||
std::uint32_t RelocationType = 1;
|
||||
std::string Library;
|
||||
bool Function = false;
|
||||
};
|
||||
|
||||
struct GuestRuntime {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ struct NidReference {
|
|||
VirtualAddress RelocationAddress;
|
||||
std::int64_t Addend;
|
||||
bool Weak = false;
|
||||
bool Function = false;
|
||||
};
|
||||
|
||||
struct CallSiteInfo {
|
||||
|
|
@ -98,6 +99,7 @@ struct SysVDynamicSection {
|
|||
std::vector<std::uint8_t> RelaPltData;
|
||||
std::vector<GuestRuntime> GuestModules;
|
||||
std::map<VirtualAddress, std::string> ImportModules;
|
||||
std::set<VirtualAddress> FunctionImports;
|
||||
std::set<std::string> UnmatchedImportModules;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ namespace Elfpatcher::Windows {
|
|||
|
||||
class WindowsPePatcher : public IElfPatcher {
|
||||
public:
|
||||
explicit WindowsPePatcher(bool windowsGui = false, std::optional<std::vector<std::uint8_t>> icon = std::nullopt, std::filesystem::path iconName = "icon0.png", std::function<void(std::uint64_t, std::uint64_t)> progress = {}, std::optional<std::uint64_t> fixedBase = std::nullopt);
|
||||
explicit WindowsPePatcher(bool windowsGui = false, std::optional<std::vector<std::uint8_t>> icon = std::nullopt, std::filesystem::path iconName = "icon0.png", std::function<void(std::uint64_t, std::uint64_t)> progress = {}, std::optional<std::uint64_t> fixedBase = std::nullopt, bool traceImports = false);
|
||||
const std::string& GetDiagnostics() const;
|
||||
|
||||
std::vector<std::uint8_t> Patch(const std::vector<std::uint8_t>& sourceElf, const std::vector<Domain::ProgramHeader>& originalHeaders, const Domain::SysVDynamicSection& dynamicSection, std::uint64_t originalPltGotVaddr, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector<Codegen::TrampolineSite>& trampolines) override;
|
||||
|
|
@ -23,6 +23,7 @@ private:
|
|||
std::string diagnostics;
|
||||
std::filesystem::path iconName;
|
||||
std::optional<std::uint64_t> fixedBase;
|
||||
bool traceImports;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct WindowsEntryStub {
|
|||
|
||||
class WindowsEntryStubBuilder {
|
||||
public:
|
||||
WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules = {}, const std::set<std::string>& unmatchedImportModules = {}) const;
|
||||
WindowsEntryStub Build(std::uint32_t dataRva, std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, bool lazyBinding, bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules = {}, const std::set<std::string>& unmatchedImportModules = {}, bool traceImports = false) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ struct PeImport {
|
|||
std::uint32_t RelocationType = 1;
|
||||
std::string Library;
|
||||
bool Weak = false;
|
||||
bool Function = false;
|
||||
};
|
||||
|
||||
struct PeRelocations {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <io/BufferUtils.hpp>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <filesystem>
|
||||
|
|
@ -66,7 +67,7 @@ std::u16string toUtf16(const std::string& text) {
|
|||
|
||||
}
|
||||
|
||||
WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules, const std::set<std::string>& unmatchedImportModules) const {
|
||||
WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, const std::uint32_t entryRva, const WindowsImports& nativeImports, const std::vector<std::string>& libraries, const std::vector<PeImport>& imports, const std::string& runPath, const bool lazyBinding, const bool dependencyDiagnostics, const std::vector<Domain::GuestRuntime>& guestModules, const std::set<std::string>& unmatchedImportModules, const bool traceImports) const {
|
||||
if (!imports.empty() && libraries.empty())
|
||||
throw Domain::RelinkerException("ELF imports have no DT_NEEDED libraries");
|
||||
const auto path = normalizeRunPath(runPath);
|
||||
|
|
@ -187,6 +188,24 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
if (unmatched.contains(stem(libraries[index]))) imported[index] = true;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> traceProvider;
|
||||
std::map<std::string, std::uint32_t> traceLibraries;
|
||||
std::uint32_t traceSymbol = 0;
|
||||
std::uint32_t traceFunction = 0;
|
||||
if (traceImports) {
|
||||
for (std::size_t index = guestModules.size(); index < libraries.size(); ++index) {
|
||||
if (libraries[index] == "libc.prx") traceProvider = index;
|
||||
}
|
||||
if (!traceProvider.has_value())
|
||||
throw Domain::RelinkerException("--trace-imports requires libc.prx among the libraries");
|
||||
imported[*traceProvider] = true;
|
||||
traceSymbol = addString("TraceImport_nid_no_patch");
|
||||
traceFunction = reserve(8);
|
||||
for (const auto& import : imports) {
|
||||
if (!traceLibraries.contains(import.Library)) traceLibraries.emplace(import.Library, addString(import.Library));
|
||||
}
|
||||
}
|
||||
|
||||
const auto lastError = reserve(4);
|
||||
const auto errorDigits = reserve(11);
|
||||
const auto errorMessage = reserve(ErrorMessageCapacity);
|
||||
|
|
@ -215,6 +234,8 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
errors.push_back("FAIL: cannot prepare command-line arguments\n");
|
||||
const auto directoryError = errors.size();
|
||||
errors.push_back("FAIL: cannot make the executable directory the current directory\n");
|
||||
const auto traceError = errors.size();
|
||||
errors.push_back("FAIL: libc.prx does not export TraceImport_nid_no_patch\n");
|
||||
std::vector<std::uint32_t> errorRvas;
|
||||
for (const auto& error : errors)
|
||||
errorRvas.push_back(addString(error));
|
||||
|
|
@ -485,6 +506,21 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
}
|
||||
}
|
||||
|
||||
if (traceImports) {
|
||||
code.Rip({0x48, 0x8b, 0x0d}, CheckedRva(handles + *traceProvider * 8));
|
||||
code.Rip({0x48, 0x8d, 0x15}, traceSymbol);
|
||||
call("GetProcAddress");
|
||||
requireNonzero(traceError, 0xc0000139u);
|
||||
code.Rip({0x48, 0x89, 0x05}, traceFunction);
|
||||
}
|
||||
const auto trace = [&](const PeImport& import, const std::uint32_t symbolName) {
|
||||
if (!traceImports || !import.Function || import.Addend != 0) return;
|
||||
code.Emit({0x48, 0x89, 0xc1});
|
||||
code.Rip({0x48, 0x8d, 0x15}, symbolName);
|
||||
code.Rip({0x4c, 0x8d, 0x05}, traceLibraries.at(import.Library));
|
||||
code.Rip({0xff, 0x15}, traceFunction);
|
||||
};
|
||||
|
||||
std::vector<std::size_t> unresolvedBranches;
|
||||
std::vector<std::size_t> lazyUnresolvedImports;
|
||||
std::vector<std::size_t> tlsResolverAddresses;
|
||||
|
|
@ -546,6 +582,7 @@ WindowsEntryStub WindowsEntryStubBuilder::Build(const std::uint32_t dataRva, con
|
|||
code.U64(imports[index].Addend);
|
||||
code.Emit({0x48, 0x01, 0xd0});
|
||||
}
|
||||
trace(imports[index], symbolNames[index]);
|
||||
guestStartup.WriteImport(code, imports[index], handles);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
|
|||
} else {
|
||||
if (symbol.Name.empty()) throw Domain::RelinkerException("Empty guest import", target);
|
||||
image.WritePointer(target, 0);
|
||||
runtime.Imports.push_back({symbol.Name, rva, addend, 1, symbol.Library});
|
||||
runtime.Imports.push_back({symbol.Name, rva, addend, 1, symbol.Library, type == 7 || (type == 6 && (symbol.Info & 15) == 2)});
|
||||
}
|
||||
} else throw Domain::RelinkerException("Unsupported Windows guest relocation " + std::to_string(type), target);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ void writeGotStub(std::vector<PeSection>& sections, const std::uint32_t targetRv
|
|||
|
||||
}
|
||||
|
||||
WindowsPePatcher::WindowsPePatcher(const bool windowsGui, std::optional<std::vector<std::uint8_t>> icon, std::filesystem::path iconName, std::function<void(std::uint64_t, std::uint64_t)> progress, const std::optional<std::uint64_t> fixedBase) : progress(std::move(progress)), windowsGui(windowsGui), icon(std::move(icon)), iconName(std::move(iconName)), fixedBase(fixedBase) {
|
||||
WindowsPePatcher::WindowsPePatcher(const bool windowsGui, std::optional<std::vector<std::uint8_t>> icon, std::filesystem::path iconName, std::function<void(std::uint64_t, std::uint64_t)> progress, const std::optional<std::uint64_t> fixedBase, const bool traceImports) : progress(std::move(progress)), windowsGui(windowsGui), icon(std::move(icon)), iconName(std::move(iconName)), fixedBase(fixedBase), traceImports(traceImports) {
|
||||
if (fixedBase && (*fixedBase < 0x20000 || *fixedBase > 0x7fffffff0000 || (*fixedBase & 0xffff) != 0))
|
||||
throw Domain::RelinkerException("Invalid fixed Windows guest image base");
|
||||
}
|
||||
|
|
@ -81,13 +81,16 @@ std::vector<std::uint8_t> WindowsPePatcher::Patch(const std::vector<std::uint8_t
|
|||
for (std::size_t index = 0; index < dynamicSection.GuestModules.size(); ++index) {
|
||||
const auto& module = dynamicSection.GuestModules[index];
|
||||
guestPaths.push_back(module.Path);
|
||||
for (const auto& import : module.Imports) relocations.Imports.push_back({import.Name, import.TargetRva, import.Addend, static_cast<std::int32_t>(index), import.RelocationType, import.Library});
|
||||
for (const auto& import : module.Imports) {
|
||||
relocations.Imports.push_back({import.Name, import.TargetRva, import.Addend, static_cast<std::int32_t>(index), import.RelocationType, import.Library});
|
||||
relocations.Imports.back().Function = import.Function;
|
||||
}
|
||||
}
|
||||
libraries.insert(libraries.begin(), guestPaths.begin(), guestPaths.end());
|
||||
if (dependencyDiagnostics)
|
||||
for (const auto& import : relocations.Imports) diagnostics += import.Name + '\n';
|
||||
report(8500);
|
||||
auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics, dynamicSection.GuestModules, dynamicSection.UnmatchedImportModules);
|
||||
auto entry = WindowsEntryStubBuilder().Build(nextRva, image.GetEntryRva(), nativeImports, libraries, relocations.Imports, runPath, lazyBinding, dependencyDiagnostics, dynamicSection.GuestModules, dynamicSection.UnmatchedImportModules, traceImports);
|
||||
report(9000);
|
||||
directories[3] = entry.ExceptionDirectory;
|
||||
const auto entryRva = entry.Code.Rva;
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ PeRelocations WindowsRelocationBuilder::Apply(WindowsLoadImage& image, const Dom
|
|||
image.WritePointer(target, 0);
|
||||
result.Imports.push_back({name, rva, addend});
|
||||
result.Imports.back().Weak = (dynamicSection.DynSymData[symbolOffset + 4] >> 4) == STB_WEAK;
|
||||
result.Imports.back().Function = type == 7 || (type == 6 && dynamicSection.FunctionImports.contains(target));
|
||||
if (const auto module = dynamicSection.ImportModules.find(target); module != dynamicSection.ImportModules.end())
|
||||
result.Imports.back().Library = module->second;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ ConvertedExecutable Convert(const Cli::Args& args, const std::string& inputPath,
|
|||
options.LazyBinding = args.lazyBinding;
|
||||
options.RunPath = args.runPath;
|
||||
options.WindowsGui = args.windowsGui;
|
||||
options.TraceImports = args.traceImports;
|
||||
options.WindowsFixedBase = mainExecutable ? args.windowsFixedBase : std::nullopt;
|
||||
options.WindowsDiagnostics = args.windowsDiagnostics && mainExecutable;
|
||||
options.ExcludedModules = args.excludedSceModules;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ struct ConversionOptions {
|
|||
bool TrapSdkSyscallStubs = false;
|
||||
bool LazyBinding = false;
|
||||
bool WindowsGui = false;
|
||||
bool TraceImports = false;
|
||||
std::optional<std::uint64_t> WindowsFixedBase;
|
||||
bool WindowsDiagnostics = false;
|
||||
std::string RunPath = "$ORIGIN/libs";
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection(
|
|||
|
||||
for (const auto& ref : nidReferences) {
|
||||
if (!ref.Library.empty()) result.ImportModules.emplace(ref.RelocationAddress, ref.Library);
|
||||
if (ref.Function) result.FunctionImports.insert(ref.RelocationAddress);
|
||||
std::uint32_t relType = ref.RelocationTypeValue;
|
||||
if (relType == 0) relType = R_X86_64_JUMP_SLOT;
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti
|
|||
if (macos) {
|
||||
patcher = std::make_shared<Elfpatcher::MacOs::MacOsMachOPatcher>();
|
||||
} else if (windows) {
|
||||
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>(options.WindowsGui, std::move(input.Icon), input.InputPath.parent_path() / "sce_sys" / "icon0.png", [&](auto completed, auto total) { inputProgress.Report({Domain::ProgressStage::Image, {}, completed, total}); }, options.WindowsFixedBase);
|
||||
patcher = std::make_shared<Elfpatcher::Windows::WindowsPePatcher>(options.WindowsGui, std::move(input.Icon), input.InputPath.parent_path() / "sce_sys" / "icon0.png", [&](auto completed, auto total) { inputProgress.Report({Domain::ProgressStage::Image, {}, completed, total}); }, options.WindowsFixedBase, options.TraceImports);
|
||||
} else {
|
||||
patcher = std::make_shared<Elfpatcher::Linux::LinuxElfPatcher>(
|
||||
std::make_shared<Elfpatcher::EntryStubBuilder>(),
|
||||
|
|
|
|||
|
|
@ -264,7 +264,9 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
|| std::any_of(raw.begin() + static_cast<std::ptrdiff_t>(symOff), raw.begin() + static_cast<std::ptrdiff_t>(symOff + symEntSize),
|
||||
[](const std::uint8_t byte) { return byte != 0; })))
|
||||
throw RelinkerException("Unnamed import symbol is not a null symbol entry", symOff);
|
||||
nidRefs.push_back({name, Domain::ImportModule(name, importModules, neededLibraries), relType, pos, rOffset, rAddend, (raw[symOff + 4] >> 4) == STB_WEAK});
|
||||
const bool weak = (raw[symOff + 4] >> 4) == STB_WEAK;
|
||||
const bool function = (raw[symOff + 4] & 15) == STT_FUNC;
|
||||
nidRefs.push_back({name, Domain::ImportModule(name, importModules, neededLibraries), relType, pos, rOffset, rAddend, weak, function});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
"""--trace-imports passes each resolved function import through libc.prx's TraceImport_nid_no_patch."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_windows_import_modules import dynamic, executable, provider
|
||||
|
||||
|
||||
HOOK = (b'\x80\x3a\x73\x75\x0e'
|
||||
b'\x41\x80\x38\x61\x75\x08'
|
||||
b'\x48\x8d\x05\x05\x00\x00\x00\xc3'
|
||||
b'\x48\x89\xc8\xc3'
|
||||
b'\xb8\x4d\x00\x00\x00\xc3')
|
||||
|
||||
|
||||
def named_provider(symbol, soname, code):
|
||||
image = provider(0, code=code)
|
||||
strings = b'\0' + symbol.encode() + b'#A#B\0' + soname.encode() + b'\0'
|
||||
image[0x800:0x800 + len(strings)] = strings
|
||||
tags = [(5, 0x2200), (10, len(strings)), (6, 0x2280), (11, 24),
|
||||
(4, 0x2240), (7, 0x2300), (8, 0), (9, 24), (14, len(symbol) + 6)]
|
||||
dynamic(image, 0x600, 176, tags)
|
||||
return image
|
||||
|
||||
|
||||
def relink(relinker, source, output, *options):
|
||||
return subprocess.run([str(relinker), '--windows', *options, str(source), str(output)],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
with tempfile.TemporaryDirectory(prefix='anyps5-trace-imports-') as directory:
|
||||
work = Path(directory)
|
||||
build = work / 'providers'
|
||||
(build / 'prx').mkdir(parents=True)
|
||||
(build / 'prx' / 'a.prx').write_bytes(provider(11))
|
||||
(build / 'prx' / 'hook.prx').write_bytes(named_provider('TraceImport_nid_no_patch', 'hook.prx', HOOK))
|
||||
(build / 'prx' / 'plain.prx').write_bytes(named_provider('Unrelated', 'plain.prx', b'\x31\xc0\xc3'))
|
||||
source = build / 'input.elf'
|
||||
source.write_bytes(executable('a.prx'))
|
||||
result = relink(relinker, source, build / 'output.exe')
|
||||
assert result.returncode == 0, (result.stdout, result.stderr)
|
||||
converted = {name: (build / 'app0' / 'prx' / (name + '.prx.guest.prx')).read_bytes() for name in ('a', 'hook', 'plain')}
|
||||
|
||||
for name, options, libc, symbol_info, expected, message in (
|
||||
('untraced', (), 'hook', 0x12, 11, None),
|
||||
('traced', ('--trace-imports',), 'hook', 0x12, 77, None),
|
||||
('data', ('--trace-imports',), 'hook', 0x11, 11, None),
|
||||
('weak', ('--trace-imports',), 'hook', 0x22, 11, None),
|
||||
('unsupported', ('--trace-imports',), 'plain', 0x12, None, 'libc.prx does not export TraceImport_nid_no_patch')):
|
||||
case = work / name
|
||||
(case / 'libs').mkdir(parents=True)
|
||||
(case / 'prx').mkdir()
|
||||
(case / 'libs' / 'a.prx').write_bytes(converted['a'])
|
||||
(case / 'libs' / 'libc.prx').write_bytes(converted[libc])
|
||||
source = case / 'input.elf'
|
||||
image = executable('a.prx', extra_dependencies=('libc.prx',))
|
||||
image[0x4898 + 4] = symbol_info
|
||||
source.write_bytes(image)
|
||||
output = case / 'output.exe'
|
||||
result = relink(relinker, source, output, *options)
|
||||
assert result.returncode == 0, (name, result.stdout, result.stderr)
|
||||
if os.name != 'nt':
|
||||
continue
|
||||
run = subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
|
||||
if expected is None:
|
||||
assert run.returncode != 0 and message in run.stderr, (name, run.returncode, run.stdout, run.stderr)
|
||||
else:
|
||||
assert run.returncode == expected, (name, run.returncode, run.stdout, run.stderr)
|
||||
|
||||
case = work / 'without-libc'
|
||||
(case / 'prx').mkdir(parents=True)
|
||||
source = case / 'input.elf'
|
||||
source.write_bytes(executable('a.prx'))
|
||||
result = relink(relinker, source, case / 'output.exe', '--trace-imports')
|
||||
assert result.returncode == 2 and '--trace-imports requires libc.prx' in result.stderr, (result.returncode, result.stderr)
|
||||
assert not (case / 'output.exe').exists()
|
||||
|
||||
result = subprocess.run([str(relinker), '--trace-imports', str(source), str(case / 'output.elf')],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
assert result.returncode != 0 and '--trace-imports requires --windows' in result.stderr, (result.returncode, result.stderr)
|
||||
result = relink(relinker, source, case / 'output.exe', '--trace-imports', '--lazy-binding')
|
||||
assert result.returncode != 0 and '--trace-imports conflicts with --lazy-binding' in result.stderr, (result.returncode, result.stderr)
|
||||
|
||||
print('Windows import tracing tests passed')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -65,3 +65,4 @@
|
|||
- [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert
|
||||
- [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.
|
||||
- [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libSceAmpr) follows Dinkumware's `thread0.cpp` as shipped with [Visual C++ 2013](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/thread0.cpp) and in [microsoft/STL](https://github.com/microsoft/STL/blob/da52dc0fcadbd83690cfd1ed32408d2847439493/stl/src/thread0.cpp), without console verification. `_Cpp_error` codes 0 to 6 throw a guest `std::system_error` whose `error_code` holds the FreeBSD errno of the `errc` value (EBUSY 16, EINVAL 22, ESRCH 3, ENOMEM 12, EPERM 1, EDEADLK 11, EAGAIN 35) and the generic category, and whose `what()` is the table message, `": "` and the generic category's message for that errno, selected by the first matching errno entry in VC12's [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/syserror.cpp); another code throws `std::out_of_range` on the host instead of reading past the table. The guest `system_error` is taken to be [VC12's](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/system_error): the 16-byte `runtime_error` followed by `error_code {int, const error_category*}`, 32 bytes, so its copy constructor and assignment copy the code too. The generic category is a static libc object with VC12's `error_category` virtual order (destructors, `name`, `message`, `default_error_condition`, the two `equivalent`), not the console's `_Error_objects<int>::_Generic_object`, and the new `generic_category()` export returns that object; a title's inline category object in a bundled libc can still have a different identity. Its `message` and direct message export construct the existing [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) model: a reserved eight-byte word, 16-byte inline buffer, length and capacity, with guest `operator new` storage above 15 bytes. This 40-byte string layout and virtual return ABI have not been verified against a console library. The deleting destructor still throws. `_Syserror_map` covers the 72 distinct Dinkumware `errc` values present in FreeBSD 11.4 [errno.h](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h), returning null for other values; category messages use `unknown error` for those. First-match aliases are `EAGAIN`/`EWOULDBLOCK` -> `operation would block` and `ENOTSUP`/`EOPNOTSUPP` -> `not supported`. STREAMS-only errno assignments absent from that header (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are not inferred
|
||||
- [Call tracing](../../../core/libs/prx/libc/src/specifics/windows/CallTrace.cpp) (`--trace-imports`, `APS5_TRACE_CALLS`) exists on Windows only and records the entry of a call, not its arguments or its result. One process-wide ring keeps the last 65536 calls, so a thread that calls often pushes the others out. Calls are also counted per traced target since start-up; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; arguments in wider vector registers pass only as long as the recording code stays free of AVX instructions, which is not tested
|
||||
|
|
|
|||
|
|
@ -13,3 +13,4 @@
|
|||
- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test.
|
||||
- `--to-rosetta` lowers what Rosetta lacks on top of `--to-intel` ([matcher](../../../core/relinker/codegen/src/x86/Amd64OnlyInstructionMatcher.cpp)). Rosetta runs AVX2, FMA, BMI1/2, F16C and MOVBE but reports none of them through CPUID, so code that checks CPUID takes its baseline path. RDSEED becomes RDRAND and CLWB a NOP. RDPID returns processor 0, because Rosetta's TSC_AUX is the constant 0xAAAAAAAB; the same value reaches RDTSCP's ECX, which is not converted. ADCX/ADOX fault under Rosetta and are not converted: compilers do not emit them, and code that checks CPUID sees no ADX. The conversions are covered by the converter tests; that the converted code runs under Rosetta was checked on an M5 Max only, outside this repository's CI.
|
||||
- `--to-intel` replaces the approximate legacy SSE and VEX.128 register forms of RSQRTPS/RCPPS and VRSQRTPS/VRCPPS with [correctly rounded](../../../core/relinker/codegen/src/x86/ReciprocalLowering.cpp) 1/sqrt(x) and 1/x, not with the AMD approximation: its tables are not public. Intel's approximation of 1/sqrt(1.0) is 0x3F7FF000, and two Newton-Raphson steps on it settle on 0.99999994 instead of 1.0; a renormalised identity quaternion then turns into a NaN axis in PPSA21564, which the console does not show. Refined results now reach the exact value; unrefined ones can still differ from the console in the low bits. Each site costs an out-of-line stub (two jumps, a spill below the red zone, SQRTPS/DIVPS). Legacy SSE forms preserve the destination's upper YMM bits; VEX forms clear them. The 256-bit, scalar and memory forms keep the native instruction, as does a site shorter than 5 bytes with no movable instruction after it for the jump (5 sites in PPSA21564, counted before a following RIP-relative instruction could move into the stub; not recounted).
|
||||
- `--trace-imports` ([entry stub](../../../core/relinker/elfpatcher/src/windows/WindowsEntryStubBuilder.cpp)) exists for Windows output only. Only imports bound through a PLT slot, or through a GOT slot whose symbol is a function, are traced; a function reached through any other relocation, or looked up at run time with `sceKernelDlsym`, is not, and neither are weak imports or the imports of modules loaded at run time
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ All switches are disabled by default. `unused-filter` defaults to `0`; `--rpath`
|
|||
| `--windows-diagnostics` | Include startup dependency diagnostics. Requires `--windows`. |
|
||||
| `--windows-gui` | Select the Windows GUI subsystem instead of the console subsystem. Requires `--windows`. |
|
||||
| `--windows-fixed-base <address>` | Map the lowest ELF load page at this fixed Windows address, given in decimal or hexadecimal (`0x`). Requires `--windows`, a 64 KiB-aligned address from `0x20000` through `0x7fffffff0000`, and a complete image fitting in the user address space. The main executable has no ASLR or base relocations; an occupied address fails to load. Bundled modules keep their normal placement. |
|
||||
| `--trace-imports` | Pass each function import through the libc.prx call tracer, see [tracing system library calls](#tracing-system-library-calls). Requires `--windows`; conflicts with `--lazy-binding`. |
|
||||
| `--to-intel` | Convert supported AMD-only instructions in the executable and bundled modules. Unsupported instructions or unreachable conversion stubs cause an error. |
|
||||
| `--to-rosetta` | Convert as `--to-intel` does and also lower what Rosetta lacks: RDSEED becomes RDRAND, CLWB a NOP, and RDPID returns processor 0. Implies `--to-intel`. Independent of `--macos`: `--macos` alone leaves these instructions as they are. Conflicts with `--windows`. |
|
||||
| `unused-filter=0` | Keep all imported NID references. |
|
||||
|
|
@ -157,6 +158,19 @@ Do not disable Dozen if it is the only Vulkan driver available; the application
|
|||
|
||||
GPU results in storage images and render targets stay on the GPU until something reads their memory. A title that frees such an image after waiting for its GPU work and reuses the memory can have its new data overwritten when the results are stored later ([TechnicalDebt](../dev/TechnicalDebt.md)). `APS5_STORE_AT_FLIP=1` stores every pending result when a display buffer is flipped, at the cost of one write-back per pending image and frame. Any other value stops the title at its first flip.
|
||||
|
||||
### Tracing system library calls
|
||||
|
||||
Windows only. An executable converted with `--trace-imports` records the calls it makes to the libraries named in `APS5_TRACE_CALLS`, a comma-separated list such as `libkernel,libSceSaveData`, or `all`. Without the variable nothing is recorded. The crash and abort reports then end with the last 64 calls: sequence number, thread, library, import name as the executable imports it (a NID) and the caller's address. They are followed by the 32 most called imports since start-up, with their number of calls.
|
||||
|
||||
```
|
||||
last traced system library calls (APS5_TRACE_CALLS):
|
||||
#1 thread 9384 libkernel.prx k+AXqu2-eBc from 0x0000000140010006 app.exe+0x10006
|
||||
most called traced imports (1 calls in total):
|
||||
1 libkernel.prx k+AXqu2-eBc
|
||||
```
|
||||
|
||||
Limits are listed in the [libc](../dev/debt/libc.md) and [relinker](../dev/debt/relinker.md) technical debt.
|
||||
|
||||
## Exit codes
|
||||
|
||||
`0`: conversion succeeded or help was displayed. `1`: invalid arguments. `2`: conversion failed; the error is printed to stderr. With `--autorun`, successful conversion returns the launched application's exit code.
|
||||
|
|
|
|||
Loading…
Reference in New Issue