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,231 @@ +//===-- 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; + +/// This class is used by MutableELFObject to provide a mutable version of an +/// ELFObjectFile Section. +template struct MutableELFSection { + using Elf_Shdr = typename ELFT::Shdr; + + 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; } +}; + +/// This class is used for doing mutations on an ELFObjectFile. The +/// ELFObjectFile class only provides an interface for reading object files. +template class MutableELFObject : public ELFObjectFile { + /// This class is used for a 'copy on write' effect with tables in an ELF + /// object file. + /// + /// The table holds entries of two different but related types. OrigType + /// represents the object type in the file originaly while NewType is the + /// type to create on modification of the entry. It is required that + /// NewType have an `operator const OrigType &()`. + /// + /// The table keeps a list of mappings, these mappings can have one of two + /// states either Original or New. In the case of Original the index + /// associated with the mapping is into the original table in the file. For + /// a New mapping, the index is into the NewValues vector. This design allows + /// fewer copies to be made than there would otherwise need to be, entries + /// with no modifications never get copied and the only overhead for those is + /// an index. Entries which get modified can have richer types during program + /// executation than are allowed by the object file standard. + template class MutableTable { + struct MappingType { + enum MappedType { Original, New }; + + size_t Index; + MappedType Type; + + MappingType(size_t Index, MappedType Type) : Index(Index), Type(Type) {} + + operator size_t() const { return Index; } + }; + + ArrayRef OriginalValues; + std::vector NewValues; + std::vector Mappings; + + public: + explicit MutableTable(ArrayRef TheOriginalValues) + : OriginalValues(TheOriginalValues) { + size_t Count = 0; + llvm::transform(OriginalValues, std::back_inserter(Mappings), + [&Count](const OrigType &) { + return MappingType(Count++, MappingType::Original); + }); + } + + /// Get the OrigType at index Index regardless of whether it is an OrigType + /// or NewType. In the case that Mappings[Index].Type == New, call NewTypes + /// operator OrigType to make the proper conversion. + const OrigType &operator[](size_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]]; + } + + /// Get the OrigType at index Index. This method ignores any changes made + /// and always returns the OrigType from its original state at its original + /// index. + const OrigType &getOriginal(size_t Index) const { + assert(Index < OriginalValues.size() && "Out of bounds"); + return OriginalValues[Index]; + } + + /// If the entry at index Index has already been made mutable, this returns + /// a reference to that. Otherwise, this replaces the current entry at the + /// specified index with a NewType constructued with Arguments. + template + NewType &makeMutable(size_t Index, Args &&... Arguments) { + assert(Index < Mappings.size() && "Out of bounds"); + if (Mappings[Index].Type == MappingType::New) + return NewValues[Mappings[Index]]; + Mappings[Index] = MappingType(NewValues.size(), MappingType::New); + NewValues.emplace_back(std::forward(Arguments)...); + return NewValues.back(); + } + + /// Returns a pointer to the NewType if the entry at the specified index + /// has had makeMutable called on it. Otherwise this method returns nullptr. + const NewType *getConstIfNew(size_t Index) const { + assert(Index < Mappings.size() && "Out of bounds"); + return Mappings[Index].Type == MappingType::New + ? &NewValues[Mappings[Index]] + : nullptr; + } + + /// Return the number of elements in the table. + size_t size() const { return Mappings.size(); } + }; + + using Elf_Shdr = typename ELFT::Shdr; + using Elf_Ehdr = typename ELFT::Ehdr; + + MutableTable> Sections; + + const Elf_Ehdr &getHeader() const { + return *reinterpret_cast(this->base()); + } + + /// Many getSection* methods in ELFObjectFile use getSection to get the + /// the header associated with that section. This override returns a valid + /// section header whether the section has been modified or not. + const Elf_Shdr *getSection(DataRefImpl Sec) const override { + return &Sections[Sec.p]; + } + + /// moveSectionNext must be overridden because MutableELFObject::section_begin + /// works differently than in ELFObjectFile. In this class, sections are + /// iterated with their index, not address in the file, which allows use with + /// MutableTable. + 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()) { + /// if (auto MutSecOrErr = MutObj.getMutableSection(Sec)) + /// then; + /// else + /// handleError(...); + /// MutSecOrErr->Header.sh_addralign = 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); + } +}; + +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,210 @@ +//===- 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/Support/YAMLTraits.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) { + StringRef Yaml = 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")"; + + SmallString<0> Storage; + raw_svector_ostream OS(Storage); + yaml::Input Input(Yaml); + ASSERT_THAT_ERROR(convertYAML(Input, OS), Succeeded()); + + Expected> ErrOrObj = + ObjectFile::createObjectFile(MemoryBufferRef(OS.str(), "YamlObject")); + ASSERT_THAT_EXPECTED(ErrOrObj, Succeeded()); + auto *ELFObjFile = dyn_cast>(ErrOrObj->get()); + ASSERT_TRUE(ELFObjFile); + + // Create a new ObjectFile from the same yaml. There is no copy constructor. + auto NewErrOrObj = + ObjectFile::createObjectFile(MemoryBufferRef(OS.str(), "YamlObject")); + ASSERT_THAT_EXPECTED(NewErrOrObj, Succeeded()); + auto *MutELFObjFile = dyn_cast>(NewErrOrObj->get()); + ASSERT_TRUE(MutELFObjFile); + MutableELFObject MutableObject(std::move(*MutELFObjFile)); + + 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(), ELFObjFile->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 (const SectionRef Sec : MutableObject.sections()) + ASSERT_THAT_EXPECTED(MutableObject.getMutableSection(Sec), Succeeded()); + + for (const auto &Tuple : + zip(MutableObject.sections(), ELFObjFile->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) { + auto NameOrErr = Iter->getName(); + ASSERT_THAT_EXPECTED(NameOrErr, Succeeded()); + EXPECT_EQ(*NameOrErr, 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); + ASSERT_EQ(std::distance(MutableObject.section_begin(), Iter), 2); + ASSERT_EQ( + std::distance(MutableObject.section_begin(), MutableObject.section_end()), + 7); + 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"); +} + +// 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); +}