forked from OSchip/llvm-project
[llvm-objcopy][ELF] Implement a mutable section visitor that updates size-related fields (Size, EntrySize, Align) before layout.
Summary: Fix EntrySize, Size, and Align before doing layout calculation. As a side cleanup, this removes a dependence on sizeof(Elf_Sym) within BinaryReader, so we can untemplatize that. This unblocks a cleaner implementation of handling the -O<format> flag. See D53667 for a previous attempt. Actual implementation of the -O<format> flag will come in an upcoming commit, this is largely a NFC (although not _totally_ one, because alignment on binary input was actually wrong before). Reviewers: jakehehrlich, jhenderson, alexshap, espindola Reviewed By: jhenderson Subscribers: emaste, arichardson, llvm-commits Differential Revision: https://reviews.llvm.org/D56211 llvm-svn: 350336
This commit is contained in:
parent
57fc9582f9
commit
1f82176f7d
|
@ -45,7 +45,7 @@
|
|||
# CHECK-NEXT: Size:
|
||||
# CHECK-NEXT: Link: 1
|
||||
# CHECK-NEXT: Info: 1
|
||||
# CHECK-NEXT: AddressAlignment: 1
|
||||
# CHECK-NEXT: AddressAlignment: 8
|
||||
# CHECK-NEXT: EntrySize: 24
|
||||
# CHECK-NEXT: }
|
||||
# CHECK-NEXT: Section {
|
||||
|
|
|
@ -71,7 +71,47 @@ template <class ELFT> void ELFWriter<ELFT>::writeShdr(const SectionBase &Sec) {
|
|||
Shdr.sh_entsize = Sec.EntrySize;
|
||||
}
|
||||
|
||||
SectionVisitor::~SectionVisitor() {}
|
||||
template <class ELFT> void ELFSectionSizer<ELFT>::visit(Section &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(OwnedDataSection &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(StringTableSection &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(DynamicRelocationSection &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(SymbolTableSection &Sec) {
|
||||
Sec.EntrySize = sizeof(Elf_Sym);
|
||||
Sec.Size = Sec.Symbols.size() * Sec.EntrySize;
|
||||
// Align to the larget field in Elf_Sym.
|
||||
Sec.Align = sizeof(Elf_Sym::st_size);
|
||||
}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(RelocationSection &Sec) {
|
||||
Sec.EntrySize = Sec.Type == SHT_REL ? sizeof(Elf_Rel) : sizeof(Elf_Rela);
|
||||
Sec.Size = Sec.Relocations.size() * Sec.EntrySize;
|
||||
// Align to the larget field in Elf_Rel(a).
|
||||
Sec.Align =
|
||||
Sec.Type == SHT_REL ? sizeof(Elf_Rel::r_info) : sizeof(Elf_Rela::r_info);
|
||||
}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(GnuDebugLinkSection &Sec) {}
|
||||
|
||||
template <class ELFT> void ELFSectionSizer<ELFT>::visit(GroupSection &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(SectionIndexSection &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(CompressedSection &Sec) {}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionSizer<ELFT>::visit(DecompressedSection &Sec) {}
|
||||
|
||||
void BinarySectionWriter::visit(const SectionIndexSection &Sec) {
|
||||
error("Cannot write symbol section index table '" + Sec.Name + "' ");
|
||||
|
@ -102,6 +142,8 @@ void SectionWriter::visit(const Section &Sec) {
|
|||
|
||||
void Section::accept(SectionVisitor &Visitor) const { Visitor.visit(*this); }
|
||||
|
||||
void Section::accept(MutableSectionVisitor &Visitor) { Visitor.visit(*this); }
|
||||
|
||||
void SectionWriter::visit(const OwnedDataSection &Sec) {
|
||||
uint8_t *Buf = Out.getBufferStart() + Sec.Offset;
|
||||
llvm::copy(Sec.Data, Buf);
|
||||
|
@ -164,10 +206,18 @@ void DecompressedSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void DecompressedSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void OwnedDataSection::accept(SectionVisitor &Visitor) const {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void OwnedDataSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void BinarySectionWriter::visit(const CompressedSection &Sec) {
|
||||
error("Cannot write compressed section '" + Sec.Name + "' ");
|
||||
}
|
||||
|
@ -246,6 +296,10 @@ void CompressedSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void CompressedSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void StringTableSection::addString(StringRef Name) {
|
||||
StrTabBuilder.add(Name);
|
||||
Size = StrTabBuilder.getSize();
|
||||
|
@ -265,6 +319,10 @@ void StringTableSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void StringTableSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionWriter<ELFT>::visit(const SectionIndexSection &Sec) {
|
||||
uint8_t *Buf = Out.getBufferStart() + Sec.Offset;
|
||||
|
@ -288,6 +346,10 @@ void SectionIndexSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void SectionIndexSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
static bool isValidReservedSectionIndex(uint16_t Index, uint16_t Machine) {
|
||||
switch (Index) {
|
||||
case SHN_ABS:
|
||||
|
@ -470,6 +532,10 @@ void SymbolTableSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void SymbolTableSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
template <class SymTabType>
|
||||
void RelocSectionWithSymtabBase<SymTabType>::removeSectionReferences(
|
||||
const SectionBase *Sec) {
|
||||
|
@ -540,6 +606,10 @@ void RelocationSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void RelocationSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void RelocationSection::removeSymbols(
|
||||
function_ref<bool(const Symbol &)> ToRemove) {
|
||||
for (const Relocation &Reloc : Relocations)
|
||||
|
@ -562,6 +632,10 @@ void DynamicRelocationSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void DynamicRelocationSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void Section::removeSectionReferences(const SectionBase *Sec) {
|
||||
if (LinkSection == Sec) {
|
||||
error("Section " + LinkSection->Name +
|
||||
|
@ -648,6 +722,10 @@ void GnuDebugLinkSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void GnuDebugLinkSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
template <class ELFT>
|
||||
void ELFSectionWriter<ELFT>::visit(const GroupSection &Sec) {
|
||||
ELF::Elf32_Word *Buf =
|
||||
|
@ -661,6 +739,10 @@ void GroupSection::accept(SectionVisitor &Visitor) const {
|
|||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
void GroupSection::accept(MutableSectionVisitor &Visitor) {
|
||||
Visitor.visit(*this);
|
||||
}
|
||||
|
||||
// Returns true IFF a section is wholly inside the range of a segment
|
||||
static bool sectionWithinSegment(const SectionBase &Section,
|
||||
const Segment &Segment) {
|
||||
|
@ -700,7 +782,7 @@ static bool compareSegmentsByPAddr(const Segment *A, const Segment *B) {
|
|||
return A->Index < B->Index;
|
||||
}
|
||||
|
||||
template <class ELFT> void BinaryELFBuilder<ELFT>::initFileHeader() {
|
||||
void BinaryELFBuilder::initFileHeader() {
|
||||
Obj->Flags = 0x0;
|
||||
Obj->Type = ET_REL;
|
||||
Obj->OSABI = ELFOSABI_NONE;
|
||||
|
@ -710,11 +792,9 @@ template <class ELFT> void BinaryELFBuilder<ELFT>::initFileHeader() {
|
|||
Obj->Version = 1;
|
||||
}
|
||||
|
||||
template <class ELFT> void BinaryELFBuilder<ELFT>::initHeaderSegment() {
|
||||
Obj->ElfHdrSegment.Index = 0;
|
||||
}
|
||||
void BinaryELFBuilder::initHeaderSegment() { Obj->ElfHdrSegment.Index = 0; }
|
||||
|
||||
template <class ELFT> StringTableSection *BinaryELFBuilder<ELFT>::addStrTab() {
|
||||
StringTableSection *BinaryELFBuilder::addStrTab() {
|
||||
auto &StrTab = Obj->addSection<StringTableSection>();
|
||||
StrTab.Name = ".strtab";
|
||||
|
||||
|
@ -722,15 +802,11 @@ template <class ELFT> StringTableSection *BinaryELFBuilder<ELFT>::addStrTab() {
|
|||
return &StrTab;
|
||||
}
|
||||
|
||||
template <class ELFT>
|
||||
SymbolTableSection *
|
||||
BinaryELFBuilder<ELFT>::addSymTab(StringTableSection *StrTab) {
|
||||
SymbolTableSection *BinaryELFBuilder::addSymTab(StringTableSection *StrTab) {
|
||||
auto &SymTab = Obj->addSection<SymbolTableSection>();
|
||||
|
||||
SymTab.Name = ".symtab";
|
||||
SymTab.Link = StrTab->Index;
|
||||
// TODO: Factor out dependence on ElfType here.
|
||||
SymTab.EntrySize = sizeof(Elf_Sym);
|
||||
|
||||
// The symbol table always needs a null symbol
|
||||
SymTab.addSymbol("", 0, 0, nullptr, 0, 0, 0, 0);
|
||||
|
@ -739,8 +815,7 @@ BinaryELFBuilder<ELFT>::addSymTab(StringTableSection *StrTab) {
|
|||
return &SymTab;
|
||||
}
|
||||
|
||||
template <class ELFT>
|
||||
void BinaryELFBuilder<ELFT>::addData(SymbolTableSection *SymTab) {
|
||||
void BinaryELFBuilder::addData(SymbolTableSection *SymTab) {
|
||||
auto Data = ArrayRef<uint8_t>(
|
||||
reinterpret_cast<const uint8_t *>(MemBuf->getBufferStart()),
|
||||
MemBuf->getBufferSize());
|
||||
|
@ -763,13 +838,13 @@ void BinaryELFBuilder<ELFT>::addData(SymbolTableSection *SymTab) {
|
|||
/*Value=*/DataSection.Size, STV_DEFAULT, SHN_ABS, 0);
|
||||
}
|
||||
|
||||
template <class ELFT> void BinaryELFBuilder<ELFT>::initSections() {
|
||||
void BinaryELFBuilder::initSections() {
|
||||
for (auto &Section : Obj->sections()) {
|
||||
Section.initialize(Obj->sections());
|
||||
}
|
||||
}
|
||||
|
||||
template <class ELFT> std::unique_ptr<Object> BinaryELFBuilder<ELFT>::build() {
|
||||
std::unique_ptr<Object> BinaryELFBuilder::build() {
|
||||
initFileHeader();
|
||||
initHeaderSegment();
|
||||
StringTableSection *StrTab = addStrTab();
|
||||
|
@ -1122,14 +1197,7 @@ Writer::~Writer() {}
|
|||
Reader::~Reader() {}
|
||||
|
||||
std::unique_ptr<Object> BinaryReader::create() const {
|
||||
if (MInfo.Is64Bit)
|
||||
return MInfo.IsLittleEndian
|
||||
? BinaryELFBuilder<ELF64LE>(MInfo.EMachine, MemBuf).build()
|
||||
: BinaryELFBuilder<ELF64BE>(MInfo.EMachine, MemBuf).build();
|
||||
else
|
||||
return MInfo.IsLittleEndian
|
||||
? BinaryELFBuilder<ELF32LE>(MInfo.EMachine, MemBuf).build()
|
||||
: BinaryELFBuilder<ELF32BE>(MInfo.EMachine, MemBuf).build();
|
||||
return BinaryELFBuilder(MInfo.EMachine, MemBuf).build();
|
||||
}
|
||||
|
||||
std::unique_ptr<Object> ELFReader::create() const {
|
||||
|
@ -1483,10 +1551,16 @@ template <class ELFT> void ELFWriter<ELFT>::finalize() {
|
|||
}
|
||||
|
||||
initEhdrSegment();
|
||||
|
||||
// Before we can prepare for layout the indexes need to be finalized.
|
||||
// Also, the output arch may not be the same as the input arch, so fix up
|
||||
// size-related fields before doing layout calculations.
|
||||
uint64_t Index = 0;
|
||||
for (auto &Sec : Obj.sections())
|
||||
auto SecSizer = llvm::make_unique<ELFSectionSizer<ELFT>>();
|
||||
for (auto &Sec : Obj.sections()) {
|
||||
Sec.Index = Index++;
|
||||
Sec.accept(*SecSizer);
|
||||
}
|
||||
|
||||
// The symbol table does not update all other sections on update. For
|
||||
// instance, symbol names are not added as new symbols are added. This means
|
||||
|
@ -1607,11 +1681,6 @@ void BinaryWriter::finalize() {
|
|||
SecWriter = llvm::make_unique<BinarySectionWriter>(Buf);
|
||||
}
|
||||
|
||||
template class BinaryELFBuilder<ELF64LE>;
|
||||
template class BinaryELFBuilder<ELF64BE>;
|
||||
template class BinaryELFBuilder<ELF32LE>;
|
||||
template class BinaryELFBuilder<ELF32BE>;
|
||||
|
||||
template class ELFBuilder<ELF64LE>;
|
||||
template class ELFBuilder<ELF64BE>;
|
||||
template class ELFBuilder<ELF32LE>;
|
||||
|
|
|
@ -71,7 +71,7 @@ enum ElfType { ELFT_ELF32LE, ELFT_ELF64LE, ELFT_ELF32BE, ELFT_ELF64BE };
|
|||
|
||||
class SectionVisitor {
|
||||
public:
|
||||
virtual ~SectionVisitor();
|
||||
virtual ~SectionVisitor() = default;
|
||||
|
||||
virtual void visit(const Section &Sec) = 0;
|
||||
virtual void visit(const OwnedDataSection &Sec) = 0;
|
||||
|
@ -86,6 +86,23 @@ public:
|
|||
virtual void visit(const DecompressedSection &Sec) = 0;
|
||||
};
|
||||
|
||||
class MutableSectionVisitor {
|
||||
public:
|
||||
virtual ~MutableSectionVisitor() = default;
|
||||
|
||||
virtual void visit(Section &Sec) = 0;
|
||||
virtual void visit(OwnedDataSection &Sec) = 0;
|
||||
virtual void visit(StringTableSection &Sec) = 0;
|
||||
virtual void visit(SymbolTableSection &Sec) = 0;
|
||||
virtual void visit(RelocationSection &Sec) = 0;
|
||||
virtual void visit(DynamicRelocationSection &Sec) = 0;
|
||||
virtual void visit(GnuDebugLinkSection &Sec) = 0;
|
||||
virtual void visit(GroupSection &Sec) = 0;
|
||||
virtual void visit(SectionIndexSection &Sec) = 0;
|
||||
virtual void visit(CompressedSection &Sec) = 0;
|
||||
virtual void visit(DecompressedSection &Sec) = 0;
|
||||
};
|
||||
|
||||
class SectionWriter : public SectionVisitor {
|
||||
protected:
|
||||
Buffer &Out;
|
||||
|
@ -128,9 +145,30 @@ public:
|
|||
explicit ELFSectionWriter(Buffer &Buf) : SectionWriter(Buf) {}
|
||||
};
|
||||
|
||||
template <class ELFT> class ELFSectionSizer : public MutableSectionVisitor {
|
||||
private:
|
||||
using Elf_Rel = typename ELFT::Rel;
|
||||
using Elf_Rela = typename ELFT::Rela;
|
||||
using Elf_Sym = typename ELFT::Sym;
|
||||
|
||||
public:
|
||||
void visit(Section &Sec) override;
|
||||
void visit(OwnedDataSection &Sec) override;
|
||||
void visit(StringTableSection &Sec) override;
|
||||
void visit(DynamicRelocationSection &Sec) override;
|
||||
void visit(SymbolTableSection &Sec) override;
|
||||
void visit(RelocationSection &Sec) override;
|
||||
void visit(GnuDebugLinkSection &Sec) override;
|
||||
void visit(GroupSection &Sec) override;
|
||||
void visit(SectionIndexSection &Sec) override;
|
||||
void visit(CompressedSection &Sec) override;
|
||||
void visit(DecompressedSection &Sec) override;
|
||||
};
|
||||
|
||||
#define MAKE_SEC_WRITER_FRIEND \
|
||||
friend class SectionWriter; \
|
||||
template <class ELFT> friend class ELFSectionWriter;
|
||||
template <class ELFT> friend class ELFSectionWriter; \
|
||||
template <class ELFT> friend class ELFSectionSizer;
|
||||
|
||||
class BinarySectionWriter : public SectionWriter {
|
||||
public:
|
||||
|
@ -237,6 +275,7 @@ public:
|
|||
virtual void removeSectionReferences(const SectionBase *Sec);
|
||||
virtual void removeSymbols(function_ref<bool(const Symbol &)> ToRemove);
|
||||
virtual void accept(SectionVisitor &Visitor) const = 0;
|
||||
virtual void accept(MutableSectionVisitor &Visitor) = 0;
|
||||
virtual void markSymbols();
|
||||
};
|
||||
|
||||
|
@ -293,6 +332,7 @@ public:
|
|||
explicit Section(ArrayRef<uint8_t> Data) : Contents(Data) {}
|
||||
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
void removeSectionReferences(const SectionBase *Sec) override;
|
||||
void initialize(SectionTableRef SecTable) override;
|
||||
void finalize() override;
|
||||
|
@ -313,6 +353,7 @@ public:
|
|||
}
|
||||
|
||||
void accept(SectionVisitor &Sec) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
};
|
||||
|
||||
class CompressedSection : public SectionBase {
|
||||
|
@ -333,6 +374,7 @@ public:
|
|||
uint64_t getDecompressedAlign() const { return DecompressedAlign; }
|
||||
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
|
||||
static bool classof(const SectionBase *S) {
|
||||
return (S->Flags & ELF::SHF_COMPRESSED) ||
|
||||
|
@ -354,6 +396,7 @@ public:
|
|||
}
|
||||
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
};
|
||||
|
||||
// There are two types of string tables that can exist, dynamic and not dynamic.
|
||||
|
@ -378,6 +421,7 @@ public:
|
|||
uint32_t findIndex(StringRef Name) const;
|
||||
void finalize() override;
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
|
||||
static bool classof(const SectionBase *S) {
|
||||
if (S->Flags & ELF::SHF_ALLOC)
|
||||
|
@ -435,6 +479,7 @@ public:
|
|||
void initialize(SectionTableRef SecTable) override;
|
||||
void finalize() override;
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
|
||||
SectionIndexSection() {
|
||||
Name = ".symtab_shndx";
|
||||
|
@ -479,6 +524,7 @@ public:
|
|||
void initialize(SectionTableRef SecTable) override;
|
||||
void finalize() override;
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
void removeSymbols(function_ref<bool(const Symbol &)> ToRemove) override;
|
||||
|
||||
static bool classof(const SectionBase *S) {
|
||||
|
@ -540,6 +586,7 @@ class RelocationSection
|
|||
public:
|
||||
void addRelocation(Relocation Rel) { Relocations.push_back(Rel); }
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
void removeSymbols(function_ref<bool(const Symbol &)> ToRemove) override;
|
||||
void markSymbols() override;
|
||||
|
||||
|
@ -573,6 +620,7 @@ public:
|
|||
void addMember(SectionBase *Sec) { GroupMembers.push_back(Sec); }
|
||||
|
||||
void accept(SectionVisitor &) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
void finalize() override;
|
||||
void removeSymbols(function_ref<bool(const Symbol &)> ToRemove) override;
|
||||
void markSymbols() override;
|
||||
|
@ -611,6 +659,7 @@ public:
|
|||
explicit DynamicRelocationSection(ArrayRef<uint8_t> Data) : Contents(Data) {}
|
||||
|
||||
void accept(SectionVisitor &) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
|
||||
static bool classof(const SectionBase *S) {
|
||||
if (!(S->Flags & ELF::SHF_ALLOC))
|
||||
|
@ -632,6 +681,7 @@ public:
|
|||
// If we add this section from an external source we can use this ctor.
|
||||
explicit GnuDebugLinkSection(StringRef File);
|
||||
void accept(SectionVisitor &Visitor) const override;
|
||||
void accept(MutableSectionVisitor &Visitor) override;
|
||||
};
|
||||
|
||||
class Reader {
|
||||
|
@ -645,9 +695,7 @@ using object::ELFFile;
|
|||
using object::ELFObjectFile;
|
||||
using object::OwningBinary;
|
||||
|
||||
template <class ELFT> class BinaryELFBuilder {
|
||||
using Elf_Sym = typename ELFT::Sym;
|
||||
|
||||
class BinaryELFBuilder {
|
||||
uint16_t EMachine;
|
||||
MemoryBuffer *MemBuf;
|
||||
std::unique_ptr<Object> Obj;
|
||||
|
|
Loading…
Reference in New Issue