Index: llvm/include/llvm/Object/ELFObjectFile.h =================================================================== --- llvm/include/llvm/Object/ELFObjectFile.h +++ llvm/include/llvm/Object/ELFObjectFile.h @@ -401,7 +401,7 @@ return *Ret; } - const Elf_Shdr *getSection(DataRefImpl Sec) const { + virtual const Elf_Shdr *getSection(DataRefImpl Sec) const { return reinterpret_cast(Sec.p); } Index: llvm/include/llvm/Object/MutableELFObject.h =================================================================== --- /dev/null +++ llvm/include/llvm/Object/MutableELFObject.h @@ -0,0 +1,201 @@ +//===-- MutableELFObject.h --------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_OBJECT_MUTABLEELFOBJECT_H +#define LLVM_OBJECT_MUTABLEELFOBJECT_H + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/Object/ELFObjectFile.h" + +namespace llvm { +namespace object { + +template class MutableELFObject; + +template struct MutableELFSection { + using Elf_Shdr = Elf_Shdr_Impl; + + Elf_Shdr Header; + std::string Name; + OwningArrayRef Data; + + MutableELFSection(const Elf_Shdr &Header, StringRef Name, void *Base) + : Header(Header), Name(Name), + Data(OwningArrayRef(Header.sh_size)) { + std::memcpy(Data.data(), + reinterpret_cast(Base) + Header.sh_offset, + Header.sh_size); + } + + void setData(ArrayRef Ref) { + Data = OwningArrayRef(Ref); + Header.sh_size = Data.size(); + } + + operator const Elf_Shdr &() const { return Header; } +}; + +template class MutableELFObject : public ELFObjectFile { + // NewType must have a conversion operator to OrigType. + template class MutableTable { + struct MappingType { + enum MappedType { Original, New }; + + uint64_t Index; + MappedType Type; + + MappingType(uint64_t Index, MappedType Type) : Index(Index), Type(Type) {} + + operator uint64_t() const { return Index; } + }; + + ArrayRef OriginalValues; + std::vector NewValues; + std::vector Mappings; + + public: + explicit MutableTable(ArrayRef OriginalValues) + : OriginalValues(OriginalValues) { + uint64_t Count = 0; + std::transform(OriginalValues.begin(), OriginalValues.end(), + std::back_inserter(Mappings), [&Count](const OrigType &) { + return MappingType(Count++, MappingType::Original); + }); + } + + const OrigType &operator[](uint64_t Index) const { + assert(Index < Mappings.size() && "Out of bounds"); + if (Mappings[Index].Type == MappingType::New) + return static_cast(NewValues[Mappings[Index]]); + return OriginalValues[Mappings[Index]]; + } + + const OrigType &getOriginal(uint64_t Index) const { + assert(Index < OriginalValues.size() && "Out of bounds"); + return OriginalValues[Index]; + } + + template + NewType &makeMutable(uint64_t Index, Args &&... Arguments) { + assert(Index < Mappings.size() && "Out of bounds"); + if (Mappings[Index].Type == MappingType::New) + return NewValues[Mappings[Index]]; + NewValues.emplace_back(Arguments...); + Mappings[Index] = MappingType(NewValues.size() - 1, MappingType::New); + return NewValues.back(); + } + + // This can't be a const version of getIfNew because those are only called + // when the object is const. + const NewType *getConstIfNew(uint64_t Index) const { + assert(Index < Mappings.size() && "Out of bounds"); + return Mappings[Index].Type == MappingType::New + ? &NewValues[Mappings[Index]] + : nullptr; + } + + size_t size() const { return Mappings.size(); } + }; + + using Elf_Shdr = Elf_Shdr_Impl; + using Elf_Ehdr = Elf_Ehdr_Impl; + + MutableTable> Sections; + + const Elf_Ehdr &getHeader() const { + return *reinterpret_cast(this->base()); + } + + const Elf_Shdr *getSection(DataRefImpl Sec) const override { + return &Sections[Sec.p]; + } + + void moveSectionNext(DataRefImpl &Sec) const override; + Expected getSectionName(DataRefImpl Sec) const override; + uint64_t getSectionIndex(DataRefImpl Sec) const override; + Expected> + getSectionContents(DataRefImpl Sec) const override; + + static DataRefImpl toDataRef(uintptr_t Ptr) { + DataRefImpl Ref; + Ref.p = Ptr; + return Ref; + } + +public: + explicit MutableELFObject(ELFObjectFile &&B) + : ELFObjectFile(std::move(B)), + Sections(ArrayRef(reinterpret_cast( + this->base() + getHeader().e_shoff), + getHeader().e_shnum)) {} + + section_iterator section_begin() const override { + return section_iterator(SectionRef(toDataRef(0), this)); + } + + section_iterator section_end() const override { + return section_iterator(SectionRef(toDataRef(Sections.size()), this)); + } + + /// Returns a mutable reference to the section pointed to by Sec. A possible + /// usage to change all sections with alignment of 0 to 1 could be: + /// @code{.cpp} + /// for (const SectionRef &Sec : MutObj.sections()) + /// if (!Sec.getAlignment()) { + /// auto MutSecOrErr = MutObj.getMutableSection(Sec); + /// if (!MutSecOrErr) + /// handleError(MutSecOrErr.takeError()); + /// MutSecOrErr->Header.sh_addrallgin = 1; + /// } + /// @endcode + Expected &> getMutableSection(SectionRef Sec) { + const Elf_Shdr_Impl &Header = Sections[Sec.getRawDataRefImpl().p]; + Expected Name = getSectionName(Sec.getRawDataRefImpl()); + if (!Name) + return Name.takeError(); + return Sections.makeMutable(Sec.getRawDataRefImpl().p, Header, *Name, this); + } + + /// Returns a mutable reference to the section pointed to by the + /// section_iterator. It is usually more ergonomic to use the overload + /// which takes a SectionRef. + Expected &> getMutableSection(section_iterator Sec) { + return getMutableSection(*Sec); + } +}; + +template +void MutableELFObject::moveSectionNext(DataRefImpl &Sec) const { + ++Sec.p; +} + +template +uint64_t MutableELFObject::getSectionIndex(DataRefImpl Sec) const { + return Sec.p; +} + +template +Expected +MutableELFObject::getSectionName(DataRefImpl Sec) const { + if (const MutableELFSection *SecOrNull = Sections.getConstIfNew(Sec.p)) + return SecOrNull->Name; + return ELFObjectFile::getSectionName(Sec); +} + +template +Expected> +MutableELFObject::getSectionContents(DataRefImpl Sec) const { + if (const MutableELFSection *SecOrNull = Sections.getConstIfNew(Sec.p)) + return ArrayRef(SecOrNull->Data.data(), SecOrNull->Header.sh_size); + return ELFObjectFile::getSectionContents(Sec); +} + +} // namespace object +} // namespace llvm + +#endif // LLVM_OBJECT_MUTABLEELFOBJECT_H Index: llvm/unittests/Object/CMakeLists.txt =================================================================== --- llvm/unittests/Object/CMakeLists.txt +++ llvm/unittests/Object/CMakeLists.txt @@ -1,10 +1,12 @@ set(LLVM_LINK_COMPONENTS BinaryFormat Object + ObjectYAML ) add_llvm_unittest(ObjectTests MinidumpTest.cpp + MutableELFObjectTest.cpp SymbolSizeTest.cpp SymbolicFileTest.cpp ) Index: llvm/unittests/Object/MutableELFObjectTest.cpp =================================================================== --- /dev/null +++ llvm/unittests/Object/MutableELFObjectTest.cpp @@ -0,0 +1,205 @@ +//===- MutableELFObjectTest.cpp -------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Object/MutableELFObject.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/ObjectYAML/yaml2obj.h" +#include "llvm/Support/Error.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace object; +using namespace yaml; + +// Test that when no modifications have been made SectionRef's methods work +// the same in both ELFObjectFile and MutableELFObject. +TEST(MutableELFObject, NoChange) { + SmallString<0> Storage; + Expected> ErrOrObj = yaml2ObjectFile(Storage, R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Content: "DEADBEEF")"); + + ASSERT_THAT_EXPECTED(ErrOrObj, Succeeded()); + auto *ELFObjFile = dyn_cast>(ErrOrObj->get()); + ASSERT_TRUE(ELFObjFile); + const ObjectFile &ObjFile = *ELFObjFile; + + // For now, moving *ELFObjFile is safe to still use it because the move + // constructor on ObjectFile is a copy constructor. + MutableELFObject MutableObject(std::move(*ELFObjFile)); + + ptrdiff_t ObjFileSecCount = + std::distance(ELFObjFile->section_begin(), ELFObjFile->section_end()); + ptrdiff_t MutObjSecCount = + std::distance(MutableObject.section_begin(), MutableObject.section_end()); + ASSERT_EQ(ObjFileSecCount, MutObjSecCount); + + auto TestSections = [](SectionRef ObjFile, SectionRef MutObj) { + EXPECT_EQ(ObjFile.getAddress(), MutObj.getAddress()); + EXPECT_EQ(ObjFile.getAlignment(), MutObj.getAlignment()); + EXPECT_EQ(ObjFile.getIndex(), MutObj.getIndex()); + EXPECT_EQ(ObjFile.getSize(), MutObj.getSize()); + EXPECT_EQ(ObjFile.isBerkeleyData(), MutObj.isBerkeleyData()); + EXPECT_EQ(ObjFile.isBerkeleyText(), MutObj.isBerkeleyText()); + EXPECT_EQ(ObjFile.isBitcode(), MutObj.isBitcode()); + EXPECT_EQ(ObjFile.isBSS(), MutObj.isBSS()); + EXPECT_EQ(ObjFile.isCompressed(), MutObj.isCompressed()); + EXPECT_EQ(ObjFile.isData(), MutObj.isData()); + EXPECT_EQ(ObjFile.isStripped(), MutObj.isStripped()); + EXPECT_EQ(ObjFile.isText(), MutObj.isText()); + EXPECT_EQ(ObjFile.isVirtual(), MutObj.isVirtual()); + }; + + for (const auto &Tuple : zip(MutableObject.sections(), ObjFile.sections())) + TestSections(std::get<0>(Tuple), std::get<1>(Tuple)); + + // Copy every section header but make no changes. SectionRefs now point to + // section headers outside of the file's mapping. + for (auto Iter = MutableObject.section_begin(), + End = MutableObject.section_end(); + Iter != End; ++Iter) { + auto Expect = MutableObject.getMutableSection(Iter); + if (!Expect) + consumeError(Expect.takeError()); + } + + for (const auto &Tuple : zip(MutableObject.sections(), ObjFile.sections())) + TestSections(std::get<0>(Tuple), std::get<1>(Tuple)); +} + +// Change a section's name and test that SectionRef::getName() returns the new +// name. +TEST(MutableELFObject, ChangeSectionName) { + SmallString<0> Storage; + Expected> ErrOrObj = yaml2ObjectFile(Storage, R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .sec0 + Type: SHT_PROGBITS + - Name: .sec1 + Type: SHT_PROGBITS + - Name: .sec2 + Type: SHT_PROGBITS)"); + + ASSERT_THAT_EXPECTED(ErrOrObj, Succeeded()); + auto *ELFObjFile = dyn_cast>(ErrOrObj->get()); + ASSERT_TRUE(ELFObjFile); + MutableELFObject MutableObject(std::move(*ELFObjFile)); + + auto compareSectionName = [](section_iterator Iter, const char *Name) { + StringRef SecName; + EXPECT_FALSE(Iter->getName(SecName)); + EXPECT_EQ(SecName, Name); + }; + + ptrdiff_t NumSections = + std::distance(MutableObject.section_begin(), MutableObject.section_end()); + ASSERT_EQ(NumSections, 7); + + auto Iter = MutableObject.section_begin(); + compareSectionName(Iter, nullptr); + + compareSectionName(++Iter, ".sec0"); + compareSectionName(++Iter, ".sec1"); + compareSectionName(++Iter, ".sec2"); + + Iter = MutableObject.section_begin(); + std::advance(Iter, 2); + auto MutSectionOrErr = MutableObject.getMutableSection(Iter); + EXPECT_EQ(std::distance(MutableObject.section_begin(), Iter), 2); + ASSERT_THAT_EXPECTED(MutSectionOrErr, Succeeded()); + MutSectionOrErr->Name = ".new_name"; + + Iter = MutableObject.section_begin(); + compareSectionName(Iter, nullptr); + compareSectionName(++Iter, ".sec0"); + compareSectionName(++Iter, ".new_name"); + compareSectionName(++Iter, ".sec2"); + + // Make sure a section wasn't added. + ptrdiff_t NewNumSections = + std::distance(MutableObject.section_begin(), MutableObject.section_end()); + EXPECT_EQ(NewNumSections, NumSections); +} + +// Test MutableELFSection::setData(). +TEST(MutableELFObject, ChangeSectionContents) { + SmallString<0> Storage; + Expected> ErrOrObj = yaml2ObjectFile(Storage, R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_REL + Machine: EM_X86_64 +Sections: + - Name: .text + Type: SHT_PROGBITS + Flags: [ SHF_ALLOC, SHF_EXECINSTR ] + Content: "DEADBEEF")"); + + ASSERT_THAT_EXPECTED(ErrOrObj, Succeeded()); + auto *ELFObjFile = dyn_cast>(ErrOrObj->get()); + ASSERT_TRUE(ELFObjFile); + MutableELFObject MutableObject(std::move(*ELFObjFile)); + + ptrdiff_t NumSections = + std::distance(MutableObject.section_begin(), MutableObject.section_end()); + + auto FirstSec = ++MutableObject.section_begin(); + Expected Contents = FirstSec->getContents(); + ASSERT_THAT_EXPECTED(Contents, Succeeded()); + + EXPECT_EQ(*Contents, "\xDE\xAD\xBE\xEF"); + EXPECT_EQ(FirstSec->getSize(), Contents->size()); + + ArrayRef Data{'1', '2', '3', '4'}; + + auto MutSecOrErr = MutableObject.getMutableSection(FirstSec); + ASSERT_THAT_EXPECTED(MutSecOrErr, Succeeded()); + MutSecOrErr->setData(Data); + + FirstSec = ++MutableObject.section_begin(); + Contents = FirstSec->getContents(); + ASSERT_THAT_EXPECTED(Contents, Succeeded()); + EXPECT_EQ(*Contents, StringRef(reinterpret_cast(Data.data()), + Data.size())); + + MutSecOrErr->Header.sh_size = 2; + Contents = FirstSec->getContents(); + ASSERT_THAT_EXPECTED(Contents, Succeeded()); + EXPECT_EQ(*Contents, + StringRef(reinterpret_cast(Data.data()), 2)); + + // Check that getSize properly uses the header's sh_size value. + EXPECT_EQ(FirstSec->getSize(), 2U); + + // Check that Contents has size 2 because header's sh_size was changed. + EXPECT_EQ(Contents->size(), 2U); + + // Make sure a section wasn't added. + ptrdiff_t NewNumSections = + std::distance(MutableObject.section_begin(), MutableObject.section_end()); + EXPECT_EQ(NewNumSections, NumSections); +}