feat(relinker): strict unused nids filtering mode
This commit is contained in:
parent
77cfe65b9c
commit
5bf78ab8b4
|
|
@ -28,6 +28,10 @@ add_executable(relinker
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/SyscallScanner.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/CallSiteResolver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp
|
||||
|
|
@ -46,3 +50,29 @@ target_include_directories(relinker PRIVATE
|
|||
)
|
||||
|
||||
add_subdirectory(libs)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_executable(strict_nid_filter_tests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/StrictNidFilterTests.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictNidFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/StrictReachability.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/EhFrameReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/PltCompactor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/parsing/ElfReader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/output/SysVDynamicSectionBuilder.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/X64InstructionDecoder.cpp
|
||||
)
|
||||
target_sources(strict_nid_filter_tests PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/ControlFlowGraph.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/EntryPointCollector.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/GotAccessIndex.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/src/analysis/UnusedNidFilter/RelativeRelocationIndex.cpp
|
||||
)
|
||||
target_include_directories(strict_nid_filter_tests PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/domain/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/relinker/include
|
||||
)
|
||||
add_test(NAME strict_nid_filter COMMAND strict_nid_filter_tests)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ std::size_t X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t
|
|||
throw CodegenException("Instruction truncated after VEX2 prefix");
|
||||
}
|
||||
vexPresent = true;
|
||||
vexMap = TwoByteOpcodeEscape;
|
||||
vexMap = 1;
|
||||
pos += 1;
|
||||
} else if (opcode == OneByteVex3) {
|
||||
if (pos >= available) {
|
||||
|
|
@ -117,18 +117,20 @@ std::size_t X64InstructionDecoder::Decode(const std::uint8_t* data, std::size_t
|
|||
bool hasModRm = false;
|
||||
std::size_t immediateSize = ImmSizeNone;
|
||||
|
||||
const bool vectorImmediate = vexMap == 1 && ((opcode >= 0x70 && opcode <= 0x73) || opcode == 0xC2 || opcode == 0xC4 || opcode == 0xC5 || opcode == 0xC6);
|
||||
|
||||
if (vexPresent) {
|
||||
if (vexMap == TwoByteOpcodeEscape && opcode >= VexNoModRmMin && opcode <= VexNoModRmMax) {
|
||||
if (vexMap == 1 && opcode >= VexNoModRmMin && opcode <= VexNoModRmMax) {
|
||||
hasModRm = false;
|
||||
} else {
|
||||
hasModRm = true;
|
||||
if (vexMap == Vex3Map0F3A) {
|
||||
if (vexMap == Vex3Map0F3A || vectorImmediate) {
|
||||
immediateSize = ImmSize8;
|
||||
}
|
||||
}
|
||||
} else if (evexPresent) {
|
||||
hasModRm = true;
|
||||
if (vexMap == EvexMap0F3A) {
|
||||
if (vexMap == EvexMap0F3A || vectorImmediate) {
|
||||
immediateSize = ImmSize8;
|
||||
}
|
||||
} else if (threeByteEscape) {
|
||||
|
|
|
|||
|
|
@ -106,12 +106,17 @@ int main(const int argc, char* argv[]) {
|
|||
Relinker::MakeCallSiteResolver(),
|
||||
std::make_shared<Relinker::ValidationPolicy>(),
|
||||
std::make_shared<Relinker::SysVDynamicSectionBuilder>(),
|
||||
Relinker::MakeUnusedNidFilter(),
|
||||
unusedFilterLevel == 2 ? Relinker::MakeStrictUnusedNidFilter() : Relinker::MakeUnusedNidFilter(),
|
||||
unusedFilterLevel
|
||||
);
|
||||
|
||||
std::cout << "System: " << (toWindows ? "Windows" : "Linux") << "; unused-filter=" << unusedFilterLevel << "\n";
|
||||
auto result = pipeline->Relink(sourceBytes);
|
||||
for (const auto& patch : result.Patches) {
|
||||
if (patch.Offset > sourceBytes.size() || patch.Bytes.size() > sourceBytes.size() - patch.Offset)
|
||||
throw Domain::RelinkerException("Relinker patch exceeds source image", patch.Offset);
|
||||
for (std::size_t index = 0; index < patch.Bytes.size(); ++index) sourceBytes[patch.Offset + index] = patch.Bytes[index];
|
||||
}
|
||||
|
||||
if (writeRegistry) {
|
||||
const std::filesystem::path outFsPath(outputPath);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
namespace Relinker {
|
||||
|
||||
std::shared_ptr<IUnusedNidFilter> MakeUnusedNidFilter();
|
||||
std::shared_ptr<IUnusedNidFilter> MakeStrictUnusedNidFilter();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef RELINKER_ANALYSIS_UNUSED_NID_FILTER_EHFRAMEREADER_HPP
|
||||
#define RELINKER_ANALYSIS_UNUSED_NID_FILTER_EHFRAMEREADER_HPP
|
||||
|
||||
#include <relinker/analysis/UnusedNidFilter/StrictReachability.hpp>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
std::vector<StrictCodeRegion> ReadExceptionFunctions(const std::vector<std::uint8_t>& bytes, const std::vector<ProgramHeader>& headers, const std::map<VirtualAddress, VirtualAddress>& pointers, const std::set<VirtualAddress>& importSlots);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#ifndef RELINKER_ANALYSIS_UNUSED_NID_FILTER_PLTCOMPACTOR_HPP
|
||||
#define RELINKER_ANALYSIS_UNUSED_NID_FILTER_PLTCOMPACTOR_HPP
|
||||
|
||||
#include <relinker/domain/RelinkResult.hpp>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
struct CompactedPlt {
|
||||
std::vector<NidReference> References;
|
||||
std::vector<RelinkPatch> Patches;
|
||||
std::uint32_t SlotCount = 0;
|
||||
};
|
||||
|
||||
CompactedPlt CompactPlt(const std::vector<NidReference>& originalReferences, const std::vector<NidReference>& keptReferences, const std::vector<std::uint8_t>& text, VirtualAddress textVaddr, FileByteOffset textOffset, FileByteOffset tableOffset);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef RELINKER_ANALYSIS_UNUSED_NID_FILTER_STRICTREACHABILITY_HPP
|
||||
#define RELINKER_ANALYSIS_UNUSED_NID_FILTER_STRICTREACHABILITY_HPP
|
||||
|
||||
#include <relinker/domain/Types.hpp>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
struct StrictCodeRegion {
|
||||
VirtualAddress Begin;
|
||||
VirtualAddress End;
|
||||
std::vector<VirtualAddress> ExtraTargets;
|
||||
};
|
||||
|
||||
struct StrictDataRegion {
|
||||
VirtualAddress Address;
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
};
|
||||
|
||||
struct StrictReachabilityInput {
|
||||
std::vector<std::uint8_t> Text;
|
||||
VirtualAddress TextVaddr = 0;
|
||||
std::vector<VirtualAddress> Entries;
|
||||
std::map<VirtualAddress, VirtualAddress> Pointers;
|
||||
std::set<VirtualAddress> ImportSlots;
|
||||
std::vector<StrictCodeRegion> Functions;
|
||||
std::vector<StrictDataRegion> Data;
|
||||
};
|
||||
|
||||
struct StrictReachabilityResult {
|
||||
std::set<VirtualAddress> Instructions;
|
||||
std::set<VirtualAddress> ImportSlots;
|
||||
std::size_t IndirectTransfers = 0;
|
||||
std::size_t LiveRegions = 0;
|
||||
std::size_t TotalRegions = 0;
|
||||
std::size_t AddressTakenRoots = 0;
|
||||
};
|
||||
|
||||
StrictReachabilityResult AnalyzeStrictReachability(const StrictReachabilityInput& input);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -6,11 +6,17 @@
|
|||
|
||||
namespace Relinker {
|
||||
|
||||
struct RelinkPatch {
|
||||
FileByteOffset Offset;
|
||||
std::vector<std::uint8_t> Bytes;
|
||||
};
|
||||
|
||||
struct RelinkResult {
|
||||
std::vector<CallRegistryEntry> RegistryEntries;
|
||||
std::vector<ProgramHeader> OriginalHeaders;
|
||||
SysVDynamicSection DynamicSection;
|
||||
VirtualAddress OriginalPltGotVaddr;
|
||||
std::vector<RelinkPatch> Patches;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,219 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Cie {
|
||||
std::uint8_t PointerEncoding = 0;
|
||||
std::uint8_t LsdaEncoding = 0xFF;
|
||||
bool Augmented = false;
|
||||
VirtualAddress Personality = 0;
|
||||
};
|
||||
|
||||
class Reader {
|
||||
public:
|
||||
Reader(const std::vector<std::uint8_t>& bytes, const std::vector<ProgramHeader>& headers, const std::map<VirtualAddress, VirtualAddress>& pointers, const std::set<VirtualAddress>& importSlots) : bytes(bytes), headers(headers), pointers(pointers), importSlots(importSlots) {}
|
||||
|
||||
std::vector<StrictCodeRegion> Read() {
|
||||
std::vector<StrictCodeRegion> result;
|
||||
for (const auto& segment : headers) {
|
||||
if (segment.Type != 0x6474E550 || segment.FileSize == 0) continue;
|
||||
auto position = segment.MappedAddress;
|
||||
const auto version = read<std::uint8_t>(position);
|
||||
const auto frameEncoding = read<std::uint8_t>(position);
|
||||
const auto countEncoding = read<std::uint8_t>(position);
|
||||
const auto tableEncoding = read<std::uint8_t>(position);
|
||||
if (version != 1 || frameEncoding == 0xFF || countEncoding == 0xFF || tableEncoding == 0xFF)
|
||||
throw RelinkerException("Strict filter: unsupported EH frame header", segment.MappedAddress);
|
||||
encoded(position, frameEncoding, segment.MappedAddress);
|
||||
const auto count = encoded(position, countEncoding, segment.MappedAddress);
|
||||
if (count > segment.FileSize / 2) throw RelinkerException("Strict filter: invalid EH frame count", position);
|
||||
for (std::uint64_t index = 0; index < count; ++index) {
|
||||
const auto begin = encoded(position, tableEncoding, segment.MappedAddress);
|
||||
const auto fde = encoded(position, tableEncoding, segment.MappedAddress);
|
||||
if (position > segment.MappedAddress + segment.FileSize) throw RelinkerException("Strict filter: EH frame table exceeds segment", position);
|
||||
result.push_back(readFde(begin, fde));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<std::uint8_t>& bytes;
|
||||
const std::vector<ProgramHeader>& headers;
|
||||
const std::map<VirtualAddress, VirtualAddress>& pointers;
|
||||
const std::set<VirtualAddress>& importSlots;
|
||||
std::map<VirtualAddress, Cie> cies;
|
||||
|
||||
template<typename TValue>
|
||||
TValue read(VirtualAddress& address) const {
|
||||
for (const auto& segment : headers) {
|
||||
if (segment.Type != 1 || address < segment.MappedAddress || address - segment.MappedAddress > segment.FileSize || sizeof(TValue) > segment.FileSize - (address - segment.MappedAddress)) continue;
|
||||
const auto offset = segment.Offset + address - segment.MappedAddress;
|
||||
if (offset > bytes.size() || sizeof(TValue) > bytes.size() - offset) throw RelinkerException("Strict filter: unwind data exceeds file", address);
|
||||
TValue value;
|
||||
std::memcpy(&value, bytes.data() + offset, sizeof(value));
|
||||
address += sizeof(value);
|
||||
return value;
|
||||
}
|
||||
throw RelinkerException("Strict filter: unwind address is not file-backed", address);
|
||||
}
|
||||
|
||||
std::uint64_t leb(VirtualAddress& position, bool signedValue = false) const {
|
||||
std::uint64_t value = 0;
|
||||
unsigned shift = 0;
|
||||
for (;;) {
|
||||
const auto byte = read<std::uint8_t>(position);
|
||||
if (shift == 63 && ((byte & 0x7F) != 0 && (byte & 0x7F) != 1 && (!signedValue || (byte & 0x7F) != 0x7F)))
|
||||
throw RelinkerException("Strict filter: overflowing LEB128 value", position);
|
||||
value |= static_cast<std::uint64_t>(byte & 0x7F) << shift;
|
||||
shift += 7;
|
||||
if ((byte & 0x80) == 0) {
|
||||
if (signedValue && (byte & 0x40) != 0 && shift < 64) value |= (~std::uint64_t{0}) << shift;
|
||||
return value;
|
||||
}
|
||||
if (shift >= 64) throw RelinkerException("Strict filter: unterminated LEB128 value", position);
|
||||
}
|
||||
}
|
||||
|
||||
std::uint64_t encoded(VirtualAddress& position, std::uint8_t encoding, VirtualAddress dataBase = 0, VirtualAddress functionBase = 0) const {
|
||||
const auto field = position;
|
||||
std::uint64_t value;
|
||||
switch (encoding & 0x0F) {
|
||||
case 0: value = read<std::uint64_t>(position); break;
|
||||
case 1: value = leb(position); break;
|
||||
case 2: value = read<std::uint16_t>(position); break;
|
||||
case 3: value = read<std::uint32_t>(position); break;
|
||||
case 4: value = read<std::uint64_t>(position); break;
|
||||
case 9: value = leb(position, true); break;
|
||||
case 10: value = static_cast<std::uint64_t>(static_cast<std::int64_t>(read<std::int16_t>(position))); break;
|
||||
case 11: value = static_cast<std::uint64_t>(static_cast<std::int64_t>(read<std::int32_t>(position))); break;
|
||||
case 12: value = static_cast<std::uint64_t>(read<std::int64_t>(position)); break;
|
||||
default: throw RelinkerException("Strict filter: unsupported unwind pointer format", field);
|
||||
}
|
||||
if (value == 0) return 0;
|
||||
switch (encoding & 0x70) {
|
||||
case 0: break;
|
||||
case 0x10: value += field; break;
|
||||
case 0x30: value += dataBase; break;
|
||||
case 0x40: value += functionBase; break;
|
||||
default: throw RelinkerException("Strict filter: unsupported unwind pointer base", field);
|
||||
}
|
||||
if ((encoding & 0x80) != 0 && !importSlots.contains(value)) {
|
||||
const auto pointer = pointers.find(value);
|
||||
if (pointer != pointers.end()) value = pointer->second;
|
||||
else value = read<std::uint64_t>(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
Cie readCie(VirtualAddress address) {
|
||||
if (const auto found = cies.find(address); found != cies.end()) return found->second;
|
||||
auto position = address;
|
||||
const auto length = read<std::uint32_t>(position);
|
||||
if (length == 0xFFFFFFFF || length < 8) throw RelinkerException("Strict filter: unsupported CIE record size", address);
|
||||
const auto end = position + length;
|
||||
if (read<std::uint32_t>(position) != 0) throw RelinkerException("Strict filter: invalid CIE identifier", address);
|
||||
const auto version = read<std::uint8_t>(position);
|
||||
if (version != 1 && version != 3 && version != 4) throw RelinkerException("Strict filter: unsupported CIE version", address);
|
||||
std::string augmentation;
|
||||
for (;;) {
|
||||
if (position >= end) throw RelinkerException("Strict filter: unterminated CIE augmentation", address);
|
||||
const auto character = read<std::uint8_t>(position);
|
||||
if (character == 0) break;
|
||||
augmentation.push_back(static_cast<char>(character));
|
||||
}
|
||||
if (version == 4 && (read<std::uint8_t>(position) != 8 || read<std::uint8_t>(position) != 0))
|
||||
throw RelinkerException("Strict filter: unsupported CIE address size", address);
|
||||
leb(position);
|
||||
leb(position, true);
|
||||
if (version == 1) read<std::uint8_t>(position);
|
||||
else leb(position);
|
||||
Cie cie;
|
||||
if (!augmentation.empty()) {
|
||||
if (augmentation.front() != 'z') throw RelinkerException("Strict filter: unsupported CIE augmentation", address);
|
||||
cie.Augmented = true;
|
||||
const auto size = leb(position);
|
||||
const auto augmentationEnd = position + size;
|
||||
if (augmentationEnd > end) throw RelinkerException("Strict filter: CIE augmentation exceeds record", address);
|
||||
for (std::size_t index = 1; index < augmentation.size(); ++index) {
|
||||
switch (augmentation[index]) {
|
||||
case 'L': cie.LsdaEncoding = read<std::uint8_t>(position); break;
|
||||
case 'R': cie.PointerEncoding = read<std::uint8_t>(position); break;
|
||||
case 'P': {
|
||||
const auto encoding = read<std::uint8_t>(position);
|
||||
cie.Personality = encoded(position, encoding);
|
||||
break;
|
||||
}
|
||||
case 'S': break;
|
||||
default: throw RelinkerException("Strict filter: unknown CIE augmentation field", position);
|
||||
}
|
||||
}
|
||||
if (position != augmentationEnd) throw RelinkerException("Strict filter: CIE augmentation size mismatch", address);
|
||||
}
|
||||
if (position > end) throw RelinkerException("Strict filter: CIE fields exceed record", address);
|
||||
cies.emplace(address, cie);
|
||||
return cie;
|
||||
}
|
||||
|
||||
StrictCodeRegion readFde(VirtualAddress expectedBegin, VirtualAddress address) {
|
||||
auto position = address;
|
||||
const auto length = read<std::uint32_t>(position);
|
||||
if (length == 0xFFFFFFFF || length < 8) throw RelinkerException("Strict filter: unsupported FDE record size", address);
|
||||
const auto end = position + length;
|
||||
const auto cieField = position;
|
||||
const auto cieOffset = read<std::uint32_t>(position);
|
||||
if (cieOffset > cieField) throw RelinkerException("Strict filter: invalid FDE CIE offset", address);
|
||||
const auto cie = readCie(cieField - cieOffset);
|
||||
const auto begin = encoded(position, cie.PointerEncoding);
|
||||
const auto size = encoded(position, cie.PointerEncoding & 0x0F);
|
||||
if (begin != expectedBegin || size == 0 || size > std::numeric_limits<VirtualAddress>::max() - begin)
|
||||
throw RelinkerException("Strict filter: inconsistent FDE function range", address);
|
||||
StrictCodeRegion region{begin, begin + size, {}};
|
||||
if (cie.Personality != 0) region.ExtraTargets.push_back(cie.Personality);
|
||||
if (cie.Augmented) {
|
||||
const auto augmentationSize = leb(position);
|
||||
const auto augmentationEnd = position + augmentationSize;
|
||||
if (augmentationEnd > end) throw RelinkerException("Strict filter: FDE augmentation exceeds record", address);
|
||||
if (cie.LsdaEncoding != 0xFF) {
|
||||
const auto lsda = encoded(position, cie.LsdaEncoding, 0, begin);
|
||||
if (lsda != 0) readLsda(lsda, region);
|
||||
}
|
||||
if (position > augmentationEnd) throw RelinkerException("Strict filter: FDE augmentation size mismatch", address);
|
||||
}
|
||||
if (position > end) throw RelinkerException("Strict filter: FDE fields exceed record", address);
|
||||
return region;
|
||||
}
|
||||
|
||||
void readLsda(VirtualAddress address, StrictCodeRegion& region) const {
|
||||
auto position = address;
|
||||
const auto landingEncoding = read<std::uint8_t>(position);
|
||||
const auto landingBase = landingEncoding == 0xFF ? region.Begin : encoded(position, landingEncoding, 0, region.Begin);
|
||||
const auto typeEncoding = read<std::uint8_t>(position);
|
||||
if (typeEncoding != 0xFF) leb(position);
|
||||
const auto callEncoding = read<std::uint8_t>(position);
|
||||
if ((callEncoding & 0xF0) != 0) throw RelinkerException("Strict filter: unsupported LSDA call-site encoding", address);
|
||||
const auto tableSize = leb(position);
|
||||
if (tableSize > bytes.size()) throw RelinkerException("Strict filter: invalid LSDA table size", address);
|
||||
const auto tableEnd = position + tableSize;
|
||||
while (position < tableEnd) {
|
||||
encoded(position, callEncoding);
|
||||
encoded(position, callEncoding);
|
||||
const auto landing = encoded(position, callEncoding);
|
||||
leb(position);
|
||||
if (landing != 0) region.ExtraTargets.push_back(landingBase + landing);
|
||||
}
|
||||
if (position != tableEnd) throw RelinkerException("Strict filter: LSDA call-site table size mismatch", address);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::vector<StrictCodeRegion> ReadExceptionFunctions(const std::vector<std::uint8_t>& bytes, const std::vector<ProgramHeader>& headers, const std::map<VirtualAddress, VirtualAddress>& pointers, const std::set<VirtualAddress>& importSlots) {
|
||||
return Reader(bytes, headers, pointers, importSlots).Read();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/PltCompactor.hpp>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
CompactedPlt CompactPlt(const std::vector<NidReference>& originalReferences, const std::vector<NidReference>& keptReferences, const std::vector<std::uint8_t>& text, VirtualAddress textVaddr, FileByteOffset textOffset, FileByteOffset tableOffset) {
|
||||
CompactedPlt result;
|
||||
result.References = keptReferences;
|
||||
std::map<VirtualAddress, std::uint32_t> originalSlots;
|
||||
for (const auto& reference : originalReferences) {
|
||||
if (reference.RelocationTypeValue != 7) continue;
|
||||
if (reference.RelocationTableOffset < tableOffset || (reference.RelocationTableOffset - tableOffset) % 24 != 0 || (reference.RelocationTableOffset - tableOffset) / 24 > std::numeric_limits<std::uint32_t>::max())
|
||||
throw RelinkerException("Strict filter: invalid original PLT relocation index", reference.RelocationTableOffset);
|
||||
if (!originalSlots.emplace(reference.RelocationAddress, static_cast<std::uint32_t>((reference.RelocationTableOffset - tableOffset) / 24)).second)
|
||||
throw RelinkerException("Strict filter: duplicate original PLT slot", reference.RelocationAddress);
|
||||
}
|
||||
std::map<VirtualAddress, std::uint32_t> newSlots;
|
||||
for (auto& reference : result.References) {
|
||||
if (reference.RelocationTypeValue != 7) continue;
|
||||
if (!originalSlots.contains(reference.RelocationAddress) || !newSlots.emplace(reference.RelocationAddress, result.SlotCount).second)
|
||||
throw RelinkerException("Strict filter: invalid retained PLT slot", reference.RelocationAddress);
|
||||
reference.RelocationTableOffset = tableOffset + static_cast<std::uint64_t>(result.SlotCount++) * 24;
|
||||
}
|
||||
std::set<VirtualAddress> found;
|
||||
for (std::size_t offset = 0; offset + 16 <= text.size(); ++offset) {
|
||||
if (text[offset] != 0xFF || text[offset + 1] != 0x25) continue;
|
||||
std::int32_t displacement;
|
||||
std::memcpy(&displacement, text.data() + offset + 2, sizeof(displacement));
|
||||
const auto slot = textVaddr + offset + 6 + static_cast<std::uint64_t>(static_cast<std::int64_t>(displacement));
|
||||
const auto original = originalSlots.find(slot);
|
||||
if (original == originalSlots.end()) continue;
|
||||
if (text[offset + 6] != 0x68 || text[offset + 11] != 0xE9) continue;
|
||||
std::uint32_t index;
|
||||
std::memcpy(&index, text.data() + offset + 7, sizeof(index));
|
||||
if (index != original->second) throw RelinkerException("Strict filter: PLT thunk index disagrees with its relocation", textVaddr + offset);
|
||||
found.insert(slot);
|
||||
if (const auto kept = newSlots.find(slot); kept != newSlots.end()) {
|
||||
std::vector<std::uint8_t> bytes(sizeof(std::uint32_t));
|
||||
std::memcpy(bytes.data(), &kept->second, bytes.size());
|
||||
result.Patches.push_back({textOffset + offset + 7, std::move(bytes)});
|
||||
} else {
|
||||
result.Patches.push_back({textOffset + offset, {0x0F, 0x0B}});
|
||||
result.Patches.push_back({textOffset + offset + 6, {0x0F, 0x0B}});
|
||||
}
|
||||
offset += 15;
|
||||
}
|
||||
for (const auto& [slot, index] : originalSlots)
|
||||
if (!found.contains(slot)) throw RelinkerException("Strict filter: canonical PLT thunk was not found", slot);
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
#include <relinker/analysis/UnusedNidFilter.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/StrictReachability.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <relinker/parsing/ElfReader.hpp>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
|
||||
namespace Relinker {
|
||||
|
||||
namespace {
|
||||
|
||||
class StrictImage {
|
||||
public:
|
||||
explicit StrictImage(const std::vector<std::uint8_t>& bytes) : bytes(bytes), reader(bytes) {
|
||||
if (bytes.size() < 64 || bytes[4] != 2 || bytes[5] != 1 || bytes[6] != 1)
|
||||
throw RelinkerException("Strict filter: expected a little-endian ELF64 image");
|
||||
const auto header = reader.ReadHeader();
|
||||
if (header.Machine != 62 || header.ProgramHeaderEntrySize != 56)
|
||||
throw RelinkerException("Strict filter: unsupported ELF machine or program header format");
|
||||
requireRange(header.ProgramHeaderOffset, static_cast<std::uint64_t>(header.ProgramHeaderCount) * 56);
|
||||
headers = reader.ReadProgramHeaders();
|
||||
for (const auto& segment : headers) {
|
||||
requireRange(segment.Offset, segment.FileSize);
|
||||
if (segment.MemorySize > std::numeric_limits<VirtualAddress>::max() - segment.MappedAddress)
|
||||
throw RelinkerException("Strict filter: segment address overflow", segment.MappedAddress);
|
||||
if (segment.Type == 1 && segment.FileSize > segment.MemorySize)
|
||||
throw RelinkerException("Strict filter: invalid load segment size", segment.Offset);
|
||||
if (segment.Type != 2) continue;
|
||||
if (!tags.empty()) throw RelinkerException("Strict filter: multiple dynamic segments");
|
||||
if (segment.FileSize % 16 != 0) throw RelinkerException("Strict filter: truncated dynamic segment", segment.Offset);
|
||||
bool terminated = false;
|
||||
for (std::uint64_t offset = 0; offset < segment.FileSize; offset += 16) {
|
||||
const auto tag = read<std::int64_t>(segment.Offset + offset);
|
||||
if (tag == 0) {
|
||||
terminated = true;
|
||||
break;
|
||||
}
|
||||
if (tag == 1 || (tag >= 0x61000000 && tag != 0x6100002F && tag != 0x61000031 && tag != 0x61000033 && tag != 0x61000039 && tag != 0x6100003B && tag != 0x6100003F)) continue;
|
||||
if (!tags.emplace(tag, read<std::uint64_t>(segment.Offset + offset + 8)).second)
|
||||
throw RelinkerException("Strict filter: duplicate dynamic tag", segment.Offset + offset);
|
||||
}
|
||||
if (!terminated) throw RelinkerException("Strict filter: unterminated dynamic segment", segment.Offset);
|
||||
}
|
||||
}
|
||||
|
||||
UnusedNidFilter::StrictReachabilityInput Build(const std::vector<NidReference>& references, const std::vector<std::uint8_t>& text, VirtualAddress textVaddr) const {
|
||||
UnusedNidFilter::StrictReachabilityInput input{text, textVaddr, {reader.ReadHeader().EntryPoint}, {}, {}, {}, {}};
|
||||
std::size_t codeSegments = 0;
|
||||
for (const auto& segment : headers) {
|
||||
if (segment.Type == 1 && (segment.Flags & 1) != 0) {
|
||||
++codeSegments;
|
||||
if (segment.MappedAddress != textVaddr || segment.FileSize != text.size() || segment.MemorySize != segment.FileSize || (segment.Flags & 2) != 0)
|
||||
throw RelinkerException("Strict filter: unsupported executable segment layout", segment.MappedAddress);
|
||||
}
|
||||
}
|
||||
if (codeSegments != 1) throw RelinkerException("Strict filter: requires exactly one immutable code segment");
|
||||
for (const auto& reference : references) {
|
||||
if (reference.RelocationTypeValue != 1 && reference.RelocationTypeValue != 6 && reference.RelocationTypeValue != 7)
|
||||
throw RelinkerException("Strict filter: unsupported imported relocation", reference.RelocationAddress);
|
||||
if (!input.ImportSlots.insert(reference.RelocationAddress).second)
|
||||
throw RelinkerException("Strict filter: duplicate imported slot", reference.RelocationAddress);
|
||||
}
|
||||
|
||||
const auto rela = table(7, 0x6100002F);
|
||||
const auto relaSize = value(8, 0x61000031);
|
||||
if (rela.has_value() != relaSize.has_value()) throw RelinkerException("Strict filter: incomplete relocation table metadata");
|
||||
if (rela) {
|
||||
if (value(9, 0x61000033) != 24 || *relaSize % 24 != 0)
|
||||
throw RelinkerException("Strict filter: invalid RELA entry size");
|
||||
requireRange(*rela, *relaSize);
|
||||
for (std::uint64_t offset = 0; offset < *relaSize; offset += 24) {
|
||||
const auto slot = read<std::uint64_t>(*rela + offset);
|
||||
const auto info = read<std::uint64_t>(*rela + offset + 8);
|
||||
if (static_cast<std::uint32_t>(info) != 8) continue;
|
||||
if (info >> 32) throw RelinkerException("Strict filter: relative relocation has a symbol", slot);
|
||||
const auto target = read<std::uint64_t>(*rela + offset + 16);
|
||||
if (input.ImportSlots.contains(slot) || !input.Pointers.emplace(slot, target).second)
|
||||
throw RelinkerException("Strict filter: overlapping pointer relocations", slot);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto tag : {12, 13}) {
|
||||
if (const auto entry = value(tag, 0x60000000 + tag); entry && *entry != 0) input.Entries.push_back(*entry);
|
||||
}
|
||||
for (const auto& [addressTag, sizeTag] : {std::pair{25, 27}, std::pair{26, 28}, std::pair{32, 33}}) {
|
||||
const auto array = value(addressTag, 0x60000000 + addressTag);
|
||||
const auto size = value(sizeTag, 0x60000000 + sizeTag);
|
||||
if (array.has_value() != size.has_value()) throw RelinkerException("Strict filter: incomplete constructor array metadata");
|
||||
if (!array) continue;
|
||||
if (*size % 8 != 0) throw RelinkerException("Strict filter: invalid constructor array size", *array);
|
||||
for (std::uint64_t offset = 0; offset < *size; offset += 8) {
|
||||
const auto slot = *array + offset;
|
||||
const auto position = input.Pointers.find(slot);
|
||||
const auto entry = position == input.Pointers.end() ? read<std::uint64_t>(fileOffset(slot, 8)) : position->second;
|
||||
if (entry != 0 && entry != std::numeric_limits<std::uint64_t>::max()) input.Entries.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
const auto symbols = table(6, 0x61000039);
|
||||
if (symbols) {
|
||||
if (value(11, 0x6100003B) != 24) throw RelinkerException("Strict filter: invalid symbol entry size");
|
||||
std::uint64_t count = 0;
|
||||
if (const auto size = tags.find(0x6100003F); size != tags.end()) {
|
||||
if (size->second % 24 != 0) throw RelinkerException("Strict filter: invalid symbol table size");
|
||||
count = size->second / 24;
|
||||
} else if (const auto hash = tags.find(4); hash != tags.end()) {
|
||||
count = read<std::uint32_t>(fileOffset(hash->second, 8) + 4);
|
||||
} else {
|
||||
throw RelinkerException("Strict filter: cannot establish dynamic symbol table bounds");
|
||||
}
|
||||
if (count > bytes.size() / 24) throw RelinkerException("Strict filter: symbol count exceeds image size");
|
||||
requireRange(*symbols, count * 24);
|
||||
for (std::uint64_t index = 0; index < count; ++index) {
|
||||
const auto position = *symbols + index * 24;
|
||||
const auto section = read<std::uint16_t>(position + 6);
|
||||
const auto info = read<std::uint8_t>(position + 4);
|
||||
const auto type = info & 15;
|
||||
if (type == 10) throw RelinkerException("Strict filter: IFUNC resolver is not modeled", position);
|
||||
if (section == 0 || (info >> 4) == 0 || type != 2) continue;
|
||||
input.Entries.push_back(read<std::uint64_t>(position + 8));
|
||||
}
|
||||
}
|
||||
input.Functions = UnusedNidFilter::ReadExceptionFunctions(bytes, headers, input.Pointers, input.ImportSlots);
|
||||
for (const auto& segment : headers) {
|
||||
if (segment.Type != 1 || (segment.Flags & 1) != 0 || (segment.Flags & 6) == 0 || segment.FileSize == 0) continue;
|
||||
input.Data.push_back({segment.MappedAddress, reader.ReadSegment(segment)});
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<std::uint8_t>& bytes;
|
||||
ElfReader reader;
|
||||
std::vector<ProgramHeader> headers;
|
||||
std::map<std::int64_t, std::uint64_t> tags;
|
||||
|
||||
void requireRange(std::uint64_t offset, std::uint64_t size) const {
|
||||
if (offset > bytes.size() || size > bytes.size() - offset) throw RelinkerException("Strict filter: file range out of bounds", offset);
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
TValue read(std::uint64_t offset) const {
|
||||
requireRange(offset, sizeof(TValue));
|
||||
TValue result;
|
||||
std::memcpy(&result, bytes.data() + offset, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::uint64_t fileOffset(VirtualAddress address, std::uint64_t size) const {
|
||||
for (const auto& segment : headers)
|
||||
if (segment.Type == 1 && address >= segment.MappedAddress && address - segment.MappedAddress <= segment.FileSize && size <= segment.FileSize - (address - segment.MappedAddress))
|
||||
return segment.Offset + address - segment.MappedAddress;
|
||||
throw RelinkerException("Strict filter: virtual range is not file-backed", address);
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t> value(std::int64_t standardTag, std::int64_t osTag) const {
|
||||
const auto standard = tags.find(standardTag);
|
||||
const auto os = tags.find(osTag);
|
||||
if (standard != tags.end() && os != tags.end()) throw RelinkerException("Strict filter: conflicting standard and OS dynamic tags");
|
||||
if (standard != tags.end()) return standard->second;
|
||||
if (os != tags.end()) return os->second;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::uint64_t> table(std::int64_t standardTag, std::int64_t osTag) const {
|
||||
const auto address = value(standardTag, osTag);
|
||||
if (!address) return std::nullopt;
|
||||
return tags.contains(osTag) ? *address : fileOffset(*address, 0);
|
||||
}
|
||||
};
|
||||
|
||||
class StrictNidFilter : public IUnusedNidFilter {
|
||||
public:
|
||||
std::vector<NidReference> Filter(const std::vector<NidReference>& nidRefs, const std::vector<std::uint8_t>& elfBytes, const std::vector<std::uint8_t>& textSection, VirtualAddress textVAddr) override {
|
||||
const StrictImage image(elfBytes);
|
||||
const auto input = image.Build(nidRefs, textSection, textVAddr);
|
||||
const auto analysis = UnusedNidFilter::AnalyzeStrictReachability(input);
|
||||
std::vector<NidReference> nonPlt;
|
||||
for (const auto& reference : nidRefs)
|
||||
if (reference.RelocationTypeValue != 7) nonPlt.push_back(reference);
|
||||
const auto originalNonPltCount = nonPlt.size();
|
||||
if (!nonPlt.empty()) nonPlt = MakeUnusedNidFilter()->Filter(nonPlt, elfBytes, textSection, textVAddr);
|
||||
std::set<VirtualAddress> nonPltSlots;
|
||||
for (const auto& reference : nonPlt) nonPltSlots.insert(reference.RelocationAddress);
|
||||
std::cout << "CFG/GOT filtering: " << originalNonPltCount << " -> " << nonPlt.size() << "; filtered=" << originalNonPltCount - nonPlt.size() << "\n";
|
||||
std::vector<NidReference> result;
|
||||
for (const auto& reference : nidRefs) {
|
||||
if (reference.RelocationTypeValue == 7 ? analysis.ImportSlots.contains(reference.RelocationAddress) : nonPltSlots.contains(reference.RelocationAddress)) result.push_back(reference);
|
||||
}
|
||||
std::cout << "Strict reachability: " << analysis.Instructions.size() << " instructions; conservative indirect transfers=" << analysis.IndirectTransfers << "\n";
|
||||
std::cout << "Function graph: " << analysis.LiveRegions << "/" << analysis.TotalRegions << " live regions; address-taken roots=" << analysis.AddressTakenRoots << "; unwind functions=" << input.Functions.size() << "\n";
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
std::shared_ptr<IUnusedNidFilter> MakeStrictUnusedNidFilter() {
|
||||
return std::make_shared<StrictNidFilter>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
#include <relinker/analysis/UnusedNidFilter/StrictReachability.hpp>
|
||||
#include <codegen/x86/X64InstructionDecoder.hpp>
|
||||
#include <codegen/CodegenException.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <limits>
|
||||
#include <iterator>
|
||||
|
||||
namespace Relinker::UnusedNidFilter {
|
||||
|
||||
namespace {
|
||||
|
||||
struct Instruction {
|
||||
VirtualAddress Address;
|
||||
Codegen::DecodedInstructionInfo Info;
|
||||
};
|
||||
|
||||
struct Region {
|
||||
VirtualAddress Begin;
|
||||
VirtualAddress End;
|
||||
std::set<VirtualAddress> Edges;
|
||||
std::set<VirtualAddress> Imports;
|
||||
std::vector<VirtualAddress> Instructions;
|
||||
std::size_t IndirectTransfers = 0;
|
||||
};
|
||||
|
||||
bool endsFlow(Codegen::ControlFlowKind kind) {
|
||||
using enum Codegen::ControlFlowKind;
|
||||
return kind == Return || kind == Trap || kind == UnconditionalJump || kind == IndirectJump;
|
||||
}
|
||||
|
||||
class Analyzer {
|
||||
public:
|
||||
explicit Analyzer(const StrictReachabilityInput& input) : input(input) {}
|
||||
|
||||
StrictReachabilityResult Run() {
|
||||
if (input.Text.empty() || input.Text.size() > std::numeric_limits<VirtualAddress>::max() - input.TextVaddr)
|
||||
throw RelinkerException("Strict filter: invalid text range");
|
||||
if (input.Entries.empty()) throw RelinkerException("Strict filter: no entry points");
|
||||
buildRegions();
|
||||
buildEdges();
|
||||
for (const auto& [slot, target] : input.Pointers) addAddressTaken(target);
|
||||
collectDataPointers();
|
||||
collectRelativeTables();
|
||||
std::deque<VirtualAddress> pending;
|
||||
std::set<VirtualAddress> live;
|
||||
const auto enqueue = [&](VirtualAddress target) {
|
||||
if (input.ImportSlots.contains(target)) result.ImportSlots.insert(target);
|
||||
if (!isCode(target)) return;
|
||||
const auto begin = owner(target).Begin;
|
||||
if (live.insert(begin).second) pending.push_back(begin);
|
||||
};
|
||||
for (const auto entry : input.Entries) {
|
||||
if (!isCode(entry)) throw RelinkerException("Strict filter: entry point lies outside code", entry);
|
||||
enqueue(entry);
|
||||
}
|
||||
for (const auto root : addressTaken) enqueue(root);
|
||||
while (!pending.empty()) {
|
||||
const auto begin = pending.front();
|
||||
pending.pop_front();
|
||||
const auto& region = regions.at(begin);
|
||||
result.Instructions.insert(region.Instructions.begin(), region.Instructions.end());
|
||||
result.ImportSlots.insert(region.Imports.begin(), region.Imports.end());
|
||||
result.IndirectTransfers += region.IndirectTransfers;
|
||||
for (const auto target : region.Edges) enqueue(target);
|
||||
}
|
||||
result.LiveRegions = live.size();
|
||||
result.TotalRegions = regions.size();
|
||||
result.AddressTakenRoots = addressTaken.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
const StrictReachabilityInput& input;
|
||||
Codegen::X64InstructionDecoder decoder;
|
||||
std::map<VirtualAddress, Region> regions;
|
||||
std::set<VirtualAddress> addressTaken;
|
||||
std::set<VirtualAddress> tableBases;
|
||||
StrictReachabilityResult result;
|
||||
|
||||
bool isCode(VirtualAddress address) const {
|
||||
return address >= input.TextVaddr && address - input.TextVaddr < input.Text.size();
|
||||
}
|
||||
|
||||
Region& owner(VirtualAddress address) {
|
||||
auto position = regions.upper_bound(address);
|
||||
if (position == regions.begin() || std::prev(position)->second.End <= address)
|
||||
throw RelinkerException("Strict filter: code address has no region", address);
|
||||
return std::prev(position)->second;
|
||||
}
|
||||
|
||||
Instruction decode(VirtualAddress address, VirtualAddress end) const {
|
||||
const auto offset = static_cast<std::size_t>(address - input.TextVaddr);
|
||||
Codegen::DecodedInstructionInfo info;
|
||||
try {
|
||||
info = decoder.DecodeInstruction(input.Text.data() + offset, static_cast<std::size_t>(end - address));
|
||||
} catch (const Codegen::CodegenException& error) {
|
||||
throw RelinkerException(std::string("Strict filter: ") + error.what(), address);
|
||||
}
|
||||
if (info.Length == 0 || info.Length > end - address) throw RelinkerException("Strict filter: instruction crosses a region boundary", address);
|
||||
return {address, info};
|
||||
}
|
||||
|
||||
bool isZeroPadding(VirtualAddress begin, VirtualAddress end) const {
|
||||
const auto first = input.Text.begin() + static_cast<std::ptrdiff_t>(begin - input.TextVaddr);
|
||||
const auto last = input.Text.begin() + static_cast<std::ptrdiff_t>(end - input.TextVaddr);
|
||||
return std::all_of(first, last, [](std::uint8_t byte) { return byte == 0; });
|
||||
}
|
||||
|
||||
void addGap(VirtualAddress begin, VirtualAddress end) {
|
||||
VirtualAddress regionBegin = begin;
|
||||
for (auto address = begin; address < end;) {
|
||||
if (isZeroPadding(address, end)) break;
|
||||
const auto instruction = decode(address, end);
|
||||
address += instruction.Info.Length;
|
||||
if (endsFlow(instruction.Info.FlowKind)) {
|
||||
regions.emplace(regionBegin, Region{regionBegin, address, {}, {}, {}});
|
||||
regionBegin = address;
|
||||
}
|
||||
}
|
||||
if (regionBegin != end) regions.emplace(regionBegin, Region{regionBegin, end, {}, {}, {}});
|
||||
}
|
||||
|
||||
void buildRegions() {
|
||||
auto functions = input.Functions;
|
||||
std::sort(functions.begin(), functions.end(), [](const auto& left, const auto& right) { return left.Begin < right.Begin; });
|
||||
auto previousEnd = input.TextVaddr;
|
||||
const auto textEnd = input.TextVaddr + input.Text.size();
|
||||
for (const auto& function : functions) {
|
||||
if (function.Begin < previousEnd || function.End <= function.Begin || function.End > textEnd)
|
||||
throw RelinkerException("Strict filter: invalid or overlapping function range", function.Begin);
|
||||
addGap(previousEnd, function.Begin);
|
||||
Region region{function.Begin, function.End, {}, {}, {}};
|
||||
region.Edges.insert(function.ExtraTargets.begin(), function.ExtraTargets.end());
|
||||
regions.emplace(function.Begin, std::move(region));
|
||||
previousEnd = function.End;
|
||||
}
|
||||
addGap(previousEnd, textEnd);
|
||||
}
|
||||
|
||||
void addAddressTaken(VirtualAddress target) {
|
||||
if (isCode(target) || input.ImportSlots.contains(target)) addressTaken.insert(target);
|
||||
}
|
||||
|
||||
void buildEdges() {
|
||||
for (auto& [begin, region] : regions) {
|
||||
Codegen::ControlFlowKind lastFlow = Codegen::ControlFlowKind::Sequential;
|
||||
for (auto address = begin; address < region.End;) {
|
||||
if (isZeroPadding(address, region.End)) {
|
||||
lastFlow = Codegen::ControlFlowKind::Sequential;
|
||||
break;
|
||||
}
|
||||
const auto instruction = decode(address, region.End);
|
||||
const auto& info = instruction.Info;
|
||||
const auto next = address + info.Length;
|
||||
const auto offset = static_cast<std::size_t>(address - input.TextVaddr);
|
||||
region.Instructions.push_back(address);
|
||||
if (info.HasBranchTarget && !info.HasRipRelativeDisp) {
|
||||
const auto target = next + static_cast<VirtualAddress>(info.BranchDisp);
|
||||
if (!isCode(target)) throw RelinkerException("Strict filter: direct branch leaves code", address);
|
||||
region.Edges.insert(target);
|
||||
}
|
||||
if (info.HasRipRelativeDisp) {
|
||||
std::int32_t displacement;
|
||||
std::memcpy(&displacement, input.Text.data() + offset + info.RipRelativeDispOffset, sizeof(displacement));
|
||||
const auto target = next + static_cast<VirtualAddress>(static_cast<std::int64_t>(displacement));
|
||||
if (input.ImportSlots.contains(target)) region.Imports.insert(target);
|
||||
if (!info.IsTwoByteOpcode && info.Opcode == 0x8D) {
|
||||
addAddressTaken(target);
|
||||
tableBases.insert(target);
|
||||
}
|
||||
if (const auto pointer = input.Pointers.find(target); pointer != input.Pointers.end()) region.Edges.insert(pointer->second);
|
||||
}
|
||||
using enum Codegen::ControlFlowKind;
|
||||
if (info.FlowKind == IndirectCall || info.FlowKind == IndirectJump || ((info.FlowKind == Call || info.FlowKind == UnconditionalJump) && info.HasRipRelativeDisp)) ++region.IndirectTransfers;
|
||||
if (!info.IsTwoByteOpcode && ((info.Opcode >= 0xB8 && info.Opcode <= 0xBF) || info.Opcode == 0x68 || info.Opcode == 0xC7)) {
|
||||
for (const auto width : {4u, 8u}) {
|
||||
if (info.Length <= width) continue;
|
||||
std::uint64_t value = 0;
|
||||
std::memcpy(&value, input.Text.data() + offset + info.Length - width, width);
|
||||
addAddressTaken(value);
|
||||
}
|
||||
}
|
||||
lastFlow = info.FlowKind;
|
||||
address = next;
|
||||
}
|
||||
if (!endsFlow(lastFlow) && isCode(region.End)) region.Edges.insert(region.End);
|
||||
}
|
||||
}
|
||||
|
||||
void collectDataPointers() {
|
||||
for (const auto& data : input.Data) {
|
||||
for (std::size_t offset = 0; offset + 8 <= data.Bytes.size(); ++offset) {
|
||||
std::uint64_t target;
|
||||
std::memcpy(&target, data.Bytes.data() + offset, sizeof(target));
|
||||
addAddressTaken(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collectRelativeTables() {
|
||||
for (const auto base : tableBases) {
|
||||
for (const auto& data : input.Data) {
|
||||
if (base < data.Address || base - data.Address >= data.Bytes.size()) continue;
|
||||
for (auto offset = static_cast<std::size_t>(base - data.Address); offset + 4 <= data.Bytes.size(); offset += 4) {
|
||||
std::int32_t displacement;
|
||||
std::memcpy(&displacement, data.Bytes.data() + offset, sizeof(displacement));
|
||||
const auto target = base + static_cast<std::uint64_t>(static_cast<std::int64_t>(displacement));
|
||||
if (!isCode(target)) break;
|
||||
addAddressTaken(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
StrictReachabilityResult AnalyzeStrictReachability(const StrictReachabilityInput& input) {
|
||||
return Analyzer(input).Run();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#include <relinker/pipeline/RelinkerPipeline.hpp>
|
||||
#include <relinker/analysis/ValidationPolicy.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/PltCompactor.hpp>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
|
@ -197,9 +198,14 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
static constexpr std::uint32_t R_X86_64_JUMP_SLOT = 7;
|
||||
|
||||
const std::size_t originalNidCount = nidRefs.size();
|
||||
const auto originalNidRefs = nidRefs;
|
||||
std::cout << "NID input: " << originalNidCount << " references\n";
|
||||
|
||||
if (unusedFilterLevel != 0) {
|
||||
if (unusedFilterLevel == 2) {
|
||||
nidRefs = _unusedNidFilter->Filter(nidRefs, raw, textSection, textVAddr);
|
||||
if (nidRefs.size() > originalNidCount) throw RelinkerException("Strict NID filter increased the reference count");
|
||||
std::cout << "Strict filtering total: " << originalNidCount << " -> " << nidRefs.size() << "; filtered=" << originalNidCount - nidRefs.size() << "\n";
|
||||
} else if (unusedFilterLevel == 1) {
|
||||
std::vector<NidReference> pltRefs;
|
||||
std::vector<NidReference> nonPltRefs;
|
||||
for (const auto& ref : nidRefs) {
|
||||
|
|
@ -215,9 +221,6 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
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));
|
||||
|
|
@ -228,8 +231,17 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
|
||||
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));
|
||||
auto dynamicRefs = nidRefs;
|
||||
std::vector<RelinkPatch> patches;
|
||||
auto pltCount = static_cast<std::uint32_t>(dynJmpRelSize / relaEntSize);
|
||||
if (unusedFilterLevel == 2) {
|
||||
auto compacted = UnusedNidFilter::CompactPlt(originalNidRefs, nidRefs, textSection, textVAddr, _elfReader->TranslateVirtualAddress(textVAddr), dynJmpRelOffset);
|
||||
dynamicRefs = std::move(compacted.References);
|
||||
patches = std::move(compacted.Patches);
|
||||
std::cout << "PLT compaction: " << pltCount << " -> " << compacted.SlotCount << "; lazy binding preserved\n";
|
||||
pltCount = compacted.SlotCount;
|
||||
}
|
||||
auto dynSection = _dynamicSectionBuilder->BuildDynamicSection(dynamicRefs, neededLibraries, dynJmpRelOffset, pltCount);
|
||||
|
||||
static constexpr std::uint32_t R_X86_64_RELATIVE = 8;
|
||||
|
||||
|
|
@ -280,7 +292,7 @@ RelinkResult RelinkerPipeline::Relink(const std::vector<std::uint8_t>& sourceElf
|
|||
entries.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
return RelinkResult{std::move(entries), std::move(programHeaders), std::move(dynSection), gotVAddr};
|
||||
return RelinkResult{std::move(entries), std::move(programHeaders), std::move(dynSection), gotVAddr, std::move(patches)};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,283 @@
|
|||
#include <relinker/analysis/UnusedNidFilter.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/StrictReachability.hpp>
|
||||
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/EhFrameReader.hpp>
|
||||
#include <relinker/analysis/UnusedNidFilter/PltCompactor.hpp>
|
||||
#include <codegen/x86/X64InstructionDecoder.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace {
|
||||
|
||||
using Relinker::UnusedNidFilter::AnalyzeStrictReachability;
|
||||
using Relinker::UnusedNidFilter::StrictReachabilityInput;
|
||||
|
||||
void require(bool condition, const char* message) {
|
||||
if (!condition) throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
void requireFailure(const std::function<void()>& operation, const char* message) {
|
||||
try {
|
||||
operation();
|
||||
} catch (const Relinker::RelinkerException&) {
|
||||
return;
|
||||
}
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
template<typename TValue>
|
||||
void write(std::vector<std::uint8_t>& bytes, std::size_t offset, TValue value) {
|
||||
if (offset > bytes.size() || sizeof(value) > bytes.size() - offset) throw std::runtime_error("Test fixture write is out of bounds");
|
||||
std::memcpy(bytes.data() + offset, &value, sizeof(value));
|
||||
}
|
||||
|
||||
StrictReachabilityInput fixture() {
|
||||
StrictReachabilityInput input;
|
||||
input.Text.assign(128, 0xCC);
|
||||
input.TextVaddr = 0x1000;
|
||||
input.Entries = {0x1000};
|
||||
input.ImportSlots = {0x2000, 0x2008};
|
||||
return input;
|
||||
}
|
||||
|
||||
void emit(StrictReachabilityInput& input, std::size_t offset, std::initializer_list<std::uint8_t> bytes) {
|
||||
for (const auto byte : bytes) input.Text.at(offset++) = byte;
|
||||
}
|
||||
|
||||
void ripOperand(StrictReachabilityInput& input, std::size_t offset, std::initializer_list<std::uint8_t> opcode, std::uint64_t target) {
|
||||
emit(input, offset, opcode);
|
||||
const auto next = input.TextVaddr + offset + opcode.size() + 4;
|
||||
write(input.Text, offset + opcode.size(), static_cast<std::int32_t>(target - next));
|
||||
}
|
||||
|
||||
void importThunk(StrictReachabilityInput& input, std::size_t offset, std::uint64_t slot) {
|
||||
ripOperand(input, offset, {0xFF, 0x25}, slot);
|
||||
}
|
||||
|
||||
void deadTailAndExplicitEntry() {
|
||||
auto input = fixture();
|
||||
emit(input, 0, {0x0F, 0x0B});
|
||||
importThunk(input, 16, 0x2000);
|
||||
const auto dead = AnalyzeStrictReachability(input);
|
||||
require(dead.ImportSlots.empty(), "UD2 fallthrough retained a dead import");
|
||||
input.Entries.push_back(0x1010);
|
||||
const auto live = AnalyzeStrictReachability(input);
|
||||
require(live.ImportSlots == std::set<std::uint64_t>{0x2000}, "Explicit entry after UD2 lost its import");
|
||||
}
|
||||
|
||||
void conditionalTailCall() {
|
||||
auto input = fixture();
|
||||
emit(input, 0, {0x75, 0x0E, 0xC3});
|
||||
importThunk(input, 16, 0x2000);
|
||||
require(AnalyzeStrictReachability(input).ImportSlots.contains(0x2000), "Conditional tail call was removed");
|
||||
}
|
||||
|
||||
void callbackAndRelocationRoots() {
|
||||
auto input = fixture();
|
||||
ripOperand(input, 0, {0x48, 0x8D, 0x3D}, 0x1020);
|
||||
emit(input, 7, {0xC3});
|
||||
importThunk(input, 32, 0x2000);
|
||||
importThunk(input, 48, 0x2008);
|
||||
input.Pointers.emplace(0x3000, 0x1030);
|
||||
require(AnalyzeStrictReachability(input).ImportSlots == input.ImportSlots, "Address-taken callback or relocation root was removed");
|
||||
}
|
||||
|
||||
void registerImportCall() {
|
||||
auto input = fixture();
|
||||
ripOperand(input, 0, {0x48, 0x8B, 0x05}, 0x2000);
|
||||
emit(input, 7, {0xFF, 0xD0, 0xC3});
|
||||
const auto result = AnalyzeStrictReachability(input);
|
||||
require(result.ImportSlots.contains(0x2000) && result.IndirectTransfers == 1, "Imported register call was not resolved");
|
||||
|
||||
}
|
||||
|
||||
void mergedJumpTable() {
|
||||
auto input = fixture();
|
||||
emit(input, 0, {0x75, 0x07, 0xB9, 0, 0, 0, 0, 0xEB, 0x05, 0xB9, 1, 0, 0, 0});
|
||||
ripOperand(input, 14, {0x48, 0x8D, 0x05}, 0x3000);
|
||||
emit(input, 21, {0xFF, 0x24, 0xC8});
|
||||
importThunk(input, 48, 0x2000);
|
||||
importThunk(input, 64, 0x2008);
|
||||
input.Pointers = {{0x3000, 0x1030}, {0x3008, 0x1040}};
|
||||
require(AnalyzeStrictReachability(input).ImportSlots == input.ImportSlots, "Merged indexed jump table lost a target");
|
||||
}
|
||||
|
||||
void conservativeIndirectTargets() {
|
||||
auto input = fixture();
|
||||
emit(input, 0, {0xFF, 0xE0});
|
||||
importThunk(input, 32, 0x2000);
|
||||
importThunk(input, 64, 0x2008);
|
||||
input.Pointers.emplace(0x3000, 0x1020);
|
||||
const auto result = AnalyzeStrictReachability(input);
|
||||
require(result.ImportSlots == std::set<std::uint64_t>{0x2000}, "Unknown indirect transfer lost its address-taken target");
|
||||
input.Functions = {{0x1000, 0x1040, {}}, {0x1020, 0x1050, {}}};
|
||||
requireFailure([&] { AnalyzeStrictReachability(input); }, "Overlapping unwind functions were accepted");
|
||||
}
|
||||
|
||||
void isolatedFunctionCycle() {
|
||||
auto input = fixture();
|
||||
emit(input, 0, {0xC3});
|
||||
ripOperand(input, 32, {0xE8}, 0x1030);
|
||||
emit(input, 37, {0xC3});
|
||||
ripOperand(input, 48, {0xE8}, 0x1020);
|
||||
importThunk(input, 53, 0x2000);
|
||||
input.Functions = {{0x1000, 0x1001, {}}, {0x1020, 0x1026, {}}, {0x1030, 0x103B, {}}};
|
||||
require(AnalyzeStrictReachability(input).ImportSlots.empty(), "Unrooted function cycle kept its import");
|
||||
input.Pointers.emplace(0x3000, 0x1020);
|
||||
require(AnalyzeStrictReachability(input).ImportSlots.contains(0x2000), "Rooted function cycle lost its import");
|
||||
}
|
||||
|
||||
void relativeTableAndWholeFunction() {
|
||||
auto input = fixture();
|
||||
ripOperand(input, 0, {0x48, 0x8D, 0x05}, 0x3000);
|
||||
emit(input, 7, {0xFF, 0xE0});
|
||||
importThunk(input, 32, 0x2000);
|
||||
importThunk(input, 64, 0x2008);
|
||||
Relinker::UnusedNidFilter::StrictDataRegion data{0x3000, std::vector<std::uint8_t>(8)};
|
||||
write<std::int32_t>(data.Bytes, 0, 0x1020 - 0x3000);
|
||||
input.Data.push_back(data);
|
||||
require(AnalyzeStrictReachability(input).ImportSlots == std::set<std::uint64_t>{0x2000}, "Relative jump-table target was removed");
|
||||
input = fixture();
|
||||
emit(input, 0, {0xFF, 0xE0});
|
||||
importThunk(input, 32, 0x2000);
|
||||
input.Functions = {{0x1000, 0x1026, {}}};
|
||||
require(AnalyzeStrictReachability(input).ImportSlots.contains(0x2000), "Unknown switch target inside a live function was removed");
|
||||
}
|
||||
|
||||
void vectorInstructionLengths() {
|
||||
const Codegen::X64InstructionDecoder decoder;
|
||||
const std::vector<std::vector<std::uint8_t>> instructions = {
|
||||
{0xC5, 0xD9, 0x73, 0xD4, 0x20},
|
||||
{0xC4, 0xE1, 0x79, 0x70, 0xC0, 0x1B},
|
||||
{0xC4, 0xE1, 0x78, 0x77},
|
||||
{0x62, 0xF1, 0x7D, 0x48, 0x72, 0xD0, 0x04}
|
||||
};
|
||||
for (const auto& instruction : instructions)
|
||||
require(decoder.Decode(instruction.data(), instruction.size()) == instruction.size(), "Vector immediate or VZEROUPPER was decoded with the wrong length");
|
||||
}
|
||||
|
||||
void exceptionLandingPads() {
|
||||
std::vector<std::uint8_t> bytes(0x400);
|
||||
write<std::uint32_t>(bytes, 0x200, 15);
|
||||
write<std::uint8_t>(bytes, 0x208, 1);
|
||||
const std::vector<std::uint8_t> augmentation = {'z', 'L', 'R', 0, 1, 0x78, 16, 2, 0, 0};
|
||||
std::copy(augmentation.begin(), augmentation.end(), bytes.begin() + 0x209);
|
||||
write<std::uint32_t>(bytes, 0x220, 29);
|
||||
write<std::uint32_t>(bytes, 0x224, 0x24);
|
||||
write<std::uint64_t>(bytes, 0x228, 0x1000);
|
||||
write<std::uint64_t>(bytes, 0x230, 0x20);
|
||||
write<std::uint8_t>(bytes, 0x238, 8);
|
||||
write<std::uint64_t>(bytes, 0x239, 0x280);
|
||||
const std::vector<std::uint8_t> lsda = {0xFF, 0xFF, 1, 4, 0, 1, 0x40, 0};
|
||||
std::copy(lsda.begin(), lsda.end(), bytes.begin() + 0x280);
|
||||
write<std::uint8_t>(bytes, 0x300, 1);
|
||||
write<std::uint8_t>(bytes, 0x302, 3);
|
||||
write<std::uint64_t>(bytes, 0x304, 0x200);
|
||||
write<std::uint32_t>(bytes, 0x30C, 1);
|
||||
write<std::uint64_t>(bytes, 0x310, 0x1000);
|
||||
write<std::uint64_t>(bytes, 0x318, 0x220);
|
||||
const std::vector<Relinker::ProgramHeader> headers = {{1, 4, 0, 0, 0, bytes.size(), bytes.size(), 8}, {0x6474E550, 4, 0x300, 0x300, 0, 32, 32, 4}};
|
||||
auto input = fixture();
|
||||
input.Functions = Relinker::UnusedNidFilter::ReadExceptionFunctions(bytes, headers, {}, {});
|
||||
require(input.Functions.size() == 1 && input.Functions[0].ExtraTargets == std::vector<std::uint64_t>{0x1040}, "LSDA landing pad outside the function was not recovered");
|
||||
emit(input, 0, {0x0F, 0x0B});
|
||||
emit(input, 31, {0xC3});
|
||||
importThunk(input, 64, 0x2000);
|
||||
require(AnalyzeStrictReachability(input).ImportSlots.contains(0x2000), "Exception-only import was removed");
|
||||
write<std::uint8_t>(bytes, 0x282, 0xFF);
|
||||
requireFailure([&] { Relinker::UnusedNidFilter::ReadExceptionFunctions(bytes, headers, {}, {}); }, "Malformed LSDA was accepted");
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> elfFixture(const StrictReachabilityInput& input) {
|
||||
std::vector<std::uint8_t> bytes(0x400);
|
||||
bytes[0] = 0x7F;
|
||||
bytes[1] = 'E';
|
||||
bytes[2] = 'L';
|
||||
bytes[3] = 'F';
|
||||
bytes[4] = 2;
|
||||
bytes[5] = 1;
|
||||
bytes[6] = 1;
|
||||
write<std::uint16_t>(bytes, 16, 3);
|
||||
write<std::uint16_t>(bytes, 18, 62);
|
||||
write<std::uint64_t>(bytes, 24, input.Entries.at(0));
|
||||
write<std::uint64_t>(bytes, 32, 64);
|
||||
write<std::uint16_t>(bytes, 54, 56);
|
||||
write<std::uint16_t>(bytes, 56, 2);
|
||||
write<std::uint32_t>(bytes, 64, 1);
|
||||
write<std::uint32_t>(bytes, 68, 5);
|
||||
write<std::uint64_t>(bytes, 72, 0x200);
|
||||
write<std::uint64_t>(bytes, 80, input.TextVaddr);
|
||||
write<std::uint64_t>(bytes, 96, input.Text.size());
|
||||
write<std::uint64_t>(bytes, 104, input.Text.size());
|
||||
write<std::uint32_t>(bytes, 120, 1);
|
||||
write<std::uint32_t>(bytes, 124, 6);
|
||||
write<std::uint64_t>(bytes, 128, 0x300);
|
||||
write<std::uint64_t>(bytes, 136, 0x2000);
|
||||
write<std::uint64_t>(bytes, 152, 0x100);
|
||||
write<std::uint64_t>(bytes, 160, 0x100);
|
||||
std::copy(input.Text.begin(), input.Text.end(), bytes.begin() + 0x200);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void filterAndPltCompaction() {
|
||||
auto input = fixture();
|
||||
importThunk(input, 0, 0x2000);
|
||||
importThunk(input, 16, 0x2008);
|
||||
const auto bytes = elfFixture(input);
|
||||
const std::vector<Relinker::NidReference> references = {{"live", {}, 7, 0x300, 0x2000, 0}, {"dead", {}, 7, 0x318, 0x2008, 0}};
|
||||
const auto filtered = Relinker::MakeStrictUnusedNidFilter()->Filter(references, bytes, input.Text, input.TextVaddr);
|
||||
require(filtered.size() == 1 && filtered[0].Nid == references[0].Nid, "Strict ELF filter did not remove only the dead PLT import");
|
||||
for (std::size_t index = 0; index < references.size(); ++index) {
|
||||
const auto offset = index * 16;
|
||||
emit(input, offset + 6, {0x68, 0, 0, 0, 0, 0xE9, 0, 0, 0, 0});
|
||||
write<std::uint32_t>(input.Text, offset + 7, static_cast<std::uint32_t>(index));
|
||||
}
|
||||
const std::vector<Relinker::NidReference> kept = {references[1]};
|
||||
auto compacted = Relinker::UnusedNidFilter::CompactPlt(references, kept, input.Text, input.TextVaddr, 0, 0x300);
|
||||
require(compacted.SlotCount == 1 && compacted.References[0].RelocationTableOffset == 0x300, "PLT relocation indices were not compacted");
|
||||
auto invalidThunks = input.Text;
|
||||
write<std::uint32_t>(invalidThunks, 23, 99);
|
||||
requireFailure([&] { Relinker::UnusedNidFilter::CompactPlt(references, kept, invalidThunks, input.TextVaddr, 0, 0x300); }, "Mismatched original PLT index was accepted");
|
||||
for (const auto& patch : compacted.Patches)
|
||||
std::copy(patch.Bytes.begin(), patch.Bytes.end(), input.Text.begin() + static_cast<std::ptrdiff_t>(patch.Offset));
|
||||
std::uint32_t newIndex;
|
||||
std::memcpy(&newIndex, input.Text.data() + 23, sizeof(newIndex));
|
||||
require(newIndex == 0 && input.Text[0] == 0x0F && input.Text[1] == 0x0B && input.Text[6] == 0x0F && input.Text[7] == 0x0B, "PLT thunk index or removed thunk traps are incorrect");
|
||||
Relinker::SysVDynamicSectionBuilder builder;
|
||||
const auto section = builder.BuildDynamicSection(compacted.References, {}, 0x300, compacted.SlotCount);
|
||||
require(section.RelaPltData.size() == 24 && section.DynSymData.size() == 48, "Compacted PLT retained a dead dynamic symbol");
|
||||
auto truncated = bytes;
|
||||
truncated.resize(70);
|
||||
requireFailure([&] { Relinker::MakeStrictUnusedNidFilter()->Filter(references, truncated, input.Text, input.TextVaddr); }, "Truncated ELF was accepted");
|
||||
auto exceptional = bytes;
|
||||
write<std::uint16_t>(exceptional, 56, 3);
|
||||
write<std::uint32_t>(exceptional, 176, 0x6474E550);
|
||||
write<std::uint64_t>(exceptional, 184, 0x380);
|
||||
write<std::uint64_t>(exceptional, 208, 8);
|
||||
requireFailure([&] { Relinker::MakeStrictUnusedNidFilter()->Filter(references, exceptional, input.Text, input.TextVaddr); }, "Exception metadata was ignored");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
deadTailAndExplicitEntry();
|
||||
conditionalTailCall();
|
||||
callbackAndRelocationRoots();
|
||||
registerImportCall();
|
||||
mergedJumpTable();
|
||||
conservativeIndirectTargets();
|
||||
isolatedFunctionCycle();
|
||||
relativeTableAndWholeFunction();
|
||||
vectorInstructionLengths();
|
||||
exceptionLandingPads();
|
||||
filterAndPltCompaction();
|
||||
std::cout << "Strict NID filter tests passed\n";
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue