diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a62aced6..828a9fcf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,3 +23,4 @@ jobs: mkdir build && cd build ../configure --with-asan --with-ubsan make -j$(nproc) check + env PATCHELF_ONLY_LAYOUT_ENGINE=1 make -j$(nproc) check diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3301712..f17a261c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -90,6 +90,7 @@ jobs: cd patchelf-* ./configure --prefix /patchelf make check + env PATCHELF_ONLY_LAYOUT_ENGINE=1 make check make install-strip cd - tar -czf ./dist/patchelf-\$(cat patchelf-*/version)-\$(uname -m).tar.gz -C /patchelf . diff --git a/src/Makefile.am b/src/Makefile.am index 93e92d8c..e8c4778f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,4 +19,4 @@ endif bin_PROGRAMS = patchelf -patchelf_SOURCES = patchelf.cc elf.h patchelf.h +patchelf_SOURCES = patchelf.cc elf.h patchelf.h layout.cc layout.h utl.h diff --git a/src/layout.cc b/src/layout.cc new file mode 100644 index 00000000..a69fa874 --- /dev/null +++ b/src/layout.cc @@ -0,0 +1,590 @@ +#include "layout.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace le; +using namespace utl; + +namespace le +{ +struct VSection +{ + bool isPinned() const { return sec && sec->pinned; } + bool isBlank() const { return !sec; } + size_t align() const { return sec ? sec->align : 1; } + Section* sec; + utl::Range addrRange {0,0}; +}; + +struct VSegment +{ + utl::Range addrRange; + Segment* load {nullptr}; + std::vector vsecs {}; +}; + +class LayoutEngineImpl +{ +public: + LayoutEngineImpl(Layout lo, size_t nonLoadSegments, size_t sizeOfPHTEntry); + bool resize(Layout::SecId id, size_t size); + + bool updateFileLayout(); + const Layout& layout() const { return m_lo; } + + size_t getVirtualAddress(size_t secid) const; + +private: + void dumpSections(); + void dumpSegments(); + + void buildAddrSpace(); + void placeSections(); + + Layout m_lo; + size_t m_nonLoadSegments, m_sizeOfPHTEntry; + std::vector m_addrSpace; + + bool resizeNonPinned(Section* sec, size_t newSize); + bool tryToPlacePendingSections(); + bool moveToPending(Section* sec, Access access, size_t align); + + bool adjustPHTSize(); + size_t phtRequiredSize() const { + return (m_nonLoadSegments + m_lo.m_segs.size()) * m_sizeOfPHTEntry; + } + + std::map m_pp; +}; +} + +static void dump(const std::vector& vsecs); +static void dump(const std::vector& addrSpace); +static std::string toHex(size_t n); +static std::string toHex(const Range& rng); +static std::string toString(Access a); +static auto findVSection(std::vector& addrSpace, const Section* sec) -> std::pair; +static bool tryToFitSectionInABlank(std::vector& vsecs, Section* theSec); +static void normalize(VSegment& vseg); + +LayoutEngineImpl::LayoutEngineImpl(Layout lo, size_t nonLoadSegments, size_t sizeOfPHTEntry) + : m_lo(std::move(lo)), m_nonLoadSegments(nonLoadSegments), m_sizeOfPHTEntry(sizeOfPHTEntry) +{ + std::cerr << "========== START ===============" << std::endl; + dumpSections(); + dumpSegments(); + buildAddrSpace(); + dump(m_addrSpace); +} + +void LayoutEngineImpl::buildAddrSpace() +{ + // Build address space layout by iterating on all segments + // and checking which sections are within the loaded range + + // Sort the segments by virtual address + std::vector sortedSegs; + + for (auto& seg : m_lo.m_segs) + sortedSegs.push_back(&seg); + + std::sort(sortedSegs.begin(), sortedSegs.end(), [] (auto* l, auto* r) { + return l->vRange.begin() < r->vRange.begin(); + }); + + // For each segment fill the VSegment + // Add empty VSegment items for holes between loaded + // segments + // Also round the segments by page since a segment + // from 0x1500 to 0x2500 is actually placed + // from 0x1000 to 0x3000 in the virtual space + size_t curVAddr = 0; + for (auto* seg : sortedSegs) + { + auto segRng = seg->vRange.aligned(seg->align); + if (curVAddr < segRng.begin()) { // empty space + auto sz = segRng.begin() - curVAddr; + m_addrSpace.push_back( VSegment{Range(curVAddr, sz)} ); + curVAddr = segRng.begin(); + } + if (!m_addrSpace.empty() && m_addrSpace.back().addrRange.intersects(segRng)) + { + auto lastRng = m_addrSpace.back().addrRange; + std::cerr << "Realigned segment " << toHex(lastRng) << " overlaps with " << toHex(segRng) << std::endl; + // assert(0); + } + m_addrSpace.push_back(VSegment{segRng, seg}); + curVAddr = segRng.end(); + } + + // Fill each VSegment with the VSections it mounts + // VSections are the representation of a Section when + // placed in the virtual memory + std::set mappedSections; + for (auto& vseg : m_addrSpace) + { + if (!vseg.load) continue; + + std::vector> secs; + for (auto& sec : m_lo.m_secs) + { + if (vseg.load->fRange.wraps(sec.fRange)) + { + if (auto [_, inserted] = mappedSections.insert(&sec); !inserted) + { + std::cerr << "The section " << sec.name << " is being mapped by two different segments" << std::endl; + assert(!"We don't support sections mapped by multiple segments right now"); + } + auto secAtVAddr = vseg.load->vRange.begin() + (sec.fRange.begin() - vseg.load->fRange.begin()); + secs.emplace_back(secAtVAddr, &sec); + sec.access = vseg.load->access; + } + else if (vseg.load->fRange.intersects(sec.fRange)) + { + std::cerr << "Load segment takes from file: " << toHex(vseg.load->fRange) << std::endl; + std::cerr << "We have a section a file rng: " << toHex(sec.fRange) << std::endl; + assert(!"Segment loads only a part of a section"); + } + } + std::sort(secs.begin(), secs.end(), + [] (auto& l, auto& r) { return l.first < r.first; }); + + auto curVAddr = vseg.addrRange.begin(); + for (auto& [secVAddr, sec] : secs) + { + if (secVAddr > curVAddr) { + auto sz = secVAddr - curVAddr; + vseg.vsecs.push_back(VSection{nullptr, Range{curVAddr, sz}}); + curVAddr = secVAddr; + } + auto secVAddrRange = Range{curVAddr, sec->fRange.size()}; + vseg.vsecs.push_back(VSection{sec, secVAddrRange}); + curVAddr = secVAddrRange.end(); + } + if (curVAddr != vseg.addrRange.end()) + { + auto sz = vseg.addrRange.end() - curVAddr; + vseg.vsecs.push_back(VSection{nullptr, Range{curVAddr, sz}}); + } + } +} + +bool LayoutEngineImpl::resize(Layout::SecId id, size_t newSize) +{ + auto* sec = &m_lo.m_secs[id]; + auto [oldVSeg, vsecid] = ::findVSection(m_addrSpace, sec); + if (!oldVSeg) + { + // Section is not mapped in virtual memory. + sec->fRange.resize(newSize); + return true; + } + assert(!sec->pinned); + return resizeNonPinned(sec, newSize); +} + +bool LayoutEngineImpl::resizeNonPinned(Section* sec, size_t newSize) +{ + std::cerr << "############## Resize non pinned section" << std::endl; + auto [oldVSeg, vsecid] = ::findVSection(m_addrSpace, sec); + assert(oldVSeg); + + // Detach the section from the virtual space + oldVSeg->vsecs[vsecid].sec = nullptr; + // Merge any consecutive blanks that might have been created + normalize(*oldVSeg); + + sec->fRange.resize(newSize); + + // Try to place this new section in any segment with matching + // access rights and with a blank that is big enought to hold it + for (auto& vseg : m_addrSpace) + { + if (vseg.load && vseg.load->access == oldVSeg->load->access && tryToFitSectionInABlank(vseg.vsecs, sec)) + { + std::cerr << "############## Placed in a pre-existing blank" << std::endl; + return true; + } + } + + // We will need to create a new segment later to place this section + if (!moveToPending(sec, oldVSeg->load->access, oldVSeg->load->align)) + return false; + + return true; +} + +// Merge any consecutive blank segments in this VSegment +static void normalize(VSegment& vseg) +{ + std::vector nvsecs; + for (auto& vsec : vseg.vsecs) + { + if (nvsecs.empty() || !nvsecs.back().isBlank() || !vsec.isBlank()) + nvsecs.push_back(vsec); + else + nvsecs.back().addrRange.setEnd(vsec.addrRange.end()); + } + vseg.vsecs = std::move(nvsecs); +} + +bool LayoutEngineImpl::moveToPending(Section* sec, Access access, size_t align) +{ + std::cerr << "############## Moving to pending: " << sec->name << std::endl; + auto it = m_pp.find(access); + if (it == m_pp.end()) + { + std::cerr << "Entered here" << std::endl; + auto newSid = m_lo.add(Segment{Range{0,0}, Range{0,0}, access, align}); + auto& newSeg = m_lo.m_segs.at(newSid); + it = m_pp.emplace(access, VSegment{Range{0,0}, &newSeg}).first; + if (!adjustPHTSize()) + return false; + } + it->second.vsecs.push_back(VSection{sec}); + std::cerr << "############## Done moving to pending: " << sec->name << std::endl; + return true; +} + +bool LayoutEngineImpl::adjustPHTSize() +{ + std::cerr << "############## Increasing PHT" << std::endl; + auto requiredSize = phtRequiredSize(); + + auto* sec = &m_lo.m_secs[/*phdr id*/1]; + auto [vseg, id] = ::findVSection(m_addrSpace, sec); + assert(vseg); + + size_t availableSize = vseg->vsecs[id].addrRange.size(); + + if (requiredSize <= availableSize) + return true; + + for (size_t nid = id + 1; availableSize < requiredSize && nid < vseg->vsecs.size(); ++nid) + { + auto& nvsec = vseg->vsecs[nid]; + if (nvsec.isBlank()) + { + std::cerr << "############## Found blank of size " << toHex(nvsec.addrRange.size()) << std::endl; + availableSize += nvsec.addrRange.size(); + } + else if (!nvsec.sec->pinned) + { + std::cerr << "############## Evicted " << nvsec.sec->name + << " of size " << toHex(nvsec.addrRange.size()) << std::endl; + + availableSize += nvsec.addrRange.size(); + auto moveSec = std::exchange(nvsec.sec, nullptr); + if (!moveToPending(moveSec, vseg->load->access, vseg->load->align)) + return false; + // Number of needed segments might have increased + requiredSize = phtRequiredSize(); + } + else + break; + } + + if (availableSize < requiredSize) + { + std::cerr << "############## Could not find enough memory. Needed "<< requiredSize << " found: " << availableSize << std::endl; + return false; + } + + std::cerr << "############## Found enough memory!"<< std::endl; + normalize(*vseg); + + auto& vsec = vseg->vsecs[id]; + + auto& nvsec = vseg->vsecs[id + 1]; + assert(nvsec.isBlank()); + + availableSize = vsec.addrRange.size() + nvsec.addrRange.size(); + assert(availableSize >= requiredSize); + + vsec.addrRange.resize(requiredSize); + sec->fRange.resize(requiredSize); + + nvsec.addrRange = Range::beginEnd(vsec.addrRange.end(), nvsec.addrRange.end()); + return true; +} + +static bool tryToFitSectionInABlank(std::vector& vsecs, Section* theSec) +{ + for (size_t i = 0; i < vsecs.size(); ++i) + { + auto c = vsecs[i]; + if (c.isBlank() && c.addrRange.size() >= theSec->fRange.size()) + { + auto vstart = roundUp(c.addrRange.begin(), theSec->align); + auto vend = vstart + theSec->fRange.size(); + if (vend > c.addrRange.end()) + continue; + + size_t leftOver = c.addrRange.size(); + if (vstart != c.addrRange.begin()) + { + auto blankRange = Range(c.addrRange.begin(), vstart-c.addrRange.begin()); + vsecs.insert(vsecs.begin()+i, VSection{nullptr, blankRange}); + leftOver -= blankRange.size(); + ++i; + } + vsecs[i].sec = theSec; + vsecs[i].addrRange = Range(vstart, theSec->fRange.size()); + leftOver -= theSec->fRange.size(); + + if (leftOver) { + vsecs.insert(vsecs.begin()+i+1, VSection{nullptr, Range(vend,leftOver)}); + } + + return true; + } + } + return false; +} + +static std::vector::const_iterator findVSection(const std::vector& vsecs, const Section* sec) +{ + return std::find_if(vsecs.begin(), vsecs.end(), [&] (auto& vsec) { return vsec.sec == sec; }); +} + +static auto findVSection(std::vector& addrSpace, const Section* sec) -> std::pair +{ + for (auto& vseg : addrSpace) + { + auto it = findVSection(vseg.vsecs, sec); + if (it != vseg.vsecs.end()) + return {&vseg, it - vseg.vsecs.begin()}; + } + return {nullptr, 0}; +} + +size_t LayoutEngineImpl::getVirtualAddress(size_t secid) const +{ + auto* sec = &m_lo.m_secs.at(secid); + auto [vseg, id] = ::findVSection(const_cast&>(m_addrSpace), sec); + return vseg ? vseg->vsecs[id].addrRange.begin() : 0; +} + +bool LayoutEngineImpl::updateFileLayout() +{ + if (!tryToPlacePendingSections()) + return false; + + for (auto& vseg : m_addrSpace) + { + if (!vseg.load) continue; + + auto& first = vseg.vsecs.front(); + auto& last = vseg.vsecs.back(); + + auto b = first.isBlank() ? first.addrRange.end() : first.addrRange.begin(); + auto e = last.isBlank() ? last.addrRange.begin() : last.addrRange.end(); + + if (e < b) e = b; + std::cerr << "######## Setting segment vaddr to: " << toHex(Range::beginEnd(b,e)) << std::endl; + vseg.load->vRange = Range::beginEnd(b,e); + } + + std::vector sortedSecs; + for (auto& sec : m_lo.m_secs) sortedSecs.push_back(&sec); + std::sort(sortedSecs.begin(), sortedSecs.end(), [] (auto& l, auto& r) { return l->fRange.begin() < r->fRange.begin(); }); + + size_t fileOffset = 0; + std::set loadedSections; + for (auto* sec : sortedSecs) + { + auto [vsegPtr, _] = ::findVSection(m_addrSpace, sec); + if (!vsegPtr || !loadedSections.insert(sec).second) continue; + + auto& vseg = *vsegPtr; + + std::cerr << "###################################### New segment" << std::endl; + + std::cerr << "Previous fileOffset: " << toHex(fileOffset) << std::endl; + auto addrModAlign = vseg.load->vRange.begin() % vseg.load->align; + auto foffModAlign = fileOffset % vseg.load->align; + if (addrModAlign > foffModAlign) + fileOffset += addrModAlign - foffModAlign; + else if (addrModAlign < foffModAlign) + fileOffset += (vseg.load->align - foffModAlign) + addrModAlign; + assert(vseg.load->vRange.begin() % vseg.load->align == fileOffset % vseg.load->align); + std::cerr << "New fileOffset: " << toHex(fileOffset) << std::endl; + + size_t firstOffset = fileOffset; + bool updated = false; + size_t i = 0; + for (auto& vsec : vseg.vsecs) + { + if (!vsec.isBlank()) + { + if (vsec.sec->fRange.size() > 0) + fileOffset = fileOffset ? roundUp(fileOffset, vsec.sec->align) : 0; + + if (!updated) firstOffset = fileOffset; + updated = true; + + auto fRangeBefore = vsec.sec->fRange; + vsec.sec->fRange.rebase(fileOffset); + if (fRangeBefore != vsec.sec->fRange) + { + std::cerr << "##### adjusting sec " << vsec.sec->name + << "\tfrom: " << toHex(fRangeBefore) + << "\tto: " << toHex(vsec.sec->fRange) + << std::endl; + } + fileOffset += vsec.sec->fRange.size(); + loadedSections.insert(vsec.sec); + } + else if (i != 0 && i != vseg.vsecs.size()-1) + { + fileOffset += vsec.addrRange.size(); + } + ++i; + } + vseg.load->fRange = Range::beginEnd(firstOffset, fileOffset); + } + + for (auto* sec : sortedSecs) + { + if (!loadedSections.count(sec)) + { + fileOffset = fileOffset ? roundUp(fileOffset, sec->align) : 0; + auto fRangeBefore = sec->fRange; + sec->fRange.rebase(fileOffset); + if (fRangeBefore != sec->fRange) + { + std::cerr << "##### adjusting non loaded sec " << sec->name + << "\tfrom: " << toHex(fRangeBefore) + << "\tto: " << toHex(sec->fRange) + << std::endl; + } + fileOffset += sec->fRange.size(); + } + } + + std::cerr << "========== END ===============" << std::endl; + dumpSections(); + dumpSegments(); + + std::cerr << "Old file size: " << m_lo.m_fileSz << std::endl; + std::cerr << "New file size: " << fileOffset << std::endl; + std::cerr << " diff: " << long(fileOffset) - long(m_lo.m_fileSz) << std::endl; + m_lo.m_fileSz = fileOffset; + return true; +} + +bool LayoutEngineImpl::tryToPlacePendingSections() +{ + std::cerr << "############## Placing pending sections" << std::endl; + auto lastVAddr = m_addrSpace.back().addrRange.end(); + for (auto& [_, vseg] : m_pp) + { + lastVAddr = roundUp(lastVAddr, vseg.load->align); + vseg.addrRange.rebase(lastVAddr); + for (auto& vsec : vseg.vsecs) + { + lastVAddr = roundUp(lastVAddr, vsec.sec->align); + vsec.addrRange.rebase(lastVAddr); + lastVAddr += vsec.sec->fRange.size(); + vsec.addrRange.resize(vsec.sec->fRange.size()); + } + vseg.addrRange.resize(roundUp(lastVAddr, vseg.load->align) - vseg.addrRange.begin()); + m_addrSpace.push_back(vseg); + } + dump(m_addrSpace); + return true; +} + +void LayoutEngineImpl::dumpSections() +{ + std::cerr << "================ SECTIONS =====================" << std::endl; + for (auto& sec : m_lo.m_secs) + { + std::cerr << " " << toHex(sec.fRange) << " " << sec.name << std::endl; + } +} + +void LayoutEngineImpl::dumpSegments() +{ + std::cerr << "================ SEGMENTS =====================" << std::endl; + for (auto& seg : m_lo.m_segs) + { + std::cerr << " " << toString(seg.access) << toHex(seg.fRange) << " --> " << toHex(seg.vRange) << std::endl; + } +} + +static void dump(const std::vector& vsecs) +{ + size_t totalBlank = 0; + for (auto& vsec : vsecs) + { + auto* sec = vsec.sec; + std::cerr << " " << toHex(vsec.addrRange) << "\t(align: "; + if (sec) + { + std::cerr << sec->align << ")\t" << sec->name; + if (sec->pinned) + std::cerr << "\tpinned"; + std::cerr << std::endl; + } + else + { + totalBlank += vsec.addrRange.size(); + std::cerr << "X)\t" << std::endl; + } + } + std::cerr << " (Total blank size: " << toHex(totalBlank) << ")" << std::endl; +} + +static void dump(const std::vector& addrSpace) +{ + std::cerr << "================ ADDR SPACE =====================" << std::endl; + for (auto& vseg : addrSpace) + { + std::cerr << toHex(vseg.addrRange) << (vseg.load ? " (LOAD)" : " (BLANK)") + << (vseg.load ? "\t(align: " + toHex(vseg.load->align) + ")" : "") + << std::endl; + dump(vseg.vsecs); + std::cerr << std::endl; + } +} + +static std::string toHex(size_t n) +{ + std::stringstream ss; + ss << std::hex << "0x" << n; + return ss.str(); +} + +static std::string toHex(const Range& rng) +{ + std::stringstream ss; + ss << "[" << toHex(rng.begin()) << " -> " << toHex(rng.end()) << " (" << toHex(rng.size()) << ")]"; + return ss.str(); +} + +static std::string toString(Access a) +{ + std::string str; + str.resize(3); + str[0] = (a & Access::Read) ? 'R' : ' '; + str[1] = (a & Access::Write) ? 'W' : ' '; + str[2] = (a & Access::Exec) ? 'E' : ' '; + return str; +} + +LayoutEngine::LayoutEngine(Layout lo, size_t nonLoadSegments, size_t sizeOfPHTEntry) { + m_impl = std::make_unique(std::move(lo), nonLoadSegments, sizeOfPHTEntry); +} +LayoutEngine::~LayoutEngine() = default; +bool LayoutEngine::resize(Layout::SecId id, size_t size) { return m_impl->resize(id, size); } +bool LayoutEngine::updateFileLayout() { return m_impl->updateFileLayout(); } +const Layout& LayoutEngine::layout() const { return m_impl->layout(); } +size_t LayoutEngine::getVirtualAddress(size_t secid) const { return m_impl->getVirtualAddress(secid); } diff --git a/src/layout.h b/src/layout.h new file mode 100644 index 00000000..f3196eeb --- /dev/null +++ b/src/layout.h @@ -0,0 +1,87 @@ +#pragma once + +#include "utl.h" + +#include +#include +#include +#include + +namespace le +{ +enum Access {None, Read=1, Write=2, Exec=4}; +inline void addIf(Access& lhs, Access rhs, bool cond = true) { + if (cond) + lhs = static_cast(lhs | rhs); +} + +struct Section +{ + enum class Type { ElfHeader, PHTable, SHTable, Regular }; + + std::string name; + Type type; + utl::Range fRange; + size_t align; + bool pinned; + + // Filled by the engine + Access access {Access::None}; + ssize_t id {-1}; +}; + +struct Segment +{ + utl::Range vRange, fRange; + Access access; + size_t align; + // Filled by the engine + ssize_t id {-1}; +}; + +struct Layout +{ + using SecId = size_t; + using SegId = size_t; + + SecId add(const Section& sec) + { + m_secs.push_back(sec); + auto id = m_secs.size() - 1; + m_secs.back().id = id; + m_fileSz = std::max(m_fileSz, sec.fRange.end()); + return id; + } + + SegId add(const Segment& seg) + { + m_segs.push_back(seg); + auto id = m_segs.size() - 1; + m_segs.back().id = id; + return id; + } + + std::deque
m_secs; + std::deque m_segs; + size_t m_fileSz {0}; +}; + +class LayoutEngineImpl; +class LayoutEngine +{ +public: + LayoutEngine(Layout lo, size_t nonLoadSegments, size_t sizeOfPHTEntry); + ~LayoutEngine(); + + bool resize(Layout::SecId id, size_t size); + + bool updateFileLayout(); + const Layout& layout() const; + + size_t getVirtualAddress(size_t secid) const; + +private: + std::unique_ptr m_impl; +}; + +} diff --git a/src/patchelf.cc b/src/patchelf.cc index ca247c12..364d4e96 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -45,6 +46,7 @@ #include "elf.h" #include "patchelf.h" +#include "layout.h" #ifndef PACKAGE_STRING #define PACKAGE_STRING "patchelf" @@ -55,7 +57,11 @@ #define O_BINARY 0 #endif +using utl::roundUp; + static bool debugMode = false; +static bool tryLayoutEngine = false; +static bool onlyLayoutEngine = false; static bool forceRPath = false; @@ -477,14 +483,6 @@ static void writeFile(const std::string & fileName, const FileContents & content } -static uint64_t roundUp(uint64_t n, uint64_t m) -{ - if (n == 0) - return m; - return ((n - 1) / m + 1) * m; -} - - template void ElfFile::shiftFile(unsigned int extraPages, size_t startOffset, size_t extraBytes) { @@ -1086,6 +1084,14 @@ void ElfFile::rewriteSections(bool force) if (!force && replacedSections.empty()) return; + if (tryLayoutEngine) + { + if (runLayoutEngine()) + return; + else if (onlyLayoutEngine) + error("Layout engine failed"); + } + for (auto & i : replacedSections) debug("replacing section '%s' with size %d\n", i.first.c_str(), i.second.size()); @@ -2257,6 +2263,234 @@ void ElfFile::modifyExecstack(ExecstackMode op) printf("execstack: %c\n", result); } +template +bool ElfFile::runLayoutEngine() +{ + using namespace le; + + auto [layout, maps] = elf2layout(); + + LayoutEngine le(std::move(layout), phdrs.size() - layout.m_segs.size(), sizeof(Elf_Phdr)); + + for (auto& [name, data] : replacedSections) + { + auto secid = maps.shdr2id[&findSectionHeader(name)]; + if (!le.resize(secid, data.size()+1)) + return false; + } + if (!le.updateFileLayout()) + return false; + + layout2elf(le, maps); + return true; +} + +template +auto ElfFile::elf2layout() -> std::pair +{ + using namespace le; + + Layout lo; + Elf2LayoutMaps maps; + + lo.add( + Section{"", Section::Type::ElfHeader, {0, sizeof(Elf_Ehdr)}, 1, true} + ); + + lo.add( + Section{"", Section::Type::PHTable, utl::Range{rdi(hdr()->e_phoff), rdi(hdr()->e_phnum)*sizeof(Elf_Phdr)}, 1, true} + ); + + lo.add( + Section{"", Section::Type::SHTable, utl::Range{rdi(hdr()->e_shoff), rdi(hdr()->e_shnum)*sizeof(Elf_Shdr)}, 8, false} + ); + + for (auto& shdr : shdrs) + { + auto name = getSectionName(shdr); + if (name.empty()) continue; + + auto size = rdi(shdr.sh_type) != SHT_NOBITS ? rdi(shdr.sh_size) : 0; + bool pinned = + (rdi(shdr.sh_type) == SHT_PROGBITS && name != ".interp") || + rdi(shdr.sh_type) == SHT_NOBITS; + + auto id = lo.add( + Section{name, Section::Type::Regular, {rdi(shdr.sh_offset), size}, rdi(shdr.sh_addralign), pinned} + ); + + maps.id2shdr[id] = &shdr; + maps.shdr2id[&shdr] = id; + } + + for (auto& phdr : phdrs) + { + // Only loads contribute to the address space + if (rdi(phdr.p_type) != PT_LOAD) + continue; + + assert(rdi(phdr.p_vaddr) == rdi(phdr.p_paddr)); + + auto convertFlags = [] (auto flags) { + Access a = None; + addIf(a, Access::Exec, flags & PF_X); + addIf(a, Access::Read, flags & PF_R); + addIf(a, Access::Write, flags & PF_W); + return a; + }; + + auto id = lo.add( + Segment{{rdi(phdr.p_vaddr), rdi(phdr.p_memsz)}, + {rdi(phdr.p_offset), rdi(phdr.p_filesz)}, + convertFlags(rdi(phdr.p_flags)), + getPageSize() + } + ); + + maps.id2phdr[id] = &phdr; + } + + return {std::move(lo), std::move(maps)}; +} + +template +void ElfFile::layout2elf(const le::LayoutEngine& le, const Elf2LayoutMaps& maps) +{ + using namespace le; + + auto& nlo = le.layout(); + + std::vector newFile(nlo.m_fileSz); + for (auto& sec : nlo.m_secs) + { + size_t at = sec.fRange.begin(); + switch (sec.type) + { + case Section::Type::ElfHeader: + { + auto begin = fileContents->data(); + auto end = begin + sec.fRange.size(); + std::copy(begin, end, &newFile[at]); + break; + } + case Section::Type::PHTable: + { + auto begin = fileContents->data() + rdi(hdr()->e_phoff); + auto end = begin + sec.fRange.size(); + std::copy(begin, end, &newFile[at]); + break; + } + case Section::Type::SHTable: + { + assert(sec.fRange.size() == rdi(hdr()->e_shnum)*sizeof(Elf_Shdr)); + auto begin = fileContents->data() + rdi(hdr()->e_shoff); + auto end = begin + sec.fRange.size(); + std::copy(begin, end, &newFile[at]); + break; + } + case Section::Type::Regular: + { + auto it = replacedSections.find(sec.name); + if (it != replacedSections.end()) + { + assert(it->second.size() + 1 == sec.fRange.size()); + std::copy(it->second.begin(), it->second.end()+1, &newFile[at]); + } + else + { + if (sec.fRange.size() == 0) + continue; + + auto& shdr = *maps.id2shdr.at(sec.id); + auto shdrSpan = getSectionSpan(shdr); + if (shdrSpan.size() != sec.fRange.size()) + { + std::cerr << "Section name with wrong sizes: " << sec.name << std::endl; + std::cerr << "shdr is:" << shdrSpan.size() << std::endl; + std::cerr << "sec is:" << sec.fRange.size() << std::endl; + } + assert(shdrSpan.size() == sec.fRange.size()); + std::copy(shdrSpan.begin(), shdrSpan.end(), &newFile[at]); + } + break; + } + } + } + + for (auto& seg : nlo.m_segs) + { + if (maps.id2phdr.count(seg.id)) + { + auto& phdr = *maps.id2phdr.at(seg.id); + wri(phdr.p_offset, seg.fRange.begin()); + wri(phdr.p_filesz, seg.fRange.size()); + wri(phdr.p_paddr, wri(phdr.p_vaddr, seg.vRange.begin())); + wri(phdr.p_memsz, seg.vRange.size()); + } + else + { + Elf_Phdr phdr; + wri(phdr.p_align, getPageSize()); + wri(phdr.p_filesz, seg.fRange.size()); + size_t flags = + ((seg.access & Access::Read) ? PF_R : 0) | + ((seg.access & Access::Write) ? PF_W : 0) | + ((seg.access & Access::Exec) ? PF_X : 0); + wri(phdr.p_flags, flags); + wri(phdr.p_memsz, seg.vRange.size()); + wri(phdr.p_offset, seg.fRange.begin()); + wri(phdr.p_paddr, wri(phdr.p_vaddr, seg.vRange.begin())); + wri(phdr.p_type, PT_LOAD); + phdrs.push_back(phdr); + } + } + + for (auto& sec : nlo.m_secs) + { + if (sec.type == Section::Type::Regular) + { + auto& shdr = *maps.id2shdr.at(sec.id); + wri(shdr.sh_offset, sec.fRange.begin()); + wri(shdr.sh_size, sec.fRange.size()); + wri(shdr.sh_addralign, sec.align); + wri(shdr.sh_addr, le.getVirtualAddress(sec.id)); + + /* If this is the .interp section, then the PT_INTERP segment + must be sync'ed with it. */ + if (sec.name == ".interp") { + for (auto & phdr : phdrs) { + if (rdi(phdr.p_type) == PT_INTERP) { + phdr.p_offset = shdr.sh_offset; + phdr.p_vaddr = phdr.p_paddr = shdr.sh_addr; + phdr.p_filesz = phdr.p_memsz = shdr.sh_size; + } + } + } + /* If this is the .dynamic section, then the PT_DYNAMIC segment + must be sync'ed with it. */ + else if (sec.name == ".dynamic") { + for (auto & phdr : phdrs) { + if (rdi(phdr.p_type) == PT_DYNAMIC) { + phdr.p_offset = shdr.sh_offset; + phdr.p_vaddr = phdr.p_paddr = shdr.sh_addr; + phdr.p_filesz = phdr.p_memsz = shdr.sh_size; + } + } + } + } + } + + *fileContents = std::move(newFile); + + wri(hdr()->e_phoff, nlo.m_secs[1].fRange.begin()); + wri(hdr()->e_shoff, nlo.m_secs[2].fRange.begin()); + wri(hdr()->e_phnum, phdrs.size()); + wri(hdr()->e_shnum, shdrs.size()); + + size_t phAddr = le.getVirtualAddress(/*phdr*/1); + rewriteHeaders(phAddr); +} + static bool printInterpreter = false; static bool printOsAbi = false; static bool setOsAbi = false; @@ -2408,6 +2642,8 @@ static void showHelp(const std::string & progName) [--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\ [--output FILE]\n\ [--debug]\n\ + [--try-layout-engine]\n\ + [--only-layout-engine]\n\ [--version]\n\ FILENAME...\n", progName.c_str()); } @@ -2423,6 +2659,9 @@ static int mainWrapped(int argc, char * * argv) if (getenv("PATCHELF_DEBUG") != nullptr) debugMode = true; + if (getenv("PATCHELF_ONLY_LAYOUT_ENGINE") != nullptr) + tryLayoutEngine = onlyLayoutEngine = true; + int i; for (i = 1; i < argc; ++i) { std::string arg(argv[i]); @@ -2531,6 +2770,13 @@ static int mainWrapped(int argc, char * * argv) else if (arg == "--debug") { debugMode = true; } + else if (arg == "--try-layout-engine") { + tryLayoutEngine = true; + } + else if (arg == "--only-layout-engine") { + tryLayoutEngine = true; + onlyLayoutEngine = true; + } else if (arg == "--no-default-lib") { noDefaultLib = true; } diff --git a/src/patchelf.h b/src/patchelf.h index 9fab18c0..951e9d9c 100644 --- a/src/patchelf.h +++ b/src/patchelf.h @@ -7,6 +7,7 @@ #include #include "elf.h" +#include "layout.h" using FileContents = std::shared_ptr>; @@ -237,6 +238,17 @@ class ElfFile } } + struct Elf2LayoutMaps + { + std::map id2shdr; + std::map shdr2id; + std::map id2phdr; + }; + + bool runLayoutEngine(); + std::pair elf2layout(); + void layout2elf(const le::LayoutEngine& , const Elf2LayoutMaps&); + /* Convert an integer in big or little endian representation (as specified by the ELF header) to this platform's integer representation. */ diff --git a/src/utl.h b/src/utl.h new file mode 100644 index 00000000..c6331e29 --- /dev/null +++ b/src/utl.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +namespace utl +{ + +inline uint64_t roundUp(uint64_t n, uint64_t m) +{ + if (n == 0) + return m; + return ((n - 1) / m + 1) * m; +} + +class Range +{ +public: + Range() = delete; + Range(size_t begin, size_t size) + : m_begin(begin) + , m_size(size) + { + } + + static Range beginEnd(size_t begin, size_t end) + { + return Range(begin, end-begin); + } + + size_t begin() const { return m_begin; } + size_t end() const { return begin() + size(); } + size_t size() const { return m_size; } + + bool operator==(const Range& rhs) const + { + return m_begin == rhs.m_begin && m_size == rhs.m_size; + } + bool operator!=(const Range& rhs) const { return !(*this == rhs); } + + bool wraps(const Range& rng) const + { + return begin() <= rng.begin() && end() >= rng.end(); + } + + bool within(const Range& rng) const + { + return rng.wraps(*this); + } + + bool intersects(const Range& rng) const + { + if (begin() == rng.begin()) return true; + + if (begin() < rng.begin()) + return end() > rng.begin(); + else + return rng.end() > begin(); + } + + Range aligned(size_t align) const + { + if (align == 0) return *this; + auto b = begin() - begin() % align; + auto e = end() + (align - end()%align); + return Range{b, e-b}; + } + + void rebase(size_t begin) { m_begin = begin; } + void resize(size_t sz) { m_size = sz; } + void setEnd(size_t e) { m_size = e - m_begin; } + + void extendBack(long sz) + { + m_begin -= sz; + m_size += sz; + } + + void extendForw(long sz) + { + m_size += sz; + } + +private: + size_t m_begin, m_size; +}; + +}