feat(relinker): unused nid filter fix + cli update

This commit is contained in:
boykopovar 2026-09-09 18:17:10 +03:00
parent febac8978e
commit 77cfe65b9c
6 changed files with 46 additions and 28 deletions

View File

@ -41,6 +41,7 @@ enum class ControlFlowKind : std::uint8_t {
Return,
IndirectJump,
IndirectCall,
Trap,
};
struct DecodedInstructionInfo {

View File

@ -440,6 +440,10 @@ DecodedInstructionInfo X64InstructionDecoder::DecodeInstruction(
}
}
} else {
if (op == 0x0B) {
info.FlowKind = ControlFlowKind::Trap;
return info;
}
if (op >= TwoByteJccRel32Min && op <= TwoByteJccRel32Max) {
std::int32_t disp = 0;
std::memcpy(&disp, data + pos, 4);

View File

@ -26,7 +26,8 @@
int main(const int argc, char* argv[]) {
bool skipSyscallCheck = false;
bool toIntel = false;
bool skipUnusedNidFilter = false;
std::uint32_t unusedFilterLevel = 1;
bool unusedFilterSpecified = false;
bool writeRegistry = false;
bool toWindows = false;
@ -40,8 +41,14 @@ int main(const int argc, char* argv[]) {
skipSyscallCheck = true;
} else if (arg == "--to-intel") {
toIntel = true;
} else if (arg == "--skip-unused-nid-filter") {
skipUnusedNidFilter = true;
} else if (arg.rfind("unused-filter=", 0) == 0) {
const std::string value = arg.substr(14);
if (unusedFilterSpecified || value.size() != 1 || value[0] < '0' || value[0] > '2') {
std::cerr << "FAIL: unused-filter must be specified once with a value of 0, 1 or 2\n";
return 1;
}
unusedFilterLevel = static_cast<std::uint32_t>(value[0] - '0');
unusedFilterSpecified = true;
} else if (arg == "--registry") {
writeRegistry = true;
} else if (arg == "--rpath") {
@ -52,6 +59,9 @@ int main(const int argc, char* argv[]) {
runPath = argv[++i];
} else if (arg == "--windows") {
toWindows = true;
} else if (arg.rfind("--", 0) == 0 || arg == "unused-filter") {
std::cerr << "FAIL: unknown option: " << arg << "\n";
return 1;
} else if (inputPath.empty()) {
inputPath = arg;
} else if (outputPath.empty()) {
@ -63,7 +73,7 @@ int main(const int argc, char* argv[]) {
}
if (inputPath.empty() || outputPath.empty()) {
std::cerr << "Usage: relinker [--windows] [--skip-syscall-check] [--to-intel] [--skip-unused-nid-filter] [--registry] [--rpath <path>] <input.elf> <output.elf>\n"
std::cerr << "Usage: relinker [--windows] [--skip-syscall-check] [--to-intel] [unused-filter=0|1|2] [--registry] [--rpath <path>] <input.elf> <output.elf>\n"
"Example: relinker input.elf output.elf\n";
return 1;
}
@ -75,6 +85,7 @@ int main(const int argc, char* argv[]) {
auto sourceBytes = fileReader.Read(inputPath);
if (toIntel) {
std::cout << "Mode: Intel instruction conversion; system unchanged; unused-filter=" << unusedFilterLevel << " (not applied)\n";
const Relinker::ElfReader elfReader(sourceBytes);
const auto converter = Codegen::MakeAmd64OnlyConverter();
auto result = converter->Convert(std::move(sourceBytes), elfReader.ReadCodeSegments());
@ -96,9 +107,10 @@ int main(const int argc, char* argv[]) {
std::make_shared<Relinker::ValidationPolicy>(),
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
Relinker::MakeUnusedNidFilter(),
!skipUnusedNidFilter
unusedFilterLevel
);
std::cout << "System: " << (toWindows ? "Windows" : "Linux") << "; unused-filter=" << unusedFilterLevel << "\n";
auto result = pipeline->Relink(sourceBytes);
if (writeRegistry) {
@ -126,6 +138,7 @@ int main(const int argc, char* argv[]) {
}
auto patched = patcher->Patch(sourceBytes, result.OriginalHeaders, result.DynamicSection, result.OriginalPltGotVaddr, runPath);
fileWriter.Write(outputPath, patched);
std::cout << "OK: " << result.RegistryEntries.size() << " NID references processed; output written\n";
} catch (const Domain::RelinkerException& e) {
std::cerr << "FAIL: " << e.what();

View File

@ -14,15 +14,7 @@ namespace Relinker {
class RelinkerPipeline : public IRelinkerPipeline {
public:
RelinkerPipeline(
std::shared_ptr<IElfReader> elfReader,
std::shared_ptr<ISyscallScanner> syscallScanner,
std::shared_ptr<ICallSiteResolver> callSiteResolver,
std::shared_ptr<IValidationPolicy> validationPolicy,
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder,
std::shared_ptr<IUnusedNidFilter> unusedNidFilter,
bool filterUnusedNids
);
RelinkerPipeline(std::shared_ptr<IElfReader> elfReader, std::shared_ptr<ISyscallScanner> syscallScanner, std::shared_ptr<ICallSiteResolver> callSiteResolver, std::shared_ptr<IValidationPolicy> validationPolicy, std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder, std::shared_ptr<IUnusedNidFilter> unusedNidFilter, std::uint32_t unusedFilterLevel);
RelinkResult Relink(const std::vector<std::uint8_t>& sourceElf) override;
@ -33,7 +25,7 @@ private:
std::shared_ptr<IValidationPolicy> _validationPolicy;
std::shared_ptr<ISysVDynamicSectionBuilder> _dynamicSectionBuilder;
std::shared_ptr<IUnusedNidFilter> _unusedNidFilter;
bool _filterUnusedNids;
std::uint32_t unusedFilterLevel;
static constexpr std::uint32_t PT_LOAD = 1;
static constexpr std::uint32_t PT_DYNAMIC = 2;

View File

@ -98,6 +98,7 @@ std::unique_ptr<IControlFlowGraph> BuildControlFlowGraph(
break;
case Return:
case IndirectJump:
case Trap:
break;
}
}

View File

@ -6,22 +6,17 @@
namespace Relinker {
RelinkerPipeline::RelinkerPipeline(
std::shared_ptr<IElfReader> elfReader,
std::shared_ptr<ISyscallScanner> syscallScanner,
std::shared_ptr<ICallSiteResolver> callSiteResolver,
std::shared_ptr<IValidationPolicy> validationPolicy,
std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder,
std::shared_ptr<IUnusedNidFilter> unusedNidFilter,
bool filterUnusedNids)
RelinkerPipeline::RelinkerPipeline(std::shared_ptr<IElfReader> elfReader, std::shared_ptr<ISyscallScanner> syscallScanner, std::shared_ptr<ICallSiteResolver> callSiteResolver, std::shared_ptr<IValidationPolicy> validationPolicy, std::shared_ptr<ISysVDynamicSectionBuilder> dynamicSectionBuilder, std::shared_ptr<IUnusedNidFilter> unusedNidFilter, std::uint32_t unusedFilterLevel)
: _elfReader(std::move(elfReader))
, _syscallScanner(std::move(syscallScanner))
, _callSiteResolver(std::move(callSiteResolver))
, _validationPolicy(std::move(validationPolicy))
, _dynamicSectionBuilder(std::move(dynamicSectionBuilder))
, _unusedNidFilter(std::move(unusedNidFilter))
, _filterUnusedNids(filterUnusedNids)
{}
, unusedFilterLevel(unusedFilterLevel)
{
if (unusedFilterLevel > 2) throw RelinkerException("Unused NID filter level must be 0, 1 or 2");
}
std::string RelinkerPipeline::_relocationTypeName(std::uint32_t type) {
switch (type) {
@ -201,7 +196,10 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
static constexpr std::uint32_t R_X86_64_JUMP_SLOT = 7;
if (_filterUnusedNids) {
const std::size_t originalNidCount = nidRefs.size();
std::cout << "NID input: " << originalNidCount << " references\n";
if (unusedFilterLevel != 0) {
std::vector<NidReference> pltRefs;
std::vector<NidReference> nonPltRefs;
for (const auto& ref : nidRefs) {
@ -211,14 +209,25 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
nonPltRefs.push_back(ref);
}
std::cout << "PLT preservation: " << pltRefs.size() << " -> " << pltRefs.size() << "; filtered=0\n";
const std::size_t nonPltCount = nonPltRefs.size();
nonPltRefs = _unusedNidFilter->Filter(nonPltRefs, raw, textSection, textVAddr);
if (nonPltRefs.size() > nonPltCount) throw RelinkerException("Unused NID filter increased the reference count");
std::cout << "CFG/GOT filtering: " << nonPltCount << " -> " << nonPltRefs.size() << "; filtered=" << nonPltCount - nonPltRefs.size() << "\n";
if (unusedFilterLevel == 2)
std::cout << "Strict filtering: " << nonPltRefs.size() << " -> " << nonPltRefs.size() << "; filtered=0 (reserved; currently identical to level 1)\n";
nidRefs.clear();
nidRefs.reserve(pltRefs.size() + nonPltRefs.size());
for (auto& ref : pltRefs) nidRefs.push_back(std::move(ref));
for (auto& ref : nonPltRefs) nidRefs.push_back(std::move(ref));
} else {
std::cout << "Unused NID filtering: disabled; filtered=0\n";
}
std::cout << "NID total: " << originalNidCount << " -> " << nidRefs.size() << "; filtered=" << originalNidCount - nidRefs.size() << "\n";
auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(
nidRefs, neededLibraries, dynJmpRelOffset, static_cast<std::uint32_t>(dynJmpRelSize / relaEntSize));
@ -271,8 +280,6 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
entries.push_back(std::move(entry));
}
std::cout << "OK: " << entries.size() << " NID references processed\n";
return RelinkResult{std::move(entries), std::move(programHeaders), std::move(dynSection), gotVAddr};
}