From 33c955a8e3f90e2cee8a37d5b0fb90d172c9af58 Mon Sep 17 00:00:00 2001 From: Evgeniy Kazarin Date: Mon, 12 Oct 2026 16:15:06 +0300 Subject: [PATCH] perf(relinker): index guest exports for dependency resolution --- core/relinker/CMakeLists.txt | 3 + .../relinker/src/guest/GuestModuleBuilder.cpp | 36 +++-- .../tests/test_guest_export_lookup.py | 128 ++++++++++++++++++ 3 files changed, 152 insertions(+), 15 deletions(-) create mode 100644 core/relinker/relinker/tests/test_guest_export_lookup.py diff --git a/core/relinker/CMakeLists.txt b/core/relinker/CMakeLists.txt index 4d6fd565d..181044433 100644 --- a/core/relinker/CMakeLists.txt +++ b/core/relinker/CMakeLists.txt @@ -447,6 +447,9 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_absolute_symbols.py $) add_test(NAME guest_module_identity COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_module_identity.py $) + add_test(NAME guest_export_lookup COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_export_lookup.py $) + set_tests_properties(guest_export_lookup PROPERTIES TIMEOUT 30) add_test(NAME guest_needed_nested_modules COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_needed_nested_modules.py $) add_test(NAME windows_import_modules COMMAND ${Python3_EXECUTABLE} diff --git a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp index 08aec4772..a036b7474 100644 --- a/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp +++ b/core/relinker/relinker/src/guest/GuestModuleBuilder.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,11 @@ namespace Relinker { namespace { +struct GuestExport { + std::size_t Provider; + std::uint8_t Type; +}; + std::vector ReadNeededNames(const Domain::SysVDynamicSection& dynamic) { if (dynamic.DynamicSegmentData.size() % 16 != 0) throw Domain::RelinkerException("Invalid executable dependency table"); std::vector names; @@ -136,7 +142,7 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (std::filesystem::equivalent(path, outputPath)) throw Domain::RelinkerException("Executable output would overwrite an input module: " + Io::Utf8Path(outputPath)); } - std::map> exports; + std::unordered_map> exports; std::map> sharedExports; std::set outputNames; for (const auto& path : paths) { @@ -153,10 +159,11 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path for (const auto& symbol : image.Symbols) { if (symbol.Section == 0 || symbol.Section == AbsoluteSection || (symbol.Info >> 4) == 0 || symbol.Visibility == 1 || symbol.Visibility == 2) continue; auto& providers = exports[symbol.Name]; - const bool repeated = std::find(providers.begin(), providers.end(), images.size()) != providers.end(); - if (!windows && repeated) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + Io::Utf8Path(path) + " and " + Io::Utf8Path(images[providers.front()].SourcePath)); - if (!repeated) providers.push_back(images.size()); - if (!windows && providers.size() > 1) sharedExports[symbol.Name].insert(providers.begin(), providers.end()); + const bool repeated = !providers.empty() && providers.back().Provider == images.size(); + if (!windows && repeated) throw Domain::RelinkerException("Duplicate guest export after stripping #: " + symbol.Name + " in " + Io::Utf8Path(path) + " and " + Io::Utf8Path(images[providers.front().Provider].SourcePath)); + if (!repeated) providers.push_back({images.size(), static_cast(symbol.Info & 15)}); + if (!windows && providers.size() > 1) + for (const auto& provider : providers) sharedExports[symbol.Name].insert(provider.Provider); } images.push_back(std::move(image)); } @@ -264,20 +271,19 @@ std::vector GuestModuleBuilder::Build(const std::filesystem::path if (shared == sharedExports.end() || declared == guestNames.end() || !shared->second.contains(declared->second)) rejectSharedImport(symbol.Name, Io::Utf8Path(images[index].SourcePath)); const auto found = exports.find(symbol.Name); - std::vector providers; + const GuestExport* provider = nullptr; if (found != exports.end()) { - for (const auto provider : found->second) { - if (symbol.Library.empty() || (declared != guestNames.end() && declared->second == provider)) providers.push_back(provider); + for (const auto& candidate : found->second) { + if (!symbol.Library.empty() && (declared == guestNames.end() || declared->second != candidate.Provider)) continue; + if (provider != nullptr) throw Domain::RelinkerException("Ambiguous guest import after stripping #: " + symbol.Name); + provider = &candidate; } } - if (providers.size() > 1) throw Domain::RelinkerException("Ambiguous guest import after stripping #: " + symbol.Name); - if (!providers.empty()) { - const auto& provider = images[providers.front()]; - const auto exported = std::find_if(provider.Symbols.begin(), provider.Symbols.end(), [&](const auto& candidate) { return candidate.Section != 0 && candidate.Section != AbsoluteSection && candidate.Name == symbol.Name && (candidate.Info >> 4) != 0 && candidate.Visibility != 1 && candidate.Visibility != 2; }); - if (exported == provider.Symbols.end() || ((symbol.Info & 15) != 0 && (symbol.Info & 15) != (exported->Info & 15))) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name); - if (providers.front() != index) dependencies[index].insert(providers.front()); + if (provider != nullptr) { + if ((symbol.Info & 15) != 0 && (symbol.Info & 15) != provider->Type) throw Domain::RelinkerException("Guest import/export type mismatch: " + symbol.Name); + if (provider->Provider != index) dependencies[index].insert(provider->Provider); } else if (windows && (symbol.Info & 15) == 6) throw Domain::RelinkerException("Windows guest TLS import requires a guest TLS export: " + symbol.Name); - if (providers.empty()) systemImports[index].insert(symbolIndex); + if (provider == nullptr) systemImports[index].insert(symbolIndex); } } std::vector required(images.size()); diff --git a/core/relinker/relinker/tests/test_guest_export_lookup.py b/core/relinker/relinker/tests/test_guest_export_lookup.py new file mode 100644 index 000000000..0204d1633 --- /dev/null +++ b/core/relinker/relinker/tests/test_guest_export_lookup.py @@ -0,0 +1,128 @@ +from pathlib import Path +import os +import struct +import subprocess +import sys +import tempfile + +from test_guest_intel_trampolines import PLAIN_SITE, guest_fixture, main_fixture +from test_guest_module_directories import needed_libraries +from test_guest_symbol_names import imported_symbols, module_symbols +from test_windows_import_modules import executable + + +def guest_symbols(symbols, dependency=None): + image = guest_fixture(PLAIN_SITE) + image[0x400:0x406] = b'\xb8\x2a\x00\x00\x00\xc3' + strings = bytearray(b'\0') + table = bytearray(24) + for name, info, visibility, section in symbols: + offset = len(strings) + strings.extend(name.encode() + b'\0') + table.extend(struct.pack('