Merge pull request #5199 from Zenodeon/conversion-diagnostics
fix(relinker): identify failed inputs and invalid dynamic values
This commit is contained in:
commit
0defec4386
|
|
@ -390,6 +390,9 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(windows_fixed_base PROPERTIES TIMEOUT 20)
|
||||
add_test(NAME elf_offsets COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_elf_offsets.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME conversion_diagnostics COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_conversion_diagnostics.py $<TARGET_FILE:relinker>)
|
||||
set_tests_properties(conversion_diagnostics PROPERTIES TIMEOUT 20)
|
||||
add_test(NAME dynamic_segment COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_dynamic_segment.py $<TARGET_FILE:relinker>)
|
||||
add_test(NAME rela_size COMMAND ${Python3_EXECUTABLE}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <elfpatcher/windows/WindowsLoadImage.hpp>
|
||||
#include <elfpatcher/windows/WindowsPeFormat.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <io/NativePath.hpp>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
|
|
@ -114,7 +115,7 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteMacOs(const Relinker::GuestIma
|
|||
try {
|
||||
return WriteMacOsImage(input);
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
error.InputPath = guest.SourcePath.string();
|
||||
if (error.InputPath.empty()) error.InputPath = Io::Utf8Path(guest.SourcePath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <elfpatcher/windows/WindowsRelocationBuilder.hpp>
|
||||
#include <elfpatcher/windows/WindowsTrampolineBuilder.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <io/NativePath.hpp>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <iterator>
|
||||
|
|
@ -84,7 +85,7 @@ std::vector<std::uint8_t> GuestModuleWriter::WriteWindows(const Relinker::GuestI
|
|||
try {
|
||||
directories[9] = WindowsTlsBuilder().Build(guest.Bytes, guest.Headers, image, sections, relocations, nextRva, &tlsIndex);
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
error.InputPath = guest.SourcePath.string();
|
||||
if (error.InputPath.empty()) error.InputPath = Io::Utf8Path(guest.SourcePath);
|
||||
throw;
|
||||
}
|
||||
WindowsTrampolineBuilder().Build(guest.Trampolines, image, sections, nextRva);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <relinker/guest/GuestImage.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <io/NativePath.hpp>
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <limits>
|
||||
|
|
@ -87,8 +88,8 @@ std::vector<std::string> GuestImageReader::ReadModuleNames(const std::vector<std
|
|||
return names;
|
||||
}
|
||||
|
||||
GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes, const bool windows) const {
|
||||
const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(path.string() + ": " + message); };
|
||||
GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector<std::uint8_t> bytes, const bool windows) const try {
|
||||
const auto fail = [&](const std::string& message) { throw Domain::RelinkerException(Io::Utf8Path(path) + ": " + message); };
|
||||
const auto range = [&](std::uint64_t offset, std::uint64_t size) {
|
||||
if (offset > bytes.size() || size > bytes.size() - offset) {
|
||||
std::ostringstream message;
|
||||
|
|
@ -361,6 +362,9 @@ GuestImage GuestImageReader::Read(const std::filesystem::path& path, std::vector
|
|||
if (tags.contains(33) && tags.at(33) != 0 && image.Init == 0) fail("Guest PREINIT_ARRAY has no module initializer");
|
||||
image.Bytes = std::move(bytes);
|
||||
return image;
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
if (error.InputPath.empty()) error.InputPath = Io::Utf8Path(path);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ namespace Relinker {
|
|||
|
||||
Conversion::Conversion(Domain::IProgress& progress) : progress(progress) {}
|
||||
|
||||
ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOptions& options) const {
|
||||
ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOptions& options) const try {
|
||||
Domain::InputProgress inputProgress(progress, Io::Utf8Path(input.InputPath));
|
||||
static_assert(std::endian::native == std::endian::little);
|
||||
if (options.Target != TargetSystem::Linux && options.Target != TargetSystem::Windows && options.Target != TargetSystem::MacOs) throw Domain::RelinkerException("Unknown target system");
|
||||
|
|
@ -120,7 +120,7 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti
|
|||
try {
|
||||
executableBytes = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, options.RunPath, options.LazyBinding, options.WindowsDiagnostics, trampolines);
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
error.InputPath = Io::Utf8Path(input.InputPath);
|
||||
if (error.InputPath.empty()) error.InputPath = Io::Utf8Path(input.InputPath);
|
||||
throw;
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +129,9 @@ ConversionOutput Conversion::Convert(ConversionInput input, const ConversionOpti
|
|||
if (windows) diagnostics = std::static_pointer_cast<Elfpatcher::Windows::WindowsPePatcher>(patcher)->GetDiagnostics();
|
||||
inputProgress.Report({Domain::ProgressStage::Executable, Io::Utf8Path(input.InputPath), 1, 1});
|
||||
return {std::move(executableBytes), std::move(guestArtifacts), std::move(result), std::move(diagnostics)};
|
||||
} catch (Domain::RelinkerException& error) {
|
||||
if (error.InputPath.empty()) error.InputPath = Io::Utf8Path(input.InputPath);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,7 +112,35 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
for (const auto& t : dynTags)
|
||||
if (t.Tag == tag)
|
||||
return t.Value;
|
||||
throw RelinkerException("DT tag not found");
|
||||
const char* name = "unknown";
|
||||
switch (tag) {
|
||||
case DT_OS_STRTAB: name = "DT_OS_STRTAB"; break;
|
||||
case DT_STRTAB: name = "DT_STRTAB"; break;
|
||||
case DT_OS_STRSZ: name = "DT_OS_STRSZ"; break;
|
||||
case DT_STRSZ: name = "DT_STRSZ"; break;
|
||||
case DT_OS_SYMTAB: name = "DT_OS_SYMTAB"; break;
|
||||
case DT_SYMTAB: name = "DT_SYMTAB"; break;
|
||||
case DT_OS_SYMENT: name = "DT_OS_SYMENT"; break;
|
||||
case DT_SYMENT: name = "DT_SYMENT"; break;
|
||||
case DT_OS_PLTGOT: name = "DT_OS_PLTGOT"; break;
|
||||
case DT_PLTGOT: name = "DT_PLTGOT"; break;
|
||||
case DT_OS_PLTREL: name = "DT_OS_PLTREL"; break;
|
||||
case DT_PLTREL: name = "DT_PLTREL"; break;
|
||||
case DT_OS_PLTRELSZ: name = "DT_OS_PLTRELSZ"; break;
|
||||
case DT_PLTRELSZ: name = "DT_PLTRELSZ"; break;
|
||||
case DT_OS_JMPREL: name = "DT_OS_JMPREL"; break;
|
||||
case DT_JMPREL: name = "DT_JMPREL"; break;
|
||||
case DT_OS_RELA: name = "DT_OS_RELA"; break;
|
||||
case DT_RELA: name = "DT_RELA"; break;
|
||||
case DT_OS_RELASZ: name = "DT_OS_RELASZ"; break;
|
||||
case DT_RELASZ: name = "DT_RELASZ"; break;
|
||||
case DT_OS_RELAENT: name = "DT_OS_RELAENT"; break;
|
||||
case DT_RELAENT: name = "DT_RELAENT"; break;
|
||||
case DT_OS_SYMTABSZ: name = "DT_OS_SYMTABSZ"; break;
|
||||
}
|
||||
std::ostringstream message;
|
||||
message << "Required dynamic tag " << name << " (0x" << std::hex << tag << ") not found";
|
||||
throw RelinkerException(message.str());
|
||||
};
|
||||
|
||||
auto requireExactlyOneOf = [&](const std::int64_t osTag, const std::int64_t sysvTag, const char* name) {
|
||||
|
|
@ -120,8 +148,11 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
const bool hasSysv = hasTag(sysvTag);
|
||||
if (hasOs && hasSysv)
|
||||
throw RelinkerException(std::string("Both DT_OS_ and DT_ variants present for ") + name);
|
||||
if (!hasOs && !hasSysv)
|
||||
throw RelinkerException(std::string("Neither DT_OS_ nor DT_ variant present for ") + name);
|
||||
if (!hasOs && !hasSysv) {
|
||||
std::ostringstream message;
|
||||
message << "Neither DT_OS_ nor DT_ variant present for " << name << " (tags 0x" << std::hex << osTag << " and 0x" << sysvTag << ")";
|
||||
throw RelinkerException(message.str());
|
||||
}
|
||||
return hasOs;
|
||||
};
|
||||
|
||||
|
|
@ -154,8 +185,9 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
|
||||
const FileByteOffset dynSymTabOffset = readAsOffset(DT_OS_SYMTAB, DT_SYMTAB, "DT_SYMTAB");
|
||||
constexpr std::size_t symEntSize = 24;
|
||||
if (readAsSize(DT_OS_SYMENT, DT_SYMENT, "DT_SYMENT") != symEntSize)
|
||||
throw RelinkerException("Unsupported DT_SYMENT value");
|
||||
const auto symbolEntrySize = readAsSize(DT_OS_SYMENT, DT_SYMENT, "DT_SYMENT");
|
||||
if (symbolEntrySize != symEntSize)
|
||||
throw RelinkerException("Unsupported DT_SYMENT value: " + std::to_string(symbolEntrySize) + "; expected 24 bytes");
|
||||
|
||||
FileByteOffset dynJmpRelOffset = 0;
|
||||
if (hasPltRelocations) {
|
||||
|
|
@ -164,20 +196,21 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
? getTagValue(DT_OS_PLTREL)
|
||||
: getTagValue(DT_PLTREL);
|
||||
if (jmprelType != DT_RELA)
|
||||
throw RelinkerException("Unsupported DT_PLTREL type");
|
||||
throw RelinkerException("Unsupported DT_PLTREL type: " + std::to_string(jmprelType) + "; expected DT_RELA (7)");
|
||||
dynJmpRelOffset = readAsOffset(DT_OS_JMPREL, DT_JMPREL, "DT_JMPREL");
|
||||
if (gotSize % 24 != 0)
|
||||
throw RelinkerException("Invalid DT_PLTRELSZ value");
|
||||
throw RelinkerException("Invalid DT_PLTRELSZ value: " + std::to_string(gotSize) + " bytes; expected a multiple of 24 bytes");
|
||||
}
|
||||
const ByteCount dynJmpRelSize = gotSize;
|
||||
|
||||
const FileByteOffset dynRelaOffset = readAsOffset(DT_OS_RELA, DT_RELA, "DT_RELA");
|
||||
const ByteCount dynRelaSize = readAsSize(DT_OS_RELASZ, DT_RELASZ, "DT_RELASZ");
|
||||
constexpr std::size_t relaEntSize = 24;
|
||||
if (readAsSize(DT_OS_RELAENT, DT_RELAENT, "DT_RELAENT") != relaEntSize)
|
||||
throw RelinkerException("Unsupported DT_RELAENT value");
|
||||
const auto relocationEntrySize = readAsSize(DT_OS_RELAENT, DT_RELAENT, "DT_RELAENT");
|
||||
if (relocationEntrySize != relaEntSize)
|
||||
throw RelinkerException("Unsupported DT_RELAENT value: " + std::to_string(relocationEntrySize) + "; expected 24 bytes");
|
||||
if (dynRelaSize % relaEntSize != 0)
|
||||
throw RelinkerException("Invalid DT_RELASZ value");
|
||||
throw RelinkerException("Invalid DT_RELASZ value: " + std::to_string(dynRelaSize) + " bytes; expected a multiple of 24 bytes");
|
||||
|
||||
std::vector<std::pair<std::uint64_t, std::string>> neededLibraryNamesByStrOffset;
|
||||
for (const auto& tag : dynTags)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <relinker/analysis/SyscallScanner.hpp>
|
||||
#include <relinker/analysis/CodeInstructionCollector.hpp>
|
||||
#include <io/BufferUtils.hpp>
|
||||
#include <io/NativePath.hpp>
|
||||
#include <codegen/IAmd64OnlyConverter.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
|
@ -162,6 +163,33 @@ void scanning() {
|
|||
require(progress.Events.back().Completed < progress.Events.back().Total, "Failed scan reported completion");
|
||||
}
|
||||
|
||||
class FailingProgress final : public Domain::IProgress {
|
||||
public:
|
||||
std::string InputPath;
|
||||
void Report(const Domain::ProgressEvent&) override {
|
||||
Domain::RelinkerException error("diagnostic context", 0x1234);
|
||||
error.InputPath = InputPath;
|
||||
throw error;
|
||||
}
|
||||
void Message(std::string_view) override {}
|
||||
};
|
||||
|
||||
void diagnosticContext() {
|
||||
for (const bool specific : {false, true}) {
|
||||
FailingProgress progress;
|
||||
if (specific) progress.InputPath = "virtual/title/sce_module/plugin.prx";
|
||||
const auto source = input();
|
||||
try { Relinker::Conversion(progress).Convert(source, {}); }
|
||||
catch (const Domain::RelinkerException& error) {
|
||||
require(error.InputPath == (specific ? progress.InputPath : Io::Utf8Path(source.InputPath)), "Wrong diagnostic input path");
|
||||
require(error.FailureOffset == 0x1234, "Diagnostic offset changed");
|
||||
require(std::string(error.what()) == "diagnostic context", "Diagnostic message changed");
|
||||
continue;
|
||||
}
|
||||
throw std::runtime_error("Diagnostic failure was swallowed");
|
||||
}
|
||||
}
|
||||
|
||||
class CancellingProgress final : public Domain::IProgress {
|
||||
public:
|
||||
Domain::ProgressStage Stage = Domain::ProgressStage::Decode;
|
||||
|
|
@ -192,6 +220,7 @@ int main() {
|
|||
try {
|
||||
convert();
|
||||
scanning();
|
||||
diagnosticContext();
|
||||
cancellation();
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
from pathlib import Path
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from test_optional_plt import fixture
|
||||
from test_guest_intel_trampolines import guest_fixture, main_fixture, PLAIN_SITE
|
||||
|
||||
|
||||
def main():
|
||||
relinker = Path(sys.argv[1]).resolve()
|
||||
failures = []
|
||||
with tempfile.TemporaryDirectory(prefix="anyps5-diagnostics-") as directory:
|
||||
work = Path(directory) / "title space \u00e9"
|
||||
work.mkdir()
|
||||
|
||||
def convert(name, image, expected, flags, module=None, extra=False):
|
||||
case = work / name
|
||||
case.mkdir()
|
||||
source = case / "eboot.bin"
|
||||
source.write_bytes(main_fixture() if module is not None or extra else image)
|
||||
options = []
|
||||
offending = source
|
||||
if module is not None:
|
||||
modules = case / "sce_module"
|
||||
modules.mkdir()
|
||||
offending = modules / "broken \u00e9.prx"
|
||||
offending.write_bytes(module)
|
||||
options = ["--sce-module-path", str(case)]
|
||||
elif extra:
|
||||
offending = case / "picker.bin"
|
||||
offending.write_bytes(image)
|
||||
(case / "sce_module").mkdir()
|
||||
options = ["--sce-module-path", str(case), "--executable", "picker.bin"]
|
||||
else:
|
||||
options = ["--skip-sce-module"]
|
||||
output = case / "converted.out"
|
||||
result = subprocess.run([str(relinker), *flags, *options, str(source), str(output)],
|
||||
capture_output=True, text=True, encoding="utf-8", timeout=20)
|
||||
valid = (result.returncode == 2 and expected in result.stderr
|
||||
and result.stderr.splitlines().count(f"Input: {offending}") == 1
|
||||
and not output.exists() and not list(case.rglob("*.guest.prx")))
|
||||
if extra:
|
||||
valid = valid and not (case / "picker.bin.out").exists()
|
||||
if not valid:
|
||||
failures.append((name, result.returncode, result.stdout, result.stderr))
|
||||
|
||||
cases = []
|
||||
for tag, value, message in (
|
||||
(11, 16, "Unsupported DT_SYMENT value: 16; expected 24 bytes"),
|
||||
(9, 8, "Unsupported DT_RELAENT value: 8; expected 24 bytes"),
|
||||
(8, 25, "Invalid DT_RELASZ value: 25 bytes; expected a multiple of 24 bytes"),
|
||||
):
|
||||
image = fixture()
|
||||
for offset in range(0x400, 0x470, 16):
|
||||
if struct.unpack_from("<q", image, offset)[0] == tag:
|
||||
struct.pack_into("<Q", image, offset + 8, value)
|
||||
cases.append((str(tag), image, message))
|
||||
missing = fixture()
|
||||
struct.pack_into("<q", missing, 0x400, 0x70000000)
|
||||
cases.append(("missing", missing, "Neither DT_OS_ nor DT_ variant present for DT_STRTAB (tags 0x61000035 and 0x5)"))
|
||||
cases.append(("plt-type", fixture([(3, 0x300), (2, 0), (20, 17), (23, 0x720)]),
|
||||
"Unsupported DT_PLTREL type: 17; expected DT_RELA (7)"))
|
||||
cases.append(("plt-size", fixture([(3, 0x300), (2, 25), (20, 7), (23, 0x720)]),
|
||||
"Invalid DT_PLTRELSZ value: 25 bytes; expected a multiple of 24 bytes"))
|
||||
invalid_header = fixture()
|
||||
struct.pack_into("<I", invalid_header, 20, 2)
|
||||
cases.append(("header", invalid_header, "Unsupported ELF object version: expected 1 (offset 0x14)"))
|
||||
for index, flags in enumerate(([], ["--windows"], ["--to-intel"], ["--windows", "--to-intel"])):
|
||||
for name, image, message in cases:
|
||||
convert(f"{index}-{name}", image, message, flags)
|
||||
convert(f"{index}-extra", cases[0][1], cases[0][2], flags, extra=True)
|
||||
module = guest_fixture(PLAIN_SITE)
|
||||
struct.pack_into("<QQ", module, 176 + 32, 17, 17)
|
||||
convert(f"{index}-module", None, "Missing or invalid ELF load/dynamic segments", flags, module=module)
|
||||
if failures:
|
||||
raise AssertionError(failures)
|
||||
print("Conversion diagnostics tests passed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue